diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index 76a0bd83613f9..8e558458feb04 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -98,7 +98,8 @@ enabled: - test/api_integration/config.js - test/examples/config.js - test/functional/apps/bundles/config.ts - - test/functional/apps/console/config.ts + - test/functional/apps/console/monaco/config.ts + - test/functional/apps/console/ace/config.ts - test/functional/apps/context/config.ts - test/functional/apps/dashboard_elements/controls/common/config.ts - test/functional/apps/dashboard_elements/controls/options_list/config.ts @@ -115,6 +116,7 @@ enabled: - test/functional/apps/discover/ccs_compatibility/config.ts - test/functional/apps/discover/classic/config.ts - test/functional/apps/discover/embeddable/config.ts + - test/functional/apps/discover/esql/config.ts - test/functional/apps/discover/group1/config.ts - test/functional/apps/discover/group2_data_grid1/config.ts - test/functional/apps/discover/group2_data_grid2/config.ts @@ -125,6 +127,7 @@ enabled: - test/functional/apps/discover/group6/config.ts - test/functional/apps/discover/group7/config.ts - test/functional/apps/discover/group8/config.ts + - test/functional/apps/discover/context_awareness/config.ts - test/functional/apps/getting_started/config.ts - test/functional/apps/home/config.ts - test/functional/apps/kibana_overview/config.ts @@ -246,6 +249,7 @@ enabled: - x-pack/test/fleet_api_integration/config.epm.ts - x-pack/test/fleet_api_integration/config.fleet.ts - x-pack/test/fleet_api_integration/config.package_policy.ts + - x-pack/test/fleet_api_integration/config.space_awareness.ts - x-pack/test/fleet_functional/config.ts - x-pack/test/ftr_apis/security_and_spaces/config.ts - x-pack/test/functional_basic/apps/ml/permissions/config.ts @@ -324,6 +328,7 @@ enabled: - x-pack/test/functional/apps/search_playground/config.ts - x-pack/test/functional/apps/snapshot_restore/config.ts - x-pack/test/functional/apps/spaces/config.ts + - x-pack/test/functional/apps/spaces/solution_view_flag_enabled/config.ts - x-pack/test/functional/apps/status_page/config.ts - x-pack/test/functional/apps/transform/creation/index_pattern/config.ts - x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/config.ts @@ -405,6 +410,7 @@ enabled: - x-pack/test/spaces_api_integration/security_and_spaces/config_trial.ts - x-pack/test/spaces_api_integration/security_and_spaces/copy_to_space_config_trial.ts - x-pack/test/spaces_api_integration/spaces_only/config.ts + - x-pack/test/task_manager_claimer_mget/config.ts - x-pack/test/ui_capabilities/security_and_spaces/config.ts - x-pack/test/ui_capabilities/spaces_only/config.ts - x-pack/test/upgrade_assistant_integration/config.js @@ -421,6 +427,7 @@ enabled: - x-pack/test_serverless/functional/test_suites/observability/config.ts - x-pack/test_serverless/functional/test_suites/observability/config.examples.ts - x-pack/test_serverless/functional/test_suites/observability/config.saved_objects_management.ts + - x-pack/test_serverless/functional/test_suites/observability/config.context_awareness.ts - x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group1.ts - x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group2.ts - x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group3.ts @@ -433,6 +440,7 @@ enabled: - x-pack/test_serverless/functional/test_suites/search/config.examples.ts - x-pack/test_serverless/functional/test_suites/search/config.screenshots.ts - x-pack/test_serverless/functional/test_suites/search/config.saved_objects_management.ts + - x-pack/test_serverless/functional/test_suites/search/config.context_awareness.ts - x-pack/test_serverless/functional/test_suites/search/common_configs/config.group1.ts - x-pack/test_serverless/functional/test_suites/search/common_configs/config.group2.ts - x-pack/test_serverless/functional/test_suites/search/common_configs/config.group3.ts @@ -441,8 +449,10 @@ enabled: - x-pack/test_serverless/functional/test_suites/search/common_configs/config.group6.ts - x-pack/test_serverless/functional/test_suites/security/config.ts - x-pack/test_serverless/functional/test_suites/security/config.examples.ts - - x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.ts + - x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.basic.ts + - x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.essentials.ts - x-pack/test_serverless/functional/test_suites/security/config.saved_objects_management.ts + - x-pack/test_serverless/functional/test_suites/security/config.context_awareness.ts - x-pack/test_serverless/functional/test_suites/security/common_configs/config.group1.ts - x-pack/test_serverless/functional/test_suites/security/common_configs/config.group2.ts - x-pack/test_serverless/functional/test_suites/security/common_configs/config.group3.ts @@ -571,4 +581,3 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/configs/serverless.integrations.config.ts - x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/configs/serverless.integrations_feature_flag.config.ts - x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/configs/integrations_feature_flag.config.ts - diff --git a/.buildkite/pipeline-resource-definitions/kibana-on-merge.yml b/.buildkite/pipeline-resource-definitions/kibana-on-merge.yml index 8952951a27a5b..497968f39ad95 100644 --- a/.buildkite/pipeline-resource-definitions/kibana-on-merge.yml +++ b/.buildkite/pipeline-resource-definitions/kibana-on-merge.yml @@ -25,7 +25,7 @@ spec: REPORT_FAILED_TESTS_TO_GITHUB: 'true' ELASTIC_SLACK_NOTIFICATIONS_ENABLED: 'true' allow_rebuilds: true - branch_configuration: main 7.17 8.13 8.14 + branch_configuration: main 7.17 8.14 default_branch: main repository: elastic/kibana pipeline_file: .buildkite/pipelines/on_merge.yml diff --git a/.buildkite/pipeline-utils/buildkite/client.ts b/.buildkite/pipeline-utils/buildkite/client.ts index be2cb81dd51c0..c58c3cb7e42e3 100644 --- a/.buildkite/pipeline-utils/buildkite/client.ts +++ b/.buildkite/pipeline-utils/buildkite/client.ts @@ -282,7 +282,7 @@ export class BuildkiteClient { hasRetries = true; const isPreemptionFailure = job.state === 'failed' && - job.agent?.meta_data?.includes('spot=true') && + job.agent?.meta_data?.some((el) => ['spot=true', 'gcp:preemptible=true'].includes(el)) && job.exit_status === -1; if (!isPreemptionFailure) { diff --git a/.buildkite/pipeline-utils/test-failures/annotate.ts b/.buildkite/pipeline-utils/test-failures/annotate.ts index c49128ae3c62b..39aa2d36b9ddb 100644 --- a/.buildkite/pipeline-utils/test-failures/annotate.ts +++ b/.buildkite/pipeline-utils/test-failures/annotate.ts @@ -177,7 +177,7 @@ export const annotateTestFailures = async () => { ); } - if (process.env.SLACK_NOTIFICATIONS_ENABLED === 'true') { + if (process.env.ELASTIC_SLACK_NOTIFICATIONS_ENABLED === 'true') { buildkite.setMetadata( 'slack:test_failures:body', getSlackMessage(failures, failureHtmlArtifacts) diff --git a/.buildkite/pipelines/esql_grammar_sync.yml b/.buildkite/pipelines/esql_grammar_sync.yml index 2bf14af24b20e..6337dc051ef11 100644 --- a/.buildkite/pipelines/esql_grammar_sync.yml +++ b/.buildkite/pipelines/esql_grammar_sync.yml @@ -8,9 +8,9 @@ steps: provider: gcp machineType: n2-standard-2 preemptible: true - - command: .buildkite/scripts/steps/esql_generate_function_definitions.sh - label: Generate Function Definitions - timeout_in_minutes: 10 + - command: .buildkite/scripts/steps/esql_generate_function_metadata.sh + label: Generate Function Metadata + timeout_in_minutes: 15 agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod diff --git a/.buildkite/scripts/common/deployment_credentials.sh b/.buildkite/scripts/common/deployment_credentials.sh new file mode 100755 index 0000000000000..48f5be56a3bfb --- /dev/null +++ b/.buildkite/scripts/common/deployment_credentials.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source .buildkite/scripts/common/vault_fns.sh + +## Usage +# ./deployment_credentials.sh set ... +# ./deployment_credentials.sh unset +# ./deployment_credentials.sh print + +if [[ "${1:-}" == "set" ]]; then + set_in_legacy_vault "${@:2}" +elif [[ "${1:-}" == "unset" ]]; then + unset_in_legacy_vault "${@:2}" +elif [[ "${1:-}" == "print" ]]; then + print_legacy_vault_read "${2}" +else + echo "Unknown command: $1" + exit 1 +fi diff --git a/.buildkite/scripts/common/vault_fns.sh b/.buildkite/scripts/common/vault_fns.sh index 022a22541d6bf..c9c51b2c7bb92 100644 --- a/.buildkite/scripts/common/vault_fns.sh +++ b/.buildkite/scripts/common/vault_fns.sh @@ -1,7 +1,8 @@ #!/bin/bash -# TODO: remove after https://github.com/elastic/kibana-operations/issues/15 is done -if [[ "${VAULT_ADDR:-}" == *"secrets.elastic.co"* ]]; then +# TODO: rewrite after https://github.com/elastic/kibana-operations/issues/15 is done +export LEGACY_VAULT_ADDR="https://secrets.elastic.co:8200" +if [[ "${VAULT_ADDR:-}" == "$LEGACY_VAULT_ADDR" ]]; then VAULT_PATH_PREFIX="secret/kibana-issues/dev" VAULT_KV_PREFIX="secret/kibana-issues/dev" IS_LEGACY_VAULT_ADDR=true @@ -85,3 +86,56 @@ function get_vault_secret_id() { echo "$VAULT_SECRET_ID" } + +function set_in_legacy_vault() { + key_path=$1 + shift + fields=("$@") + + VAULT_ROLE_ID="$(get_vault_role_id)" + VAULT_SECRET_ID="$(get_vault_secret_id)" + VAULT_TOKEN_BAK="$VAULT_TOKEN" + + # Make sure to either keep this variable name `VAULT_TOKEN` or unset `VAULT_TOKEN`, + # otherwise the VM's default token will be used, that's connected to the ci-prod vault instance + VAULT_TOKEN=$(VAULT_ADDR=$LEGACY_VAULT_ADDR vault write -field=token auth/approle/login role_id="$VAULT_ROLE_ID" secret_id="$VAULT_SECRET_ID") + VAULT_ADDR=$LEGACY_VAULT_ADDR vault login -no-print "$VAULT_TOKEN" + + set +e + # shellcheck disable=SC2068 + vault write -address=$LEGACY_VAULT_ADDR "secret/kibana-issues/dev/cloud-deploy/$key_path" ${fields[@]} + EXIT_CODE=$? + set -e + + VAULT_TOKEN="$VAULT_TOKEN_BAK" + + return $EXIT_CODE +} + +function unset_in_legacy_vault() { + key_path=$1 + + VAULT_ROLE_ID="$(get_vault_role_id)" + VAULT_SECRET_ID="$(get_vault_secret_id)" + VAULT_TOKEN_BAK="$VAULT_TOKEN" + + # Make sure to either keep this variable name `VAULT_TOKEN` or unset `VAULT_TOKEN`, + # otherwise the VM's default token will be used, that's connected to the ci-prod vault instance + VAULT_TOKEN=$(VAULT_ADDR=$LEGACY_VAULT_ADDR vault write -field=token auth/approle/login role_id="$VAULT_ROLE_ID" secret_id="$VAULT_SECRET_ID") + VAULT_ADDR=$LEGACY_VAULT_ADDR vault login -no-print "$VAULT_TOKEN" + + set +e + vault delete -address=$LEGACY_VAULT_ADDR "secret/kibana-issues/dev/cloud-deploy/$key_path" + EXIT_CODE=$? + set -e + + VAULT_TOKEN="$VAULT_TOKEN_BAK" + + return $EXIT_CODE +} + +function print_legacy_vault_read() { + key_path=$1 + + echo "vault read -address=$LEGACY_VAULT_ADDR secret/kibana-issues/dev/cloud-deploy/$key_path" +} diff --git a/.buildkite/scripts/packer_cache.sh b/.buildkite/scripts/packer_cache.sh index 66d663180ec3c..57118b242d3e7 100755 --- a/.buildkite/scripts/packer_cache.sh +++ b/.buildkite/scripts/packer_cache.sh @@ -17,6 +17,3 @@ done for version in $(cat versions.json | jq -r '.versions[].version'); do node x-pack/plugins/security_solution/scripts/endpoint/agent_downloader --version "$version" done - -echo "--- Cloning repos for docs build" -node scripts/validate_next_docs --clone-only diff --git a/.buildkite/scripts/pipelines/pull_request/pipeline.ts b/.buildkite/scripts/pipelines/pull_request/pipeline.ts index b1c3e0810644d..035ab108a6b88 100644 --- a/.buildkite/scripts/pipelines/pull_request/pipeline.ts +++ b/.buildkite/scripts/pipelines/pull_request/pipeline.ts @@ -146,15 +146,14 @@ const getPipeline = (filename: string, removeSteps = true) => { pipeline.push(getPipeline('.buildkite/pipelines/pull_request/fips.yml')); } - if (GITHUB_PR_LABELS.includes('ci:build-serverless-image')) { - pipeline.push(getPipeline('.buildkite/pipelines/pull_request/build_project.yml')); - } if ( GITHUB_PR_LABELS.includes('ci:project-deploy-elasticsearch') || GITHUB_PR_LABELS.includes('ci:project-deploy-observability') || GITHUB_PR_LABELS.includes('ci:project-deploy-security') ) { pipeline.push(getPipeline('.buildkite/pipelines/pull_request/deploy_project.yml')); + } else if (GITHUB_PR_LABELS.includes('ci:build-serverless-image')) { + pipeline.push(getPipeline('.buildkite/pipelines/pull_request/build_project.yml')); } if ( diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh index 23231465932dd..1aede759481a5 100755 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api-integration-tests.sh @@ -15,7 +15,11 @@ buildkite-agent meta-data set "${BUILDKITE_JOB_ID}_is_test_execution_step" "true source .buildkite/scripts/pipelines/security_solution_quality_gate/prepare_vault_entries.sh echo "--- Running test script $1" -TARGET_SCRIPT=$1 node .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/start_api_ftr_execution + +cd x-pack/test/security_solution_api_integration +set +e + +TARGET_SCRIPT=$1 node ./scripts/mki_start_api_ftr_execution cmd_status=$? echo "Exit code with status: $cmd_status" exit $cmd_status diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/prepare_vault_entries.sh b/.buildkite/scripts/pipelines/security_solution_quality_gate/prepare_vault_entries.sh index a7f1529e1fb9b..5c21851e7585f 100644 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/prepare_vault_entries.sh +++ b/.buildkite/scripts/pipelines/security_solution_quality_gate/prepare_vault_entries.sh @@ -8,6 +8,13 @@ vault_get security-quality-gate/role-users data -format=json > .ftr/role_users.j vault_get security-quality-gate/role-users/sec-sol-auto-01 data -format=json > .ftr/sec-sol-auto-01.json vault_get security-quality-gate/role-users/sec-sol-auto-02 data -format=json > .ftr/sec-sol-auto-02.json vault_get security-quality-gate/role-users/sec-sol-auto-03 data -format=json > .ftr/sec-sol-auto-03.json +vault_get security-quality-gate/role-users/sec-sol-auto-04 data -format=json > .ftr/sec-sol-auto-04.json +vault_get security-quality-gate/role-users/sec-sol-auto-05 data -format=json > .ftr/sec-sol-auto-05.json +vault_get security-quality-gate/role-users/sec-sol-auto-06 data -format=json > .ftr/sec-sol-auto-06.json +vault_get security-quality-gate/role-users/sec-sol-auto-07 data -format=json > .ftr/sec-sol-auto-07.json +vault_get security-quality-gate/role-users/sec-sol-auto-08 data -format=json > .ftr/sec-sol-auto-08.json +vault_get security-quality-gate/role-users/sec-sol-auto-09 data -format=json > .ftr/sec-sol-auto-09.json +vault_get security-quality-gate/role-users/sec-sol-auto-10 data -format=json > .ftr/sec-sol-auto-10.json # The vault entries relevant to QA Cloud export CLOUD_QA_API_KEY=$(vault_get security-solution-quality-gate qa_api_key) diff --git a/.buildkite/scripts/steps/cloud/build_and_deploy.sh b/.buildkite/scripts/steps/cloud/build_and_deploy.sh index 1287d0f0328c3..10d18030c8811 100755 --- a/.buildkite/scripts/steps/cloud/build_and_deploy.sh +++ b/.buildkite/scripts/steps/cloud/build_and_deploy.sh @@ -82,7 +82,9 @@ if [ -z "${CLOUD_DEPLOYMENT_ID}" ] || [ "${CLOUD_DEPLOYMENT_ID}" = 'null' ]; the echo "Writing to vault..." - vault_kv_set "cloud-deploy/$CLOUD_DEPLOYMENT_NAME" username="$CLOUD_DEPLOYMENT_USERNAME" password="$CLOUD_DEPLOYMENT_PASSWORD" + set_in_legacy_vault "$CLOUD_DEPLOYMENT_NAME" \ + username="$CLOUD_DEPLOYMENT_USERNAME" \ + password="$CLOUD_DEPLOYMENT_PASSWORD" echo "Enabling Stack Monitoring..." jq ' @@ -114,6 +116,7 @@ else ecctl deployment update "$CLOUD_DEPLOYMENT_ID" --track --output json --file /tmp/deploy.json > "$ECCTL_LOGS" fi +VAULT_READ_COMMAND=$(print_legacy_vault_read "$CLOUD_DEPLOYMENT_NAME") CLOUD_DEPLOYMENT_KIBANA_URL=$(ecctl deployment show "$CLOUD_DEPLOYMENT_ID" | jq -r '.resources.kibana[0].info.metadata.aliased_url') CLOUD_DEPLOYMENT_ELASTICSEARCH_URL=$(ecctl deployment show "$CLOUD_DEPLOYMENT_ID" | jq -r '.resources.elasticsearch[0].info.metadata.aliased_url') @@ -125,9 +128,7 @@ Kibana: $CLOUD_DEPLOYMENT_KIBANA_URL Elasticsearch: $CLOUD_DEPLOYMENT_ELASTICSEARCH_URL -Credentials: \`vault kv get $VAULT_KV_PREFIX/cloud-deploy/$CLOUD_DEPLOYMENT_NAME\` - -(Stored in the production vault: VAULT_ADDR=https://vault-ci-prod.elastic.dev, more info: https://docs.elastic.dev/ci/using-secrets) +Credentials: \`$VAULT_READ_COMMAND\` Kibana image: \`$KIBANA_CLOUD_IMAGE\` diff --git a/.buildkite/scripts/steps/cloud/purge_deployments.ts b/.buildkite/scripts/steps/cloud/purge_deployments.ts index d50e62569b86f..67f39afc67b3c 100644 --- a/.buildkite/scripts/steps/cloud/purge_deployments.ts +++ b/.buildkite/scripts/steps/cloud/purge_deployments.ts @@ -7,12 +7,10 @@ */ import { execSync } from 'child_process'; +import { getKibanaDir } from '#pipeline-utils'; const deploymentsListJson = execSync('ecctl deployment list --output json').toString(); const { deployments } = JSON.parse(deploymentsListJson); -const secretBasePath = process.env.VAULT_ADDR?.match(/secrets\.elastic\.co/g) - ? 'secret/kibana-issues/dev' - : 'secret/ci/elastic-kibana'; const prDeployments = deployments.filter((deployment: any) => deployment.name.startsWith('kibana-pr-') @@ -70,7 +68,9 @@ for (const deployment of deploymentsToPurge) { console.log(`Scheduling deployment for deletion: ${deployment.name} / ${deployment.id}`); try { execSync(`ecctl deployment shutdown --force '${deployment.id}'`, { stdio: 'inherit' }); - execSync(`vault delete ${secretBasePath}/cloud-deploy/${deployment.name}`, { + + execSync(`.buildkite/scripts/common/deployment_credentials.sh unset ${deployment.name}`, { + cwd: getKibanaDir(), stdio: 'inherit', }); } catch (ex) { diff --git a/.buildkite/scripts/steps/cloud/purge_projects.ts b/.buildkite/scripts/steps/cloud/purge_projects.ts index 84083417f6267..efd54eeeb0c70 100644 --- a/.buildkite/scripts/steps/cloud/purge_projects.ts +++ b/.buildkite/scripts/steps/cloud/purge_projects.ts @@ -8,6 +8,7 @@ import { execSync } from 'child_process'; import axios from 'axios'; +import { getKibanaDir } from '#pipeline-utils'; async function getPrProjects() { const match = /^(keep.?)?kibana-pr-([0-9]+)-(elasticsearch|security|observability)$/; @@ -43,12 +44,19 @@ async function getPrProjects() { async function deleteProject({ type, id, + name, }: { type: 'elasticsearch' | 'observability' | 'security'; id: number; + name: string; }) { try { await projectRequest.delete(`/api/v1/serverless/projects/${type}/${id}`); + + execSync(`.buildkite/scripts/common/deployment_credentials.sh unset ${name}`, { + cwd: getKibanaDir(), + stdio: 'inherit', + }); } catch (e) { if (e.isAxiosError) { const message = @@ -61,7 +69,7 @@ async function deleteProject({ async function purgeProjects() { const prProjects = await getPrProjects(); - const projectsToPurge = []; + const projectsToPurge: typeof prProjects = []; for (const project of prProjects) { const NOW = new Date().getTime() / 1000; const DAY_IN_SECONDS = 60 * 60 * 24; diff --git a/.buildkite/scripts/steps/code_generation/security_solution_codegen.sh b/.buildkite/scripts/steps/code_generation/security_solution_codegen.sh index b36a9e52001e4..5a37c8a08d99a 100755 --- a/.buildkite/scripts/steps/code_generation/security_solution_codegen.sh +++ b/.buildkite/scripts/steps/code_generation/security_solution_codegen.sh @@ -6,5 +6,17 @@ source .buildkite/scripts/common/util.sh echo --- Security Solution OpenAPI Code Generation +echo OpenAPI Common Package + +(cd packages/kbn-openapi-common && yarn openapi:generate) +check_for_changed_files "yarn openapi:generate" true + +echo Lists API Common Package + +(cd packages/kbn-securitysolution-lists-common && yarn openapi:generate) +check_for_changed_files "yarn openapi:generate" true + +echo Security Solution Plugin + (cd x-pack/plugins/security_solution && yarn openapi:generate) check_for_changed_files "yarn openapi:generate" true \ No newline at end of file diff --git a/.buildkite/scripts/steps/esql_generate_function_definitions.sh b/.buildkite/scripts/steps/esql_generate_function_metadata.sh similarity index 66% rename from .buildkite/scripts/steps/esql_generate_function_definitions.sh rename to .buildkite/scripts/steps/esql_generate_function_metadata.sh index ecb5b996df4c2..30e0fef47c100 100755 --- a/.buildkite/scripts/steps/esql_generate_function_definitions.sh +++ b/.buildkite/scripts/steps/esql_generate_function_metadata.sh @@ -1,6 +1,10 @@ #!/usr/bin/env bash set -euo pipefail +VALIDATION_PACKAGE_DIR="packages/kbn-esql-validation-autocomplete" +EDITOR_PACKAGE_DIR="packages/kbn-text-based-editor" +GIT_SCOPE="$VALIDATION_PACKAGE_DIR/**/* $EDITOR_PACKAGE_DIR/**/*" + report_main_step () { echo "--- $1" } @@ -19,7 +23,7 @@ main () { .buildkite/scripts/bootstrap.sh - cd "$KIBANA_DIR/packages/kbn-esql-validation-autocomplete" + cd "$KIBANA_DIR/$VALIDATION_PACKAGE_DIR" report_main_step "Generate function definitions" @@ -29,9 +33,21 @@ main () { yarn make:tests + report_main_step "Generate inline function docs" + + cd "$KIBANA_DIR/$EDITOR_PACKAGE_DIR" + + yarn make:docs $PARENT_DIR/elasticsearch + + report_main_step "Run i18n check" + + cd "$KIBANA_DIR" + + node scripts/i18n_check.js --fix + # Check for differences set +e - git diff --exit-code --quiet . + git diff --exit-code --quiet $GIT_SCOPE if [ $? -eq 0 ]; then echo "No differences found. Our work is done here." exit @@ -44,8 +60,8 @@ main () { git config --global user.name "$KIBANA_MACHINE_USERNAME" git config --global user.email '42973632+kibanamachine@users.noreply.github.com' - PR_TITLE='[ES|QL] Update function definitions' - PR_BODY='This PR updates the function definitions based on the latest metadata from Elasticsearch.' + PR_TITLE='[ES|QL] Update function metadata' + PR_BODY='This PR updates the function definitions and inline docs based on the latest metadata from Elasticsearch.' # Check if a PR already exists pr_search_result=$(gh pr list --search "$PR_TITLE" --state open --author "$KIBANA_MACHINE_USERNAME" --limit 1 --json title -q ".[].title") @@ -58,12 +74,12 @@ main () { echo "No existing PR found. Committing changes." # Make a commit - BRANCH_NAME="esql_generate_function_definitions_$(date +%s)" + BRANCH_NAME="esql_generate_function_metadata_$(date +%s)" git checkout -b "$BRANCH_NAME" - git add ./**/* - git commit -m "Update function definitions" + git add $GIT_SCOPE + git commit -m "Update function metadata" report_main_step "Changes committed. Creating pull request." diff --git a/.buildkite/scripts/steps/openapi_bundling/security_solution_openapi_bundling.sh b/.buildkite/scripts/steps/openapi_bundling/security_solution_openapi_bundling.sh new file mode 100644 index 0000000000000..be17669d980b6 --- /dev/null +++ b/.buildkite/scripts/steps/openapi_bundling/security_solution_openapi_bundling.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source .buildkite/scripts/common/util.sh + +echo --- Security Solution OpenAPI Bundling + +(cd x-pack/plugins/security_solution && yarn openapi:bundle) +check_for_changed_files "yarn openapi:bundle" true diff --git a/.buildkite/scripts/steps/serverless/deploy.sh b/.buildkite/scripts/steps/serverless/deploy.sh index e67795fcbf65d..a439df65aa671 100644 --- a/.buildkite/scripts/steps/serverless/deploy.sh +++ b/.buildkite/scripts/steps/serverless/deploy.sh @@ -88,7 +88,10 @@ deploy() { echo "Write to vault..." - vault_kv_set "cloud-deploy/$VAULT_KEY_NAME" username="$PROJECT_USERNAME" password="$PROJECT_PASSWORD" id="$PROJECT_ID" + set_in_legacy_vault "$VAULT_KEY_NAME" \ + username="$PROJECT_USERNAME" \ + password="$PROJECT_PASSWORD" \ + id="$PROJECT_ID" else echo "Updating project..." @@ -96,7 +99,7 @@ deploy() { -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" &> $PROJECT_DEPLOY_LOGS + -XPATCH -d "$PROJECT_UPDATE_CONFIGURATION" &> $PROJECT_DEPLOY_LOGS fi echo "Getting project info..." @@ -109,6 +112,8 @@ deploy() { PROJECT_KIBANA_LOGIN_URL="${PROJECT_KIBANA_URL}/login" PROJECT_ELASTICSEARCH_URL=$(jq -r '.endpoints.elasticsearch' $PROJECT_INFO_LOGS) + VAULT_READ_COMMAND=$(print_legacy_vault_read "$VAULT_KEY_NAME") + cat << EOF | buildkite-agent annotate --style "info" --context "project-$PROJECT_TYPE" ### $PROJECT_TYPE_LABEL Deployment @@ -116,9 +121,7 @@ Kibana: $PROJECT_KIBANA_LOGIN_URL Elasticsearch: $PROJECT_ELASTICSEARCH_URL -Credentials: \`vault kv get $VAULT_KV_PREFIX/cloud-deploy/$VAULT_KEY_NAME\` - -(Stored in the production vault: VAULT_ADDR=https://vault-ci-prod.elastic.dev, more info: https://docs.elastic.dev/ci/using-secrets) +Credentials: \`$VAULT_READ_COMMAND\` Kibana image: \`$KIBANA_IMAGE\` EOF diff --git a/.eslintrc.js b/.eslintrc.js index 4e6c367970c02..09c7428599ca2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1004,6 +1004,29 @@ module.exports = { }, }, + /** + * Integration assistant overrides + */ + { + // front end and common typescript and javascript files only + files: [ + 'x-pack/plugins/integration_assistant/public/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/integration_assistant/common/**/*.{js,mjs,ts,tsx}', + ], + rules: { + 'import/no-nodejs-modules': 'error', + 'no-duplicate-imports': 'off', + '@typescript-eslint/no-duplicate-imports': 'error', + 'no-restricted-imports': [ + 'error', + { + // prevents UI code from importing server side code and then webpack including it when doing builds + patterns: ['**/server/*'], + }, + ], + }, + }, + /** * ML overrides */ @@ -1068,6 +1091,7 @@ module.exports = { files: [ 'x-pack/plugins/ecs_data_quality_dashboard/**/*.{ts,tsx}', 'x-pack/plugins/elastic_assistant/**/*.{ts,tsx}', + 'x-pack/plugins/integration_assistant/**/*.{ts,tsx}', 'x-pack/packages/kbn-elastic-assistant/**/*.{ts,tsx}', 'x-pack/packages/kbn-elastic-assistant-common/**/*.{ts,tsx}', 'x-pack/packages/kbn-langchain/**/*.{ts,tsx}', @@ -1082,6 +1106,7 @@ module.exports = { excludedFiles: [ 'x-pack/plugins/ecs_data_quality_dashboard/**/*.{test,mock,test_helper}.{ts,tsx}', 'x-pack/plugins/elastic_assistant/**/*.{test,mock,test_helper}.{ts,tsx}', + 'x-pack/plugins/integration_assistant/**/*.{test,mock,test_helper}.{ts,tsx}', 'x-pack/packages/kbn-elastic-assistant/**/*.{test,mock,test_helper}.{ts,tsx}', 'x-pack/packages/kbn-elastic-assistant-common/**/*.{test,mock,test_helper}.{ts,tsx}', 'x-pack/packages/kbn-langchain/**/*.{test,mock,test_helper}.{ts,tsx}', @@ -1102,6 +1127,7 @@ module.exports = { files: [ 'x-pack/plugins/ecs_data_quality_dashboard/**/*.{ts,tsx}', 'x-pack/plugins/elastic_assistant/**/*.{ts,tsx}', + 'x-pack/plugins/integration_assistant/**/*.{ts,tsx}', 'x-pack/packages/kbn-elastic-assistant/**/*.{ts,tsx}', 'x-pack/packages/kbn-elastic-assistant-common/**/*.{ts,tsx}', 'x-pack/packages/kbn-langchain/**/*.{ts,tsx}', @@ -1141,6 +1167,7 @@ module.exports = { files: [ 'x-pack/plugins/ecs_data_quality_dashboard/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/elastic_assistant/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/integration_assistant/**/*.{js,mjs,ts,tsx}', 'x-pack/packages/kbn-elastic-assistant/**/*.{js,mjs,ts,tsx}', 'x-pack/packages/kbn-elastic-assistant-common/**/*.{js,mjs,ts,tsx}', 'x-pack/packages/kbn-langchain/**/*.{js,mjs,ts,tsx}', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 93ec4be707181..a6a45a24266a8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -35,14 +35,9 @@ packages/kbn-ambient-ftr-types @elastic/kibana-operations @elastic/appex-qa packages/kbn-ambient-storybook-types @elastic/kibana-operations packages/kbn-ambient-ui-types @elastic/kibana-operations packages/kbn-analytics @elastic/kibana-core -packages/analytics/client @elastic/kibana-core packages/analytics/utils/analytics_collection_utils @elastic/kibana-core test/analytics/plugins/analytics_ftr_helpers @elastic/kibana-core test/analytics/plugins/analytics_plugin_a @elastic/kibana-core -packages/analytics/shippers/elastic_v3/browser @elastic/kibana-core -packages/analytics/shippers/elastic_v3/common @elastic/kibana-core -packages/analytics/shippers/elastic_v3/server @elastic/kibana-core -packages/analytics/shippers/fullstory @elastic/kibana-core packages/kbn-apm-config-loader @elastic/kibana-core @vigneshshanmugam x-pack/plugins/observability_solution/apm_data_access @elastic/obs-knowledge-team @elastic/obs-ux-infra_services-team packages/kbn-apm-data-view @elastic/obs-ux-infra_services-team @@ -106,6 +101,7 @@ packages/content-management/tabbed_table_list_view @elastic/appex-sharedux packages/content-management/table_list_view @elastic/appex-sharedux packages/content-management/table_list_view_common @elastic/appex-sharedux packages/content-management/table_list_view_table @elastic/appex-sharedux +packages/content-management/user_profiles @elastic/appex-sharedux packages/kbn-content-management-utils @elastic/kibana-data-discovery examples/controls_example @elastic/kibana-presentation src/plugins/controls @elastic/kibana-presentation @@ -378,6 +374,7 @@ packages/kbn-discover-utils @elastic/kibana-data-discovery packages/kbn-doc-links @elastic/docs packages/kbn-docs-utils @elastic/kibana-operations packages/kbn-dom-drag-drop @elastic/kibana-visualizations @elastic/kibana-data-discovery +packages/analytics/ebt @elastic/kibana-core packages/kbn-ebt-tools @elastic/kibana-core x-pack/packages/security-solution/ecs_data_quality_dashboard @elastic/security-threat-hunting-explore x-pack/plugins/ecs_data_quality_dashboard @elastic/security-threat-hunting-explore @@ -503,6 +500,7 @@ x-pack/plugins/observability_solution/infra @elastic/obs-ux-logs-team @elastic/o x-pack/plugins/ingest_pipelines @elastic/kibana-management src/plugins/input_control_vis @elastic/kibana-presentation src/plugins/inspector @elastic/kibana-presentation +x-pack/plugins/integration_assistant @elastic/security-solution src/plugins/interactive_setup @elastic/kibana-security test/interactive_setup_api_integration/plugins/test_endpoints @elastic/kibana-security packages/kbn-interpreter @elastic/kibana-visualizations @@ -624,6 +622,7 @@ x-pack/plugins/observability_solution/observability_shared @elastic/observabilit x-pack/test/security_api_integration/plugins/oidc_provider @elastic/kibana-security test/common/plugins/otel_metrics @elastic/obs-ux-infra_services-team packages/kbn-openapi-bundler @elastic/security-detection-rule-management +packages/kbn-openapi-common @elastic/security-detection-rule-management packages/kbn-openapi-generator @elastic/security-detection-rule-management packages/kbn-optimizer @elastic/kibana-operations packages/kbn-optimizer-webpack-helpers @elastic/kibana-operations @@ -681,6 +680,7 @@ packages/kbn-reporting/server @elastic/appex-sharedux packages/kbn-resizable-layout @elastic/kibana-data-discovery examples/resizable_layout_examples @elastic/kibana-data-discovery x-pack/test/plugin_functional/plugins/resolver_test @elastic/security-solution +packages/response-ops/feature_flag_service @elastic/response-ops examples/response_stream @elastic/ml-ui packages/kbn-rison @elastic/kibana-operations x-pack/plugins/rollup @elastic/kibana-management @@ -694,6 +694,7 @@ x-pack/plugins/runtime_fields @elastic/kibana-management packages/kbn-safer-lodash-set @elastic/kibana-security x-pack/test/security_api_integration/plugins/saml_provider @elastic/kibana-security x-pack/test/plugin_api_integration/plugins/sample_task_plugin @elastic/response-ops +x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget @elastic/response-ops test/plugin_functional/plugins/saved_object_export_transforms @elastic/kibana-core test/plugin_functional/plugins/saved_object_import_warnings @elastic/kibana-core x-pack/test/saved_object_api_integration/common/plugins/saved_object_test_plugin @elastic/kibana-security @@ -715,7 +716,9 @@ packages/kbn-search-connectors @elastic/search-kibana x-pack/plugins/search_connectors @elastic/search-kibana packages/kbn-search-errors @elastic/kibana-data-discovery examples/search_examples @elastic/kibana-data-discovery +x-pack/plugins/search_homepage @elastic/search-kibana packages/kbn-search-index-documents @elastic/search-kibana +x-pack/plugins/search_inference_endpoints @elastic/search-kibana x-pack/plugins/search_notebooks @elastic/search-kibana x-pack/plugins/search_playground @elastic/search-kibana packages/kbn-search-response-warnings @elastic/kibana-data-discovery @@ -751,6 +754,7 @@ packages/kbn-securitysolution-list-api @elastic/security-detection-engine packages/kbn-securitysolution-list-constants @elastic/security-detection-engine packages/kbn-securitysolution-list-hooks @elastic/security-detection-engine packages/kbn-securitysolution-list-utils @elastic/security-detection-engine +packages/kbn-securitysolution-lists-common @elastic/security-detection-engine 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 @@ -1224,7 +1228,7 @@ x-pack/test/observability_ai_assistant_functional @elastic/obs-ai-assistant /WORKSPACE.bazel @elastic/kibana-operations /.buildkite/ @elastic/kibana-operations /.buildkite/scripts/steps/esql_grammar_sync.sh @elastic/kibana-esql -/.buildkite/scripts/steps/esql_generate_function_definitions.sh @elastic/kibana-esql +/.buildkite/scripts/steps/esql_generate_function_metadata.sh @elastic/kibana-esql /.buildkite/pipelines/esql_grammar_sync.yml @elastic/kibana-esql /kbn_pm/ @elastic/kibana-operations /x-pack/dev-tools @elastic/kibana-operations @@ -1324,6 +1328,7 @@ x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kib /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 +/x-pack/test/task_manager_claimer_mget/ @elastic/response-ops /docs/user/alerting/ @elastic/response-ops /docs/management/connectors/ @elastic/response-ops /x-pack/test/cases_api_integration/ @elastic/response-ops @@ -1440,6 +1445,7 @@ x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout @elastic/ /x-pack/plugins/security_solution/public/detections/components/alerts_info @elastic/security-threat-hunting-investigations /x-pack/plugins/security_solution/public/flyout/document_details @elastic/security-threat-hunting-investigations /x-pack/plugins/security_solution/public/flyout/shared @elastic/security-threat-hunting-investigations +/x-pack/plugins/security_solution/public/notes @elastic/security-threat-hunting-investigations /x-pack/plugins/security_solution/public/resolver @elastic/security-threat-hunting-investigations /x-pack/plugins/security_solution/public/threat_intelligence @elastic/security-threat-hunting-investigations /x-pack/plugins/security_solution/public/timelines @elastic/security-threat-hunting-investigations @@ -1508,6 +1514,8 @@ x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout @elastic/ /x-pack/plugins/stack_connectors/public/connector_types/sentinelone @elastic/security-defend-workflows /x-pack/plugins/stack_connectors/server/connector_types/sentinelone @elastic/security-defend-workflows /x-pack/plugins/stack_connectors/common/sentinelone @elastic/security-defend-workflows +/x-pack/plugins/stack_connectors/server/connector_types/crowdstrike @elastic/security-defend-workflows +/x-pack/plugins/stack_connectors/common/crowdstrike @elastic/security-defend-workflows ## Security Solution shared OAS schemas /x-pack/plugins/security_solution/common/api/model @elastic/security-detection-rule-management @elastic/security-detection-engine @@ -1591,12 +1599,14 @@ x-pack/test/security_solution_cypress/cypress/tasks/expandable_flyout @elastic/ ## Security Solution sub teams - security-defend-workflows /x-pack/plugins/security_solution/public/management/ @elastic/security-defend-workflows -/x-pack/plugins/security_solution/public/common/lib/endpoint*/ @elastic/security-defend-workflows -/x-pack/plugins/security_solution/public/common/components/agents/ @elastic/security-defend-workflows +/x-pack/plugins/security_solution/public/common/lib/endpoint/ @elastic/security-defend-workflows /x-pack/plugins/security_solution/public/common/components/endpoint/ @elastic/security-defend-workflows +/x-pack/plugins/security_solution/public/common/hooks/endpoint/ @elastic/security-defend-workflows +/x-pack/plugins/security_solution/public/common/mock/endpoint @elastic/security-defend-workflows +/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/ @elastic/security-defend-workflows /x-pack/plugins/security_solution/common/endpoint/ @elastic/security-defend-workflows -/x-pack/plugins/security_solution/server/endpoint/ @elastic/security-defend-workflows /x-pack/plugins/security_solution/common/api/endpoint/ @elastic/security-defend-workflows +/x-pack/plugins/security_solution/server/endpoint/ @elastic/security-defend-workflows /x-pack/plugins/security_solution/server/lists_integration/endpoint/ @elastic/security-defend-workflows /x-pack/plugins/security_solution/server/lib/license/ @elastic/security-defend-workflows /x-pack/plugins/security_solution/server/fleet_integration/ @elastic/security-defend-workflows @@ -1631,6 +1641,7 @@ x-pack/plugins/security_solution/common/api/entity_analytics @elastic/security-e x-pack/test/security_solution_api_integration/test_suites/genai @elastic/security-generative-ai # Security Defend Workflows - OSQuery Ownership +x-pack/plugins/osquery @elastic/security-defend-workflows /x-pack/plugins/security_solution/common/api/detection_engine/model/rule_response_actions @elastic/security-defend-workflows /x-pack/plugins/security_solution/public/detection_engine/rule_response_actions @elastic/security-defend-workflows /x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions @elastic/security-defend-workflows @@ -1646,7 +1657,8 @@ x-pack/test/security_solution_api_integration/test_suites/genai @elastic/securit /x-pack/test/cloud_security_posture_functional/ @elastic/kibana-cloud-security-posture /x-pack/test/cloud_security_posture_api/ @elastic/kibana-cloud-security-posture /x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/ @elastic/kibana-cloud-security-posture -/x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.ts @elastic/kibana-cloud-security-posture +/x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.basic.ts @elastic/kibana-cloud-security-posture +/x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.essentials.ts @elastic/kibana-cloud-security-posture /x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/ @elastic/kibana-cloud-security-posture /x-pack/plugins/fleet/public/components/cloud_security_posture @elastic/fleet @elastic/kibana-cloud-security-posture /x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/cloud_security_posture @elastic/fleet @elastic/kibana-cloud-security-posture diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 452f246d361e4..246481da11766 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2024-06-11 +date: 2024-06-19 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 f6fa39ca9fb75..467d9cf5a7b1f 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/ai_assistant_management_selection.mdx b/api_docs/ai_assistant_management_selection.mdx index 3d914c047aa22..e2ebb27cfb3f1 100644 --- a/api_docs/ai_assistant_management_selection.mdx +++ b/api_docs/ai_assistant_management_selection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiAssistantManagementSelection title: "aiAssistantManagementSelection" image: https://source.unsplash.com/400x175/?github description: API docs for the aiAssistantManagementSelection plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiAssistantManagementSelection'] --- import aiAssistantManagementSelectionObj from './ai_assistant_management_selection.devdocs.json'; diff --git a/api_docs/aiops.devdocs.json b/api_docs/aiops.devdocs.json index b0444a67e7314..5d8cd183f7416 100644 --- a/api_docs/aiops.devdocs.json +++ b/api_docs/aiops.devdocs.json @@ -242,9 +242,9 @@ "signature": [ "{ optIn: (optInConfig: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.OptInConfig", "text": "OptInConfig" }, @@ -252,9 +252,9 @@ "Observable", "<", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.TelemetryCounter", "text": "TelemetryCounter" }, @@ -1068,22 +1068,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "aiops", - "id": "def-public.LogRateAnalysisAppStateProps.stickyHistogram", - "type": "CompoundType", - "tags": [], - "label": "stickyHistogram", - "description": [ - "Option to make main histogram sticky" - ], - "signature": [ - "boolean | undefined" - ], - "path": "x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "aiops", "id": "def-public.LogRateAnalysisAppStateProps.showFrozenDataTierChoice", @@ -1138,22 +1122,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "aiops", - "id": "def-public.LogRateAnalysisContentWrapperProps.stickyHistogram", - "type": "CompoundType", - "tags": [], - "label": "stickyHistogram", - "description": [ - "Option to make main histogram sticky" - ], - "signature": [ - "boolean | undefined" - ], - "path": "x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_content_wrapper.tsx", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "aiops", "id": "def-public.LogRateAnalysisContentWrapperProps.appDependencies", diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index ef89afae00eb9..f5e85bf416a3c 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) for questi | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 74 | 0 | 9 | 2 | +| 72 | 0 | 9 | 2 | ## Client diff --git a/api_docs/alerting.devdocs.json b/api_docs/alerting.devdocs.json index 7c1e8fca1c41f..51ed0ee394747 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -1646,9 +1646,9 @@ "signature": [ "() => Promise<", { - "pluginId": "alerting", + "pluginId": "@kbn/alerting-types", "scope": "common", - "docId": "kibAlertingPluginApi", + "docId": "kibKbnAlertingTypesPluginApi", "section": "def-common.AlertsHealth", "text": "AlertsHealth" }, @@ -2906,9 +2906,9 @@ "signature": [ "() => Promise<", { - "pluginId": "alerting", + "pluginId": "@kbn/alerting-types", "scope": "common", - "docId": "kibAlertingPluginApi", + "docId": "kibKbnAlertingTypesPluginApi", "section": "def-common.AlertsHealth", "text": "AlertsHealth" }, @@ -5305,9 +5305,9 @@ "ResolveParams", ") => Promise<", { - "pluginId": "alerting", + "pluginId": "@kbn/alerting-types", "scope": "common", - "docId": "kibAlertingPluginApi", + "docId": "kibKbnAlertingTypesPluginApi", "section": "def-common.ResolvedSanitizedRule", "text": "ResolvedSanitizedRule" }, @@ -6704,7 +6704,7 @@ "tags": [], "label": "AlertingFrameworkHealth", "description": [], - "path": "x-pack/plugins/alerting/common/index.ts", + "path": "packages/kbn-alerting-types/alerting_framework_health_types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -6715,7 +6715,7 @@ "tags": [], "label": "isSufficientlySecure", "description": [], - "path": "x-pack/plugins/alerting/common/index.ts", + "path": "packages/kbn-alerting-types/alerting_framework_health_types.ts", "deprecated": false, "trackAdoption": false }, @@ -6726,7 +6726,7 @@ "tags": [], "label": "hasPermanentEncryptionKey", "description": [], - "path": "x-pack/plugins/alerting/common/index.ts", + "path": "packages/kbn-alerting-types/alerting_framework_health_types.ts", "deprecated": false, "trackAdoption": false }, @@ -6739,14 +6739,14 @@ "description": [], "signature": [ { - "pluginId": "alerting", + "pluginId": "@kbn/alerting-types", "scope": "common", - "docId": "kibAlertingPluginApi", + "docId": "kibKbnAlertingTypesPluginApi", "section": "def-common.AlertsHealth", "text": "AlertsHealth" } ], - "path": "x-pack/plugins/alerting/common/index.ts", + "path": "packages/kbn-alerting-types/alerting_framework_health_types.ts", "deprecated": false, "trackAdoption": false } @@ -6904,7 +6904,7 @@ "tags": [], "label": "AlertsHealth", "description": [], - "path": "x-pack/plugins/alerting/common/rule.ts", + "path": "packages/kbn-alerting-types/alerting_framework_health_types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -6918,15 +6918,15 @@ "signature": [ "{ status: ", { - "pluginId": "alerting", + "pluginId": "@kbn/alerting-types", "scope": "common", - "docId": "kibAlertingPluginApi", + "docId": "kibKbnAlertingTypesPluginApi", "section": "def-common.HealthStatus", "text": "HealthStatus" }, "; timestamp: string; }" ], - "path": "x-pack/plugins/alerting/common/rule.ts", + "path": "packages/kbn-alerting-types/alerting_framework_health_types.ts", "deprecated": false, "trackAdoption": false }, @@ -6940,15 +6940,15 @@ "signature": [ "{ status: ", { - "pluginId": "alerting", + "pluginId": "@kbn/alerting-types", "scope": "common", - "docId": "kibAlertingPluginApi", + "docId": "kibKbnAlertingTypesPluginApi", "section": "def-common.HealthStatus", "text": "HealthStatus" }, "; timestamp: string; }" ], - "path": "x-pack/plugins/alerting/common/rule.ts", + "path": "packages/kbn-alerting-types/alerting_framework_health_types.ts", "deprecated": false, "trackAdoption": false }, @@ -6962,15 +6962,15 @@ "signature": [ "{ status: ", { - "pluginId": "alerting", + "pluginId": "@kbn/alerting-types", "scope": "common", - "docId": "kibAlertingPluginApi", + "docId": "kibKbnAlertingTypesPluginApi", "section": "def-common.HealthStatus", "text": "HealthStatus" }, "; timestamp: string; }" ], - "path": "x-pack/plugins/alerting/common/rule.ts", + "path": "packages/kbn-alerting-types/alerting_framework_health_types.ts", "deprecated": false, "trackAdoption": false } @@ -10422,18 +10422,6 @@ } ], "enums": [ - { - "parentPluginId": "alerting", - "id": "def-common.HealthStatus", - "type": "Enum", - "tags": [], - "label": "HealthStatus", - "description": [], - "path": "x-pack/plugins/alerting/common/rule.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, { "parentPluginId": "alerting", "id": "def-common.MaintenanceWindowStatus", @@ -10845,6 +10833,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "alerting", + "id": "def-common.INTERNAL_ALERTING_BACKFILL_SCHEDULE_API_PATH", + "type": "string", + "tags": [], + "label": "INTERNAL_ALERTING_BACKFILL_SCHEDULE_API_PATH", + "description": [], + "signature": [ + "\"/internal/alerting/rules/backfill/_schedule\"" + ], + "path": "x-pack/plugins/alerting/common/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "alerting", "id": "def-common.INTERNAL_ALERTING_SNOOZE_RULE", @@ -11517,7 +11520,7 @@ }, ", \"saved_object\"> & { outcome: string; alias_target_id?: string | undefined; }" ], - "path": "x-pack/plugins/alerting/common/rule.ts", + "path": "packages/kbn-alerting-types/rule_types.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -11654,7 +11657,7 @@ }, "[]" ], - "path": "x-pack/plugins/alerting/common/rule.ts", + "path": "packages/kbn-alerting-types/rule_types.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index 326ece3403d68..174daf92bb302 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index 36c6ac4d3328d..d9ccc16a8acb4 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/apm_data_access.mdx b/api_docs/apm_data_access.mdx index be7c15ae8a7a2..cf0bc62f7d985 100644 --- a/api_docs/apm_data_access.mdx +++ b/api_docs/apm_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apmDataAccess title: "apmDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the apmDataAccess plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apmDataAccess'] --- import apmDataAccessObj from './apm_data_access.devdocs.json'; diff --git a/api_docs/asset_manager.mdx b/api_docs/asset_manager.mdx index 7c7f65746be91..64cfd25389da0 100644 --- a/api_docs/asset_manager.mdx +++ b/api_docs/asset_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/assetManager title: "assetManager" image: https://source.unsplash.com/400x175/?github description: API docs for the assetManager plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'assetManager'] --- import assetManagerObj from './asset_manager.devdocs.json'; diff --git a/api_docs/assets_data_access.mdx b/api_docs/assets_data_access.mdx index 00e47ddffb67b..76dad9651578f 100644 --- a/api_docs/assets_data_access.mdx +++ b/api_docs/assets_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/assetsDataAccess title: "assetsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the assetsDataAccess plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'assetsDataAccess'] --- import assetsDataAccessObj from './assets_data_access.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 165afe849a284..4c8b9bbe19251 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index b6590a9582761..7d4dd4fd3ca99 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.devdocs.json'; diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index e4fdf5899e899..91e867a7999af 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index ee2d917d38526..2beb8d47c6930 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index ab140b3a910a7..7f1e24b9fea6d 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index 5befd22c819cf..190013ec6c9cf 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_data_migration.mdx b/api_docs/cloud_data_migration.mdx index 2f9c7701192b5..bb3b69a7c63f1 100644 --- a/api_docs/cloud_data_migration.mdx +++ b/api_docs/cloud_data_migration.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDataMigration title: "cloudDataMigration" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDataMigration plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDataMigration'] --- import cloudDataMigrationObj from './cloud_data_migration.devdocs.json'; diff --git a/api_docs/cloud_defend.mdx b/api_docs/cloud_defend.mdx index 0ce107d1f2e6a..2d88f69aeec5c 100644 --- a/api_docs/cloud_defend.mdx +++ b/api_docs/cloud_defend.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDefend title: "cloudDefend" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDefend plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDefend'] --- import cloudDefendObj from './cloud_defend.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index 667833277e872..daf262ab94138 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] --- import cloudExperimentsObj from './cloud_experiments.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index 27ca3dcf27d21..a812d76502208 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index 2688a65a8bc99..8b056c595d8d8 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/content_management.mdx b/api_docs/content_management.mdx index 5858ab30fb235..a061227fb6cae 100644 --- a/api_docs/content_management.mdx +++ b/api_docs/content_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/contentManagement title: "contentManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the contentManagement plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'contentManagement'] --- import contentManagementObj from './content_management.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 8d75ddaa6f881..97ce881289142 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index b75da42b12a78..aef3f276d8a1c 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index f51da81ae8c4f..96515a7692a6c 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index b4eaf0acae1f1..119f9b57bf172 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2024-06-11 +date: 2024-06-19 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 2c47124b9625f..4bc38d07ab1b5 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_quality.mdx b/api_docs/data_quality.mdx index a01c9ebd97348..c9508d873d3ce 100644 --- a/api_docs/data_quality.mdx +++ b/api_docs/data_quality.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataQuality title: "dataQuality" image: https://source.unsplash.com/400x175/?github description: API docs for the dataQuality plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataQuality'] --- import dataQualityObj from './data_quality.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index a38faf0c3b362..3123c6516428a 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2024-06-11 +date: 2024-06-19 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 210bea4f1f2d1..181a37757cacb 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2024-06-11 +date: 2024-06-19 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 d88f73c871d21..3af5d0fe9268a 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2024-06-11 +date: 2024-06-19 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 ab06c8dd85b73..206f19c400122 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2024-06-11 +date: 2024-06-19 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 e52caca77119a..b7a04fc6457ca 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.devdocs.json b/api_docs/data_views.devdocs.json index f1fc2955eefaa..8dad9a0ac323d 100644 --- a/api_docs/data_views.devdocs.json +++ b/api_docs/data_views.devdocs.json @@ -12941,10 +12941,6 @@ "deprecated": true, "trackAdoption": false, "references": [ - { - "plugin": "data", - "path": "src/plugins/data/public/query/filter_manager/lib/get_display_value.ts" - }, { "plugin": "data", "path": "src/plugins/data/common/search/search_source/inspect/inspector_stats.ts" @@ -12954,12 +12950,16 @@ "path": "src/plugins/data/common/search/tabify/response_writer.ts" }, { - "plugin": "@kbn/search-errors", - "path": "packages/kbn-search-errors/src/painless_error.tsx" + "plugin": "data", + "path": "src/plugins/data/common/search/aggs/param_types/field.ts" }, { "plugin": "data", - "path": "src/plugins/data/common/search/aggs/param_types/field.ts" + "path": "src/plugins/data/public/query/filter_manager/lib/get_display_value.ts" + }, + { + "plugin": "@kbn/search-errors", + "path": "packages/kbn-search-errors/src/painless_error.tsx" }, { "plugin": "savedObjectsManagement", @@ -13225,10 +13225,6 @@ "plugin": "infra", "path": "x-pack/plugins/observability_solution/infra/public/pages/logs/settings/validation_errors.ts" }, - { - "plugin": "timelines", - "path": "x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/sourcerer/containers/create_sourcerer_data_view.ts" @@ -13245,6 +13241,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.ts" }, + { + "plugin": "timelines", + "path": "x-pack/plugins/timelines/server/search_strategy/index_fields/index.ts" + }, { "plugin": "transform", "path": "x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/filter_agg/components/filter_term_form.tsx" diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index f46a76b6eefea..74c481eaad31c 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2024-06-11 +date: 2024-06-19 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 5c0efe11e212c..d432893bc384f 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/dataset_quality.devdocs.json b/api_docs/dataset_quality.devdocs.json index 14df933533652..48297e463fbda 100644 --- a/api_docs/dataset_quality.devdocs.json +++ b/api_docs/dataset_quality.devdocs.json @@ -185,7 +185,7 @@ "Type", "; }>; }> | undefined; handler: ({}: ", "DatasetQualityRouteHandlerResources", - " & { params: { path: { dataStream: string; }; query: { start: number; end: number; }; }; }) => Promise<{ lastActivity?: number | undefined; degradedDocsCount?: number | undefined; docsCount?: number | undefined; sizeBytes?: number | null | undefined; services?: { [x: string]: string[]; } | undefined; hosts?: { [x: string]: string[]; } | undefined; }>; } & ", + " & { params: { path: { dataStream: string; }; query: { start: number; end: number; }; }; }) => Promise<{ lastActivity?: number | undefined; degradedDocsCount?: number | undefined; docsCount?: number | undefined; sizeBytes?: number | null | undefined; services?: { [x: string]: string[]; } | undefined; hosts?: { [x: string]: string[]; } | undefined; userPrivileges?: { canMonitor: boolean; } | undefined; }>; } & ", "DatasetQualityRouteCreateOptions", "; \"GET /internal/dataset_quality/data_streams/{dataStream}/degraded_fields\": { endpoint: \"GET /internal/dataset_quality/data_streams/{dataStream}/degraded_fields\"; params?: ", "TypeC", @@ -261,7 +261,7 @@ "StringC", "; }>]>; }> | undefined; handler: ({}: ", "DatasetQualityRouteHandlerResources", - " & { params: { query: { type?: \"metrics\" | \"synthetics\" | \"traces\" | \"logs\" | \"profiling\" | undefined; } & { datasetQuery?: string | undefined; }; }; }) => Promise<{ dataStreamsStats: ({ name: string; } & { size?: string | undefined; sizeBytes?: number | undefined; lastActivity?: number | undefined; integration?: string | undefined; totalDocs?: number | null | undefined; })[]; }>; } & ", + " & { params: { query: { type?: \"metrics\" | \"synthetics\" | \"traces\" | \"logs\" | \"profiling\" | undefined; } & { datasetQuery?: string | undefined; }; }; }) => Promise<{ datasetUserPrivileges: { canMonitor: boolean; } & { canRead: boolean; canViewIntegrations: boolean; }; dataStreamsStats: ({ name: string; userPrivileges: { canMonitor: boolean; }; } & { size?: string | undefined; sizeBytes?: number | undefined; lastActivity?: number | undefined; integration?: string | undefined; totalDocs?: number | null | undefined; })[]; }>; } & ", "DatasetQualityRouteCreateOptions", "; }[TEndpoint] extends { endpoint: any; params?: infer TRouteParamsRT extends ", { @@ -340,7 +340,7 @@ "Type", "; }>; }> | undefined; handler: ({}: ", "DatasetQualityRouteHandlerResources", - " & { params: { path: { dataStream: string; }; query: { start: number; end: number; }; }; }) => Promise<{ lastActivity?: number | undefined; degradedDocsCount?: number | undefined; docsCount?: number | undefined; sizeBytes?: number | null | undefined; services?: { [x: string]: string[]; } | undefined; hosts?: { [x: string]: string[]; } | undefined; }>; } & ", + " & { params: { path: { dataStream: string; }; query: { start: number; end: number; }; }; }) => Promise<{ lastActivity?: number | undefined; degradedDocsCount?: number | undefined; docsCount?: number | undefined; sizeBytes?: number | null | undefined; services?: { [x: string]: string[]; } | undefined; hosts?: { [x: string]: string[]; } | undefined; userPrivileges?: { canMonitor: boolean; } | undefined; }>; } & ", "DatasetQualityRouteCreateOptions", "; \"GET /internal/dataset_quality/data_streams/{dataStream}/degraded_fields\": { endpoint: \"GET /internal/dataset_quality/data_streams/{dataStream}/degraded_fields\"; params?: ", "TypeC", @@ -416,7 +416,7 @@ "StringC", "; }>]>; }> | undefined; handler: ({}: ", "DatasetQualityRouteHandlerResources", - " & { params: { query: { type?: \"metrics\" | \"synthetics\" | \"traces\" | \"logs\" | \"profiling\" | undefined; } & { datasetQuery?: string | undefined; }; }; }) => Promise<{ dataStreamsStats: ({ name: string; } & { size?: string | undefined; sizeBytes?: number | undefined; lastActivity?: number | undefined; integration?: string | undefined; totalDocs?: number | null | undefined; })[]; }>; } & ", + " & { params: { query: { type?: \"metrics\" | \"synthetics\" | \"traces\" | \"logs\" | \"profiling\" | undefined; } & { datasetQuery?: string | undefined; }; }; }) => Promise<{ datasetUserPrivileges: { canMonitor: boolean; } & { canRead: boolean; canViewIntegrations: boolean; }; dataStreamsStats: ({ name: string; userPrivileges: { canMonitor: boolean; }; } & { size?: string | undefined; sizeBytes?: number | undefined; lastActivity?: number | undefined; integration?: string | undefined; totalDocs?: number | null | undefined; })[]; }>; } & ", "DatasetQualityRouteCreateOptions", "; }[TEndpoint] extends { endpoint: any; params?: any; handler: ({}: any) => Promise; } & ", "ServerRouteCreateOptions", diff --git a/api_docs/dataset_quality.mdx b/api_docs/dataset_quality.mdx index 343adf6529277..9be5bb8dcf960 100644 --- a/api_docs/dataset_quality.mdx +++ b/api_docs/dataset_quality.mdx @@ -8,12 +8,12 @@ slug: /kibana-dev-docs/api/datasetQuality title: "datasetQuality" image: https://source.unsplash.com/400x175/?github description: API docs for the datasetQuality plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'datasetQuality'] --- import datasetQualityObj from './dataset_quality.devdocs.json'; -This plugin introduces the concept of dataset quality, where users can easily get an overview on the datasets they have. +This plugin introduces the concept of data set quality, where users can easily get an overview on the data sets they have. Contact [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) for questions regarding this plugin. diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index 9565e69bae0a1..5c6f96d6421d7 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -17,7 +17,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Referencing plugin(s) | Remove By | | ---------------|-----------|-----------| | | ml, stackAlerts | - | -| | data, @kbn/search-errors, savedObjectsManagement, unifiedSearch, @kbn/unified-field-list, lens, controls, triggersActionsUi, dataVisualizer, canvas, presentationUtil, logsShared, fleet, ml, @kbn/lens-embeddable-utils, @kbn/ml-data-view-utils, enterpriseSearch, graph, visTypeTimeseries, exploratoryView, stackAlerts, infra, timelines, securitySolution, transform, upgradeAssistant, uptime, ux, maps, dataViewManagement, eventAnnotationListing, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega | - | +| | data, @kbn/search-errors, savedObjectsManagement, unifiedSearch, @kbn/unified-field-list, lens, controls, triggersActionsUi, dataVisualizer, canvas, presentationUtil, logsShared, fleet, ml, @kbn/lens-embeddable-utils, @kbn/ml-data-view-utils, enterpriseSearch, graph, visTypeTimeseries, exploratoryView, stackAlerts, infra, securitySolution, timelines, transform, upgradeAssistant, uptime, ux, maps, dataViewManagement, eventAnnotationListing, inputControlVis, visDefaultEditor, visTypeTimelion, visTypeVega | - | | | encryptedSavedObjects, actions, data, ml, logstash, securitySolution, cloudChat | - | | | actions, savedObjectsTagging, ml, enterpriseSearch | - | | | @kbn/core-saved-objects-browser-internal, @kbn/core, savedObjects, visualizations, aiops, dataVisualizer, ml, dashboardEnhanced, graph, lens, securitySolution, eventAnnotation, @kbn/core-saved-objects-browser-mocks | - | @@ -37,6 +37,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | @kbn/core-saved-objects-api-browser, @kbn/core-saved-objects-browser-internal, @kbn/core-saved-objects-browser-mocks, @kbn/core-saved-objects-api-server-internal, @kbn/core-saved-objects-import-export-server-internal, @kbn/core-saved-objects-server-internal, fleet, graph, lists, osquery, securitySolution, alerting | - | | | alerting, discover, securitySolution | - | | | securitySolution | - | +| | cloudDefend, osquery, securitySolution, synthetics | - | +| | cloudDefend, osquery, securitySolution, synthetics | - | | | actions, alerting, files, cases, observabilityAIAssistant, fleet, cloudDefend, cloudSecurityPosture, elasticAssistant, enterpriseSearch, lists, osquery, securitySolution, reporting, serverlessSearch, transform, upgradeAssistant, apm, synthetics, security | - | | | cases, securitySolution, security | - | | | @kbn/securitysolution-data-table, securitySolution | - | @@ -48,7 +50,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | securitySolution | - | | | @kbn/core-saved-objects-api-browser, @kbn/core-saved-objects-browser-internal, @kbn/core-saved-objects-api-server, @kbn/core, home, savedObjectsTagging, canvas, savedObjects, @kbn/core-saved-objects-browser-mocks, @kbn/core-saved-objects-import-export-server-internal, savedObjectsTaggingOss, lists, securitySolution, upgradeAssistant, savedObjectsManagement, @kbn/core-ui-settings-server-internal | - | | | @kbn/core-saved-objects-migration-server-internal, actions, dataViews, data, alerting, lens, cases, savedSearch, canvas, savedObjectsTagging, graph, lists, maps, visualizations, securitySolution, dashboard, @kbn/core-test-helpers-so-type-serializer | - | -| | dataViews, security, maps, imageEmbeddable, enterpriseSearch, securitySolution, serverlessSearch, cloudLinks, observabilityAIAssistantApp, cases, apm | - | +| | dataViews, security, maps, imageEmbeddable, securitySolution, serverlessSearch, cloudLinks, observabilityAIAssistantApp, cases, apm | - | | | security, cases, searchPlayground, securitySolution | - | | | lists, securitySolution, @kbn/securitysolution-io-ts-list-types | - | | | lists, securitySolution, @kbn/securitysolution-io-ts-list-types | - | @@ -65,14 +67,14 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | @kbn/monaco, securitySolution | - | | | fleet, cloudSecurityPosture, exploratoryView, osquery, synthetics | - | | | actions, alerting | - | -| | visualizations, lens, controls, dashboard, maps, discover, profiling, links | - | +| | visualizations, lens, controls, dashboard, discover, links | - | | | discover, @kbn/reporting-public | - | | | data, discover, imageEmbeddable, embeddable | - | | | @kbn/core-plugins-browser-internal, @kbn/core-root-browser-internal, home, savedObjects, unifiedSearch, visualizations, fileUpload, dashboardEnhanced, transform, dashboard, discover, dataVisualizer | - | | | @kbn/core-saved-objects-browser-mocks, discover, @kbn/core-saved-objects-browser-internal | - | | | discover, @kbn/management-settings-field-definition | - | | | monitoring | - | -| | @kbn/core-saved-objects-api-browser, @kbn/core, savedObjects, savedObjectsManagement, visualizations, savedObjectsTagging, eventAnnotation, lens, graph, dashboard, savedObjectsTaggingOss, kibanaUtils, expressions, data, embeddable, uiActionsEnhanced, controls, canvas, maps, dashboardEnhanced, globalSearchProviders | - | +| | @kbn/core-saved-objects-api-browser, @kbn/core, savedObjects, savedObjectsManagement, visualizations, savedObjectsTagging, eventAnnotation, lens, maps, graph, dashboard, savedObjectsTaggingOss, kibanaUtils, expressions, data, embeddable, uiActionsEnhanced, controls, canvas, dashboardEnhanced, globalSearchProviders | - | | | @kbn/core-saved-objects-browser, @kbn/core-saved-objects-browser-internal, @kbn/core, home, savedObjects, visualizations, lens, visTypeTimeseries, @kbn/core-saved-objects-browser-mocks | - | | | @kbn/core-saved-objects-browser-internal, @kbn/core-saved-objects-browser-mocks, savedObjects, dashboard | - | | | @kbn/core-saved-objects-browser-mocks, home, @kbn/core-saved-objects-browser-internal | - | @@ -111,7 +113,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | kubernetesSecurity, osquery, threatIntelligence | - | | | @kbn/core, lens, savedObjects | - | | | lens, dashboard, canvas | - | -| | lens, controls, dashboard, observabilityShared | - | +| | lens, controls, dashboard | - | | | dashboard, canvas | - | | | dashboard | - | | | embeddable, dashboard | - | @@ -123,12 +125,12 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | dataViews, dataViewManagement | - | | | dataViews, dataViewManagement | - | | | maps | - | -| | savedSearch, visualizations, lens, maps | - | | | dataViewManagement | - | | | dataViewManagement | - | | | spaces, savedObjectsManagement | - | | | unifiedSearch | - | | | unifiedSearch | - | +| | savedSearch, visualizations, lens | - | | | canvas | - | | | canvas | - | | | canvas | - | @@ -144,11 +146,11 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | embeddableEnhanced | - | | | embeddableEnhanced | - | | | uiActionsEnhanced | - | -| | observabilityShared | - | | | visTypeGauge | - | | | visTypePie | - | | | visTypePie | - | | | @kbn/core-logging-server-internal, security | - | +| | observabilityShared | - | | | @kbn/react-kibana-context-styled, kibanaReact | - | | | encryptedSavedObjects | - | | | reporting | - | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index c011a0954604b..4752ee8c2b541 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -360,7 +360,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [esql_theme.ts](https://github.com/elastic/kibana/tree/main/packages/kbn-monaco/src/esql/lib/esql_theme.ts#:~:text=darkMode), [esql_theme.ts](https://github.com/elastic/kibana/tree/main/packages/kbn-monaco/src/esql/lib/esql_theme.ts#:~:text=darkMode), [esql_theme.ts](https://github.com/elastic/kibana/tree/main/packages/kbn-monaco/src/esql/lib/esql_theme.ts#:~:text=darkMode), [esql_theme.ts](https://github.com/elastic/kibana/tree/main/packages/kbn-monaco/src/esql/lib/esql_theme.ts#:~:text=darkMode), [esql_theme.ts](https://github.com/elastic/kibana/tree/main/packages/kbn-monaco/src/esql/lib/esql_theme.ts#:~:text=darkMode), [theme.ts](https://github.com/elastic/kibana/tree/main/packages/kbn-monaco/src/console/theme.ts#:~:text=darkMode), [theme.ts](https://github.com/elastic/kibana/tree/main/packages/kbn-monaco/src/console/theme.ts#:~:text=darkMode) | - | +| | [esql_theme.ts](https://github.com/elastic/kibana/tree/main/packages/kbn-monaco/src/esql/lib/esql_theme.ts#:~:text=darkMode), [esql_theme.ts](https://github.com/elastic/kibana/tree/main/packages/kbn-monaco/src/esql/lib/esql_theme.ts#:~:text=darkMode), [theme.ts](https://github.com/elastic/kibana/tree/main/packages/kbn-monaco/src/console/theme.ts#:~:text=darkMode), [theme.ts](https://github.com/elastic/kibana/tree/main/packages/kbn-monaco/src/console/theme.ts#:~:text=darkMode) | - | @@ -566,6 +566,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| +| | [installation_stats_collector.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_defend/server/lib/telemetry/collectors/installation_stats_collector.ts#:~:text=policy_id), [mocks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_defend/public/test/mocks.ts#:~:text=policy_id), [mocks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_defend/public/test/mocks.ts#:~:text=policy_id) | - | +| | [installation_stats_collector.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_defend/server/lib/telemetry/collectors/installation_stats_collector.ts#:~:text=policy_id), [mocks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_defend/public/test/mocks.ts#:~:text=policy_id), [mocks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_defend/public/test/mocks.ts#:~:text=policy_id) | - | | | [setup_routes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_defend/server/routes/setup_routes.ts#:~:text=authc), [setup_routes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cloud_defend/server/routes/setup_routes.ts#:~:text=authc) | - | @@ -655,7 +657,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [get_display_value.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/query/filter_manager/lib/get_display_value.ts#:~:text=title), [inspector_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/search_source/inspect/inspector_stats.ts#:~:text=title), [response_writer.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/tabify/response_writer.ts#:~:text=title), [field.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/param_types/field.ts#:~:text=title), [agg_config.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/agg_config.test.ts#:~:text=title), [_terms_other_bucket_helper.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts#:~:text=title), [multi_terms.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/multi_terms.test.ts#:~:text=title), [multi_terms.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/multi_terms.test.ts#:~:text=title), [rare_terms.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/rare_terms.test.ts#:~:text=title), [terms.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/terms.test.ts#:~:text=title)+ 2 more | - | +| | [inspector_stats.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/search_source/inspect/inspector_stats.ts#:~:text=title), [response_writer.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/tabify/response_writer.ts#:~:text=title), [field.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/param_types/field.ts#:~:text=title), [get_display_value.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/query/filter_manager/lib/get_display_value.ts#:~:text=title), [agg_config.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/agg_config.test.ts#:~:text=title), [_terms_other_bucket_helper.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/_terms_other_bucket_helper.test.ts#:~:text=title), [multi_terms.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/multi_terms.test.ts#:~:text=title), [multi_terms.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/multi_terms.test.ts#:~:text=title), [rare_terms.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/rare_terms.test.ts#:~:text=title), [terms.test.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/search/aggs/buckets/terms.test.ts#:~:text=title)+ 2 more | - | | | [session_service.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/server/search/session/session_service.ts#:~:text=authc) | - | | | [data_table.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/utils/table_inspector_view/components/data_table.tsx#:~:text=executeTriggerActions), [data_table.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/data/public/utils/table_inspector_view/components/data_table.tsx#:~:text=executeTriggerActions) | - | | | [persistable_state.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/query/filters/persistable_state.ts#:~:text=SavedObjectReference), [persistable_state.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/query/filters/persistable_state.ts#:~:text=SavedObjectReference), [persistable_state.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/query/filters/persistable_state.ts#:~:text=SavedObjectReference), [persistable_state.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/query/persistable_state.ts#:~:text=SavedObjectReference), [persistable_state.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/query/persistable_state.ts#:~:text=SavedObjectReference), [persistable_state.ts](https://github.com/elastic/kibana/tree/main/src/plugins/data/common/query/persistable_state.ts#:~:text=SavedObjectReference) | - | @@ -773,10 +775,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [find_or_create_data_view.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/public/applications/analytics/utils/find_or_create_data_view.ts#:~:text=title), [analytics_collection_explore_table_formulas.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/public/applications/analytics/components/analytics_collection_view/analytics_collection_explore_table_formulas.ts#:~:text=title), [find_or_create_data_view.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/public/applications/analytics/utils/find_or_create_data_view.test.ts#:~:text=title) | - | | | [account_settings.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.tsx#:~:text=uiApi), [account_settings.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.tsx#:~:text=uiApi), [account_settings.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.tsx#:~:text=uiApi), [account_settings.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.tsx#:~:text=uiApi) | - | | | [check_access.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/lib/check_access.ts#:~:text=authz), [check_access.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/lib/check_access.ts#:~:text=authz), [check_access.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/lib/check_access.ts#:~:text=authz) | - | -| | [create_api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/lib/indices/create_api_key.ts#:~:text=authc), [api_keys.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts#:~:text=authc), [api_keys.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts#:~:text=authc), [create_api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/lib/indices/create_api_key.ts#:~:text=authc), [api_keys.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts#:~:text=authc), [api_keys.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts#:~:text=authc) | - | +| | [create_api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/lib/indices/create_api_key.ts#:~:text=authc), [api_keys.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts#:~:text=authc), [create_api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/lib/indices/create_api_key.ts#:~:text=authc), [api_keys.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts#:~:text=authc) | - | | | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/plugin.ts#:~:text=legacy) | - | | | [telemetry.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/collectors/lib/telemetry.ts#:~:text=SavedObjectAttributes), [telemetry.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/collectors/lib/telemetry.ts#:~:text=SavedObjectAttributes), [telemetry.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/collectors/lib/telemetry.ts#:~:text=SavedObjectAttributes), [telemetry.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/collectors/lib/telemetry.ts#:~:text=SavedObjectAttributes), [telemetry.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/collectors/lib/telemetry.ts#:~:text=SavedObjectAttributes), [telemetry.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/server/collectors/lib/telemetry.ts#:~:text=SavedObjectAttributes) | - | -| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/public/applications/index.tsx#:~:text=authc), [roles_empty_prompt.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.tsx#:~:text=authc), [roles_empty_prompt.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.tsx#:~:text=authc), [account_settings.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.tsx#:~:text=authc), [account_settings.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.tsx#:~:text=authc) | - | @@ -859,7 +860,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [use_get_logs_discover_link.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/hooks/use_get_logs_discover_link.tsx#:~:text=indexPatternId) | - | | | [agent_policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/common/services/agent_policy_config.test.ts#:~:text=mode), [agent_policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/common/services/agent_policy_config.test.ts#:~:text=mode), [agent_policy_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/services/agent_policy_watch.test.ts#:~:text=mode), [agent_policy_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/services/agent_policy_watch.test.ts#:~:text=mode) | 8.8.0 | | | [agent_policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/common/services/agent_policy_config.test.ts#:~:text=mode), [agent_policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/common/services/agent_policy_config.test.ts#:~:text=mode), [agent_policy_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/services/agent_policy_watch.test.ts#:~:text=mode), [agent_policy_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/services/agent_policy_watch.test.ts#:~:text=mode) | 8.8.0 | -| | [security.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/services/security/security.ts#:~:text=authc), [transform_api_keys.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/services/api_keys/transform_api_keys.ts#:~:text=authc), [security.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/services/api_keys/security.ts#:~:text=authc), [handlers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts#:~:text=authc), [handlers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts#:~:text=authc), [handlers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts#:~:text=authc), [handlers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts#:~:text=authc), [handlers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts#:~:text=authc), [handlers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/routes/epm/handlers.ts#:~:text=authc), [handlers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/routes/epm/handlers.ts#:~:text=authc)+ 36 more | - | +| | [security.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/services/api_keys/security.ts#:~:text=authc), [security.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/services/security/security.ts#:~:text=authc), [transform_api_keys.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/services/api_keys/transform_api_keys.ts#:~:text=authc), [handlers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/routes/epm/handlers.ts#:~:text=authc), [handlers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/routes/epm/handlers.ts#:~:text=authc), [handlers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/routes/epm/handlers.ts#:~:text=authc), [handlers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/routes/epm/handlers.ts#:~:text=authc), [handlers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/routes/epm/handlers.ts#:~:text=authc), [handlers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts#:~:text=authc), [handlers.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts#:~:text=authc)+ 36 more | - | | | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/public/applications/integrations/index.tsx#:~:text=appBasePath) | 8.8.0 | | | [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/saved_objects/index.ts#:~:text=migrations), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/saved_objects/index.ts#:~:text=migrations), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/saved_objects/index.ts#:~:text=migrations), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/saved_objects/index.ts#:~:text=migrations), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/saved_objects/index.ts#:~:text=migrations) | - | | | [audit_logging.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/services/audit_logging.ts#:~:text=audit), [audit_logging.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/fleet/server/services/audit_logging.ts#:~:text=audit) | - | @@ -1067,10 +1068,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit) | - | | | [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit), [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=flattenHit) | - | | | [es_tooltip_property.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.test.ts#:~:text=title) | - | -| | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/plugin.ts#:~:text=registerSavedObjectToPanelMethod) | - | -| | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/plugin.ts#:~:text=registerEmbeddableFactory) | - | -| | [map_attribute_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/map_attribute_service.ts#:~:text=ResolvedSimpleSavedObject), [map_attribute_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/map_attribute_service.ts#:~:text=ResolvedSimpleSavedObject), [map_attribute_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/map_attribute_service.ts#:~:text=ResolvedSimpleSavedObject), [map_attribute_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/map_attribute_service.ts#:~:text=ResolvedSimpleSavedObject) | - | -| | [references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/common/migrations/references.ts#:~:text=SavedObjectReference), [references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/common/migrations/references.ts#:~:text=SavedObjectReference), [references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/common/migrations/references.ts#:~:text=SavedObjectReference), [references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/common/migrations/references.ts#:~:text=SavedObjectReference), [references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/common/migrations/references.ts#:~:text=SavedObjectReference), [map_attribute_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/map_attribute_service.ts#:~:text=SavedObjectReference), [map_attribute_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/map_attribute_service.ts#:~:text=SavedObjectReference) | - | +| | [load_from_library.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/routes/map_page/saved_map/load_from_library.ts#:~:text=ResolvedSimpleSavedObject), [load_from_library.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/routes/map_page/saved_map/load_from_library.ts#:~:text=ResolvedSimpleSavedObject), [load_from_library.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/routes/map_page/saved_map/load_from_library.ts#:~:text=ResolvedSimpleSavedObject), [load_from_library.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/routes/map_page/saved_map/load_from_library.ts#:~:text=ResolvedSimpleSavedObject) | - | +| | [load_from_library.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/routes/map_page/saved_map/load_from_library.ts#:~:text=SavedObjectReference), [load_from_library.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/routes/map_page/saved_map/load_from_library.ts#:~:text=SavedObjectReference), [save_to_library.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/routes/map_page/saved_map/save_to_library.ts#:~:text=SavedObjectReference), [save_to_library.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/routes/map_page/saved_map/save_to_library.ts#:~:text=SavedObjectReference), [references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/common/migrations/references.ts#:~:text=SavedObjectReference), [references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/common/migrations/references.ts#:~:text=SavedObjectReference), [references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/common/migrations/references.ts#:~:text=SavedObjectReference), [references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/common/migrations/references.ts#:~:text=SavedObjectReference), [references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/common/migrations/references.ts#:~:text=SavedObjectReference) | - | | | [setup_saved_objects.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/server/saved_objects/setup_saved_objects.ts#:~:text=migrations) | - | | | [setup_saved_objects.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/server/saved_objects/setup_saved_objects.ts#:~:text=convertToMultiNamespaceTypeVersion) | - | | | [es_search_source.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx#:~:text=authc) | - | @@ -1138,7 +1137,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [profiling_embeddable.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/profiling_embeddable.tsx#:~:text=getEmbeddableFactory), [embeddable_profiling_search_bar.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_profiling_search_bar.tsx#:~:text=getEmbeddableFactory) | - | | | [header_menu_portal.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/observability_shared/public/components/header_menu/header_menu_portal.tsx#:~:text=toMountPoint), [header_menu_portal.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/observability_shared/public/components/header_menu/header_menu_portal.tsx#:~:text=toMountPoint) | - | @@ -1150,6 +1148,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [read_pack_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts#:~:text=migrationVersion) | - | | | [read_pack_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts#:~:text=migrationVersion), [read_pack_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts#:~:text=migrationVersion), [read_pack_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts#:~:text=migrationVersion), [read_pack_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/server/routes/pack/read_pack_route.ts#:~:text=migrationVersion) | - | | | [pack_queries_status_table.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx#:~:text=indexPatternId), [view_results_in_discover.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/discover/view_results_in_discover.tsx#:~:text=indexPatternId), [use_discover_link.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/common/hooks/use_discover_link.tsx#:~:text=indexPatternId) | - | +| | [osquery_managed_policy_create_import_extension.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx#:~:text=policy_id), [osquery_managed_policy_create_import_extension.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx#:~:text=policy_id), [osquery_managed_policy_create_import_extension.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx#:~:text=policy_id), [osquery_managed_policy_create_import_extension.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx#:~:text=policy_id), [osquery_managed_policy_create_import_extension.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx#:~:text=policy_id), [osquery_managed_policy_create_import_extension.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx#:~:text=policy_id), [osquery_managed_policy_create_import_extension.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx#:~:text=policy_id), [osquery_managed_policy_create_import_extension.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx#:~:text=policy_id), [osquery_managed_policy_create_import_extension.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx#:~:text=policy_id) | - | +| | [osquery_managed_policy_create_import_extension.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx#:~:text=policy_id), [osquery_managed_policy_create_import_extension.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx#:~:text=policy_id), [osquery_managed_policy_create_import_extension.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx#:~:text=policy_id), [osquery_managed_policy_create_import_extension.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx#:~:text=policy_id), [osquery_managed_policy_create_import_extension.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx#:~:text=policy_id), [osquery_managed_policy_create_import_extension.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx#:~:text=policy_id), [osquery_managed_policy_create_import_extension.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx#:~:text=policy_id), [osquery_managed_policy_create_import_extension.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx#:~:text=policy_id), [osquery_managed_policy_create_import_extension.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx#:~:text=policy_id) | - | | | [create_action_service.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/server/handlers/action/create_action_service.ts#:~:text=license%24) | 8.8.0 | | | [create_live_query_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts#:~:text=authc), [create_saved_query_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/server/routes/saved_query/create_saved_query_route.ts#:~:text=authc), [update_saved_query_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts#:~:text=authc), [create_pack_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts#:~:text=authc), [update_pack_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/server/routes/pack/update_pack_route.ts#:~:text=authc), [update_assets_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/server/routes/asset/update_assets_route.ts#:~:text=authc), [create_live_query_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts#:~:text=authc), [create_saved_query_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/server/routes/saved_query/create_saved_query_route.ts#:~:text=authc), [update_saved_query_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/server/routes/saved_query/update_saved_query_route.ts#:~:text=authc), [create_pack_route.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/server/routes/pack/create_pack_route.ts#:~:text=authc)+ 2 more | - | | | [add_to_timeline_button.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/osquery/public/timelines/add_to_timeline_button.tsx#:~:text=getHoverActions) | - | @@ -1178,7 +1178,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [register_embeddables.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/profiling/public/embeddables/register_embeddables.ts#:~:text=registerEmbeddableFactory), [register_embeddables.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/profiling/public/embeddables/register_embeddables.ts#:~:text=registerEmbeddableFactory), [register_embeddables.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/profiling/public/embeddables/register_embeddables.ts#:~:text=registerEmbeddableFactory), [register_embeddables.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/profiling/public/embeddables/register_embeddables.ts#:~:text=registerEmbeddableFactory) | - | | | [license_context.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/profiling/public/components/contexts/license/license_context.tsx#:~:text=license%24) | 8.8.0 | @@ -1350,6 +1349,8 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch), [wrap_search_source_client.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_search_source_client.test.ts#:~:text=fetch) | - | | | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/hooks/eql/api.ts#:~:text=options) | - | | | [create_sourcerer_data_view.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/sourcerer/containers/create_sourcerer_data_view.ts#:~:text=title), [create_sourcerer_data_view.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/sourcerer/containers/create_sourcerer_data_view.ts#:~:text=title), [create_sourcerer_data_view.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/sourcerer/containers/create_sourcerer_data_view.ts#:~:text=title), [validators.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/eql_query_bar/validators.ts#:~:text=title) | - | +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx#:~:text=policy_id), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx#:~:text=policy_id), [fleet_services.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts#:~:text=policy_id), [fleet_services.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts#:~:text=policy_id), [endpoint_metadata_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.test.ts#:~:text=policy_id), [endpoint_package_policies.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/endpoint_package_policies.test.ts#:~:text=policy_id), [policy_settings_middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_settings_middleware.ts#:~:text=policy_id), [policy_settings_middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_settings_middleware.ts#:~:text=policy_id), [index.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts#:~:text=policy_id) | - | +| | [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx#:~:text=policy_id), [index.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx#:~:text=policy_id), [fleet_services.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts#:~:text=policy_id), [fleet_services.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts#:~:text=policy_id), [endpoint_metadata_service.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.test.ts#:~:text=policy_id), [endpoint_package_policies.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/endpoint_package_policies.test.ts#:~:text=policy_id), [policy_settings_middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_settings_middleware.ts#:~:text=policy_id), [policy_settings_middleware.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_settings_middleware.ts#:~:text=policy_id), [index.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts#:~:text=policy_id) | - | | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 7 more | 8.8.0 | | | [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [policy_config.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/license/policy_config.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [fleet_integration.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [create_default_policy.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode), [license_watch.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts#:~:text=mode)+ 7 more | 8.8.0 | | | [get_is_alert_suppression_active.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_is_alert_suppression_active.ts#:~:text=license%24), [create_threat_signals.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/indicator_match/threat_mapping/create_threat_signals.ts#:~:text=license%24), [query.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/query/query.ts#:~:text=license%24), [threshold.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/threshold/threshold.ts#:~:text=license%24), [get_is_alert_suppression_active.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_is_alert_suppression_active.test.ts#:~:text=license%24), [get_is_alert_suppression_active.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_is_alert_suppression_active.test.ts#:~:text=license%24), [get_is_alert_suppression_active.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_is_alert_suppression_active.test.ts#:~:text=license%24) | 8.8.0 | @@ -1371,16 +1372,16 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [links.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/links.ts#:~:text=authc), [hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts#:~:text=authc) | - | | | [use_bulk_get_user_profiles.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/user_profiles/use_bulk_get_user_profiles.tsx#:~:text=userProfiles), [use_get_current_user_profile.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/common/components/user_profiles/use_get_current_user_profile.tsx#:~:text=userProfiles), [overlay.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/assistant/overlay.tsx#:~:text=userProfiles) | - | | | [request_context_factory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/request_context_factory.ts#:~:text=audit), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/plugin.ts#:~:text=audit) | - | -| | [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [trusted_app_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [trusted_app_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID)+ 25 more | - | +| | [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID), [trusted_app_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_ID)+ 25 more | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_NAME), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_NAME) | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/trusted_apps/index.ts#:~:text=ENDPOINT_TRUSTED_APPS_LIST_DESCRIPTION) | - | -| | [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [event_filter_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [event_filter_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID)+ 24 more | - | +| | [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID), [form.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form.tsx#:~:text=ENDPOINT_EVENT_FILTERS_LIST_ID)+ 24 more | - | | | [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_NAME) | - | | | [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [create_event_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/event_filters/index.ts#:~:text=ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION) | - | -| | [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [exceptions_list_item_generator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/endpoint/data_generators/exceptions_list_item_generator.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID)+ 8 more | - | +| | [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [host_isolation_exceptions_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID), [exceptions_list_item_generator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/common/endpoint/data_generators/exceptions_list_item_generator.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID)+ 8 more | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_NAME), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/host_isolation_exceptions/index.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/host_isolation_exceptions/index.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_NAME) | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_DESCRIPTION), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/host_isolation_exceptions/index.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/host_isolation_exceptions/index.ts#:~:text=ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_DESCRIPTION) | - | -| | [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [blocklist_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [blocklist_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [blocklists_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [blocklists_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID)+ 8 more | - | +| | [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [lists.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [blocklists_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [blocklists_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [blocklists_api_client.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [policy_hooks.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID), [blocklist_validator.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_ID)+ 8 more | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_NAME), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/blocklists/index.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_NAME), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/blocklists/index.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_NAME) | - | | | [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION), [constants.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/blocklists/index.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION), [index.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/scripts/endpoint/blocklists/index.ts#:~:text=ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION) | - | | | [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode), [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode), [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode), [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode), [use_colors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/security_solution/public/resolver/view/use_colors.ts#:~:text=darkMode) | - | @@ -1391,7 +1392,7 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [api_key_routes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts#:~:text=authc), [api_key_routes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts#:~:text=authc), [indices_routes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/serverless_search/server/routes/indices_routes.ts#:~:text=authc), [api_key_routes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts#:~:text=authc), [api_key_routes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts#:~:text=authc), [indices_routes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/serverless_search/server/routes/indices_routes.ts#:~:text=authc) | - | +| | [api_key_routes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts#:~:text=authc), [api_key_routes.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts#:~:text=authc) | - | | | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/serverless_search/public/plugin.ts#:~:text=authc) | - | @@ -1454,6 +1455,8 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | ---------------|-----------|-----------| | | [common.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/synthetics/server/alert_rules/common.ts#:~:text=alertFactory), [message_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/synthetics/server/alert_rules/tls_rule/message_utils.ts#:~:text=alertFactory), [tls_rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/synthetics/server/alert_rules/tls_rule/tls_rule.ts#:~:text=alertFactory), [monitor_status_rule.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts#:~:text=alertFactory) | - | | | [stderr_logs.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/synthetics/public/apps/synthetics/components/common/components/stderr_logs.tsx#:~:text=indexPatternId) | - | +| | [synthetics_private_location.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts#:~:text=policy_id) | - | +| | [synthetics_private_location.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts#:~:text=policy_id) | - | | | [get_api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/get_api_key.ts#:~:text=authc), [get_api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/get_api_key.ts#:~:text=authc), [get_api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/get_api_key.ts#:~:text=authc), [get_api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/get_api_key.ts#:~:text=authc), [get_api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/get_api_key.ts#:~:text=authc), [get_api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/get_api_key.ts#:~:text=authc), [enablement.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/enablement.ts#:~:text=authc), [enablement.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/synthetics/server/routes/synthetics_service/enablement.ts#:~:text=authc), [get_api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/get_api_key.ts#:~:text=authc), [get_api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/synthetics/server/synthetics_service/get_api_key.ts#:~:text=authc)+ 6 more | - | | | [synthetics_monitor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/synthetics/server/saved_objects/synthetics_monitor.ts#:~:text=migrations) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index c04ca114b7c13..c99f47c97e352 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -127,9 +127,9 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | Plugin | Deprecated API | Reference location(s) | Remove By | | --------|-------|-----------|-----------| -| profiling | | [license_context.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/profiling/public/components/contexts/license/license_context.tsx#:~:text=license%24), [license_context.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/public/context/license/license_context.tsx#:~:text=license%24) | 8.8.0 | | apm | | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/public/plugin.ts#:~:text=environment) | 8.8.0 | | apm | | [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/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/observability_solution/apm/public/context/license/license_context.tsx#:~:text=license%24), [license_context.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/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/observability_solution/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/common/license_check.test.ts#:~:text=mode), [license_check.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/apm/common/license_check.test.ts#:~:text=mode)+ 2 more | 8.8.0 | diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 0c0b97f10e5bf..35498a89f5b17 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.devdocs.json b/api_docs/discover.devdocs.json index 1e2946e6fa780..26f8c41e05c83 100644 --- a/api_docs/discover.devdocs.json +++ b/api_docs/discover.devdocs.json @@ -1707,7 +1707,7 @@ "tags": [], "label": "DiscoverSetup", "description": [], - "path": "src/plugins/discover/public/plugin.tsx", + "path": "src/plugins/discover/public/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1730,7 +1730,7 @@ }, " | undefined" ], - "path": "src/plugins/discover/public/plugin.tsx", + "path": "src/plugins/discover/public/types.ts", "deprecated": false, "trackAdoption": false }, @@ -1744,7 +1744,7 @@ "signature": [ "() => void" ], - "path": "src/plugins/discover/public/plugin.tsx", + "path": "src/plugins/discover/public/types.ts", "deprecated": false, "trackAdoption": false, "children": [], @@ -1760,7 +1760,7 @@ "signature": [ "(projectNavId: string, options: { enabled: boolean; showLogsExplorerTabs: boolean; }) => void" ], - "path": "src/plugins/discover/public/plugin.tsx", + "path": "src/plugins/discover/public/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1774,7 +1774,7 @@ "signature": [ "string" ], - "path": "src/plugins/discover/public/plugin.tsx", + "path": "src/plugins/discover/public/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -1789,7 +1789,7 @@ "signature": [ "{ enabled: boolean; showLogsExplorerTabs: boolean; }" ], - "path": "src/plugins/discover/public/plugin.tsx", + "path": "src/plugins/discover/public/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -1808,7 +1808,7 @@ "tags": [], "label": "DiscoverStart", "description": [], - "path": "src/plugins/discover/public/plugin.tsx", + "path": "src/plugins/discover/public/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1831,7 +1831,7 @@ }, " | undefined" ], - "path": "src/plugins/discover/public/plugin.tsx", + "path": "src/plugins/discover/public/types.ts", "deprecated": false, "trackAdoption": false }, @@ -1861,7 +1861,7 @@ }, ">" ], - "path": "src/plugins/discover/public/plugin.tsx", + "path": "src/plugins/discover/public/types.ts", "deprecated": false, "trackAdoption": false } diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 58b7dfb6fdde3..64e53552a24f3 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2024-06-11 +date: 2024-06-19 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 46c9f2ca8c969..5583e7fac0809 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/discover_shared.mdx b/api_docs/discover_shared.mdx index 81d5bc492d7da..00441fc0a8e03 100644 --- a/api_docs/discover_shared.mdx +++ b/api_docs/discover_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverShared title: "discoverShared" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverShared plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverShared'] --- import discoverSharedObj from './discover_shared.devdocs.json'; diff --git a/api_docs/ecs_data_quality_dashboard.mdx b/api_docs/ecs_data_quality_dashboard.mdx index 5da5ea6e98077..04dbf12c64752 100644 --- a/api_docs/ecs_data_quality_dashboard.mdx +++ b/api_docs/ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ecsDataQualityDashboard title: "ecsDataQualityDashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the ecsDataQualityDashboard plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ecsDataQualityDashboard'] --- import ecsDataQualityDashboardObj from './ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/elastic_assistant.devdocs.json b/api_docs/elastic_assistant.devdocs.json index 73d9cc8d736fc..e543e43a16801 100644 --- a/api_docs/elastic_assistant.devdocs.json +++ b/api_docs/elastic_assistant.devdocs.json @@ -1469,6 +1469,21 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "elasticAssistant", + "id": "def-server.AssistantToolParams.kbDataClient", + "type": "Object", + "tags": [], + "label": "kbDataClient", + "description": [], + "signature": [ + "AIAssistantKnowledgeBaseDataClient", + " | undefined" + ], + "path": "x-pack/plugins/elastic_assistant/server/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "elasticAssistant", "id": "def-server.AssistantToolParams.langChainTimeout", @@ -1502,6 +1517,26 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "elasticAssistant", + "id": "def-server.AssistantToolParams.logger", + "type": "Object", + "tags": [], + "label": "logger", + "description": [], + "signature": [ + { + "pluginId": "@kbn/logging", + "scope": "common", + "docId": "kibKbnLoggingPluginApi", + "section": "def-common.Logger", + "text": "Logger" + } + ], + "path": "x-pack/plugins/elastic_assistant/server/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "elasticAssistant", "id": "def-server.AssistantToolParams.modelExists", diff --git a/api_docs/elastic_assistant.mdx b/api_docs/elastic_assistant.mdx index 70aefde656da9..2d685a960e6cc 100644 --- a/api_docs/elastic_assistant.mdx +++ b/api_docs/elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/elasticAssistant title: "elasticAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the elasticAssistant plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'elasticAssistant'] --- import elasticAssistantObj from './elastic_assistant.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/ | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 46 | 0 | 32 | 0 | +| 48 | 0 | 34 | 1 | ## Server diff --git a/api_docs/embeddable.devdocs.json b/api_docs/embeddable.devdocs.json index 01c91c22b075e..8c823689d0bd2 100644 --- a/api_docs/embeddable.devdocs.json +++ b/api_docs/embeddable.devdocs.json @@ -14041,10 +14041,6 @@ { "plugin": "lens", "path": "x-pack/plugins/lens/public/plugin.ts" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/plugin.ts" } ], "returnComment": [], @@ -14294,30 +14290,10 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/public/plugin.tsx" }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/plugin.ts" - }, { "plugin": "discover", "path": "src/plugins/discover/public/plugin.tsx" }, - { - "plugin": "profiling", - "path": "x-pack/plugins/observability_solution/profiling/public/embeddables/register_embeddables.ts" - }, - { - "plugin": "profiling", - "path": "x-pack/plugins/observability_solution/profiling/public/embeddables/register_embeddables.ts" - }, - { - "plugin": "profiling", - "path": "x-pack/plugins/observability_solution/profiling/public/embeddables/register_embeddables.ts" - }, - { - "plugin": "profiling", - "path": "x-pack/plugins/observability_solution/profiling/public/embeddables/register_embeddables.ts" - }, { "plugin": "links", "path": "src/plugins/links/public/plugin.ts" @@ -14712,14 +14688,6 @@ "plugin": "dashboard", "path": "src/plugins/dashboard/public/dashboard_container/external_api/dashboard_renderer.tsx" }, - { - "plugin": "observabilityShared", - "path": "x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/profiling_embeddable.tsx" - }, - { - "plugin": "observabilityShared", - "path": "x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_profiling_search_bar.tsx" - }, { "plugin": "dashboard", "path": "src/plugins/dashboard/public/services/embeddable/embeddable.stub.ts" diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index dd51d3c56f218..6a9cebf1365b0 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2024-06-11 +date: 2024-06-19 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 1edc8cf04c528..345548c4b57bf 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2024-06-11 +date: 2024-06-19 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 850e06b3a1bbf..e9373181cd163 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2024-06-11 +date: 2024-06-19 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 3436032bbd70e..c9eee95cb72b9 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2024-06-11 +date: 2024-06-19 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 8df193240e7ef..e831609643e15 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/esql_data_grid.mdx b/api_docs/esql_data_grid.mdx index 350b93cb83bdb..bd96bca5d8a5e 100644 --- a/api_docs/esql_data_grid.mdx +++ b/api_docs/esql_data_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esqlDataGrid title: "esqlDataGrid" image: https://source.unsplash.com/400x175/?github description: API docs for the esqlDataGrid plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esqlDataGrid'] --- import esqlDataGridObj from './esql_data_grid.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index 58909ef6dbbec..0f7fd892c5e5c 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2024-06-11 +date: 2024-06-19 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 e68c1ecda531a..025991c1fda51 100644 --- a/api_docs/event_annotation_listing.mdx +++ b/api_docs/event_annotation_listing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotationListing title: "eventAnnotationListing" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotationListing plugin -date: 2024-06-11 +date: 2024-06-19 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 601e68b12981f..f1ae9dc895fd4 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2024-06-11 +date: 2024-06-19 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 0c978ada3f509..81c45d0084cff 100644 --- a/api_docs/exploratory_view.mdx +++ b/api_docs/exploratory_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/exploratoryView title: "exploratoryView" image: https://source.unsplash.com/400x175/?github description: API docs for the exploratoryView plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'exploratoryView'] --- import exploratoryViewObj from './exploratory_view.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index b8ecb49dafc52..6359145989bed 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2024-06-11 +date: 2024-06-19 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 66df6a075aae8..88e7721ea46a3 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2024-06-11 +date: 2024-06-19 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 ad322d2db7764..ce962310cffe1 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2024-06-11 +date: 2024-06-19 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 13940d26cc592..0dbd83678d813 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2024-06-11 +date: 2024-06-19 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 0999bcea3c26b..f4b89b2c1e8fb 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2024-06-11 +date: 2024-06-19 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 bbc26cb7ce17a..13f027fff7e22 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2024-06-11 +date: 2024-06-19 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 19d922adabb56..f86f46f96ef8a 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2024-06-11 +date: 2024-06-19 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 ca6107a5b37f7..b13293aeedd00 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2024-06-11 +date: 2024-06-19 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 e08ed0b3c358f..06fbe7aea3fe6 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2024-06-11 +date: 2024-06-19 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 e1ecbe701d0f8..896a29a9180ed 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2024-06-11 +date: 2024-06-19 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 e5c9b31f6fc34..5f99e621e1423 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2024-06-11 +date: 2024-06-19 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 a3511b488a83c..b81f4fdee7ec1 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2024-06-11 +date: 2024-06-19 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 f6c2a89714c9f..0eae0e2e002e7 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2024-06-11 +date: 2024-06-19 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 9ef583f7e331b..0427130b0f0bf 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2024-06-11 +date: 2024-06-19 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 5d7cdd74a9672..bc5f7857bcc3c 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2024-06-11 +date: 2024-06-19 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 b31f052ac2ad1..8a1e28a04bf08 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/fields_metadata.mdx b/api_docs/fields_metadata.mdx index 10f8099e0bd06..4216234eb614b 100644 --- a/api_docs/fields_metadata.mdx +++ b/api_docs/fields_metadata.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldsMetadata title: "fieldsMetadata" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldsMetadata plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldsMetadata'] --- import fieldsMetadataObj from './fields_metadata.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 533bb35e45294..260ec37968424 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2024-06-11 +date: 2024-06-19 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 0030cc291e70e..b652e97a9a80c 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2024-06-11 +date: 2024-06-19 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 8036b02efd045..a557fd5e66e8a 100644 --- a/api_docs/files_management.mdx +++ b/api_docs/files_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/filesManagement title: "filesManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the filesManagement plugin -date: 2024-06-11 +date: 2024-06-19 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 9a94c33392803..9106288513ae1 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -1471,9 +1471,118 @@ "parentPluginId": "fleet", "id": "def-public.NewPackagePolicy.policy_id", "type": "string", - "tags": [], + "tags": [ + "deprecated" + ], "label": "policy_id", "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", + "deprecated": true, + "trackAdoption": false, + "references": [ + { + "plugin": "cloudDefend", + "path": "x-pack/plugins/cloud_defend/server/lib/telemetry/collectors/installation_stats_collector.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx" + }, + { + "plugin": "cloudDefend", + "path": "x-pack/plugins/cloud_defend/public/test/mocks.ts" + }, + { + "plugin": "cloudDefend", + "path": "x-pack/plugins/cloud_defend/public/test/mocks.ts" + }, + { + "plugin": "synthetics", + "path": "x-pack/plugins/observability_solution/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.test.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/endpoint_package_policies.test.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_settings_middleware.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_settings_middleware.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts" + } + ] + }, + { + "parentPluginId": "fleet", + "id": "def-public.NewPackagePolicy.policy_ids", + "type": "Array", + "tags": [], + "label": "policy_ids", + "description": [], + "signature": [ + "string[]" + ], "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", "deprecated": false, "trackAdoption": false @@ -2778,7 +2887,7 @@ "The updated Integration Policy to be merged back and included in the API call" ], "signature": [ - "{ id?: string | number | undefined; name?: string | undefined; description?: string | undefined; namespace?: string | undefined; enabled?: boolean | undefined; is_managed?: boolean | undefined; policy_id?: string | undefined; package?: ", + "{ id?: string | number | undefined; name?: string | undefined; description?: string | undefined; namespace?: string | undefined; enabled?: boolean | undefined; is_managed?: boolean | undefined; policy_id?: string | undefined; policy_ids?: string[] | undefined; package?: ", { "pluginId": "fleet", "scope": "common", @@ -3275,10 +3384,10 @@ "id": "def-public.UIExtensionsStorage.Unnamed", "type": "IndexSignature", "tags": [], - "label": "[key: string]: Partial>", + "label": "[key: string]: Partial>", "description": [], "signature": [ - "[key: string]: Partial | undefined; aggregations?: Record | undefined; }>" ], @@ -6234,7 +6349,7 @@ "section": "def-common.SavedObjectsClientContract", "text": "SavedObjectsClientContract" }, - ", ids: string[], options?: { fields?: string[] | undefined; withPackagePolicies?: boolean | undefined; ignoreMissing?: boolean | undefined; }) => Promise<", + ", ids: (string | { id: string; spaceId?: string | undefined; })[], options?: { fields?: string[] | undefined; withPackagePolicies?: boolean | undefined; ignoreMissing?: boolean | undefined; }) => Promise<", { "pluginId": "fleet", "scope": "common", @@ -6277,7 +6392,7 @@ "label": "ids", "description": [], "signature": [ - "string[]" + "(string | { id: string; spaceId?: string | undefined; })[]" ], "path": "x-pack/plugins/fleet/server/services/agent_policy.ts", "deprecated": false, @@ -9800,7 +9915,7 @@ "section": "def-common.ListWithKuery", "text": "ListWithKuery" }, - ") => Promise<", + " & { spaceId?: string | undefined; }) => Promise<", { "pluginId": "fleet", "scope": "common", @@ -9846,7 +9961,7 @@ { "parentPluginId": "fleet", "id": "def-server.PackagePolicyClient.list.$2", - "type": "Object", + "type": "CompoundType", "tags": [], "label": "options", "description": [], @@ -9857,7 +9972,8 @@ "docId": "kibFleetPluginApi", "section": "def-common.ListWithKuery", "text": "ListWithKuery" - } + }, + " & { spaceId?: string | undefined; }" ], "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", "deprecated": false, @@ -15986,7 +16102,7 @@ "section": "def-common.PackagePolicyPackage", "text": "PackagePolicyPackage" }, - " | undefined; policy_id?: string | undefined; statusCode?: number | undefined; body?: { message: string; } | undefined; }>, soClient: ", + " | undefined; policy_id?: string | undefined; policy_ids?: string[] | undefined; statusCode?: number | undefined; body?: { message: string; } | undefined; }>, soClient: ", { "pluginId": "@kbn/core-saved-objects-api-server", "scope": "common", @@ -16042,7 +16158,7 @@ "section": "def-common.PackagePolicyPackage", "text": "PackagePolicyPackage" }, - " | undefined; policy_id?: string | undefined; statusCode?: number | undefined; body?: { message: string; } | undefined; }>" + " | undefined; policy_id?: string | undefined; policy_ids?: string[] | undefined; statusCode?: number | undefined; body?: { message: string; } | undefined; }>" ], "path": "x-pack/plugins/fleet/server/types/extensions.ts", "deprecated": false, @@ -21184,6 +21300,22 @@ "path": "x-pack/plugins/fleet/common/types/models/agent.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-common.FleetServerAgent.namespaces", + "type": "Array", + "tags": [], + "label": "namespaces", + "description": [ + "\nNamespaces" + ], + "signature": [ + "string[] | undefined" + ], + "path": "x-pack/plugins/fleet/common/types/models/agent.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -23301,9 +23433,118 @@ "parentPluginId": "fleet", "id": "def-common.NewPackagePolicy.policy_id", "type": "string", - "tags": [], + "tags": [ + "deprecated" + ], "label": "policy_id", "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", + "deprecated": true, + "trackAdoption": false, + "references": [ + { + "plugin": "cloudDefend", + "path": "x-pack/plugins/cloud_defend/server/lib/telemetry/collectors/installation_stats_collector.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/public/fleet_integration/osquery_managed_policy_create_import_extension.tsx" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx" + }, + { + "plugin": "cloudDefend", + "path": "x-pack/plugins/cloud_defend/public/test/mocks.ts" + }, + { + "plugin": "cloudDefend", + "path": "x-pack/plugins/cloud_defend/public/test/mocks.ts" + }, + { + "plugin": "synthetics", + "path": "x-pack/plugins/observability_solution/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/scripts/endpoint/common/fleet_services.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/endpoint/services/metadata/endpoint_metadata_service.test.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/endpoint_package_policies.test.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_settings_middleware.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_settings_middleware.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts" + } + ] + }, + { + "parentPluginId": "fleet", + "id": "def-common.NewPackagePolicy.policy_ids", + "type": "Array", + "tags": [], + "label": "policy_ids", + "description": [], + "signature": [ + "string[]" + ], "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", "deprecated": false, "trackAdoption": false @@ -23746,6 +23987,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "fleet", + "id": "def-common.PackagePolicy.spaceId", + "type": "string", + "tags": [], + "label": "spaceId", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/fleet/common/types/models/package_policy.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "fleet", "id": "def-common.PackagePolicy.inputs", @@ -27312,7 +27567,7 @@ "section": "def-common.PackagePolicyPackage", "text": "PackagePolicyPackage" }, - " | undefined; policy_id?: string | undefined; statusCode?: number | undefined; body?: { message: string; } | undefined; }[]" + " | undefined; policy_id?: string | undefined; policy_ids?: string[] | undefined; statusCode?: number | undefined; body?: { message: string; } | undefined; }[]" ], "path": "x-pack/plugins/fleet/common/types/rest_spec/package_policy.ts", "deprecated": false, diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index 0ce6128d7ecb2..3730f61af825b 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2024-06-11 +date: 2024-06-19 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 | |-------------------|-----------|------------------------|-----------------| -| 1335 | 5 | 1214 | 71 | +| 1339 | 5 | 1217 | 72 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 1c82589bb2b5c..627f17964ac92 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2024-06-11 +date: 2024-06-19 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 0ccd28f592789..0a2c709cc164a 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2024-06-11 +date: 2024-06-19 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 02cbd47c500f7..899804f8cf78e 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2024-06-11 +date: 2024-06-19 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 eb930f9024f12..7557dc6075977 100644 --- a/api_docs/image_embeddable.mdx +++ b/api_docs/image_embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/imageEmbeddable title: "imageEmbeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the imageEmbeddable plugin -date: 2024-06-11 +date: 2024-06-19 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 0175ecc6b7b30..a4fc0e4c26f98 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index 23ead229bcadc..cf7e4b6bd480a 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index 63ee285cdb7f9..911075f5d65a2 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/ingest_pipelines.mdx b/api_docs/ingest_pipelines.mdx index d2fb0878f2d4c..4a0a9592144b9 100644 --- a/api_docs/ingest_pipelines.mdx +++ b/api_docs/ingest_pipelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ingestPipelines title: "ingestPipelines" image: https://source.unsplash.com/400x175/?github description: API docs for the ingestPipelines plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ingestPipelines'] --- import ingestPipelinesObj from './ingest_pipelines.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index ac3f81732eeaf..ebe749551fc4d 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/integration_assistant.devdocs.json b/api_docs/integration_assistant.devdocs.json new file mode 100644 index 0000000000000..94182a303ea0f --- /dev/null +++ b/api_docs/integration_assistant.devdocs.json @@ -0,0 +1,1890 @@ +{ + "id": "integrationAssistant", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [], + "setup": { + "parentPluginId": "integrationAssistant", + "id": "def-server.IntegrationAssistantPluginSetup", + "type": "Interface", + "tags": [], + "label": "IntegrationAssistantPluginSetup", + "description": [], + "path": "x-pack/plugins/integration_assistant/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "integrationAssistant", + "id": "def-server.IntegrationAssistantPluginStart", + "type": "Interface", + "tags": [], + "label": "IntegrationAssistantPluginStart", + "description": [], + "path": "x-pack/plugins/integration_assistant/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [ + { + "parentPluginId": "integrationAssistant", + "id": "def-common.BuildIntegrationRequestBody", + "type": "Type", + "tags": [], + "label": "BuildIntegrationRequestBody", + "description": [], + "signature": [ + "{ integration: { name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws_cloudwatch\" | \"aws_s3\" | \"azure_blob_storage\" | \"azure_eventhub\" | \"cloudfoundry\" | \"filestream\" | \"gcp_pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }[]; logo?: string | undefined; }; }" + ], + "path": "x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.CATEGORIZATION_GRAPH_PATH", + "type": "string", + "tags": [], + "label": "CATEGORIZATION_GRAPH_PATH", + "description": [], + "path": "x-pack/plugins/integration_assistant/common/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.CategorizationRequestBody", + "type": "Type", + "tags": [], + "label": "CategorizationRequestBody", + "description": [], + "signature": [ + "{ connectorId: string; packageName: string; rawSamples: string[]; datastreamName: string; currentPipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; }" + ], + "path": "x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.CategorizationResponse", + "type": "Type", + "tags": [], + "label": "CategorizationResponse", + "description": [], + "signature": [ + "{ results: { pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }; }" + ], + "path": "x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.CheckPipelineRequestBody", + "type": "Type", + "tags": [], + "label": "CheckPipelineRequestBody", + "description": [], + "signature": [ + "{ rawSamples: string[]; pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; }" + ], + "path": "x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.CheckPipelineResponse", + "type": "Type", + "tags": [], + "label": "CheckPipelineResponse", + "description": [], + "signature": [ + "{ pipelineResults: {}[]; errors?: {}[] | undefined; }" + ], + "path": "x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.Datastream", + "type": "Type", + "tags": [], + "label": "Datastream", + "description": [ + "\nThe datastream object." + ], + "signature": [ + "{ name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws_cloudwatch\" | \"aws_s3\" | \"azure_blob_storage\" | \"azure_eventhub\" | \"cloudfoundry\" | \"filestream\" | \"gcp_pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }" + ], + "path": "x-pack/plugins/integration_assistant/common/api/model/common_attributes.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.ECS_GRAPH_PATH", + "type": "string", + "tags": [], + "label": "ECS_GRAPH_PATH", + "description": [], + "path": "x-pack/plugins/integration_assistant/common/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.EcsMappingRequestBody", + "type": "Type", + "tags": [], + "label": "EcsMappingRequestBody", + "description": [], + "signature": [ + "{ connectorId: string; packageName: string; rawSamples: string[]; datastreamName: string; mapping?: {} | undefined; }" + ], + "path": "x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.EcsMappingResponse", + "type": "Type", + "tags": [], + "label": "EcsMappingResponse", + "description": [], + "signature": [ + "{ results: { pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; mapping: {}; }; }" + ], + "path": "x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.ESProcessorItem", + "type": "Type", + "tags": [], + "label": "ESProcessorItem", + "description": [ + "\nProcessor item for the Elasticsearch processor." + ], + "signature": [ + "{ [x: string]: ", + "ESProcessorOptions", + "; }" + ], + "path": "x-pack/plugins/integration_assistant/common/api/model/processor_attributes.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.InputType", + "type": "Type", + "tags": [], + "label": "InputType", + "description": [ + "\nThe input type for the datastream to pull logs from." + ], + "signature": [ + "\"kafka\" | \"aws_cloudwatch\" | \"aws_s3\" | \"azure_blob_storage\" | \"azure_eventhub\" | \"cloudfoundry\" | \"filestream\" | \"gcp_pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\"" + ], + "path": "x-pack/plugins/integration_assistant/common/api/model/common_attributes.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.Integration", + "type": "Type", + "tags": [], + "label": "Integration", + "description": [ + "\nThe integration object." + ], + "signature": [ + "{ name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws_cloudwatch\" | \"aws_s3\" | \"azure_blob_storage\" | \"azure_eventhub\" | \"cloudfoundry\" | \"filestream\" | \"gcp_pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }[]; logo?: string | undefined; }" + ], + "path": "x-pack/plugins/integration_assistant/common/api/model/common_attributes.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.INTEGRATION_ASSISTANT_APP_ROUTE", + "type": "string", + "tags": [], + "label": "INTEGRATION_ASSISTANT_APP_ROUTE", + "description": [], + "signature": [ + "\"/app/integration_assistant\"" + ], + "path": "x-pack/plugins/integration_assistant/common/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.INTEGRATION_ASSISTANT_BASE_PATH", + "type": "string", + "tags": [], + "label": "INTEGRATION_ASSISTANT_BASE_PATH", + "description": [], + "signature": [ + "\"/api/integration_assistant\"" + ], + "path": "x-pack/plugins/integration_assistant/common/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.INTEGRATION_BUILDER_PATH", + "type": "string", + "tags": [], + "label": "INTEGRATION_BUILDER_PATH", + "description": [], + "path": "x-pack/plugins/integration_assistant/common/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.Pipeline", + "type": "Type", + "tags": [], + "label": "Pipeline", + "description": [ + "\nThe pipeline object." + ], + "signature": [ + "{ processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }" + ], + "path": "x-pack/plugins/integration_assistant/common/api/model/common_attributes.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.PLUGIN_ID", + "type": "string", + "tags": [], + "label": "PLUGIN_ID", + "description": [], + "signature": [ + "\"integrationAssistant\"" + ], + "path": "x-pack/plugins/integration_assistant/common/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.RELATED_GRAPH_PATH", + "type": "string", + "tags": [], + "label": "RELATED_GRAPH_PATH", + "description": [], + "path": "x-pack/plugins/integration_assistant/common/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.RelatedRequestBody", + "type": "Type", + "tags": [], + "label": "RelatedRequestBody", + "description": [], + "signature": [ + "{ connectorId: string; packageName: string; rawSamples: string[]; datastreamName: string; currentPipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; }" + ], + "path": "x-pack/plugins/integration_assistant/common/api/related/related_route.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.RelatedResponse", + "type": "Type", + "tags": [], + "label": "RelatedResponse", + "description": [], + "signature": [ + "{ results: { pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }; }" + ], + "path": "x-pack/plugins/integration_assistant/common/api/related/related_route.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.TEST_PIPELINE_PATH", + "type": "string", + "tags": [], + "label": "TEST_PIPELINE_PATH", + "description": [], + "path": "x-pack/plugins/integration_assistant/common/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [ + { + "parentPluginId": "integrationAssistant", + "id": "def-common.BuildIntegrationRequestBody", + "type": "Object", + "tags": [], + "label": "BuildIntegrationRequestBody", + "description": [], + "signature": [ + "Zod.ZodObject<{ integration: Zod.ZodObject<{ name: Zod.ZodString; title: Zod.ZodString; description: Zod.ZodString; dataStreams: Zod.ZodArray, \"many\">; rawSamples: Zod.ZodArray; pipeline: Zod.ZodObject<{ name: Zod.ZodOptional; description: Zod.ZodOptional; version: Zod.ZodOptional; processors: Zod.ZodArray, \"many\">; on_failure: Zod.ZodOptional, \"many\">>; }, \"strip\", Zod.ZodTypeAny, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }>; docs: Zod.ZodArray, \"many\">; }, \"strip\", Zod.ZodTypeAny, { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws_cloudwatch\" | \"aws_s3\" | \"azure_blob_storage\" | \"azure_eventhub\" | \"cloudfoundry\" | \"filestream\" | \"gcp_pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }, { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws_cloudwatch\" | \"aws_s3\" | \"azure_blob_storage\" | \"azure_eventhub\" | \"cloudfoundry\" | \"filestream\" | \"gcp_pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }>, \"many\">; logo: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws_cloudwatch\" | \"aws_s3\" | \"azure_blob_storage\" | \"azure_eventhub\" | \"cloudfoundry\" | \"filestream\" | \"gcp_pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }[]; logo?: string | undefined; }, { name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws_cloudwatch\" | \"aws_s3\" | \"azure_blob_storage\" | \"azure_eventhub\" | \"cloudfoundry\" | \"filestream\" | \"gcp_pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }[]; logo?: string | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { integration: { name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws_cloudwatch\" | \"aws_s3\" | \"azure_blob_storage\" | \"azure_eventhub\" | \"cloudfoundry\" | \"filestream\" | \"gcp_pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }[]; logo?: string | undefined; }; }, { integration: { name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws_cloudwatch\" | \"aws_s3\" | \"azure_blob_storage\" | \"azure_eventhub\" | \"cloudfoundry\" | \"filestream\" | \"gcp_pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }[]; logo?: string | undefined; }; }>" + ], + "path": "x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.CategorizationRequestBody", + "type": "Object", + "tags": [], + "label": "CategorizationRequestBody", + "description": [], + "signature": [ + "Zod.ZodObject<{ packageName: Zod.ZodString; datastreamName: Zod.ZodString; rawSamples: Zod.ZodArray; currentPipeline: Zod.ZodObject<{ name: Zod.ZodOptional; description: Zod.ZodOptional; version: Zod.ZodOptional; processors: Zod.ZodArray, \"many\">; on_failure: Zod.ZodOptional, \"many\">>; }, \"strip\", Zod.ZodTypeAny, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }>; connectorId: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { connectorId: string; packageName: string; rawSamples: string[]; datastreamName: string; currentPipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; }, { connectorId: string; packageName: string; rawSamples: string[]; datastreamName: string; currentPipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; }>" + ], + "path": "x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.CategorizationResponse", + "type": "Object", + "tags": [], + "label": "CategorizationResponse", + "description": [], + "signature": [ + "Zod.ZodObject<{ results: Zod.ZodObject<{ docs: Zod.ZodArray, \"many\">; pipeline: Zod.ZodObject<{ name: Zod.ZodOptional; description: Zod.ZodOptional; version: Zod.ZodOptional; processors: Zod.ZodArray, \"many\">; on_failure: Zod.ZodOptional, \"many\">>; }, \"strip\", Zod.ZodTypeAny, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }, { pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }>; }, \"strip\", Zod.ZodTypeAny, { results: { pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }; }, { results: { pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }; }>" + ], + "path": "x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.CheckPipelineRequestBody", + "type": "Object", + "tags": [], + "label": "CheckPipelineRequestBody", + "description": [], + "signature": [ + "Zod.ZodObject<{ rawSamples: Zod.ZodArray; pipeline: Zod.ZodObject<{ name: Zod.ZodOptional; description: Zod.ZodOptional; version: Zod.ZodOptional; processors: Zod.ZodArray, \"many\">; on_failure: Zod.ZodOptional, \"many\">>; }, \"strip\", Zod.ZodTypeAny, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { rawSamples: string[]; pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; }, { rawSamples: string[]; pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; }>" + ], + "path": "x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.CheckPipelineResponse", + "type": "Object", + "tags": [], + "label": "CheckPipelineResponse", + "description": [], + "signature": [ + "Zod.ZodObject<{ pipelineResults: Zod.ZodArray, \"many\">; errors: Zod.ZodOptional, \"many\">>; }, \"strip\", Zod.ZodTypeAny, { pipelineResults: {}[]; errors?: {}[] | undefined; }, { pipelineResults: {}[]; errors?: {}[] | undefined; }>" + ], + "path": "x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.Datastream", + "type": "Object", + "tags": [], + "label": "Datastream", + "description": [], + "signature": [ + "Zod.ZodObject<{ name: Zod.ZodString; title: Zod.ZodString; description: Zod.ZodString; inputTypes: Zod.ZodArray, \"many\">; rawSamples: Zod.ZodArray; pipeline: Zod.ZodObject<{ name: Zod.ZodOptional; description: Zod.ZodOptional; version: Zod.ZodOptional; processors: Zod.ZodArray, \"many\">; on_failure: Zod.ZodOptional, \"many\">>; }, \"strip\", Zod.ZodTypeAny, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }>; docs: Zod.ZodArray, \"many\">; }, \"strip\", Zod.ZodTypeAny, { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws_cloudwatch\" | \"aws_s3\" | \"azure_blob_storage\" | \"azure_eventhub\" | \"cloudfoundry\" | \"filestream\" | \"gcp_pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }, { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws_cloudwatch\" | \"aws_s3\" | \"azure_blob_storage\" | \"azure_eventhub\" | \"cloudfoundry\" | \"filestream\" | \"gcp_pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }>" + ], + "path": "x-pack/plugins/integration_assistant/common/api/model/common_attributes.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.EcsMappingRequestBody", + "type": "Object", + "tags": [], + "label": "EcsMappingRequestBody", + "description": [], + "signature": [ + "Zod.ZodObject<{ packageName: Zod.ZodString; datastreamName: Zod.ZodString; rawSamples: Zod.ZodArray; mapping: Zod.ZodOptional>; connectorId: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { connectorId: string; packageName: string; rawSamples: string[]; datastreamName: string; mapping?: {} | undefined; }, { connectorId: string; packageName: string; rawSamples: string[]; datastreamName: string; mapping?: {} | undefined; }>" + ], + "path": "x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.EcsMappingResponse", + "type": "Object", + "tags": [], + "label": "EcsMappingResponse", + "description": [], + "signature": [ + "Zod.ZodObject<{ results: Zod.ZodObject<{ mapping: Zod.ZodObject<{}, \"strip\", Zod.ZodTypeAny, {}, {}>; pipeline: Zod.ZodObject<{ name: Zod.ZodOptional; description: Zod.ZodOptional; version: Zod.ZodOptional; processors: Zod.ZodArray, \"many\">; on_failure: Zod.ZodOptional, \"many\">>; }, \"strip\", Zod.ZodTypeAny, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; mapping: {}; }, { pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; mapping: {}; }>; }, \"strip\", Zod.ZodTypeAny, { results: { pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; mapping: {}; }; }, { results: { pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; mapping: {}; }; }>" + ], + "path": "x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.ESProcessorItem", + "type": "Object", + "tags": [], + "label": "ESProcessorItem", + "description": [], + "signature": [ + "Zod.ZodType<", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + ", Zod.ZodTypeDef, ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + ">" + ], + "path": "x-pack/plugins/integration_assistant/common/api/model/processor_attributes.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.InputType", + "type": "Object", + "tags": [], + "label": "InputType", + "description": [], + "signature": [ + "Zod.ZodEnum<[\"aws_cloudwatch\", \"aws_s3\", \"azure_blob_storage\", \"azure_eventhub\", \"cloudfoundry\", \"filestream\", \"gcp_pubsub\", \"gcs\", \"http_endpoint\", \"journald\", \"kafka\", \"tcp\", \"udp\"]>" + ], + "path": "x-pack/plugins/integration_assistant/common/api/model/common_attributes.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.Integration", + "type": "Object", + "tags": [], + "label": "Integration", + "description": [], + "signature": [ + "Zod.ZodObject<{ name: Zod.ZodString; title: Zod.ZodString; description: Zod.ZodString; dataStreams: Zod.ZodArray, \"many\">; rawSamples: Zod.ZodArray; pipeline: Zod.ZodObject<{ name: Zod.ZodOptional; description: Zod.ZodOptional; version: Zod.ZodOptional; processors: Zod.ZodArray, \"many\">; on_failure: Zod.ZodOptional, \"many\">>; }, \"strip\", Zod.ZodTypeAny, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }>; docs: Zod.ZodArray, \"many\">; }, \"strip\", Zod.ZodTypeAny, { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws_cloudwatch\" | \"aws_s3\" | \"azure_blob_storage\" | \"azure_eventhub\" | \"cloudfoundry\" | \"filestream\" | \"gcp_pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }, { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws_cloudwatch\" | \"aws_s3\" | \"azure_blob_storage\" | \"azure_eventhub\" | \"cloudfoundry\" | \"filestream\" | \"gcp_pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }>, \"many\">; logo: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws_cloudwatch\" | \"aws_s3\" | \"azure_blob_storage\" | \"azure_eventhub\" | \"cloudfoundry\" | \"filestream\" | \"gcp_pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }[]; logo?: string | undefined; }, { name: string; title: string; description: string; dataStreams: { name: string; title: string; description: string; inputTypes: (\"kafka\" | \"aws_cloudwatch\" | \"aws_s3\" | \"azure_blob_storage\" | \"azure_eventhub\" | \"cloudfoundry\" | \"filestream\" | \"gcp_pubsub\" | \"gcs\" | \"http_endpoint\" | \"journald\" | \"tcp\" | \"udp\")[]; rawSamples: string[]; pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }[]; logo?: string | undefined; }>" + ], + "path": "x-pack/plugins/integration_assistant/common/api/model/common_attributes.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.Pipeline", + "type": "Object", + "tags": [], + "label": "Pipeline", + "description": [], + "signature": [ + "Zod.ZodObject<{ name: Zod.ZodOptional; description: Zod.ZodOptional; version: Zod.ZodOptional; processors: Zod.ZodArray, \"many\">; on_failure: Zod.ZodOptional, \"many\">>; }, \"strip\", Zod.ZodTypeAny, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }>" + ], + "path": "x-pack/plugins/integration_assistant/common/api/model/common_attributes.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.RelatedRequestBody", + "type": "Object", + "tags": [], + "label": "RelatedRequestBody", + "description": [], + "signature": [ + "Zod.ZodObject<{ packageName: Zod.ZodString; datastreamName: Zod.ZodString; rawSamples: Zod.ZodArray; currentPipeline: Zod.ZodObject<{ name: Zod.ZodOptional; description: Zod.ZodOptional; version: Zod.ZodOptional; processors: Zod.ZodArray, \"many\">; on_failure: Zod.ZodOptional, \"many\">>; }, \"strip\", Zod.ZodTypeAny, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }>; connectorId: Zod.ZodString; }, \"strip\", Zod.ZodTypeAny, { connectorId: string; packageName: string; rawSamples: string[]; datastreamName: string; currentPipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; }, { connectorId: string; packageName: string; rawSamples: string[]; datastreamName: string; currentPipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; }>" + ], + "path": "x-pack/plugins/integration_assistant/common/api/related/related_route.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "integrationAssistant", + "id": "def-common.RelatedResponse", + "type": "Object", + "tags": [], + "label": "RelatedResponse", + "description": [], + "signature": [ + "Zod.ZodObject<{ results: Zod.ZodObject<{ docs: Zod.ZodArray, \"many\">; pipeline: Zod.ZodObject<{ name: Zod.ZodOptional; description: Zod.ZodOptional; version: Zod.ZodOptional; processors: Zod.ZodArray, \"many\">; on_failure: Zod.ZodOptional, \"many\">>; }, \"strip\", Zod.ZodTypeAny, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }, { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }>; }, \"strip\", Zod.ZodTypeAny, { pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }, { pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }>; }, \"strip\", Zod.ZodTypeAny, { results: { pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }; }, { results: { pipeline: { processors: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[]; name?: string | undefined; description?: string | undefined; version?: number | undefined; on_failure?: ", + { + "pluginId": "integrationAssistant", + "scope": "common", + "docId": "kibIntegrationAssistantPluginApi", + "section": "def-common.ESProcessorItem", + "text": "ESProcessorItem" + }, + "[] | undefined; }; docs: {}[]; }; }>" + ], + "path": "x-pack/plugins/integration_assistant/common/api/related/related_route.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ] + } +} \ No newline at end of file diff --git a/api_docs/integration_assistant.mdx b/api_docs/integration_assistant.mdx new file mode 100644 index 0000000000000..73b904ecb97c2 --- /dev/null +++ b/api_docs/integration_assistant.mdx @@ -0,0 +1,41 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibIntegrationAssistantPluginApi +slug: /kibana-dev-docs/api/integrationAssistant +title: "integrationAssistant" +image: https://source.unsplash.com/400x175/?github +description: API docs for the integrationAssistant plugin +date: 2024-06-19 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'integrationAssistant'] +--- +import integrationAssistantObj from './integration_assistant.devdocs.json'; + +A simple example of how to use core's routing services test + +Contact [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 38 | 0 | 33 | 1 | + +## Server + +### Setup + + +### Start + + +## Common + +### Objects + + +### Consts, variables and types + + diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 5a9f6593355de..871aeba226a58 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/investigate.devdocs.json b/api_docs/investigate.devdocs.json index aef706f884292..62e0f33e3181e 100644 --- a/api_docs/investigate.devdocs.json +++ b/api_docs/investigate.devdocs.json @@ -942,7 +942,13 @@ "description": [], "signature": [ ">(definition: Omit & { schema: TSchema; }, generateCallback: GenerateCallback, renderCallback: (options: WidgetRenderOptions<", { "pluginId": "investigate", @@ -981,7 +987,13 @@ "description": [], "signature": [ "(options: { parameters: (TSchema extends ", - "CompatibleJSONSchema", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.CompatibleJSONSchema", + "text": "CompatibleJSONSchema" + }, " ? ", "node_modules/ts-algebra/lib/meta-types/resolve", "$Resolve<", @@ -8350,7 +8362,13 @@ "description": [], "signature": [ "{ parameters: (TSchema extends ", - "CompatibleJSONSchema", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.CompatibleJSONSchema", + "text": "CompatibleJSONSchema" + }, " ? any : {}) & ", "GlobalWidgetParameters", "; signal: AbortSignal; }" diff --git a/api_docs/investigate.mdx b/api_docs/investigate.mdx index 022aabf6da366..18caa601857ed 100644 --- a/api_docs/investigate.mdx +++ b/api_docs/investigate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/investigate title: "investigate" image: https://source.unsplash.com/400x175/?github description: API docs for the investigate plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'investigate'] --- import investigateObj from './investigate.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index dc3ed3589b29a..767a83fd2f9b9 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_actions_types.mdx b/api_docs/kbn_actions_types.mdx index ef7c335a0388e..292449afdca28 100644 --- a/api_docs/kbn_actions_types.mdx +++ b/api_docs/kbn_actions_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-actions-types title: "@kbn/actions-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/actions-types plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/actions-types'] --- import kbnActionsTypesObj from './kbn_actions_types.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.devdocs.json b/api_docs/kbn_aiops_components.devdocs.json index 17ce5708ee83a..419bdbb09aee1 100644 --- a/api_docs/kbn_aiops_components.devdocs.json +++ b/api_docs/kbn_aiops_components.devdocs.json @@ -76,31 +76,26 @@ }, { "parentPluginId": "@kbn/aiops-components", - "id": "def-common.DocumentCountChartWithAutoAnalysisStart", + "id": "def-common.DocumentCountChartRedux", "type": "Function", "tags": [], - "label": "DocumentCountChartWithAutoAnalysisStart", + "label": "DocumentCountChartRedux", "description": [ - "\nFunctional component that renders a `DocumentCountChart` with additional properties\nmanaged by the log rate analysis state. It leverages the `useLogRateAnalysisStateContext`\nto acquire state variables like `initialAnalysisStart` and functions such as\n`setAutoRunAnalysis`. These values are then passed as props to the `DocumentCountChart`.\n" + "\nFunctional component that renders a `DocumentCountChart` with additional properties\nmanaged by the log rate analysis state. It leverages the `LogRateAnalysisReduxProvider`\nto acquire state variables like `initialAnalysisStart` and functions such as\n`setAutoRunAnalysis`. These values are then passed as props to the `DocumentCountChart`.\nThis wrapper component is necessary since the `DocumentCountChart` component is\nalso used for log pattern analysis which doesn't use redux.\n" ], "signature": [ - "(props: React.PropsWithChildren<", - { - "pluginId": "@kbn/aiops-components", - "scope": "common", - "docId": "kibKbnAiopsComponentsPluginApi", - "section": "def-common.DocumentCountChartProps", - "text": "DocumentCountChartProps" - }, - ">) => JSX.Element" + "React.FunctionComponent" ], - "path": "x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart.tsx", + "path": "x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart_redux.tsx", "deprecated": false, "trackAdoption": false, + "returnComment": [ + "The DocumentCountChart component enhanced with automatic analysis start capabilities." + ], "children": [ { "parentPluginId": "@kbn/aiops-components", - "id": "def-common.DocumentCountChartWithAutoAnalysisStart.$1", + "id": "def-common.DocumentCountChartRedux.$1", "type": "CompoundType", "tags": [], "label": "props", @@ -108,25 +103,27 @@ "- The properties passed to the DocumentCountChart component." ], "signature": [ - "React.PropsWithChildren<", - { - "pluginId": "@kbn/aiops-components", - "scope": "common", - "docId": "kibKbnAiopsComponentsPluginApi", - "section": "def-common.DocumentCountChartProps", - "text": "DocumentCountChartProps" - }, - ">" + "P & { children?: React.ReactNode; }" ], - "path": "x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart.tsx", + "path": "node_modules/@types/react/index.d.ts", "deprecated": false, - "trackAdoption": false, - "isRequired": true + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/aiops-components", + "id": "def-common.DocumentCountChartRedux.$2", + "type": "Any", + "tags": [], + "label": "context", + "description": [], + "signature": [ + "any" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false } ], - "returnComment": [ - "The DocumentCountChart component enhanced with automatic analysis start capabilities." - ], "initialIsOpen": false }, { @@ -207,45 +204,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "@kbn/aiops-components", - "id": "def-common.LogRateAnalysisStateProvider", - "type": "Function", - "tags": [], - "label": "LogRateAnalysisStateProvider", - "description": [ - "\nContext provider component that manages and provides global state for Log Rate Analysis.\nThis provider handles several pieces of state important for controlling and displaying\nlog rate analysis data, such as the control of automatic analysis runs, and the management\nof both pinned and selected significant items and groups.\n\nThe state includes mechanisms for setting initial analysis parameters, toggling analysis,\nand managing the current selection and pinned state of significant items and groups.\n" - ], - "signature": [ - "(props: React.PropsWithChildren>) => JSX.Element" - ], - "path": "x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/log_rate_analysis_state_provider.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/aiops-components", - "id": "def-common.LogRateAnalysisStateProvider.$1", - "type": "CompoundType", - "tags": [], - "label": "props", - "description": [ - "- Props object containing initial settings for the analysis,\nincluding children components to be wrapped by the Provider." - ], - "signature": [ - "React.PropsWithChildren>" - ], - "path": "x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/log_rate_analysis_state_provider.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [ - "A context provider wrapping children with access to log rate analysis state." - ], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/aiops-components", "id": "def-common.ProgressControls", @@ -253,7 +211,7 @@ "tags": [], "label": "ProgressControls", "description": [ - "\nProgressControls React Component\nComponent with ability to Run & cancel analysis\nby default uses `Baseline` and `Deviation` for the badge name\n" + "\nProgressControls React Component\nComponent with ability to run & cancel analysis\nby default uses `Baseline` and `Deviation` for the badge name\n" ], "signature": [ "(props: React.PropsWithChildren>) => JSX.Element" @@ -284,29 +242,6 @@ "The ProgressControls component." ], "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/aiops-components", - "id": "def-common.useLogRateAnalysisStateContext", - "type": "Function", - "tags": [ - "throws" - ], - "label": "useLogRateAnalysisStateContext", - "description": [ - "\nCustom hook for accessing the state of log rate analysis from the LogRateAnalysisStateContext.\nThis hook must be used within a component that is a descendant of the LogRateAnalysisStateContext provider.\n" - ], - "signature": [ - "() => LogRateAnalysisState" - ], - "path": "x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/log_rate_analysis_state_provider.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [ - "The current state of the log rate analysis." - ], - "initialIsOpen": false } ], "interfaces": [ @@ -448,14 +383,7 @@ "Optional callback for handling brush selection updates" ], "signature": [ - { - "pluginId": "@kbn/aiops-components", - "scope": "common", - "docId": "kibKbnAiopsComponentsPluginApi", - "section": "def-common.BrushSelectionUpdateHandler", - "text": "BrushSelectionUpdateHandler" - }, - " | undefined" + "BrushSelectionUpdateHandler | undefined" ], "path": "x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart.tsx", "deprecated": false, @@ -606,10 +534,10 @@ }, { "parentPluginId": "@kbn/aiops-components", - "id": "def-common.DocumentCountChartProps.setAutoRunAnalysis", + "id": "def-common.DocumentCountChartProps.setAutoRunAnalysisFn", "type": "Function", "tags": [], - "label": "setAutoRunAnalysis", + "label": "setAutoRunAnalysisFn", "description": [ "Callback to set the autoRunAnalysis flag" ], @@ -780,387 +708,10 @@ } ], "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/aiops-components", - "id": "def-common.GroupTableItem", - "type": "Interface", - "tags": [], - "label": "GroupTableItem", - "description": [ - "\nRepresents a single item in the group table." - ], - "path": "x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/aiops-components", - "id": "def-common.GroupTableItem.id", - "type": "string", - "tags": [], - "label": "id", - "description": [ - "Unique identifier for the group table item." - ], - "path": "x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/aiops-components", - "id": "def-common.GroupTableItem.docCount", - "type": "number", - "tags": [], - "label": "docCount", - "description": [ - "Document count associated with the item." - ], - "path": "x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/aiops-components", - "id": "def-common.GroupTableItem.pValue", - "type": "CompoundType", - "tags": [], - "label": "pValue", - "description": [ - "Statistical p-value indicating the significance of the item, nullable." - ], - "signature": [ - "number | null" - ], - "path": "x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/aiops-components", - "id": "def-common.GroupTableItem.uniqueItemsCount", - "type": "number", - "tags": [], - "label": "uniqueItemsCount", - "description": [ - "Count of unique items within the group." - ], - "path": "x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/aiops-components", - "id": "def-common.GroupTableItem.groupItemsSortedByUniqueness", - "type": "Array", - "tags": [], - "label": "groupItemsSortedByUniqueness", - "description": [ - "Array of items within the group, sorted by uniqueness." - ], - "signature": [ - { - "pluginId": "@kbn/aiops-components", - "scope": "common", - "docId": "kibKbnAiopsComponentsPluginApi", - "section": "def-common.GroupTableItemGroup", - "text": "GroupTableItemGroup" - }, - "[]" - ], - "path": "x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/aiops-components", - "id": "def-common.GroupTableItem.histogram", - "type": "Array", - "tags": [], - "label": "histogram", - "description": [ - "Histogram data for the significant item." - ], - "signature": [ - { - "pluginId": "@kbn/ml-agg-utils", - "scope": "common", - "docId": "kibKbnMlAggUtilsPluginApi", - "section": "def-common.SignificantItemHistogramItem", - "text": "SignificantItemHistogramItem" - }, - "[] | undefined" - ], - "path": "x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false } ], "enums": [], - "misc": [ - { - "parentPluginId": "@kbn/aiops-components", - "id": "def-common.BrushSelectionUpdateHandler", - "type": "Type", - "tags": [], - "label": "BrushSelectionUpdateHandler", - "description": [ - "\nCallback function which gets called when the brush selection has changed\n" - ], - "signature": [ - "(windowParameters: ", - { - "pluginId": "@kbn/aiops-log-rate-analysis", - "scope": "common", - "docId": "kibKbnAiopsLogRateAnalysisPluginApi", - "section": "def-common.WindowParameters", - "text": "WindowParameters" - }, - ", force: boolean, logRateAnalysisType: ", - { - "pluginId": "@kbn/aiops-log-rate-analysis", - "scope": "common", - "docId": "kibKbnAiopsLogRateAnalysisPluginApi", - "section": "def-common.LogRateAnalysisType", - "text": "LogRateAnalysisType" - }, - ") => void" - ], - "path": "x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart.tsx", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "@kbn/aiops-components", - "id": "def-common.BrushSelectionUpdateHandler.$1", - "type": "Object", - "tags": [], - "label": "windowParameters", - "description": [ - "Baseline and deviation time ranges." - ], - "signature": [ - { - "pluginId": "@kbn/aiops-log-rate-analysis", - "scope": "common", - "docId": "kibKbnAiopsLogRateAnalysisPluginApi", - "section": "def-common.WindowParameters", - "text": "WindowParameters" - } - ], - "path": "x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/aiops-components", - "id": "def-common.BrushSelectionUpdateHandler.$2", - "type": "boolean", - "tags": [], - "label": "force", - "description": [ - "Force update" - ], - "path": "x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/aiops-components", - "id": "def-common.BrushSelectionUpdateHandler.$3", - "type": "CompoundType", - "tags": [], - "label": "logRateAnalysisType", - "description": [ - "`spike` or `dip` based on median log rate bucket size" - ], - "signature": [ - "\"spike\" | \"dip\"" - ], - "path": "x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart.tsx", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/aiops-components", - "id": "def-common.GroupTableItemGroup", - "type": "Type", - "tags": [], - "label": "GroupTableItemGroup", - "description": [ - "\nType for defining attributes picked from\nSignificantItemGroupItem used in the grouped table." - ], - "signature": [ - "{ key: string; type: ", - { - "pluginId": "@kbn/ml-agg-utils", - "scope": "common", - "docId": "kibKbnMlAggUtilsPluginApi", - "section": "def-common.SignificantItemType", - "text": "SignificantItemType" - }, - "; fieldName: string; fieldValue: string | number; docCount: number; pValue: number | null; duplicate?: number | undefined; }" - ], - "path": "x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/aiops-components", - "id": "def-common.TableItemAction", - "type": "Type", - "tags": [], - "label": "TableItemAction", - "description": [ - "\nType for action columns in a table that involves SignificantItem or GroupTableItem." - ], - "signature": [ - "(", - "DisambiguateSet", - "<", - "DefaultItemEmptyButtonAction", - "<", - { - "pluginId": "@kbn/ml-agg-utils", - "scope": "common", - "docId": "kibKbnMlAggUtilsPluginApi", - "section": "def-common.SignificantItem", - "text": "SignificantItem" - }, - " | ", - { - "pluginId": "@kbn/aiops-components", - "scope": "common", - "docId": "kibKbnAiopsComponentsPluginApi", - "section": "def-common.GroupTableItem", - "text": "GroupTableItem" - }, - ">, ", - "DefaultItemIconButtonAction", - "<", - { - "pluginId": "@kbn/ml-agg-utils", - "scope": "common", - "docId": "kibKbnMlAggUtilsPluginApi", - "section": "def-common.SignificantItem", - "text": "SignificantItem" - }, - " | ", - { - "pluginId": "@kbn/aiops-components", - "scope": "common", - "docId": "kibKbnAiopsComponentsPluginApi", - "section": "def-common.GroupTableItem", - "text": "GroupTableItem" - }, - ">> & ", - "DefaultItemIconButtonAction", - "<", - { - "pluginId": "@kbn/ml-agg-utils", - "scope": "common", - "docId": "kibKbnMlAggUtilsPluginApi", - "section": "def-common.SignificantItem", - "text": "SignificantItem" - }, - " | ", - { - "pluginId": "@kbn/aiops-components", - "scope": "common", - "docId": "kibKbnAiopsComponentsPluginApi", - "section": "def-common.GroupTableItem", - "text": "GroupTableItem" - }, - ">) | (", - "DisambiguateSet", - "<", - "DefaultItemIconButtonAction", - "<", - { - "pluginId": "@kbn/ml-agg-utils", - "scope": "common", - "docId": "kibKbnMlAggUtilsPluginApi", - "section": "def-common.SignificantItem", - "text": "SignificantItem" - }, - " | ", - { - "pluginId": "@kbn/aiops-components", - "scope": "common", - "docId": "kibKbnAiopsComponentsPluginApi", - "section": "def-common.GroupTableItem", - "text": "GroupTableItem" - }, - ">, ", - "DefaultItemEmptyButtonAction", - "<", - { - "pluginId": "@kbn/ml-agg-utils", - "scope": "common", - "docId": "kibKbnMlAggUtilsPluginApi", - "section": "def-common.SignificantItem", - "text": "SignificantItem" - }, - " | ", - { - "pluginId": "@kbn/aiops-components", - "scope": "common", - "docId": "kibKbnAiopsComponentsPluginApi", - "section": "def-common.GroupTableItem", - "text": "GroupTableItem" - }, - ">> & ", - "DefaultItemEmptyButtonAction", - "<", - { - "pluginId": "@kbn/ml-agg-utils", - "scope": "common", - "docId": "kibKbnMlAggUtilsPluginApi", - "section": "def-common.SignificantItem", - "text": "SignificantItem" - }, - " | ", - { - "pluginId": "@kbn/aiops-components", - "scope": "common", - "docId": "kibKbnAiopsComponentsPluginApi", - "section": "def-common.GroupTableItem", - "text": "GroupTableItem" - }, - ">) | ", - "CustomItemAction", - "<", - { - "pluginId": "@kbn/ml-agg-utils", - "scope": "common", - "docId": "kibKbnMlAggUtilsPluginApi", - "section": "def-common.SignificantItem", - "text": "SignificantItem" - }, - " | ", - { - "pluginId": "@kbn/aiops-components", - "scope": "common", - "docId": "kibKbnAiopsComponentsPluginApi", - "section": "def-common.GroupTableItem", - "text": "GroupTableItem" - }, - ">" - ], - "path": "x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - } - ], + "misc": [], "objects": [] } } \ No newline at end of file diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index 36a796803b6f4..b5f13d0ca6c0c 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) for questi | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 51 | 0 | 0 | 0 | +| 36 | 0 | 0 | 0 | ## Common @@ -31,6 +31,3 @@ Contact [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) for questi ### Interfaces -### Consts, variables and types - - diff --git a/api_docs/kbn_aiops_log_pattern_analysis.mdx b/api_docs/kbn_aiops_log_pattern_analysis.mdx index 6bfe6e7f3ca0f..eb59d7fe3fd70 100644 --- a/api_docs/kbn_aiops_log_pattern_analysis.mdx +++ b/api_docs/kbn_aiops_log_pattern_analysis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-log-pattern-analysis title: "@kbn/aiops-log-pattern-analysis" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-log-pattern-analysis plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-log-pattern-analysis'] --- import kbnAiopsLogPatternAnalysisObj from './kbn_aiops_log_pattern_analysis.devdocs.json'; diff --git a/api_docs/kbn_aiops_log_rate_analysis.devdocs.json b/api_docs/kbn_aiops_log_rate_analysis.devdocs.json index 65a592c44fd56..011aa430f7194 100644 --- a/api_docs/kbn_aiops_log_rate_analysis.devdocs.json +++ b/api_docs/kbn_aiops_log_rate_analysis.devdocs.json @@ -577,6 +577,138 @@ } ], "interfaces": [ + { + "parentPluginId": "@kbn/aiops-log-rate-analysis", + "id": "def-common.DocumentCountStats", + "type": "Interface", + "tags": [], + "label": "DocumentCountStats", + "description": [ + "\nRepresents the document count statistics for a given time range." + ], + "path": "x-pack/packages/ml/aiops_log_rate_analysis/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/aiops-log-rate-analysis", + "id": "def-common.DocumentCountStats.interval", + "type": "number", + "tags": [], + "label": "interval", + "description": [ + "The time interval in milliseconds." + ], + "signature": [ + "number | undefined" + ], + "path": "x-pack/packages/ml/aiops_log_rate_analysis/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/aiops-log-rate-analysis", + "id": "def-common.DocumentCountStats.buckets", + "type": "Object", + "tags": [], + "label": "buckets", + "description": [ + "The document count per time bucket." + ], + "signature": [ + "{ [key: string]: number; } | undefined" + ], + "path": "x-pack/packages/ml/aiops_log_rate_analysis/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/aiops-log-rate-analysis", + "id": "def-common.DocumentCountStats.changePoint", + "type": "Object", + "tags": [], + "label": "changePoint", + "description": [ + "The change point in the document count statistics." + ], + "signature": [ + { + "pluginId": "@kbn/aiops-log-rate-analysis", + "scope": "common", + "docId": "kibKbnAiopsLogRateAnalysisPluginApi", + "section": "def-common.DocumentCountStatsChangePoint", + "text": "DocumentCountStatsChangePoint" + }, + " | undefined" + ], + "path": "x-pack/packages/ml/aiops_log_rate_analysis/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/aiops-log-rate-analysis", + "id": "def-common.DocumentCountStats.timeRangeEarliest", + "type": "number", + "tags": [], + "label": "timeRangeEarliest", + "description": [ + "The earliest timestamp in the time range." + ], + "signature": [ + "number | undefined" + ], + "path": "x-pack/packages/ml/aiops_log_rate_analysis/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/aiops-log-rate-analysis", + "id": "def-common.DocumentCountStats.timeRangeLatest", + "type": "number", + "tags": [], + "label": "timeRangeLatest", + "description": [ + "The latest timestamp in the time range." + ], + "signature": [ + "number | undefined" + ], + "path": "x-pack/packages/ml/aiops_log_rate_analysis/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/aiops-log-rate-analysis", + "id": "def-common.DocumentCountStats.totalCount", + "type": "number", + "tags": [], + "label": "totalCount", + "description": [ + "The total document count." + ], + "path": "x-pack/packages/ml/aiops_log_rate_analysis/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/aiops-log-rate-analysis", + "id": "def-common.DocumentCountStats.lastDocTimeStampMs", + "type": "number", + "tags": [], + "label": "lastDocTimeStampMs", + "description": [ + "The timestamp of the last document in the time range." + ], + "signature": [ + "number | undefined" + ], + "path": "x-pack/packages/ml/aiops_log_rate_analysis/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/aiops-log-rate-analysis", "id": "def-common.DocumentCountStatsChangePoint", @@ -645,6 +777,94 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/aiops-log-rate-analysis", + "id": "def-common.DocumentStats", + "type": "Interface", + "tags": [], + "label": "DocumentStats", + "description": [ + "\nRepresents the overall document stats." + ], + "path": "x-pack/packages/ml/aiops_log_rate_analysis/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/aiops-log-rate-analysis", + "id": "def-common.DocumentStats.sampleProbability", + "type": "number", + "tags": [], + "label": "sampleProbability", + "description": [ + "The probability of sampling." + ], + "path": "x-pack/packages/ml/aiops_log_rate_analysis/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/aiops-log-rate-analysis", + "id": "def-common.DocumentStats.totalCount", + "type": "number", + "tags": [], + "label": "totalCount", + "description": [ + "The total document count." + ], + "path": "x-pack/packages/ml/aiops_log_rate_analysis/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/aiops-log-rate-analysis", + "id": "def-common.DocumentStats.documentCountStats", + "type": "Object", + "tags": [], + "label": "documentCountStats", + "description": [ + "The document count statistics." + ], + "signature": [ + { + "pluginId": "@kbn/aiops-log-rate-analysis", + "scope": "common", + "docId": "kibKbnAiopsLogRateAnalysisPluginApi", + "section": "def-common.DocumentCountStats", + "text": "DocumentCountStats" + }, + " | undefined" + ], + "path": "x-pack/packages/ml/aiops_log_rate_analysis/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/aiops-log-rate-analysis", + "id": "def-common.DocumentStats.documentCountStatsCompare", + "type": "Object", + "tags": [], + "label": "documentCountStatsCompare", + "description": [ + "The document count statistics for comparison." + ], + "signature": [ + { + "pluginId": "@kbn/aiops-log-rate-analysis", + "scope": "common", + "docId": "kibKbnAiopsLogRateAnalysisPluginApi", + "section": "def-common.DocumentCountStats", + "text": "DocumentCountStats" + }, + " | undefined" + ], + "path": "x-pack/packages/ml/aiops_log_rate_analysis/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/aiops-log-rate-analysis", "id": "def-common.LogRateHistogramItem", diff --git a/api_docs/kbn_aiops_log_rate_analysis.mdx b/api_docs/kbn_aiops_log_rate_analysis.mdx index 2a257021391cb..e54e82a6351ae 100644 --- a/api_docs/kbn_aiops_log_rate_analysis.mdx +++ b/api_docs/kbn_aiops_log_rate_analysis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-log-rate-analysis title: "@kbn/aiops-log-rate-analysis" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-log-rate-analysis plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-log-rate-analysis'] --- import kbnAiopsLogRateAnalysisObj from './kbn_aiops_log_rate_analysis.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) for questi | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 41 | 0 | 0 | 0 | +| 54 | 0 | 0 | 0 | ## Common diff --git a/api_docs/kbn_alerting_api_integration_helpers.mdx b/api_docs/kbn_alerting_api_integration_helpers.mdx index b2453383a4856..e1ef20d3f3fc8 100644 --- a/api_docs/kbn_alerting_api_integration_helpers.mdx +++ b/api_docs/kbn_alerting_api_integration_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-api-integration-helpers title: "@kbn/alerting-api-integration-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-api-integration-helpers plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-api-integration-helpers'] --- import kbnAlertingApiIntegrationHelpersObj from './kbn_alerting_api_integration_helpers.devdocs.json'; diff --git a/api_docs/kbn_alerting_comparators.mdx b/api_docs/kbn_alerting_comparators.mdx index 76344858a3928..bc0c50bbd4d12 100644 --- a/api_docs/kbn_alerting_comparators.mdx +++ b/api_docs/kbn_alerting_comparators.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-comparators title: "@kbn/alerting-comparators" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-comparators plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-comparators'] --- import kbnAlertingComparatorsObj from './kbn_alerting_comparators.devdocs.json'; diff --git a/api_docs/kbn_alerting_state_types.mdx b/api_docs/kbn_alerting_state_types.mdx index 164e378e33e90..0a6362b629c29 100644 --- a/api_docs/kbn_alerting_state_types.mdx +++ b/api_docs/kbn_alerting_state_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-state-types title: "@kbn/alerting-state-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-state-types plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-state-types'] --- import kbnAlertingStateTypesObj from './kbn_alerting_state_types.devdocs.json'; diff --git a/api_docs/kbn_alerting_types.devdocs.json b/api_docs/kbn_alerting_types.devdocs.json index 2467b5805e0a1..8dafb52da895c 100644 --- a/api_docs/kbn_alerting_types.devdocs.json +++ b/api_docs/kbn_alerting_types.devdocs.json @@ -380,6 +380,62 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/alerting-types", + "id": "def-common.AlertingFrameworkHealth", + "type": "Interface", + "tags": [], + "label": "AlertingFrameworkHealth", + "description": [], + "path": "packages/kbn-alerting-types/alerting_framework_health_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerting-types", + "id": "def-common.AlertingFrameworkHealth.isSufficientlySecure", + "type": "boolean", + "tags": [], + "label": "isSufficientlySecure", + "description": [], + "path": "packages/kbn-alerting-types/alerting_framework_health_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerting-types", + "id": "def-common.AlertingFrameworkHealth.hasPermanentEncryptionKey", + "type": "boolean", + "tags": [], + "label": "hasPermanentEncryptionKey", + "description": [], + "path": "packages/kbn-alerting-types/alerting_framework_health_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerting-types", + "id": "def-common.AlertingFrameworkHealth.alertingFrameworkHealth", + "type": "Object", + "tags": [], + "label": "alertingFrameworkHealth", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerting-types", + "scope": "common", + "docId": "kibKbnAlertingTypesPluginApi", + "section": "def-common.AlertsHealth", + "text": "AlertsHealth" + } + ], + "path": "packages/kbn-alerting-types/alerting_framework_health_types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/alerting-types", "id": "def-common.AlertsFilter", @@ -524,6 +580,86 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/alerting-types", + "id": "def-common.AlertsHealth", + "type": "Interface", + "tags": [], + "label": "AlertsHealth", + "description": [], + "path": "packages/kbn-alerting-types/alerting_framework_health_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerting-types", + "id": "def-common.AlertsHealth.decryptionHealth", + "type": "Object", + "tags": [], + "label": "decryptionHealth", + "description": [], + "signature": [ + "{ status: ", + { + "pluginId": "@kbn/alerting-types", + "scope": "common", + "docId": "kibKbnAlertingTypesPluginApi", + "section": "def-common.HealthStatus", + "text": "HealthStatus" + }, + "; timestamp: string; }" + ], + "path": "packages/kbn-alerting-types/alerting_framework_health_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerting-types", + "id": "def-common.AlertsHealth.executionHealth", + "type": "Object", + "tags": [], + "label": "executionHealth", + "description": [], + "signature": [ + "{ status: ", + { + "pluginId": "@kbn/alerting-types", + "scope": "common", + "docId": "kibKbnAlertingTypesPluginApi", + "section": "def-common.HealthStatus", + "text": "HealthStatus" + }, + "; timestamp: string; }" + ], + "path": "packages/kbn-alerting-types/alerting_framework_health_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerting-types", + "id": "def-common.AlertsHealth.readHealth", + "type": "Object", + "tags": [], + "label": "readHealth", + "description": [], + "signature": [ + "{ status: ", + { + "pluginId": "@kbn/alerting-types", + "scope": "common", + "docId": "kibKbnAlertingTypesPluginApi", + "section": "def-common.HealthStatus", + "text": "HealthStatus" + }, + "; timestamp: string; }" + ], + "path": "packages/kbn-alerting-types/alerting_framework_health_types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/alerting-types", "id": "def-common.BasicFields", @@ -2467,6 +2603,18 @@ } ], "enums": [ + { + "parentPluginId": "@kbn/alerting-types", + "id": "def-common.HealthStatus", + "type": "Enum", + "tags": [], + "label": "HealthStatus", + "description": [], + "path": "packages/kbn-alerting-types/alerting_framework_health_types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/alerting-types", "id": "def-common.RuleExecutionStatusErrorReasons", @@ -2649,6 +2797,45 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/alerting-types", + "id": "def-common.ResolvedSanitizedRule", + "type": "Type", + "tags": [], + "label": "ResolvedSanitizedRule", + "description": [], + "signature": [ + "Omit<", + { + "pluginId": "@kbn/alerting-types", + "scope": "common", + "docId": "kibKbnAlertingTypesPluginApi", + "section": "def-common.Rule", + "text": "Rule" + }, + ", \"actions\" | \"apiKey\"> & { actions: ", + { + "pluginId": "@kbn/alerting-types", + "scope": "common", + "docId": "kibKbnAlertingTypesPluginApi", + "section": "def-common.SanitizedRuleAction", + "text": "SanitizedRuleAction" + }, + "[]; } & Omit<", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsResolveResponse", + "text": "SavedObjectsResolveResponse" + }, + ", \"saved_object\"> & { outcome: string; alias_target_id?: string | undefined; }" + ], + "path": "packages/kbn-alerting-types/rule_types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/alerting-types", "id": "def-common.RRuleParams", @@ -2705,6 +2892,36 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/alerting-types", + "id": "def-common.RuleActionParam", + "type": "Type", + "tags": [], + "label": "RuleActionParam", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-common", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsCommonPluginApi", + "section": "def-common.SavedObjectAttributeSingle", + "text": "SavedObjectAttributeSingle" + }, + " | ", + { + "pluginId": "@kbn/core-saved-objects-common", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsCommonPluginApi", + "section": "def-common.SavedObjectAttributeSingle", + "text": "SavedObjectAttributeSingle" + }, + "[]" + ], + "path": "packages/kbn-alerting-types/rule_types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/alerting-types", "id": "def-common.RuleActionParams", diff --git a/api_docs/kbn_alerting_types.mdx b/api_docs/kbn_alerting_types.mdx index e42dda550098a..e2ea27e979346 100644 --- a/api_docs/kbn_alerting_types.mdx +++ b/api_docs/kbn_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerting-types title: "@kbn/alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerting-types plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerting-types'] --- import kbnAlertingTypesObj from './kbn_alerting_types.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-o | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 179 | 0 | 176 | 0 | +| 190 | 0 | 187 | 0 | ## Common diff --git a/api_docs/kbn_alerts_as_data_utils.devdocs.json b/api_docs/kbn_alerts_as_data_utils.devdocs.json index 01dc34570dabc..ddb8b8183ad05 100644 --- a/api_docs/kbn_alerts_as_data_utils.devdocs.json +++ b/api_docs/kbn_alerts_as_data_utils.devdocs.json @@ -450,7 +450,7 @@ "label": "StackAlert", "description": [], "signature": [ - "{} & { 'kibana.alert.evaluation.conditions'?: string | undefined; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | undefined; 'kibana.alert.title'?: string | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; }" + "{} & { 'kibana.alert.evaluation.conditions'?: string | undefined; 'kibana.alert.evaluation.threshold'?: string | number | undefined; 'kibana.alert.evaluation.value'?: string | undefined; 'kibana.alert.title'?: string | undefined; } & { '@timestamp': string | number; 'kibana.alert.instance.id': string; 'kibana.alert.rule.category': string; 'kibana.alert.rule.consumer': string; 'kibana.alert.rule.name': string; 'kibana.alert.rule.producer': string; 'kibana.alert.rule.revision': string | number; 'kibana.alert.rule.rule_type_id': string; 'kibana.alert.rule.uuid': string; 'kibana.alert.status': string; 'kibana.alert.uuid': string; 'kibana.space_ids': string[]; } & { 'event.action'?: string | undefined; 'event.kind'?: string | undefined; 'kibana.alert.action_group'?: string | undefined; 'kibana.alert.case_ids'?: string[] | undefined; 'kibana.alert.consecutive_matches'?: string | number | undefined; 'kibana.alert.duration.us'?: string | number | undefined; 'kibana.alert.end'?: string | number | undefined; 'kibana.alert.flapping'?: boolean | undefined; 'kibana.alert.flapping_history'?: boolean[] | undefined; 'kibana.alert.last_detected'?: string | number | undefined; 'kibana.alert.maintenance_window_ids'?: string[] | undefined; 'kibana.alert.reason'?: string | undefined; 'kibana.alert.rule.execution.timestamp'?: string | number | undefined; 'kibana.alert.rule.execution.uuid'?: string | undefined; 'kibana.alert.rule.parameters'?: unknown; 'kibana.alert.rule.tags'?: string[] | undefined; 'kibana.alert.start'?: string | number | undefined; 'kibana.alert.time_range'?: { gte?: string | number | undefined; lte?: string | number | undefined; } | undefined; 'kibana.alert.url'?: string | undefined; 'kibana.alert.workflow_assignee_ids'?: string[] | undefined; 'kibana.alert.workflow_status'?: string | undefined; 'kibana.alert.workflow_tags'?: string[] | undefined; 'kibana.version'?: string | undefined; tags?: string[] | undefined; } & { '@timestamp': string | number; 'ecs.version': string; } & { 'agent.build.original'?: string | undefined; 'agent.ephemeral_id'?: string | undefined; 'agent.id'?: string | undefined; 'agent.name'?: string | undefined; 'agent.type'?: string | undefined; 'agent.version'?: string | undefined; 'client.address'?: string | undefined; 'client.as.number'?: string | number | undefined; 'client.as.organization.name'?: string | undefined; 'client.bytes'?: string | number | undefined; 'client.domain'?: string | undefined; 'client.geo.city_name'?: string | undefined; 'client.geo.continent_code'?: string | undefined; 'client.geo.continent_name'?: string | undefined; 'client.geo.country_iso_code'?: string | undefined; 'client.geo.country_name'?: string | undefined; 'client.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'client.geo.name'?: string | undefined; 'client.geo.postal_code'?: string | undefined; 'client.geo.region_iso_code'?: string | undefined; 'client.geo.region_name'?: string | undefined; 'client.geo.timezone'?: string | undefined; 'client.ip'?: string | undefined; 'client.mac'?: string | undefined; 'client.nat.ip'?: string | undefined; 'client.nat.port'?: string | number | undefined; 'client.packets'?: string | number | undefined; 'client.port'?: string | number | undefined; 'client.registered_domain'?: string | undefined; 'client.subdomain'?: string | undefined; 'client.top_level_domain'?: string | undefined; 'client.user.domain'?: string | undefined; 'client.user.email'?: string | undefined; 'client.user.full_name'?: string | undefined; 'client.user.group.domain'?: string | undefined; 'client.user.group.id'?: string | undefined; 'client.user.group.name'?: string | undefined; 'client.user.hash'?: string | undefined; 'client.user.id'?: string | undefined; 'client.user.name'?: string | undefined; 'client.user.roles'?: string[] | undefined; 'cloud.account.id'?: string | undefined; 'cloud.account.name'?: string | undefined; 'cloud.availability_zone'?: string | undefined; 'cloud.instance.id'?: string | undefined; 'cloud.instance.name'?: string | undefined; 'cloud.machine.type'?: string | undefined; 'cloud.origin.account.id'?: string | undefined; 'cloud.origin.account.name'?: string | undefined; 'cloud.origin.availability_zone'?: string | undefined; 'cloud.origin.instance.id'?: string | undefined; 'cloud.origin.instance.name'?: string | undefined; 'cloud.origin.machine.type'?: string | undefined; 'cloud.origin.project.id'?: string | undefined; 'cloud.origin.project.name'?: string | undefined; 'cloud.origin.provider'?: string | undefined; 'cloud.origin.region'?: string | undefined; 'cloud.origin.service.name'?: string | undefined; 'cloud.project.id'?: string | undefined; 'cloud.project.name'?: string | undefined; 'cloud.provider'?: string | undefined; 'cloud.region'?: string | undefined; 'cloud.service.name'?: string | undefined; 'cloud.target.account.id'?: string | undefined; 'cloud.target.account.name'?: string | undefined; 'cloud.target.availability_zone'?: string | undefined; 'cloud.target.instance.id'?: string | undefined; 'cloud.target.instance.name'?: string | undefined; 'cloud.target.machine.type'?: string | undefined; 'cloud.target.project.id'?: string | undefined; 'cloud.target.project.name'?: string | undefined; 'cloud.target.provider'?: string | undefined; 'cloud.target.region'?: string | undefined; 'cloud.target.service.name'?: string | undefined; 'container.cpu.usage'?: string | number | undefined; 'container.disk.read.bytes'?: string | number | undefined; 'container.disk.write.bytes'?: string | number | undefined; 'container.id'?: string | undefined; 'container.image.hash.all'?: string[] | undefined; 'container.image.name'?: string | undefined; 'container.image.tag'?: string[] | undefined; 'container.labels'?: unknown; 'container.memory.usage'?: string | number | undefined; 'container.name'?: string | undefined; 'container.network.egress.bytes'?: string | number | undefined; 'container.network.ingress.bytes'?: string | number | undefined; 'container.runtime'?: string | undefined; 'container.security_context.privileged'?: boolean | undefined; 'destination.address'?: string | undefined; 'destination.as.number'?: string | number | undefined; 'destination.as.organization.name'?: string | undefined; 'destination.bytes'?: string | number | undefined; 'destination.domain'?: string | undefined; 'destination.geo.city_name'?: string | undefined; 'destination.geo.continent_code'?: string | undefined; 'destination.geo.continent_name'?: string | undefined; 'destination.geo.country_iso_code'?: string | undefined; 'destination.geo.country_name'?: string | undefined; 'destination.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'destination.geo.name'?: string | undefined; 'destination.geo.postal_code'?: string | undefined; 'destination.geo.region_iso_code'?: string | undefined; 'destination.geo.region_name'?: string | undefined; 'destination.geo.timezone'?: string | undefined; 'destination.ip'?: string | undefined; 'destination.mac'?: string | undefined; 'destination.nat.ip'?: string | undefined; 'destination.nat.port'?: string | number | undefined; 'destination.packets'?: string | number | undefined; 'destination.port'?: string | number | undefined; 'destination.registered_domain'?: string | undefined; 'destination.subdomain'?: string | undefined; 'destination.top_level_domain'?: string | undefined; 'destination.user.domain'?: string | undefined; 'destination.user.email'?: string | undefined; 'destination.user.full_name'?: string | undefined; 'destination.user.group.domain'?: string | undefined; 'destination.user.group.id'?: string | undefined; 'destination.user.group.name'?: string | undefined; 'destination.user.hash'?: string | undefined; 'destination.user.id'?: string | undefined; 'destination.user.name'?: string | undefined; 'destination.user.roles'?: string[] | undefined; 'device.id'?: string | undefined; 'device.manufacturer'?: string | undefined; 'device.model.identifier'?: string | undefined; 'device.model.name'?: string | undefined; 'dll.code_signature.digest_algorithm'?: string | undefined; 'dll.code_signature.exists'?: boolean | undefined; 'dll.code_signature.signing_id'?: string | undefined; 'dll.code_signature.status'?: string | undefined; 'dll.code_signature.subject_name'?: string | undefined; 'dll.code_signature.team_id'?: string | undefined; 'dll.code_signature.timestamp'?: string | number | undefined; 'dll.code_signature.trusted'?: boolean | undefined; 'dll.code_signature.valid'?: boolean | undefined; 'dll.hash.md5'?: string | undefined; 'dll.hash.sha1'?: string | undefined; 'dll.hash.sha256'?: string | undefined; 'dll.hash.sha384'?: string | undefined; 'dll.hash.sha512'?: string | undefined; 'dll.hash.ssdeep'?: string | undefined; 'dll.hash.tlsh'?: string | undefined; 'dll.name'?: string | undefined; 'dll.path'?: string | undefined; 'dll.pe.architecture'?: string | undefined; 'dll.pe.company'?: string | undefined; 'dll.pe.description'?: string | undefined; 'dll.pe.file_version'?: string | undefined; 'dll.pe.go_import_hash'?: string | undefined; 'dll.pe.go_imports'?: unknown; 'dll.pe.go_imports_names_entropy'?: string | number | undefined; 'dll.pe.go_imports_names_var_entropy'?: string | number | undefined; 'dll.pe.go_stripped'?: boolean | undefined; 'dll.pe.imphash'?: string | undefined; 'dll.pe.import_hash'?: string | undefined; 'dll.pe.imports'?: unknown[] | undefined; 'dll.pe.imports_names_entropy'?: string | number | undefined; 'dll.pe.imports_names_var_entropy'?: string | number | undefined; 'dll.pe.original_file_name'?: string | undefined; 'dll.pe.pehash'?: string | undefined; 'dll.pe.product'?: string | undefined; 'dll.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'dns.answers'?: { class?: string | undefined; data?: string | undefined; name?: string | undefined; ttl?: string | number | undefined; type?: string | undefined; }[] | undefined; 'dns.header_flags'?: string[] | undefined; 'dns.id'?: string | undefined; 'dns.op_code'?: string | undefined; 'dns.question.class'?: string | undefined; 'dns.question.name'?: string | undefined; 'dns.question.registered_domain'?: string | undefined; 'dns.question.subdomain'?: string | undefined; 'dns.question.top_level_domain'?: string | undefined; 'dns.question.type'?: string | undefined; 'dns.resolved_ip'?: string[] | undefined; 'dns.response_code'?: string | undefined; 'dns.type'?: string | undefined; 'email.attachments'?: { 'file.extension'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.name'?: string | undefined; 'file.size'?: string | number | undefined; }[] | undefined; 'email.bcc.address'?: string[] | undefined; 'email.cc.address'?: string[] | undefined; 'email.content_type'?: string | undefined; 'email.delivery_timestamp'?: string | number | undefined; 'email.direction'?: string | undefined; 'email.from.address'?: string[] | undefined; 'email.local_id'?: string | undefined; 'email.message_id'?: string | undefined; 'email.origination_timestamp'?: string | number | undefined; 'email.reply_to.address'?: string[] | undefined; 'email.sender.address'?: string | undefined; 'email.subject'?: string | undefined; 'email.to.address'?: string[] | undefined; 'email.x_mailer'?: string | undefined; 'error.code'?: string | undefined; 'error.id'?: string | undefined; 'error.message'?: string | undefined; 'error.stack_trace'?: string | undefined; 'error.type'?: string | undefined; 'event.action'?: string | undefined; 'event.agent_id_status'?: string | undefined; 'event.category'?: string[] | undefined; 'event.code'?: string | undefined; 'event.created'?: string | number | undefined; 'event.dataset'?: string | undefined; 'event.duration'?: string | number | undefined; 'event.end'?: string | number | undefined; 'event.hash'?: string | undefined; 'event.id'?: string | undefined; 'event.ingested'?: string | number | undefined; 'event.kind'?: string | undefined; 'event.module'?: string | undefined; 'event.original'?: string | undefined; 'event.outcome'?: string | undefined; 'event.provider'?: string | undefined; 'event.reason'?: string | undefined; 'event.reference'?: string | undefined; 'event.risk_score'?: number | undefined; 'event.risk_score_norm'?: number | undefined; 'event.sequence'?: string | number | undefined; 'event.severity'?: string | number | undefined; 'event.start'?: string | number | undefined; 'event.timezone'?: string | undefined; 'event.type'?: string[] | undefined; 'event.url'?: string | undefined; 'faas.coldstart'?: boolean | undefined; 'faas.execution'?: string | undefined; 'faas.id'?: string | undefined; 'faas.name'?: string | undefined; 'faas.version'?: string | undefined; 'file.accessed'?: string | number | undefined; 'file.attributes'?: string[] | undefined; 'file.code_signature.digest_algorithm'?: string | undefined; 'file.code_signature.exists'?: boolean | undefined; 'file.code_signature.signing_id'?: string | undefined; 'file.code_signature.status'?: string | undefined; 'file.code_signature.subject_name'?: string | undefined; 'file.code_signature.team_id'?: string | undefined; 'file.code_signature.timestamp'?: string | number | undefined; 'file.code_signature.trusted'?: boolean | undefined; 'file.code_signature.valid'?: boolean | undefined; 'file.created'?: string | number | undefined; 'file.ctime'?: string | number | undefined; 'file.device'?: string | undefined; 'file.directory'?: string | undefined; 'file.drive_letter'?: string | undefined; 'file.elf.architecture'?: string | undefined; 'file.elf.byte_order'?: string | undefined; 'file.elf.cpu_type'?: string | undefined; 'file.elf.creation_date'?: string | number | undefined; 'file.elf.exports'?: unknown[] | undefined; 'file.elf.go_import_hash'?: string | undefined; 'file.elf.go_imports'?: unknown; 'file.elf.go_imports_names_entropy'?: string | number | undefined; 'file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'file.elf.go_stripped'?: boolean | undefined; 'file.elf.header.abi_version'?: string | undefined; 'file.elf.header.class'?: string | undefined; 'file.elf.header.data'?: string | undefined; 'file.elf.header.entrypoint'?: string | number | undefined; 'file.elf.header.object_version'?: string | undefined; 'file.elf.header.os_abi'?: string | undefined; 'file.elf.header.type'?: string | undefined; 'file.elf.header.version'?: string | undefined; 'file.elf.import_hash'?: string | undefined; 'file.elf.imports'?: unknown[] | undefined; 'file.elf.imports_names_entropy'?: string | number | undefined; 'file.elf.imports_names_var_entropy'?: string | number | undefined; 'file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'file.elf.shared_libraries'?: string[] | undefined; 'file.elf.telfhash'?: string | undefined; 'file.extension'?: string | undefined; 'file.fork_name'?: string | undefined; 'file.gid'?: string | undefined; 'file.group'?: string | undefined; 'file.hash.md5'?: string | undefined; 'file.hash.sha1'?: string | undefined; 'file.hash.sha256'?: string | undefined; 'file.hash.sha384'?: string | undefined; 'file.hash.sha512'?: string | undefined; 'file.hash.ssdeep'?: string | undefined; 'file.hash.tlsh'?: string | undefined; 'file.inode'?: string | undefined; 'file.macho.go_import_hash'?: string | undefined; 'file.macho.go_imports'?: unknown; 'file.macho.go_imports_names_entropy'?: string | number | undefined; 'file.macho.go_imports_names_var_entropy'?: string | number | undefined; 'file.macho.go_stripped'?: boolean | undefined; 'file.macho.import_hash'?: string | undefined; 'file.macho.imports'?: unknown[] | undefined; 'file.macho.imports_names_entropy'?: string | number | undefined; 'file.macho.imports_names_var_entropy'?: string | number | undefined; 'file.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.macho.symhash'?: string | undefined; 'file.mime_type'?: string | undefined; 'file.mode'?: string | undefined; 'file.mtime'?: string | number | undefined; 'file.name'?: string | undefined; 'file.owner'?: string | undefined; 'file.path'?: string | undefined; 'file.pe.architecture'?: string | undefined; 'file.pe.company'?: string | undefined; 'file.pe.description'?: string | undefined; 'file.pe.file_version'?: string | undefined; 'file.pe.go_import_hash'?: string | undefined; 'file.pe.go_imports'?: unknown; 'file.pe.go_imports_names_entropy'?: string | number | undefined; 'file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'file.pe.go_stripped'?: boolean | undefined; 'file.pe.imphash'?: string | undefined; 'file.pe.import_hash'?: string | undefined; 'file.pe.imports'?: unknown[] | undefined; 'file.pe.imports_names_entropy'?: string | number | undefined; 'file.pe.imports_names_var_entropy'?: string | number | undefined; 'file.pe.original_file_name'?: string | undefined; 'file.pe.pehash'?: string | undefined; 'file.pe.product'?: string | undefined; 'file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'file.size'?: string | number | undefined; 'file.target_path'?: string | undefined; 'file.type'?: string | undefined; 'file.uid'?: string | undefined; 'file.x509.alternative_names'?: string[] | undefined; 'file.x509.issuer.common_name'?: string[] | undefined; 'file.x509.issuer.country'?: string[] | undefined; 'file.x509.issuer.distinguished_name'?: string | undefined; 'file.x509.issuer.locality'?: string[] | undefined; 'file.x509.issuer.organization'?: string[] | undefined; 'file.x509.issuer.organizational_unit'?: string[] | undefined; 'file.x509.issuer.state_or_province'?: string[] | undefined; 'file.x509.not_after'?: string | number | undefined; 'file.x509.not_before'?: string | number | undefined; 'file.x509.public_key_algorithm'?: string | undefined; 'file.x509.public_key_curve'?: string | undefined; 'file.x509.public_key_exponent'?: string | number | undefined; 'file.x509.public_key_size'?: string | number | undefined; 'file.x509.serial_number'?: string | undefined; 'file.x509.signature_algorithm'?: string | undefined; 'file.x509.subject.common_name'?: string[] | undefined; 'file.x509.subject.country'?: string[] | undefined; 'file.x509.subject.distinguished_name'?: string | undefined; 'file.x509.subject.locality'?: string[] | undefined; 'file.x509.subject.organization'?: string[] | undefined; 'file.x509.subject.organizational_unit'?: string[] | undefined; 'file.x509.subject.state_or_province'?: string[] | undefined; 'file.x509.version_number'?: string | undefined; 'group.domain'?: string | undefined; 'group.id'?: string | undefined; 'group.name'?: string | undefined; 'host.architecture'?: string | undefined; 'host.boot.id'?: string | undefined; 'host.cpu.usage'?: string | number | undefined; 'host.disk.read.bytes'?: string | number | undefined; 'host.disk.write.bytes'?: string | number | undefined; 'host.domain'?: string | undefined; 'host.geo.city_name'?: string | undefined; 'host.geo.continent_code'?: string | undefined; 'host.geo.continent_name'?: string | undefined; 'host.geo.country_iso_code'?: string | undefined; 'host.geo.country_name'?: string | undefined; 'host.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'host.geo.name'?: string | undefined; 'host.geo.postal_code'?: string | undefined; 'host.geo.region_iso_code'?: string | undefined; 'host.geo.region_name'?: string | undefined; 'host.geo.timezone'?: string | undefined; 'host.hostname'?: string | undefined; 'host.id'?: string | undefined; 'host.ip'?: string[] | undefined; 'host.mac'?: string[] | undefined; 'host.name'?: string | undefined; 'host.network.egress.bytes'?: string | number | undefined; 'host.network.egress.packets'?: string | number | undefined; 'host.network.ingress.bytes'?: string | number | undefined; 'host.network.ingress.packets'?: string | number | undefined; 'host.os.family'?: string | undefined; 'host.os.full'?: string | undefined; 'host.os.kernel'?: string | undefined; 'host.os.name'?: string | undefined; 'host.os.platform'?: string | undefined; 'host.os.type'?: string | undefined; 'host.os.version'?: string | undefined; 'host.pid_ns_ino'?: string | undefined; 'host.risk.calculated_level'?: string | undefined; 'host.risk.calculated_score'?: number | undefined; 'host.risk.calculated_score_norm'?: number | undefined; 'host.risk.static_level'?: string | undefined; 'host.risk.static_score'?: number | undefined; 'host.risk.static_score_norm'?: number | undefined; 'host.type'?: string | undefined; 'host.uptime'?: string | number | undefined; 'http.request.body.bytes'?: string | number | undefined; 'http.request.body.content'?: string | undefined; 'http.request.bytes'?: string | number | undefined; 'http.request.id'?: string | undefined; 'http.request.method'?: string | undefined; 'http.request.mime_type'?: string | undefined; 'http.request.referrer'?: string | undefined; 'http.response.body.bytes'?: string | number | undefined; 'http.response.body.content'?: string | undefined; 'http.response.bytes'?: string | number | undefined; 'http.response.mime_type'?: string | undefined; 'http.response.status_code'?: string | number | undefined; 'http.version'?: string | undefined; labels?: unknown; 'log.file.path'?: string | undefined; 'log.level'?: string | undefined; 'log.logger'?: string | undefined; 'log.origin.file.line'?: string | number | undefined; 'log.origin.file.name'?: string | undefined; 'log.origin.function'?: string | undefined; 'log.syslog'?: unknown; message?: string | undefined; 'network.application'?: string | undefined; 'network.bytes'?: string | number | undefined; 'network.community_id'?: string | undefined; 'network.direction'?: string | undefined; 'network.forwarded_ip'?: string | undefined; 'network.iana_number'?: string | undefined; 'network.inner'?: unknown; 'network.name'?: string | undefined; 'network.packets'?: string | number | undefined; 'network.protocol'?: string | undefined; 'network.transport'?: string | undefined; 'network.type'?: string | undefined; 'network.vlan.id'?: string | undefined; 'network.vlan.name'?: string | undefined; 'observer.egress'?: unknown; 'observer.geo.city_name'?: string | undefined; 'observer.geo.continent_code'?: string | undefined; 'observer.geo.continent_name'?: string | undefined; 'observer.geo.country_iso_code'?: string | undefined; 'observer.geo.country_name'?: string | undefined; 'observer.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'observer.geo.name'?: string | undefined; 'observer.geo.postal_code'?: string | undefined; 'observer.geo.region_iso_code'?: string | undefined; 'observer.geo.region_name'?: string | undefined; 'observer.geo.timezone'?: string | undefined; 'observer.hostname'?: string | undefined; 'observer.ingress'?: unknown; 'observer.ip'?: string[] | undefined; 'observer.mac'?: string[] | undefined; 'observer.name'?: string | undefined; 'observer.os.family'?: string | undefined; 'observer.os.full'?: string | undefined; 'observer.os.kernel'?: string | undefined; 'observer.os.name'?: string | undefined; 'observer.os.platform'?: string | undefined; 'observer.os.type'?: string | undefined; 'observer.os.version'?: string | undefined; 'observer.product'?: string | undefined; 'observer.serial_number'?: string | undefined; 'observer.type'?: string | undefined; 'observer.vendor'?: string | undefined; 'observer.version'?: string | undefined; 'orchestrator.api_version'?: string | undefined; 'orchestrator.cluster.id'?: string | undefined; 'orchestrator.cluster.name'?: string | undefined; 'orchestrator.cluster.url'?: string | undefined; 'orchestrator.cluster.version'?: string | undefined; 'orchestrator.namespace'?: string | undefined; 'orchestrator.organization'?: string | undefined; 'orchestrator.resource.annotation'?: string[] | undefined; 'orchestrator.resource.id'?: string | undefined; 'orchestrator.resource.ip'?: string[] | undefined; 'orchestrator.resource.label'?: string[] | undefined; 'orchestrator.resource.name'?: string | undefined; 'orchestrator.resource.parent.type'?: string | undefined; 'orchestrator.resource.type'?: string | undefined; 'orchestrator.type'?: string | undefined; 'organization.id'?: string | undefined; 'organization.name'?: string | undefined; 'package.architecture'?: string | undefined; 'package.build_version'?: string | undefined; 'package.checksum'?: string | undefined; 'package.description'?: string | undefined; 'package.install_scope'?: string | undefined; 'package.installed'?: string | number | undefined; 'package.license'?: string | undefined; 'package.name'?: string | undefined; 'package.path'?: string | undefined; 'package.reference'?: string | undefined; 'package.size'?: string | number | undefined; 'package.type'?: string | undefined; 'package.version'?: string | undefined; 'process.args'?: string[] | undefined; 'process.args_count'?: string | number | undefined; 'process.code_signature.digest_algorithm'?: string | undefined; 'process.code_signature.exists'?: boolean | undefined; 'process.code_signature.signing_id'?: string | undefined; 'process.code_signature.status'?: string | undefined; 'process.code_signature.subject_name'?: string | undefined; 'process.code_signature.team_id'?: string | undefined; 'process.code_signature.timestamp'?: string | number | undefined; 'process.code_signature.trusted'?: boolean | undefined; 'process.code_signature.valid'?: boolean | undefined; 'process.command_line'?: string | undefined; 'process.elf.architecture'?: string | undefined; 'process.elf.byte_order'?: string | undefined; 'process.elf.cpu_type'?: string | undefined; 'process.elf.creation_date'?: string | number | undefined; 'process.elf.exports'?: unknown[] | undefined; 'process.elf.go_import_hash'?: string | undefined; 'process.elf.go_imports'?: unknown; 'process.elf.go_imports_names_entropy'?: string | number | undefined; 'process.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.elf.go_stripped'?: boolean | undefined; 'process.elf.header.abi_version'?: string | undefined; 'process.elf.header.class'?: string | undefined; 'process.elf.header.data'?: string | undefined; 'process.elf.header.entrypoint'?: string | number | undefined; 'process.elf.header.object_version'?: string | undefined; 'process.elf.header.os_abi'?: string | undefined; 'process.elf.header.type'?: string | undefined; 'process.elf.header.version'?: string | undefined; 'process.elf.import_hash'?: string | undefined; 'process.elf.imports'?: unknown[] | undefined; 'process.elf.imports_names_entropy'?: string | number | undefined; 'process.elf.imports_names_var_entropy'?: string | number | undefined; 'process.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.elf.shared_libraries'?: string[] | undefined; 'process.elf.telfhash'?: string | undefined; 'process.end'?: string | number | undefined; 'process.entity_id'?: string | undefined; 'process.entry_leader.args'?: string[] | undefined; 'process.entry_leader.args_count'?: string | number | undefined; 'process.entry_leader.attested_groups.name'?: string | undefined; 'process.entry_leader.attested_user.id'?: string | undefined; 'process.entry_leader.attested_user.name'?: string | undefined; 'process.entry_leader.command_line'?: string | undefined; 'process.entry_leader.entity_id'?: string | undefined; 'process.entry_leader.entry_meta.source.ip'?: string | undefined; 'process.entry_leader.entry_meta.type'?: string | undefined; 'process.entry_leader.executable'?: string | undefined; 'process.entry_leader.group.id'?: string | undefined; 'process.entry_leader.group.name'?: string | undefined; 'process.entry_leader.interactive'?: boolean | undefined; 'process.entry_leader.name'?: string | undefined; 'process.entry_leader.parent.entity_id'?: string | undefined; 'process.entry_leader.parent.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.entity_id'?: string | undefined; 'process.entry_leader.parent.session_leader.pid'?: string | number | undefined; 'process.entry_leader.parent.session_leader.start'?: string | number | undefined; 'process.entry_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.entry_leader.parent.start'?: string | number | undefined; 'process.entry_leader.parent.vpid'?: string | number | undefined; 'process.entry_leader.pid'?: string | number | undefined; 'process.entry_leader.real_group.id'?: string | undefined; 'process.entry_leader.real_group.name'?: string | undefined; 'process.entry_leader.real_user.id'?: string | undefined; 'process.entry_leader.real_user.name'?: string | undefined; 'process.entry_leader.same_as_process'?: boolean | undefined; 'process.entry_leader.saved_group.id'?: string | undefined; 'process.entry_leader.saved_group.name'?: string | undefined; 'process.entry_leader.saved_user.id'?: string | undefined; 'process.entry_leader.saved_user.name'?: string | undefined; 'process.entry_leader.start'?: string | number | undefined; 'process.entry_leader.supplemental_groups.id'?: string | undefined; 'process.entry_leader.supplemental_groups.name'?: string | undefined; 'process.entry_leader.tty'?: unknown; 'process.entry_leader.user.id'?: string | undefined; 'process.entry_leader.user.name'?: string | undefined; 'process.entry_leader.vpid'?: string | number | undefined; 'process.entry_leader.working_directory'?: string | undefined; 'process.env_vars'?: string[] | undefined; 'process.executable'?: string | undefined; 'process.exit_code'?: string | number | undefined; 'process.group_leader.args'?: string[] | undefined; 'process.group_leader.args_count'?: string | number | undefined; 'process.group_leader.command_line'?: string | undefined; 'process.group_leader.entity_id'?: string | undefined; 'process.group_leader.executable'?: string | undefined; 'process.group_leader.group.id'?: string | undefined; 'process.group_leader.group.name'?: string | undefined; 'process.group_leader.interactive'?: boolean | undefined; 'process.group_leader.name'?: string | undefined; 'process.group_leader.pid'?: string | number | undefined; 'process.group_leader.real_group.id'?: string | undefined; 'process.group_leader.real_group.name'?: string | undefined; 'process.group_leader.real_user.id'?: string | undefined; 'process.group_leader.real_user.name'?: string | undefined; 'process.group_leader.same_as_process'?: boolean | undefined; 'process.group_leader.saved_group.id'?: string | undefined; 'process.group_leader.saved_group.name'?: string | undefined; 'process.group_leader.saved_user.id'?: string | undefined; 'process.group_leader.saved_user.name'?: string | undefined; 'process.group_leader.start'?: string | number | undefined; 'process.group_leader.supplemental_groups.id'?: string | undefined; 'process.group_leader.supplemental_groups.name'?: string | undefined; 'process.group_leader.tty'?: unknown; 'process.group_leader.user.id'?: string | undefined; 'process.group_leader.user.name'?: string | undefined; 'process.group_leader.vpid'?: string | number | undefined; 'process.group_leader.working_directory'?: string | undefined; 'process.hash.md5'?: string | undefined; 'process.hash.sha1'?: string | undefined; 'process.hash.sha256'?: string | undefined; 'process.hash.sha384'?: string | undefined; 'process.hash.sha512'?: string | undefined; 'process.hash.ssdeep'?: string | undefined; 'process.hash.tlsh'?: string | undefined; 'process.interactive'?: boolean | undefined; 'process.io'?: unknown; 'process.macho.go_import_hash'?: string | undefined; 'process.macho.go_imports'?: unknown; 'process.macho.go_imports_names_entropy'?: string | number | undefined; 'process.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.macho.go_stripped'?: boolean | undefined; 'process.macho.import_hash'?: string | undefined; 'process.macho.imports'?: unknown[] | undefined; 'process.macho.imports_names_entropy'?: string | number | undefined; 'process.macho.imports_names_var_entropy'?: string | number | undefined; 'process.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.macho.symhash'?: string | undefined; 'process.name'?: string | undefined; 'process.parent.args'?: string[] | undefined; 'process.parent.args_count'?: string | number | undefined; 'process.parent.code_signature.digest_algorithm'?: string | undefined; 'process.parent.code_signature.exists'?: boolean | undefined; 'process.parent.code_signature.signing_id'?: string | undefined; 'process.parent.code_signature.status'?: string | undefined; 'process.parent.code_signature.subject_name'?: string | undefined; 'process.parent.code_signature.team_id'?: string | undefined; 'process.parent.code_signature.timestamp'?: string | number | undefined; 'process.parent.code_signature.trusted'?: boolean | undefined; 'process.parent.code_signature.valid'?: boolean | undefined; 'process.parent.command_line'?: string | undefined; 'process.parent.elf.architecture'?: string | undefined; 'process.parent.elf.byte_order'?: string | undefined; 'process.parent.elf.cpu_type'?: string | undefined; 'process.parent.elf.creation_date'?: string | number | undefined; 'process.parent.elf.exports'?: unknown[] | undefined; 'process.parent.elf.go_import_hash'?: string | undefined; 'process.parent.elf.go_imports'?: unknown; 'process.parent.elf.go_imports_names_entropy'?: string | number | undefined; 'process.parent.elf.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.go_stripped'?: boolean | undefined; 'process.parent.elf.header.abi_version'?: string | undefined; 'process.parent.elf.header.class'?: string | undefined; 'process.parent.elf.header.data'?: string | undefined; 'process.parent.elf.header.entrypoint'?: string | number | undefined; 'process.parent.elf.header.object_version'?: string | undefined; 'process.parent.elf.header.os_abi'?: string | undefined; 'process.parent.elf.header.type'?: string | undefined; 'process.parent.elf.header.version'?: string | undefined; 'process.parent.elf.import_hash'?: string | undefined; 'process.parent.elf.imports'?: unknown[] | undefined; 'process.parent.elf.imports_names_entropy'?: string | number | undefined; 'process.parent.elf.imports_names_var_entropy'?: string | number | undefined; 'process.parent.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'process.parent.elf.shared_libraries'?: string[] | undefined; 'process.parent.elf.telfhash'?: string | undefined; 'process.parent.end'?: string | number | undefined; 'process.parent.entity_id'?: string | undefined; 'process.parent.executable'?: string | undefined; 'process.parent.exit_code'?: string | number | undefined; 'process.parent.group.id'?: string | undefined; 'process.parent.group.name'?: string | undefined; 'process.parent.group_leader.entity_id'?: string | undefined; 'process.parent.group_leader.pid'?: string | number | undefined; 'process.parent.group_leader.start'?: string | number | undefined; 'process.parent.group_leader.vpid'?: string | number | undefined; 'process.parent.hash.md5'?: string | undefined; 'process.parent.hash.sha1'?: string | undefined; 'process.parent.hash.sha256'?: string | undefined; 'process.parent.hash.sha384'?: string | undefined; 'process.parent.hash.sha512'?: string | undefined; 'process.parent.hash.ssdeep'?: string | undefined; 'process.parent.hash.tlsh'?: string | undefined; 'process.parent.interactive'?: boolean | undefined; 'process.parent.macho.go_import_hash'?: string | undefined; 'process.parent.macho.go_imports'?: unknown; 'process.parent.macho.go_imports_names_entropy'?: string | number | undefined; 'process.parent.macho.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.go_stripped'?: boolean | undefined; 'process.parent.macho.import_hash'?: string | undefined; 'process.parent.macho.imports'?: unknown[] | undefined; 'process.parent.macho.imports_names_entropy'?: string | number | undefined; 'process.parent.macho.imports_names_var_entropy'?: string | number | undefined; 'process.parent.macho.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.macho.symhash'?: string | undefined; 'process.parent.name'?: string | undefined; 'process.parent.pe.architecture'?: string | undefined; 'process.parent.pe.company'?: string | undefined; 'process.parent.pe.description'?: string | undefined; 'process.parent.pe.file_version'?: string | undefined; 'process.parent.pe.go_import_hash'?: string | undefined; 'process.parent.pe.go_imports'?: unknown; 'process.parent.pe.go_imports_names_entropy'?: string | number | undefined; 'process.parent.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.go_stripped'?: boolean | undefined; 'process.parent.pe.imphash'?: string | undefined; 'process.parent.pe.import_hash'?: string | undefined; 'process.parent.pe.imports'?: unknown[] | undefined; 'process.parent.pe.imports_names_entropy'?: string | number | undefined; 'process.parent.pe.imports_names_var_entropy'?: string | number | undefined; 'process.parent.pe.original_file_name'?: string | undefined; 'process.parent.pe.pehash'?: string | undefined; 'process.parent.pe.product'?: string | undefined; 'process.parent.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.parent.pgid'?: string | number | undefined; 'process.parent.pid'?: string | number | undefined; 'process.parent.real_group.id'?: string | undefined; 'process.parent.real_group.name'?: string | undefined; 'process.parent.real_user.id'?: string | undefined; 'process.parent.real_user.name'?: string | undefined; 'process.parent.saved_group.id'?: string | undefined; 'process.parent.saved_group.name'?: string | undefined; 'process.parent.saved_user.id'?: string | undefined; 'process.parent.saved_user.name'?: string | undefined; 'process.parent.start'?: string | number | undefined; 'process.parent.supplemental_groups.id'?: string | undefined; 'process.parent.supplemental_groups.name'?: string | undefined; 'process.parent.thread.capabilities.effective'?: string[] | undefined; 'process.parent.thread.capabilities.permitted'?: string[] | undefined; 'process.parent.thread.id'?: string | number | undefined; 'process.parent.thread.name'?: string | undefined; 'process.parent.title'?: string | undefined; 'process.parent.tty'?: unknown; 'process.parent.uptime'?: string | number | undefined; 'process.parent.user.id'?: string | undefined; 'process.parent.user.name'?: string | undefined; 'process.parent.vpid'?: string | number | undefined; 'process.parent.working_directory'?: string | undefined; 'process.pe.architecture'?: string | undefined; 'process.pe.company'?: string | undefined; 'process.pe.description'?: string | undefined; 'process.pe.file_version'?: string | undefined; 'process.pe.go_import_hash'?: string | undefined; 'process.pe.go_imports'?: unknown; 'process.pe.go_imports_names_entropy'?: string | number | undefined; 'process.pe.go_imports_names_var_entropy'?: string | number | undefined; 'process.pe.go_stripped'?: boolean | undefined; 'process.pe.imphash'?: string | undefined; 'process.pe.import_hash'?: string | undefined; 'process.pe.imports'?: unknown[] | undefined; 'process.pe.imports_names_entropy'?: string | number | undefined; 'process.pe.imports_names_var_entropy'?: string | number | undefined; 'process.pe.original_file_name'?: string | undefined; 'process.pe.pehash'?: string | undefined; 'process.pe.product'?: string | undefined; 'process.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'process.pgid'?: string | number | undefined; 'process.pid'?: string | number | undefined; 'process.previous.args'?: string[] | undefined; 'process.previous.args_count'?: string | number | undefined; 'process.previous.executable'?: string | undefined; 'process.real_group.id'?: string | undefined; 'process.real_group.name'?: string | undefined; 'process.real_user.id'?: string | undefined; 'process.real_user.name'?: string | undefined; 'process.saved_group.id'?: string | undefined; 'process.saved_group.name'?: string | undefined; 'process.saved_user.id'?: string | undefined; 'process.saved_user.name'?: string | undefined; 'process.session_leader.args'?: string[] | undefined; 'process.session_leader.args_count'?: string | number | undefined; 'process.session_leader.command_line'?: string | undefined; 'process.session_leader.entity_id'?: string | undefined; 'process.session_leader.executable'?: string | undefined; 'process.session_leader.group.id'?: string | undefined; 'process.session_leader.group.name'?: string | undefined; 'process.session_leader.interactive'?: boolean | undefined; 'process.session_leader.name'?: string | undefined; 'process.session_leader.parent.entity_id'?: string | undefined; 'process.session_leader.parent.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.entity_id'?: string | undefined; 'process.session_leader.parent.session_leader.pid'?: string | number | undefined; 'process.session_leader.parent.session_leader.start'?: string | number | undefined; 'process.session_leader.parent.session_leader.vpid'?: string | number | undefined; 'process.session_leader.parent.start'?: string | number | undefined; 'process.session_leader.parent.vpid'?: string | number | undefined; 'process.session_leader.pid'?: string | number | undefined; 'process.session_leader.real_group.id'?: string | undefined; 'process.session_leader.real_group.name'?: string | undefined; 'process.session_leader.real_user.id'?: string | undefined; 'process.session_leader.real_user.name'?: string | undefined; 'process.session_leader.same_as_process'?: boolean | undefined; 'process.session_leader.saved_group.id'?: string | undefined; 'process.session_leader.saved_group.name'?: string | undefined; 'process.session_leader.saved_user.id'?: string | undefined; 'process.session_leader.saved_user.name'?: string | undefined; 'process.session_leader.start'?: string | number | undefined; 'process.session_leader.supplemental_groups.id'?: string | undefined; 'process.session_leader.supplemental_groups.name'?: string | undefined; 'process.session_leader.tty'?: unknown; 'process.session_leader.user.id'?: string | undefined; 'process.session_leader.user.name'?: string | undefined; 'process.session_leader.vpid'?: string | number | undefined; 'process.session_leader.working_directory'?: string | undefined; 'process.start'?: string | number | undefined; 'process.supplemental_groups.id'?: string | undefined; 'process.supplemental_groups.name'?: string | undefined; 'process.thread.capabilities.effective'?: string[] | undefined; 'process.thread.capabilities.permitted'?: string[] | undefined; 'process.thread.id'?: string | number | undefined; 'process.thread.name'?: string | undefined; 'process.title'?: string | undefined; 'process.tty'?: unknown; 'process.uptime'?: string | number | undefined; 'process.user.id'?: string | undefined; 'process.user.name'?: string | undefined; 'process.vpid'?: string | number | undefined; 'process.working_directory'?: string | undefined; 'registry.data.bytes'?: string | undefined; 'registry.data.strings'?: string[] | undefined; 'registry.data.type'?: string | undefined; 'registry.hive'?: string | undefined; 'registry.key'?: string | undefined; 'registry.path'?: string | undefined; 'registry.value'?: string | undefined; 'related.hash'?: string[] | undefined; 'related.hosts'?: string[] | undefined; 'related.ip'?: string[] | undefined; 'related.user'?: string[] | undefined; 'rule.author'?: string[] | undefined; 'rule.category'?: string | undefined; 'rule.description'?: string | undefined; 'rule.id'?: string | undefined; 'rule.license'?: string | undefined; 'rule.name'?: string | undefined; 'rule.reference'?: string | undefined; 'rule.ruleset'?: string | undefined; 'rule.uuid'?: string | undefined; 'rule.version'?: string | undefined; 'server.address'?: string | undefined; 'server.as.number'?: string | number | undefined; 'server.as.organization.name'?: string | undefined; 'server.bytes'?: string | number | undefined; 'server.domain'?: string | undefined; 'server.geo.city_name'?: string | undefined; 'server.geo.continent_code'?: string | undefined; 'server.geo.continent_name'?: string | undefined; 'server.geo.country_iso_code'?: string | undefined; 'server.geo.country_name'?: string | undefined; 'server.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'server.geo.name'?: string | undefined; 'server.geo.postal_code'?: string | undefined; 'server.geo.region_iso_code'?: string | undefined; 'server.geo.region_name'?: string | undefined; 'server.geo.timezone'?: string | undefined; 'server.ip'?: string | undefined; 'server.mac'?: string | undefined; 'server.nat.ip'?: string | undefined; 'server.nat.port'?: string | number | undefined; 'server.packets'?: string | number | undefined; 'server.port'?: string | number | undefined; 'server.registered_domain'?: string | undefined; 'server.subdomain'?: string | undefined; 'server.top_level_domain'?: string | undefined; 'server.user.domain'?: string | undefined; 'server.user.email'?: string | undefined; 'server.user.full_name'?: string | undefined; 'server.user.group.domain'?: string | undefined; 'server.user.group.id'?: string | undefined; 'server.user.group.name'?: string | undefined; 'server.user.hash'?: string | undefined; 'server.user.id'?: string | undefined; 'server.user.name'?: string | undefined; 'server.user.roles'?: string[] | undefined; 'service.address'?: string | undefined; 'service.environment'?: string | undefined; 'service.ephemeral_id'?: string | undefined; 'service.id'?: string | undefined; 'service.name'?: string | undefined; 'service.node.name'?: string | undefined; 'service.node.role'?: string | undefined; 'service.node.roles'?: string[] | undefined; 'service.origin.address'?: string | undefined; 'service.origin.environment'?: string | undefined; 'service.origin.ephemeral_id'?: string | undefined; 'service.origin.id'?: string | undefined; 'service.origin.name'?: string | undefined; 'service.origin.node.name'?: string | undefined; 'service.origin.node.role'?: string | undefined; 'service.origin.node.roles'?: string[] | undefined; 'service.origin.state'?: string | undefined; 'service.origin.type'?: string | undefined; 'service.origin.version'?: string | undefined; 'service.state'?: string | undefined; 'service.target.address'?: string | undefined; 'service.target.environment'?: string | undefined; 'service.target.ephemeral_id'?: string | undefined; 'service.target.id'?: string | undefined; 'service.target.name'?: string | undefined; 'service.target.node.name'?: string | undefined; 'service.target.node.role'?: string | undefined; 'service.target.node.roles'?: string[] | undefined; 'service.target.state'?: string | undefined; 'service.target.type'?: string | undefined; 'service.target.version'?: string | undefined; 'service.type'?: string | undefined; 'service.version'?: string | undefined; 'source.address'?: string | undefined; 'source.as.number'?: string | number | undefined; 'source.as.organization.name'?: string | undefined; 'source.bytes'?: string | number | undefined; 'source.domain'?: string | undefined; 'source.geo.city_name'?: string | undefined; 'source.geo.continent_code'?: string | undefined; 'source.geo.continent_name'?: string | undefined; 'source.geo.country_iso_code'?: string | undefined; 'source.geo.country_name'?: string | undefined; 'source.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'source.geo.name'?: string | undefined; 'source.geo.postal_code'?: string | undefined; 'source.geo.region_iso_code'?: string | undefined; 'source.geo.region_name'?: string | undefined; 'source.geo.timezone'?: string | undefined; 'source.ip'?: string | undefined; 'source.mac'?: string | undefined; 'source.nat.ip'?: string | undefined; 'source.nat.port'?: string | number | undefined; 'source.packets'?: string | number | undefined; 'source.port'?: string | number | undefined; 'source.registered_domain'?: string | undefined; 'source.subdomain'?: string | undefined; 'source.top_level_domain'?: string | undefined; 'source.user.domain'?: string | undefined; 'source.user.email'?: string | undefined; 'source.user.full_name'?: string | undefined; 'source.user.group.domain'?: string | undefined; 'source.user.group.id'?: string | undefined; 'source.user.group.name'?: string | undefined; 'source.user.hash'?: string | undefined; 'source.user.id'?: string | undefined; 'source.user.name'?: string | undefined; 'source.user.roles'?: string[] | undefined; 'span.id'?: string | undefined; tags?: string[] | undefined; 'threat.enrichments'?: { indicator?: unknown; 'matched.atomic'?: string | undefined; 'matched.field'?: string | undefined; 'matched.id'?: string | undefined; 'matched.index'?: string | undefined; 'matched.occurred'?: string | number | undefined; 'matched.type'?: string | undefined; }[] | undefined; 'threat.feed.dashboard_id'?: string | undefined; 'threat.feed.description'?: string | undefined; 'threat.feed.name'?: string | undefined; 'threat.feed.reference'?: string | undefined; 'threat.framework'?: string | undefined; 'threat.group.alias'?: string[] | undefined; 'threat.group.id'?: string | undefined; 'threat.group.name'?: string | undefined; 'threat.group.reference'?: string | undefined; 'threat.indicator.as.number'?: string | number | undefined; 'threat.indicator.as.organization.name'?: string | undefined; 'threat.indicator.confidence'?: string | undefined; 'threat.indicator.description'?: string | undefined; 'threat.indicator.email.address'?: string | undefined; 'threat.indicator.file.accessed'?: string | number | undefined; 'threat.indicator.file.attributes'?: string[] | undefined; 'threat.indicator.file.code_signature.digest_algorithm'?: string | undefined; 'threat.indicator.file.code_signature.exists'?: boolean | undefined; 'threat.indicator.file.code_signature.signing_id'?: string | undefined; 'threat.indicator.file.code_signature.status'?: string | undefined; 'threat.indicator.file.code_signature.subject_name'?: string | undefined; 'threat.indicator.file.code_signature.team_id'?: string | undefined; 'threat.indicator.file.code_signature.timestamp'?: string | number | undefined; 'threat.indicator.file.code_signature.trusted'?: boolean | undefined; 'threat.indicator.file.code_signature.valid'?: boolean | undefined; 'threat.indicator.file.created'?: string | number | undefined; 'threat.indicator.file.ctime'?: string | number | undefined; 'threat.indicator.file.device'?: string | undefined; 'threat.indicator.file.directory'?: string | undefined; 'threat.indicator.file.drive_letter'?: string | undefined; 'threat.indicator.file.elf.architecture'?: string | undefined; 'threat.indicator.file.elf.byte_order'?: string | undefined; 'threat.indicator.file.elf.cpu_type'?: string | undefined; 'threat.indicator.file.elf.creation_date'?: string | number | undefined; 'threat.indicator.file.elf.exports'?: unknown[] | undefined; 'threat.indicator.file.elf.go_import_hash'?: string | undefined; 'threat.indicator.file.elf.go_imports'?: unknown; 'threat.indicator.file.elf.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.go_stripped'?: boolean | undefined; 'threat.indicator.file.elf.header.abi_version'?: string | undefined; 'threat.indicator.file.elf.header.class'?: string | undefined; 'threat.indicator.file.elf.header.data'?: string | undefined; 'threat.indicator.file.elf.header.entrypoint'?: string | number | undefined; 'threat.indicator.file.elf.header.object_version'?: string | undefined; 'threat.indicator.file.elf.header.os_abi'?: string | undefined; 'threat.indicator.file.elf.header.type'?: string | undefined; 'threat.indicator.file.elf.header.version'?: string | undefined; 'threat.indicator.file.elf.import_hash'?: string | undefined; 'threat.indicator.file.elf.imports'?: unknown[] | undefined; 'threat.indicator.file.elf.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.elf.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.elf.sections'?: { chi2?: string | number | undefined; entropy?: string | number | undefined; flags?: string | undefined; name?: string | undefined; physical_offset?: string | undefined; physical_size?: string | number | undefined; type?: string | undefined; var_entropy?: string | number | undefined; virtual_address?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.elf.segments'?: { sections?: string | undefined; type?: string | undefined; }[] | undefined; 'threat.indicator.file.elf.shared_libraries'?: string[] | undefined; 'threat.indicator.file.elf.telfhash'?: string | undefined; 'threat.indicator.file.extension'?: string | undefined; 'threat.indicator.file.fork_name'?: string | undefined; 'threat.indicator.file.gid'?: string | undefined; 'threat.indicator.file.group'?: string | undefined; 'threat.indicator.file.hash.md5'?: string | undefined; 'threat.indicator.file.hash.sha1'?: string | undefined; 'threat.indicator.file.hash.sha256'?: string | undefined; 'threat.indicator.file.hash.sha384'?: string | undefined; 'threat.indicator.file.hash.sha512'?: string | undefined; 'threat.indicator.file.hash.ssdeep'?: string | undefined; 'threat.indicator.file.hash.tlsh'?: string | undefined; 'threat.indicator.file.inode'?: string | undefined; 'threat.indicator.file.mime_type'?: string | undefined; 'threat.indicator.file.mode'?: string | undefined; 'threat.indicator.file.mtime'?: string | number | undefined; 'threat.indicator.file.name'?: string | undefined; 'threat.indicator.file.owner'?: string | undefined; 'threat.indicator.file.path'?: string | undefined; 'threat.indicator.file.pe.architecture'?: string | undefined; 'threat.indicator.file.pe.company'?: string | undefined; 'threat.indicator.file.pe.description'?: string | undefined; 'threat.indicator.file.pe.file_version'?: string | undefined; 'threat.indicator.file.pe.go_import_hash'?: string | undefined; 'threat.indicator.file.pe.go_imports'?: unknown; 'threat.indicator.file.pe.go_imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.go_stripped'?: boolean | undefined; 'threat.indicator.file.pe.imphash'?: string | undefined; 'threat.indicator.file.pe.import_hash'?: string | undefined; 'threat.indicator.file.pe.imports'?: unknown[] | undefined; 'threat.indicator.file.pe.imports_names_entropy'?: string | number | undefined; 'threat.indicator.file.pe.imports_names_var_entropy'?: string | number | undefined; 'threat.indicator.file.pe.original_file_name'?: string | undefined; 'threat.indicator.file.pe.pehash'?: string | undefined; 'threat.indicator.file.pe.product'?: string | undefined; 'threat.indicator.file.pe.sections'?: { entropy?: string | number | undefined; name?: string | undefined; physical_size?: string | number | undefined; var_entropy?: string | number | undefined; virtual_size?: string | number | undefined; }[] | undefined; 'threat.indicator.file.size'?: string | number | undefined; 'threat.indicator.file.target_path'?: string | undefined; 'threat.indicator.file.type'?: string | undefined; 'threat.indicator.file.uid'?: string | undefined; 'threat.indicator.file.x509.alternative_names'?: string[] | undefined; 'threat.indicator.file.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.file.x509.issuer.country'?: string[] | undefined; 'threat.indicator.file.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.file.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.not_after'?: string | number | undefined; 'threat.indicator.file.x509.not_before'?: string | number | undefined; 'threat.indicator.file.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.file.x509.public_key_curve'?: string | undefined; 'threat.indicator.file.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.file.x509.public_key_size'?: string | number | undefined; 'threat.indicator.file.x509.serial_number'?: string | undefined; 'threat.indicator.file.x509.signature_algorithm'?: string | undefined; 'threat.indicator.file.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.file.x509.subject.country'?: string[] | undefined; 'threat.indicator.file.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.file.x509.subject.locality'?: string[] | undefined; 'threat.indicator.file.x509.subject.organization'?: string[] | undefined; 'threat.indicator.file.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.file.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.file.x509.version_number'?: string | undefined; 'threat.indicator.first_seen'?: string | number | undefined; 'threat.indicator.geo.city_name'?: string | undefined; 'threat.indicator.geo.continent_code'?: string | undefined; 'threat.indicator.geo.continent_name'?: string | undefined; 'threat.indicator.geo.country_iso_code'?: string | undefined; 'threat.indicator.geo.country_name'?: string | undefined; 'threat.indicator.geo.location'?: string | { type: string; coordinates: number[]; } | { lat: number; lon: number; } | { location: number[]; } | { location: string; } | undefined; 'threat.indicator.geo.name'?: string | undefined; 'threat.indicator.geo.postal_code'?: string | undefined; 'threat.indicator.geo.region_iso_code'?: string | undefined; 'threat.indicator.geo.region_name'?: string | undefined; 'threat.indicator.geo.timezone'?: string | undefined; 'threat.indicator.ip'?: string | undefined; 'threat.indicator.last_seen'?: string | number | undefined; 'threat.indicator.marking.tlp'?: string | undefined; 'threat.indicator.marking.tlp_version'?: string | undefined; 'threat.indicator.modified_at'?: string | number | undefined; 'threat.indicator.name'?: string | undefined; 'threat.indicator.port'?: string | number | undefined; 'threat.indicator.provider'?: string | undefined; 'threat.indicator.reference'?: string | undefined; 'threat.indicator.registry.data.bytes'?: string | undefined; 'threat.indicator.registry.data.strings'?: string[] | undefined; 'threat.indicator.registry.data.type'?: string | undefined; 'threat.indicator.registry.hive'?: string | undefined; 'threat.indicator.registry.key'?: string | undefined; 'threat.indicator.registry.path'?: string | undefined; 'threat.indicator.registry.value'?: string | undefined; 'threat.indicator.scanner_stats'?: string | number | undefined; 'threat.indicator.sightings'?: string | number | undefined; 'threat.indicator.type'?: string | undefined; 'threat.indicator.url.domain'?: string | undefined; 'threat.indicator.url.extension'?: string | undefined; 'threat.indicator.url.fragment'?: string | undefined; 'threat.indicator.url.full'?: string | undefined; 'threat.indicator.url.original'?: string | undefined; 'threat.indicator.url.password'?: string | undefined; 'threat.indicator.url.path'?: string | undefined; 'threat.indicator.url.port'?: string | number | undefined; 'threat.indicator.url.query'?: string | undefined; 'threat.indicator.url.registered_domain'?: string | undefined; 'threat.indicator.url.scheme'?: string | undefined; 'threat.indicator.url.subdomain'?: string | undefined; 'threat.indicator.url.top_level_domain'?: string | undefined; 'threat.indicator.url.username'?: string | undefined; 'threat.indicator.x509.alternative_names'?: string[] | undefined; 'threat.indicator.x509.issuer.common_name'?: string[] | undefined; 'threat.indicator.x509.issuer.country'?: string[] | undefined; 'threat.indicator.x509.issuer.distinguished_name'?: string | undefined; 'threat.indicator.x509.issuer.locality'?: string[] | undefined; 'threat.indicator.x509.issuer.organization'?: string[] | undefined; 'threat.indicator.x509.issuer.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.issuer.state_or_province'?: string[] | undefined; 'threat.indicator.x509.not_after'?: string | number | undefined; 'threat.indicator.x509.not_before'?: string | number | undefined; 'threat.indicator.x509.public_key_algorithm'?: string | undefined; 'threat.indicator.x509.public_key_curve'?: string | undefined; 'threat.indicator.x509.public_key_exponent'?: string | number | undefined; 'threat.indicator.x509.public_key_size'?: string | number | undefined; 'threat.indicator.x509.serial_number'?: string | undefined; 'threat.indicator.x509.signature_algorithm'?: string | undefined; 'threat.indicator.x509.subject.common_name'?: string[] | undefined; 'threat.indicator.x509.subject.country'?: string[] | undefined; 'threat.indicator.x509.subject.distinguished_name'?: string | undefined; 'threat.indicator.x509.subject.locality'?: string[] | undefined; 'threat.indicator.x509.subject.organization'?: string[] | undefined; 'threat.indicator.x509.subject.organizational_unit'?: string[] | undefined; 'threat.indicator.x509.subject.state_or_province'?: string[] | undefined; 'threat.indicator.x509.version_number'?: string | undefined; 'threat.software.alias'?: string[] | undefined; 'threat.software.id'?: string | undefined; 'threat.software.name'?: string | undefined; 'threat.software.platforms'?: string[] | undefined; 'threat.software.reference'?: string | undefined; 'threat.software.type'?: string | undefined; 'threat.tactic.id'?: string[] | undefined; 'threat.tactic.name'?: string[] | undefined; 'threat.tactic.reference'?: string[] | undefined; 'threat.technique.id'?: string[] | undefined; 'threat.technique.name'?: string[] | undefined; 'threat.technique.reference'?: string[] | undefined; 'threat.technique.subtechnique.id'?: string[] | undefined; 'threat.technique.subtechnique.name'?: string[] | undefined; 'threat.technique.subtechnique.reference'?: string[] | undefined; 'tls.cipher'?: string | undefined; 'tls.client.certificate'?: string | undefined; 'tls.client.certificate_chain'?: string[] | undefined; 'tls.client.hash.md5'?: string | undefined; 'tls.client.hash.sha1'?: string | undefined; 'tls.client.hash.sha256'?: string | undefined; 'tls.client.issuer'?: string | undefined; 'tls.client.ja3'?: string | undefined; 'tls.client.not_after'?: string | number | undefined; 'tls.client.not_before'?: string | number | undefined; 'tls.client.server_name'?: string | undefined; 'tls.client.subject'?: string | undefined; 'tls.client.supported_ciphers'?: string[] | undefined; 'tls.client.x509.alternative_names'?: string[] | undefined; 'tls.client.x509.issuer.common_name'?: string[] | undefined; 'tls.client.x509.issuer.country'?: string[] | undefined; 'tls.client.x509.issuer.distinguished_name'?: string | undefined; 'tls.client.x509.issuer.locality'?: string[] | undefined; 'tls.client.x509.issuer.organization'?: string[] | undefined; 'tls.client.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.client.x509.issuer.state_or_province'?: string[] | undefined; 'tls.client.x509.not_after'?: string | number | undefined; 'tls.client.x509.not_before'?: string | number | undefined; 'tls.client.x509.public_key_algorithm'?: string | undefined; 'tls.client.x509.public_key_curve'?: string | undefined; 'tls.client.x509.public_key_exponent'?: string | number | undefined; 'tls.client.x509.public_key_size'?: string | number | undefined; 'tls.client.x509.serial_number'?: string | undefined; 'tls.client.x509.signature_algorithm'?: string | undefined; 'tls.client.x509.subject.common_name'?: string[] | undefined; 'tls.client.x509.subject.country'?: string[] | undefined; 'tls.client.x509.subject.distinguished_name'?: string | undefined; 'tls.client.x509.subject.locality'?: string[] | undefined; 'tls.client.x509.subject.organization'?: string[] | undefined; 'tls.client.x509.subject.organizational_unit'?: string[] | undefined; 'tls.client.x509.subject.state_or_province'?: string[] | undefined; 'tls.client.x509.version_number'?: string | undefined; 'tls.curve'?: string | undefined; 'tls.established'?: boolean | undefined; 'tls.next_protocol'?: string | undefined; 'tls.resumed'?: boolean | undefined; 'tls.server.certificate'?: string | undefined; 'tls.server.certificate_chain'?: string[] | undefined; 'tls.server.hash.md5'?: string | undefined; 'tls.server.hash.sha1'?: string | undefined; 'tls.server.hash.sha256'?: string | undefined; 'tls.server.issuer'?: string | undefined; 'tls.server.ja3s'?: string | undefined; 'tls.server.not_after'?: string | number | undefined; 'tls.server.not_before'?: string | number | undefined; 'tls.server.subject'?: string | undefined; 'tls.server.x509.alternative_names'?: string[] | undefined; 'tls.server.x509.issuer.common_name'?: string[] | undefined; 'tls.server.x509.issuer.country'?: string[] | undefined; 'tls.server.x509.issuer.distinguished_name'?: string | undefined; 'tls.server.x509.issuer.locality'?: string[] | undefined; 'tls.server.x509.issuer.organization'?: string[] | undefined; 'tls.server.x509.issuer.organizational_unit'?: string[] | undefined; 'tls.server.x509.issuer.state_or_province'?: string[] | undefined; 'tls.server.x509.not_after'?: string | number | undefined; 'tls.server.x509.not_before'?: string | number | undefined; 'tls.server.x509.public_key_algorithm'?: string | undefined; 'tls.server.x509.public_key_curve'?: string | undefined; 'tls.server.x509.public_key_exponent'?: string | number | undefined; 'tls.server.x509.public_key_size'?: string | number | undefined; 'tls.server.x509.serial_number'?: string | undefined; 'tls.server.x509.signature_algorithm'?: string | undefined; 'tls.server.x509.subject.common_name'?: string[] | undefined; 'tls.server.x509.subject.country'?: string[] | undefined; 'tls.server.x509.subject.distinguished_name'?: string | undefined; 'tls.server.x509.subject.locality'?: string[] | undefined; 'tls.server.x509.subject.organization'?: string[] | undefined; 'tls.server.x509.subject.organizational_unit'?: string[] | undefined; 'tls.server.x509.subject.state_or_province'?: string[] | undefined; 'tls.server.x509.version_number'?: string | undefined; 'tls.version'?: string | undefined; 'tls.version_protocol'?: string | undefined; 'trace.id'?: string | undefined; 'transaction.id'?: string | undefined; 'url.domain'?: string | undefined; 'url.extension'?: string | undefined; 'url.fragment'?: string | undefined; 'url.full'?: string | undefined; 'url.original'?: string | undefined; 'url.password'?: string | undefined; 'url.path'?: string | undefined; 'url.port'?: string | number | undefined; 'url.query'?: string | undefined; 'url.registered_domain'?: string | undefined; 'url.scheme'?: string | undefined; 'url.subdomain'?: string | undefined; 'url.top_level_domain'?: string | undefined; 'url.username'?: string | undefined; 'user.changes.domain'?: string | undefined; 'user.changes.email'?: string | undefined; 'user.changes.full_name'?: string | undefined; 'user.changes.group.domain'?: string | undefined; 'user.changes.group.id'?: string | undefined; 'user.changes.group.name'?: string | undefined; 'user.changes.hash'?: string | undefined; 'user.changes.id'?: string | undefined; 'user.changes.name'?: string | undefined; 'user.changes.roles'?: string[] | undefined; 'user.domain'?: string | undefined; 'user.effective.domain'?: string | undefined; 'user.effective.email'?: string | undefined; 'user.effective.full_name'?: string | undefined; 'user.effective.group.domain'?: string | undefined; 'user.effective.group.id'?: string | undefined; 'user.effective.group.name'?: string | undefined; 'user.effective.hash'?: string | undefined; 'user.effective.id'?: string | undefined; 'user.effective.name'?: string | undefined; 'user.effective.roles'?: string[] | undefined; 'user.email'?: string | undefined; 'user.full_name'?: string | undefined; 'user.group.domain'?: string | undefined; 'user.group.id'?: string | undefined; 'user.group.name'?: string | undefined; 'user.hash'?: string | undefined; 'user.id'?: string | undefined; 'user.name'?: string | undefined; 'user.risk.calculated_level'?: string | undefined; 'user.risk.calculated_score'?: number | undefined; 'user.risk.calculated_score_norm'?: number | undefined; 'user.risk.static_level'?: string | undefined; 'user.risk.static_score'?: number | undefined; 'user.risk.static_score_norm'?: number | undefined; 'user.roles'?: string[] | undefined; 'user.target.domain'?: string | undefined; 'user.target.email'?: string | undefined; 'user.target.full_name'?: string | undefined; 'user.target.group.domain'?: string | undefined; 'user.target.group.id'?: string | undefined; 'user.target.group.name'?: string | undefined; 'user.target.hash'?: string | undefined; 'user.target.id'?: string | undefined; 'user.target.name'?: string | undefined; 'user.target.roles'?: string[] | undefined; 'user_agent.device.name'?: string | undefined; 'user_agent.name'?: string | undefined; 'user_agent.original'?: string | undefined; 'user_agent.os.family'?: string | undefined; 'user_agent.os.full'?: string | undefined; 'user_agent.os.kernel'?: string | undefined; 'user_agent.os.name'?: string | undefined; 'user_agent.os.platform'?: string | undefined; 'user_agent.os.type'?: string | undefined; 'user_agent.os.version'?: string | undefined; 'user_agent.version'?: string | undefined; 'vulnerability.category'?: string[] | undefined; 'vulnerability.classification'?: string | undefined; 'vulnerability.description'?: string | undefined; 'vulnerability.enumeration'?: string | undefined; 'vulnerability.id'?: string | undefined; 'vulnerability.reference'?: string | undefined; 'vulnerability.report_id'?: string | undefined; 'vulnerability.scanner.vendor'?: string | undefined; 'vulnerability.score.base'?: number | undefined; 'vulnerability.score.environmental'?: number | undefined; 'vulnerability.score.temporal'?: number | undefined; 'vulnerability.score.version'?: string | undefined; 'vulnerability.severity'?: string | undefined; }" ], "path": "packages/kbn-alerts-as-data-utils/src/schemas/generated/stack_schema.ts", "deprecated": false, diff --git a/api_docs/kbn_alerts_as_data_utils.mdx b/api_docs/kbn_alerts_as_data_utils.mdx index 690e79c60ccaf..d85dceff868c6 100644 --- a/api_docs/kbn_alerts_as_data_utils.mdx +++ b/api_docs/kbn_alerts_as_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-as-data-utils title: "@kbn/alerts-as-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-as-data-utils plugin -date: 2024-06-11 +date: 2024-06-19 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.devdocs.json b/api_docs/kbn_alerts_ui_shared.devdocs.json index 8d555575a8811..ad5bd582ef9ac 100644 --- a/api_docs/kbn_alerts_ui_shared.devdocs.json +++ b/api_docs/kbn_alerts_ui_shared.devdocs.json @@ -594,57 +594,6 @@ "returnComment": [], "initialIsOpen": false }, - { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.useLoadRuleTypesQuery", - "type": "Function", - "tags": [], - "label": "useLoadRuleTypesQuery", - "description": [], - "signature": [ - "({ http, toasts, filteredRuleTypes, registeredRuleTypes, enabled, }: ", - { - "pluginId": "@kbn/alerts-ui-shared", - "scope": "common", - "docId": "kibKbnAlertsUiSharedPluginApi", - "section": "def-common.UseRuleTypesProps", - "text": "UseRuleTypesProps" - }, - ") => { ruleTypesState: { initialLoad: boolean; isLoading: boolean; data: ", - "RuleTypeIndexWithDescriptions", - "; error: Error | null; }; hasAnyAuthorizedRuleType: boolean; authorizedRuleTypes: ", - "RuleTypeWithDescription", - "[]; authorizedToReadAnyRules: boolean; authorizedToCreateAnyRules: boolean; isSuccess: boolean; }" - ], - "path": "packages/kbn-alerts-ui-shared/src/common/hooks/use_load_rule_types_query.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.useLoadRuleTypesQuery.$1", - "type": "Object", - "tags": [], - "label": "{\n http,\n toasts,\n filteredRuleTypes,\n registeredRuleTypes,\n enabled = true,\n}", - "description": [], - "signature": [ - { - "pluginId": "@kbn/alerts-ui-shared", - "scope": "common", - "docId": "kibKbnAlertsUiSharedPluginApi", - "section": "def-common.UseRuleTypesProps", - "text": "UseRuleTypesProps" - } - ], - "path": "packages/kbn-alerts-ui-shared/src/common/hooks/use_load_rule_types_query.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/alerts-ui-shared", "id": "def-common.useRuleAADFields", @@ -703,459 +652,347 @@ "interfaces": [ { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertFieldsTableProps", + "id": "def-common.ActionConnectorFieldsProps", "type": "Interface", "tags": [], - "label": "AlertFieldsTableProps", + "label": "ActionConnectorFieldsProps", "description": [], - "path": "packages/kbn-alerts-ui-shared/src/alert_fields_table/index.tsx", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertFieldsTableProps.alert", - "type": "CompoundType", + "id": "def-common.ActionConnectorFieldsProps.readOnly", + "type": "boolean", "tags": [], - "label": "alert", - "description": [ - "\nThe raw alert object" - ], - "signature": [ - { - "pluginId": "@kbn/alerting-types", - "scope": "common", - "docId": "kibKbnAlertingTypesPluginApi", - "section": "def-common.BasicFields", - "text": "BasicFields" - }, - " & { \"@timestamp\"?: string[] | undefined; \"event.action\"?: string[] | undefined; tags?: string[] | undefined; kibana?: string[] | undefined; \"kibana.alert.rule.rule_type_id\"?: string[] | undefined; \"kibana.alert.rule.consumer\"?: string[] | undefined; \"kibana.alert.rule.execution.uuid\"?: string[] | undefined; \"kibana.alert.instance.id\"?: string[] | undefined; \"kibana.alert.rule.category\"?: string[] | undefined; \"kibana.alert.rule.name\"?: string[] | undefined; \"kibana.alert.rule.producer\"?: string[] | undefined; \"kibana.alert.rule.uuid\"?: string[] | undefined; \"kibana.alert.status\"?: string[] | undefined; \"kibana.alert.uuid\"?: string[] | undefined; \"kibana.space_ids\"?: string[] | undefined; \"event.kind\"?: string[] | undefined; \"kibana.alert.action_group\"?: string[] | undefined; \"kibana.alert.case_ids\"?: string[] | undefined; \"kibana.alert.duration.us\"?: string[] | undefined; \"kibana.alert.end\"?: string[] | undefined; \"kibana.alert.flapping\"?: string[] | undefined; \"kibana.alert.maintenance_window_ids\"?: string[] | undefined; \"kibana.alert.reason\"?: string[] | undefined; \"kibana.alert.rule.parameters\"?: string[] | undefined; \"kibana.alert.rule.tags\"?: string[] | undefined; \"kibana.alert.start\"?: string[] | undefined; \"kibana.alert.time_range\"?: string[] | undefined; \"kibana.alert.workflow_assignee_ids\"?: string[] | undefined; \"kibana.alert.workflow_status\"?: string[] | undefined; \"kibana.alert.workflow_tags\"?: string[] | undefined; \"kibana.version\"?: string[] | undefined; \"kibana.alert.context\"?: string[] | undefined; \"kibana.alert.evaluation.threshold\"?: string[] | undefined; \"kibana.alert.evaluation.value\"?: string[] | undefined; \"kibana.alert.evaluation.values\"?: string[] | undefined; \"kibana.alert.group\"?: string[] | undefined; \"ecs.version\"?: string[] | undefined; \"kibana.alert.risk_score\"?: string[] | undefined; \"kibana.alert.rule.author\"?: string[] | undefined; \"kibana.alert.rule.created_at\"?: string[] | undefined; \"kibana.alert.rule.created_by\"?: string[] | undefined; \"kibana.alert.rule.description\"?: string[] | undefined; \"kibana.alert.rule.enabled\"?: string[] | undefined; \"kibana.alert.rule.from\"?: string[] | undefined; \"kibana.alert.rule.interval\"?: string[] | undefined; \"kibana.alert.rule.license\"?: string[] | undefined; \"kibana.alert.rule.note\"?: string[] | undefined; \"kibana.alert.rule.references\"?: string[] | undefined; \"kibana.alert.rule.rule_id\"?: string[] | undefined; \"kibana.alert.rule.rule_name_override\"?: string[] | undefined; \"kibana.alert.rule.to\"?: string[] | undefined; \"kibana.alert.rule.type\"?: string[] | undefined; \"kibana.alert.rule.updated_at\"?: string[] | undefined; \"kibana.alert.rule.updated_by\"?: string[] | undefined; \"kibana.alert.rule.version\"?: string[] | undefined; \"kibana.alert.severity\"?: string[] | undefined; \"kibana.alert.suppression.docs_count\"?: string[] | undefined; \"kibana.alert.suppression.end\"?: string[] | undefined; \"kibana.alert.suppression.start\"?: string[] | undefined; \"kibana.alert.suppression.terms.field\"?: string[] | undefined; \"kibana.alert.suppression.terms.value\"?: string[] | undefined; \"kibana.alert.system_status\"?: string[] | undefined; \"kibana.alert.workflow_reason\"?: string[] | undefined; \"kibana.alert.workflow_user\"?: string[] | undefined; \"event.module\"?: string[] | undefined; \"kibana.alert.rule.threat.framework\"?: string[] | undefined; \"kibana.alert.rule.threat.tactic.id\"?: string[] | undefined; \"kibana.alert.rule.threat.tactic.name\"?: string[] | undefined; \"kibana.alert.rule.threat.tactic.reference\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.id\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.name\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.reference\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.subtechnique.id\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.subtechnique.name\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.subtechnique.reference\"?: string[] | undefined; \"kibana.alert.building_block_type\"?: string[] | undefined; \"kibana.alert\"?: string[] | undefined; \"kibana.alert.group.field\"?: string[] | undefined; \"kibana.alert.group.value\"?: string[] | undefined; \"kibana.alert.rule\"?: string[] | undefined; \"kibana.alert.rule.exceptions_list\"?: string[] | undefined; \"kibana.alert.rule.namespace\"?: string[] | undefined; \"kibana.alert.suppression.terms\"?: string[] | undefined; } & { [x: string]: unknown[]; }" - ], - "path": "packages/kbn-alerts-ui-shared/src/alert_fields_table/index.tsx", + "label": "readOnly", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertFieldsTableProps.fields", - "type": "Array", - "tags": [], - "label": "fields", - "description": [ - "\nA list of alert field keys to be shown in the table.\nWhen not defined, all the fields are shown." - ], - "signature": [ - "(string | number)[] | undefined" - ], - "path": "packages/kbn-alerts-ui-shared/src/alert_fields_table/index.tsx", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertLifecycleStatusBadgeProps", - "type": "Interface", - "tags": [], - "label": "AlertLifecycleStatusBadgeProps", - "description": [], - "path": "packages/kbn-alerts-ui-shared/src/alert_lifecycle_status_badge/index.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertLifecycleStatusBadgeProps.alertStatus", - "type": "CompoundType", + "id": "def-common.ActionConnectorFieldsProps.isEdit", + "type": "boolean", "tags": [], - "label": "alertStatus", + "label": "isEdit", "description": [], - "signature": [ - "\"recovered\" | \"active\" | \"untracked\"" - ], - "path": "packages/kbn-alerts-ui-shared/src/alert_lifecycle_status_badge/index.tsx", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertLifecycleStatusBadgeProps.flapping", - "type": "CompoundType", + "id": "def-common.ActionConnectorFieldsProps.registerPreSubmitValidator", + "type": "Function", "tags": [], - "label": "flapping", + "label": "registerPreSubmitValidator", "description": [], "signature": [ - "string | boolean | undefined" + "(validator: ", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ConnectorValidationFunc", + "text": "ConnectorValidationFunc" + }, + ") => void" ], - "path": "packages/kbn-alerts-ui-shared/src/alert_lifecycle_status_badge/index.tsx", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, - "trackAdoption": false + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionConnectorFieldsProps.registerPreSubmitValidator.$1", + "type": "Function", + "tags": [], + "label": "validator", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ConnectorValidationFunc", + "text": "ConnectorValidationFunc" + } + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] } ], "initialIsOpen": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps", + "id": "def-common.ActionConnectorProps", "type": "Interface", "tags": [], - "label": "AlertsSearchBarProps", + "label": "ActionConnectorProps", "description": [], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "signature": [ + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionConnectorProps", + "text": "ActionConnectorProps" + }, + "" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.appName", - "type": "string", + "id": "def-common.ActionConnectorProps.secrets", + "type": "Uncategorized", "tags": [], - "label": "appName", + "label": "secrets", "description": [], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "signature": [ + "Secrets" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.disableQueryLanguageSwitcher", - "type": "CompoundType", + "id": "def-common.ActionConnectorProps.id", + "type": "string", "tags": [], - "label": "disableQueryLanguageSwitcher", + "label": "id", "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.featureIds", - "type": "Array", + "id": "def-common.ActionConnectorProps.actionTypeId", + "type": "string", "tags": [], - "label": "featureIds", + "label": "actionTypeId", "description": [], - "signature": [ - { - "pluginId": "@kbn/rule-data-utils", - "scope": "common", - "docId": "kibKbnRuleDataUtilsPluginApi", - "section": "def-common.AlertConsumers", - "text": "AlertConsumers" - }, - "[]" - ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.rangeFrom", + "id": "def-common.ActionConnectorProps.name", "type": "string", "tags": [], - "label": "rangeFrom", + "label": "name", "description": [], - "signature": [ - "string | undefined" - ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.rangeTo", - "type": "string", + "id": "def-common.ActionConnectorProps.referencedByCount", + "type": "number", "tags": [], - "label": "rangeTo", + "label": "referencedByCount", "description": [], "signature": [ - "string | undefined" + "number | undefined" ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.query", - "type": "string", + "id": "def-common.ActionConnectorProps.config", + "type": "Uncategorized", "tags": [], - "label": "query", + "label": "config", "description": [], "signature": [ - "string | undefined" + "Config" ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.filters", - "type": "Array", + "id": "def-common.ActionConnectorProps.isPreconfigured", + "type": "boolean", "tags": [], - "label": "filters", + "label": "isPreconfigured", "description": [], - "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[] | undefined" - ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.showFilterBar", - "type": "CompoundType", + "id": "def-common.ActionConnectorProps.isDeprecated", + "type": "boolean", "tags": [], - "label": "showFilterBar", + "label": "isDeprecated", "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.showDatePicker", - "type": "CompoundType", + "id": "def-common.ActionConnectorProps.isSystemAction", + "type": "boolean", "tags": [], - "label": "showDatePicker", + "label": "isSystemAction", "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.showSubmitButton", + "id": "def-common.ActionConnectorProps.isMissingSecrets", "type": "CompoundType", "tags": [], - "label": "showSubmitButton", + "label": "isMissingSecrets", "description": [], "signature": [ "boolean | undefined" ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionParamsProps", + "type": "Interface", + "tags": [], + "label": "ActionParamsProps", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionParamsProps", + "text": "ActionParamsProps" }, + "" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.placeholder", - "type": "string", + "id": "def-common.ActionParamsProps.actionParams", + "type": "Object", "tags": [], - "label": "placeholder", + "label": "actionParams", "description": [], "signature": [ - "string | undefined" + "{ [P in keyof TParams]?: TParams[P] | undefined; }" ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.submitOnBlur", - "type": "CompoundType", + "id": "def-common.ActionParamsProps.index", + "type": "number", "tags": [], - "label": "submitOnBlur", + "label": "index", "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.ruleTypeId", - "type": "string", + "id": "def-common.ActionParamsProps.editAction", + "type": "Function", "tags": [], - "label": "ruleTypeId", + "label": "editAction", "description": [], "signature": [ - "string | undefined" - ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.onQueryChange", - "type": "Function", - "tags": [], - "label": "onQueryChange", - "description": [], - "signature": [ - "((query: { dateRange: { from: string; to: string; mode?: \"absolute\" | \"relative\" | undefined; }; query?: string | undefined; }) => void) | undefined" + "(key: string, value: ", + { + "pluginId": "@kbn/core-saved-objects-common", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsCommonPluginApi", + "section": "def-common.SavedObjectAttribute", + "text": "SavedObjectAttribute" + }, + ", index: number) => void" ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.onQueryChange.$1", - "type": "Object", + "id": "def-common.ActionParamsProps.editAction.$1", + "type": "string", "tags": [], - "label": "query", + "label": "key", "description": [], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "signature": [ + "string" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.onQueryChange.$1.dateRange", - "type": "Object", - "tags": [], - "label": "dateRange", - "description": [], - "signature": [ - "{ from: string; to: string; mode?: \"absolute\" | \"relative\" | undefined; }" - ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.onQueryChange.$1.query", - "type": "string", - "tags": [], - "label": "query", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", - "deprecated": false, - "trackAdoption": false - } - ] - } - ], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.onQuerySubmit", - "type": "Function", - "tags": [], - "label": "onQuerySubmit", - "description": [], - "signature": [ - "(query: { dateRange: { from: string; to: string; mode?: \"absolute\" | \"relative\" | undefined; }; query?: string | undefined; }) => void" - ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ + "isRequired": true + }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.onQuerySubmit.$1", - "type": "Object", + "id": "def-common.ActionParamsProps.editAction.$2", + "type": "CompoundType", "tags": [], - "label": "query", + "label": "value", "description": [], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.onQuerySubmit.$1.dateRange", - "type": "Object", - "tags": [], - "label": "dateRange", - "description": [], - "signature": [ - "{ from: string; to: string; mode?: \"absolute\" | \"relative\" | undefined; }" - ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", - "deprecated": false, - "trackAdoption": false - }, + "signature": [ { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.onQuerySubmit.$1.query", - "type": "string", - "tags": [], - "label": "query", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "@kbn/core-saved-objects-common", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsCommonPluginApi", + "section": "def-common.SavedObjectAttribute", + "text": "SavedObjectAttribute" } - ] - } - ], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.onFiltersUpdated", - "type": "Function", - "tags": [], - "label": "onFiltersUpdated", - "description": [], - "signature": [ - "((filters: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false }, - "[]) => void) | undefined" - ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.onFiltersUpdated.$1", - "type": "Array", + "id": "def-common.ActionParamsProps.editAction.$3", + "type": "number", "tags": [], - "label": "filters", + "label": "index", "description": [], "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[]" + "number" ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -1165,131 +1002,1233 @@ }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.http", + "id": "def-common.ActionParamsProps.errors", "type": "Object", "tags": [], - "label": "http", + "label": "errors", "description": [], "signature": [ { - "pluginId": "@kbn/core-http-browser", + "pluginId": "@kbn/alerts-ui-shared", "scope": "common", - "docId": "kibKbnCoreHttpBrowserPluginApi", - "section": "def-common.HttpSetup", - "text": "HttpSetup" + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.RuleFormErrors", + "text": "RuleFormErrors" } ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.toasts", - "type": "Object", + "id": "def-common.ActionParamsProps.ruleTypeId", + "type": "string", "tags": [], - "label": "toasts", + "label": "ruleTypeId", "description": [], "signature": [ - { - "pluginId": "@kbn/core-notifications-browser", - "scope": "common", - "docId": "kibKbnCoreNotificationsBrowserPluginApi", - "section": "def-common.IToasts", - "text": "IToasts" - } + "string | undefined" ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.unifiedSearchBar", - "type": "Function", + "id": "def-common.ActionParamsProps.messageVariables", + "type": "Array", "tags": [], - "label": "unifiedSearchBar", + "label": "messageVariables", "description": [], "signature": [ - "(props: ", - { - "pluginId": "unifiedSearch", - "scope": "public", - "docId": "kibUnifiedSearchPluginApi", - "section": "def-public.StatefulSearchBarProps", - "text": "StatefulSearchBarProps" - }, - "<", { - "pluginId": "@kbn/es-query", + "pluginId": "@kbn/alerting-types", "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Query", - "text": "Query" + "docId": "kibKbnAlertingTypesPluginApi", + "section": "def-common.ActionVariable", + "text": "ActionVariable" }, - ">) => React.ReactElement>" + "[] | undefined" ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.unifiedSearchBar.$1", - "type": "CompoundType", - "tags": [], - "label": "props", - "description": [], - "signature": [ - "Omit<", - "SearchBarOwnProps", - "<", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Query", - "text": "Query" - }, - ">, \"showSaveQuery\"> & { appName: string; useDefaultBehaviors?: boolean | undefined; savedQueryId?: string | undefined; saveQueryMenuVisibility?: ", - "SavedQueryMenuVisibility", - " | undefined; onSavedQueryIdChange?: ((savedQueryId?: string | undefined) => void) | undefined; onFiltersUpdated?: ((filters: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[]) => void) | undefined; }" - ], - "path": "src/plugins/unified_search/public/types.ts", - "deprecated": false, - "trackAdoption": false - } - ] + "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.AlertsSearchBarProps.dataViewsService", - "type": "Object", + "id": "def-common.ActionParamsProps.defaultMessage", + "type": "string", "tags": [], - "label": "dataViewsService", + "label": "defaultMessage", "description": [], "signature": [ - "{ create: (spec: ", - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataViewSpec", - "text": "DataViewSpec" - }, - ", skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", - { - "pluginId": "dataViews", - "scope": "common", + "string | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionParamsProps.useDefaultMessage", + "type": "CompoundType", + "tags": [], + "label": "useDefaultMessage", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionParamsProps.actionConnector", + "type": "CompoundType", + "tags": [], + "label": "actionConnector", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionConnector", + "text": "ActionConnector" + }, + ", Record> | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionParamsProps.isLoading", + "type": "CompoundType", + "tags": [], + "label": "isLoading", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionParamsProps.isDisabled", + "type": "CompoundType", + "tags": [], + "label": "isDisabled", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionParamsProps.selectedActionGroupId", + "type": "string", + "tags": [], + "label": "selectedActionGroupId", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionParamsProps.showEmailSubjectAndMessage", + "type": "CompoundType", + "tags": [], + "label": "showEmailSubjectAndMessage", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionParamsProps.executionMode", + "type": "CompoundType", + "tags": [], + "label": "executionMode", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionConnectorMode", + "text": "ActionConnectorMode" + }, + " | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionParamsProps.onBlur", + "type": "Function", + "tags": [], + "label": "onBlur", + "description": [], + "signature": [ + "((field?: string | undefined) => void) | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionParamsProps.onBlur.$1", + "type": "string", + "tags": [], + "label": "field", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionParamsProps.producerId", + "type": "string", + "tags": [], + "label": "producerId", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionReadOnlyElementProps", + "type": "Interface", + "tags": [], + "label": "ActionReadOnlyElementProps", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionReadOnlyElementProps.connectorId", + "type": "string", + "tags": [], + "label": "connectorId", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionReadOnlyElementProps.connectorName", + "type": "string", + "tags": [], + "label": "connectorName", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel", + "type": "Interface", + "tags": [], + "label": "ActionTypeModel", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", + "text": "ActionTypeModel" + }, + "" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.iconClass", + "type": "CompoundType", + "tags": [], + "label": "iconClass", + "description": [], + "signature": [ + "string | React.ComponentType<{}>" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.selectMessage", + "type": "string", + "tags": [], + "label": "selectMessage", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.actionTypeTitle", + "type": "string", + "tags": [], + "label": "actionTypeTitle", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.validateParams", + "type": "Function", + "tags": [], + "label": "validateParams", + "description": [], + "signature": [ + "(actionParams: ActionParams) => Promise<", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.GenericValidationResult", + "text": "GenericValidationResult" + }, + ">" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.validateParams.$1", + "type": "Uncategorized", + "tags": [], + "label": "actionParams", + "description": [], + "signature": [ + "ActionParams" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.actionConnectorFields", + "type": "CompoundType", + "tags": [], + "label": "actionConnectorFields", + "description": [], + "signature": [ + "React.LazyExoticComponent> | null" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.actionParamsFields", + "type": "Function", + "tags": [], + "label": "actionParamsFields", + "description": [], + "signature": [ + "React.ExoticComponent<(", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionParamsProps", + "text": "ActionParamsProps" + }, + " & React.RefAttributes, any, any>>) | (", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionParamsProps", + "text": "ActionParamsProps" + }, + " & { children?: React.ReactNode; })> & { readonly _result: React.ComponentType<", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionParamsProps", + "text": "ActionParamsProps" + }, + ">; }" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.actionParamsFields.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.actionReadOnlyExtraComponent", + "type": "Function", + "tags": [], + "label": "actionReadOnlyExtraComponent", + "description": [], + "signature": [ + "React.LazyExoticComponent> | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.defaultActionParams", + "type": "Object", + "tags": [], + "label": "defaultActionParams", + "description": [], + "signature": [ + "RecursivePartial", + " | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.defaultRecoveredActionParams", + "type": "Object", + "tags": [], + "label": "defaultRecoveredActionParams", + "description": [], + "signature": [ + "RecursivePartial", + " | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.customConnectorSelectItem", + "type": "Object", + "tags": [], + "label": "customConnectorSelectItem", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.CustomConnectorSelectionItem", + "text": "CustomConnectorSelectionItem" + }, + " | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.isExperimental", + "type": "CompoundType", + "tags": [], + "label": "isExperimental", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.subtype", + "type": "Array", + "tags": [], + "label": "subtype", + "description": [], + "signature": [ + "{ id: string; name: string; }[] | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.convertParamsBetweenGroups", + "type": "Function", + "tags": [], + "label": "convertParamsBetweenGroups", + "description": [], + "signature": [ + "((params: ActionParams) => {} | ActionParams) | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.convertParamsBetweenGroups.$1", + "type": "Uncategorized", + "tags": [], + "label": "params", + "description": [], + "signature": [ + "ActionParams" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.hideInUi", + "type": "CompoundType", + "tags": [], + "label": "hideInUi", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.modalWidth", + "type": "number", + "tags": [], + "label": "modalWidth", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeModel.isSystemActionType", + "type": "CompoundType", + "tags": [], + "label": "isSystemActionType", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertFieldsTableProps", + "type": "Interface", + "tags": [], + "label": "AlertFieldsTableProps", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/alert_fields_table/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertFieldsTableProps.alert", + "type": "CompoundType", + "tags": [], + "label": "alert", + "description": [ + "\nThe raw alert object" + ], + "signature": [ + { + "pluginId": "@kbn/alerting-types", + "scope": "common", + "docId": "kibKbnAlertingTypesPluginApi", + "section": "def-common.BasicFields", + "text": "BasicFields" + }, + " & { \"@timestamp\"?: string[] | undefined; \"event.action\"?: string[] | undefined; tags?: string[] | undefined; kibana?: string[] | undefined; \"kibana.alert.rule.rule_type_id\"?: string[] | undefined; \"kibana.alert.rule.consumer\"?: string[] | undefined; \"kibana.alert.rule.execution.uuid\"?: string[] | undefined; \"kibana.alert.instance.id\"?: string[] | undefined; \"kibana.alert.rule.category\"?: string[] | undefined; \"kibana.alert.rule.name\"?: string[] | undefined; \"kibana.alert.rule.producer\"?: string[] | undefined; \"kibana.alert.rule.uuid\"?: string[] | undefined; \"kibana.alert.status\"?: string[] | undefined; \"kibana.alert.uuid\"?: string[] | undefined; \"kibana.space_ids\"?: string[] | undefined; \"event.kind\"?: string[] | undefined; \"kibana.alert.action_group\"?: string[] | undefined; \"kibana.alert.case_ids\"?: string[] | undefined; \"kibana.alert.duration.us\"?: string[] | undefined; \"kibana.alert.end\"?: string[] | undefined; \"kibana.alert.flapping\"?: string[] | undefined; \"kibana.alert.maintenance_window_ids\"?: string[] | undefined; \"kibana.alert.reason\"?: string[] | undefined; \"kibana.alert.rule.parameters\"?: string[] | undefined; \"kibana.alert.rule.tags\"?: string[] | undefined; \"kibana.alert.start\"?: string[] | undefined; \"kibana.alert.time_range\"?: string[] | undefined; \"kibana.alert.workflow_assignee_ids\"?: string[] | undefined; \"kibana.alert.workflow_status\"?: string[] | undefined; \"kibana.alert.workflow_tags\"?: string[] | undefined; \"kibana.version\"?: string[] | undefined; \"kibana.alert.context\"?: string[] | undefined; \"kibana.alert.evaluation.threshold\"?: string[] | undefined; \"kibana.alert.evaluation.value\"?: string[] | undefined; \"kibana.alert.evaluation.values\"?: string[] | undefined; \"kibana.alert.group\"?: string[] | undefined; \"ecs.version\"?: string[] | undefined; \"kibana.alert.risk_score\"?: string[] | undefined; \"kibana.alert.rule.author\"?: string[] | undefined; \"kibana.alert.rule.created_at\"?: string[] | undefined; \"kibana.alert.rule.created_by\"?: string[] | undefined; \"kibana.alert.rule.description\"?: string[] | undefined; \"kibana.alert.rule.enabled\"?: string[] | undefined; \"kibana.alert.rule.from\"?: string[] | undefined; \"kibana.alert.rule.interval\"?: string[] | undefined; \"kibana.alert.rule.license\"?: string[] | undefined; \"kibana.alert.rule.note\"?: string[] | undefined; \"kibana.alert.rule.references\"?: string[] | undefined; \"kibana.alert.rule.rule_id\"?: string[] | undefined; \"kibana.alert.rule.rule_name_override\"?: string[] | undefined; \"kibana.alert.rule.to\"?: string[] | undefined; \"kibana.alert.rule.type\"?: string[] | undefined; \"kibana.alert.rule.updated_at\"?: string[] | undefined; \"kibana.alert.rule.updated_by\"?: string[] | undefined; \"kibana.alert.rule.version\"?: string[] | undefined; \"kibana.alert.severity\"?: string[] | undefined; \"kibana.alert.suppression.docs_count\"?: string[] | undefined; \"kibana.alert.suppression.end\"?: string[] | undefined; \"kibana.alert.suppression.start\"?: string[] | undefined; \"kibana.alert.suppression.terms.field\"?: string[] | undefined; \"kibana.alert.suppression.terms.value\"?: string[] | undefined; \"kibana.alert.system_status\"?: string[] | undefined; \"kibana.alert.workflow_reason\"?: string[] | undefined; \"kibana.alert.workflow_user\"?: string[] | undefined; \"event.module\"?: string[] | undefined; \"kibana.alert.rule.threat.framework\"?: string[] | undefined; \"kibana.alert.rule.threat.tactic.id\"?: string[] | undefined; \"kibana.alert.rule.threat.tactic.name\"?: string[] | undefined; \"kibana.alert.rule.threat.tactic.reference\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.id\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.name\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.reference\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.subtechnique.id\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.subtechnique.name\"?: string[] | undefined; \"kibana.alert.rule.threat.technique.subtechnique.reference\"?: string[] | undefined; \"kibana.alert.building_block_type\"?: string[] | undefined; \"kibana.alert\"?: string[] | undefined; \"kibana.alert.group.field\"?: string[] | undefined; \"kibana.alert.group.value\"?: string[] | undefined; \"kibana.alert.rule\"?: string[] | undefined; \"kibana.alert.rule.exceptions_list\"?: string[] | undefined; \"kibana.alert.rule.namespace\"?: string[] | undefined; \"kibana.alert.suppression.terms\"?: string[] | undefined; } & { [x: string]: unknown[]; }" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_fields_table/index.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertFieldsTableProps.fields", + "type": "Array", + "tags": [], + "label": "fields", + "description": [ + "\nA list of alert field keys to be shown in the table.\nWhen not defined, all the fields are shown." + ], + "signature": [ + "(string | number)[] | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_fields_table/index.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertLifecycleStatusBadgeProps", + "type": "Interface", + "tags": [], + "label": "AlertLifecycleStatusBadgeProps", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/alert_lifecycle_status_badge/index.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertLifecycleStatusBadgeProps.alertStatus", + "type": "CompoundType", + "tags": [], + "label": "alertStatus", + "description": [], + "signature": [ + "\"recovered\" | \"active\" | \"untracked\"" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_lifecycle_status_badge/index.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertLifecycleStatusBadgeProps.flapping", + "type": "CompoundType", + "tags": [], + "label": "flapping", + "description": [], + "signature": [ + "string | boolean | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_lifecycle_status_badge/index.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps", + "type": "Interface", + "tags": [], + "label": "AlertsSearchBarProps", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.appName", + "type": "string", + "tags": [], + "label": "appName", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.disableQueryLanguageSwitcher", + "type": "CompoundType", + "tags": [], + "label": "disableQueryLanguageSwitcher", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.featureIds", + "type": "Array", + "tags": [], + "label": "featureIds", + "description": [], + "signature": [ + { + "pluginId": "@kbn/rule-data-utils", + "scope": "common", + "docId": "kibKbnRuleDataUtilsPluginApi", + "section": "def-common.AlertConsumers", + "text": "AlertConsumers" + }, + "[]" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.rangeFrom", + "type": "string", + "tags": [], + "label": "rangeFrom", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.rangeTo", + "type": "string", + "tags": [], + "label": "rangeTo", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.query", + "type": "string", + "tags": [], + "label": "query", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.filters", + "type": "Array", + "tags": [], + "label": "filters", + "description": [], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[] | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.showFilterBar", + "type": "CompoundType", + "tags": [], + "label": "showFilterBar", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.showDatePicker", + "type": "CompoundType", + "tags": [], + "label": "showDatePicker", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.showSubmitButton", + "type": "CompoundType", + "tags": [], + "label": "showSubmitButton", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.placeholder", + "type": "string", + "tags": [], + "label": "placeholder", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.submitOnBlur", + "type": "CompoundType", + "tags": [], + "label": "submitOnBlur", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.ruleTypeId", + "type": "string", + "tags": [], + "label": "ruleTypeId", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.onQueryChange", + "type": "Function", + "tags": [], + "label": "onQueryChange", + "description": [], + "signature": [ + "((query: { dateRange: { from: string; to: string; mode?: \"absolute\" | \"relative\" | undefined; }; query?: string | undefined; }) => void) | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.onQueryChange.$1", + "type": "Object", + "tags": [], + "label": "query", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.onQueryChange.$1.dateRange", + "type": "Object", + "tags": [], + "label": "dateRange", + "description": [], + "signature": [ + "{ from: string; to: string; mode?: \"absolute\" | \"relative\" | undefined; }" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.onQueryChange.$1.query", + "type": "string", + "tags": [], + "label": "query", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.onQuerySubmit", + "type": "Function", + "tags": [], + "label": "onQuerySubmit", + "description": [], + "signature": [ + "(query: { dateRange: { from: string; to: string; mode?: \"absolute\" | \"relative\" | undefined; }; query?: string | undefined; }) => void" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.onQuerySubmit.$1", + "type": "Object", + "tags": [], + "label": "query", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.onQuerySubmit.$1.dateRange", + "type": "Object", + "tags": [], + "label": "dateRange", + "description": [], + "signature": [ + "{ from: string; to: string; mode?: \"absolute\" | \"relative\" | undefined; }" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.onQuerySubmit.$1.query", + "type": "string", + "tags": [], + "label": "query", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.onFiltersUpdated", + "type": "Function", + "tags": [], + "label": "onFiltersUpdated", + "description": [], + "signature": [ + "((filters: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]) => void) | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.onFiltersUpdated.$1", + "type": "Array", + "tags": [], + "label": "filters", + "description": [], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.http", + "type": "Object", + "tags": [], + "label": "http", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-browser", + "scope": "common", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-common.HttpSetup", + "text": "HttpSetup" + } + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.toasts", + "type": "Object", + "tags": [], + "label": "toasts", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-notifications-browser", + "scope": "common", + "docId": "kibKbnCoreNotificationsBrowserPluginApi", + "section": "def-common.IToasts", + "text": "IToasts" + } + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.unifiedSearchBar", + "type": "Function", + "tags": [], + "label": "unifiedSearchBar", + "description": [], + "signature": [ + "(props: ", + { + "pluginId": "unifiedSearch", + "scope": "public", + "docId": "kibUnifiedSearchPluginApi", + "section": "def-public.StatefulSearchBarProps", + "text": "StatefulSearchBarProps" + }, + "<", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + ">) => React.ReactElement>" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.unifiedSearchBar.$1", + "type": "CompoundType", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "Omit<", + "SearchBarOwnProps", + "<", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Query", + "text": "Query" + }, + ">, \"showSaveQuery\"> & { appName: string; useDefaultBehaviors?: boolean | undefined; savedQueryId?: string | undefined; saveQueryMenuVisibility?: ", + "SavedQueryMenuVisibility", + " | undefined; onSavedQueryIdChange?: ((savedQueryId?: string | undefined) => void) | undefined; onFiltersUpdated?: ((filters: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]) => void) | undefined; }" + ], + "path": "src/plugins/unified_search/public/types.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.AlertsSearchBarProps.dataViewsService", + "type": "Object", + "tags": [], + "label": "dataViewsService", + "description": [], + "signature": [ + "{ create: (spec: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewSpec", + "text": "DataViewSpec" + }, + ", skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", + { + "pluginId": "dataViews", + "scope": "common", "docId": "kibDataViewsPluginApi", "section": "def-common.DataView", "text": "DataView" @@ -1456,127 +2395,710 @@ }, "; getDataViewLazy: (id: string) => Promise<", { - "pluginId": "dataViews", + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, + ">; createDataViewLazy: (spec: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewSpec", + "text": "DataViewSpec" + }, + ") => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, + ">; createAndSaveDataViewLazy: (spec: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewSpec", + "text": "DataViewSpec" + }, + ", overwrite?: boolean) => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, + ">; createAndSave: (spec: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewSpec", + "text": "DataViewSpec" + }, + ", overwrite?: boolean, skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + ">; createSavedObject: (dataView: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.AbstractDataView", + "text": "AbstractDataView" + }, + ", overwrite?: boolean) => Promise; updateSavedObject: (indexPattern: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.AbstractDataView", + "text": "AbstractDataView" + }, + ", saveAttempts?: number, ignoreErrors?: boolean, displayErrors?: boolean) => Promise; defaultDataViewExists: () => Promise; getDefaultDataViewLazy: () => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, + " | null>; getDefaultDataView: (options?: { displayErrors?: boolean | undefined; refreshFields?: boolean | undefined; }) => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + " | null>; toDataView: (dataViewLazy: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, + ") => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + ">; toDataViewLazy: (dataView: ", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + ") => Promise<", + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewLazy", + "text": "DataViewLazy" + }, + ">; }" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ConnectorValidationError", + "type": "Interface", + "tags": [], + "label": "ConnectorValidationError", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ConnectorValidationError.message", + "type": "CompoundType", + "tags": [], + "label": "message", + "description": [], + "signature": [ + "boolean | React.ReactChild | React.ReactFragment | React.ReactPortal | null | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.CustomConnectorSelectionItem", + "type": "Interface", + "tags": [], + "label": "CustomConnectorSelectionItem", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.CustomConnectorSelectionItem.getText", + "type": "Function", + "tags": [], + "label": "getText", + "description": [], + "signature": [ + "(actionConnector: ", + { + "pluginId": "@kbn/alerts-ui-shared", "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataViewLazy", - "text": "DataViewLazy" + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionConnector", + "text": "ActionConnector" }, - ">; createDataViewLazy: (spec: ", + ", Record>) => string" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { - "pluginId": "dataViews", + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.CustomConnectorSelectionItem.getText.$1", + "type": "CompoundType", + "tags": [], + "label": "actionConnector", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionConnector", + "text": "ActionConnector" + }, + ", Record>" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.CustomConnectorSelectionItem.getComponent", + "type": "Function", + "tags": [], + "label": "getComponent", + "description": [], + "signature": [ + "(actionConnector: ", + { + "pluginId": "@kbn/alerts-ui-shared", "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataViewSpec", - "text": "DataViewSpec" + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionConnector", + "text": "ActionConnector" }, - ") => Promise<", + ", Record>) => React.LazyExoticComponent; createAndSaveDataViewLazy: (spec: ", + ", Record>; }>> | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { - "pluginId": "dataViews", + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.CustomConnectorSelectionItem.getComponent.$1", + "type": "CompoundType", + "tags": [], + "label": "actionConnector", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionConnector", + "text": "ActionConnector" + }, + ", Record>" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterContextType", + "type": "Interface", + "tags": [], + "label": "FilterContextType", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterContextType.allControls", + "type": "Array", + "tags": [], + "label": "allControls", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-ui-shared", "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataViewSpec", - "text": "DataViewSpec" + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.FilterControlConfig", + "text": "FilterControlConfig" }, - ", overwrite?: boolean) => Promise<", + "[] | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterContextType.addControl", + "type": "Function", + "tags": [], + "label": "addControl", + "description": [], + "signature": [ + "(controls: ", { - "pluginId": "dataViews", + "pluginId": "@kbn/alerts-ui-shared", "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataViewLazy", - "text": "DataViewLazy" + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.FilterControlConfig", + "text": "FilterControlConfig" }, - ">; createAndSave: (spec: ", + ") => void" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { - "pluginId": "dataViews", + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterContextType.addControl.$1", + "type": "CompoundType", + "tags": [], + "label": "controls", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.FilterControlConfig", + "text": "FilterControlConfig" + } + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterGroupProps", + "type": "Interface", + "tags": [], + "label": "FilterGroupProps", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.FilterGroupProps", + "text": "FilterGroupProps" + }, + " extends Pick<", + { + "pluginId": "controls", + "scope": "common", + "docId": "kibControlsPluginApi", + "section": "def-common.ControlGroupInput", + "text": "ControlGroupInput" + }, + ", \"query\" | \"filters\" | \"timeRange\" | \"chainingSystem\">" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterGroupProps.spaceId", + "type": "string", + "tags": [], + "label": "spaceId", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterGroupProps.dataViewId", + "type": "CompoundType", + "tags": [], + "label": "dataViewId", + "description": [], + "signature": [ + "string | null" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterGroupProps.featureIds", + "type": "Array", + "tags": [], + "label": "featureIds", + "description": [], + "signature": [ + { + "pluginId": "@kbn/rule-data-utils", "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataViewSpec", - "text": "DataViewSpec" + "docId": "kibKbnRuleDataUtilsPluginApi", + "section": "def-common.AlertConsumers", + "text": "AlertConsumers" }, - ", overwrite?: boolean, skipFetchFields?: boolean, displayErrors?: boolean) => Promise<", + "[]" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterGroupProps.onFiltersChange", + "type": "Function", + "tags": [], + "label": "onFiltersChange", + "description": [ + "\nFilters changed callback" + ], + "signature": [ + "((newFilters: ", { - "pluginId": "dataViews", + "pluginId": "@kbn/es-query", "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" }, - ">; createSavedObject: (dataView: ", + "[]) => void) | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.AbstractDataView", - "text": "AbstractDataView" - }, - ", overwrite?: boolean) => Promise; updateSavedObject: (indexPattern: ", + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterGroupProps.onFiltersChange.$1", + "type": "Array", + "tags": [], + "label": "newFilters", + "description": [], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterGroupProps.defaultControls", + "type": "Array", + "tags": [], + "label": "defaultControls", + "description": [], + "signature": [ { - "pluginId": "dataViews", + "pluginId": "@kbn/alerts-ui-shared", "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.AbstractDataView", - "text": "AbstractDataView" + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.FilterControlConfig", + "text": "FilterControlConfig" }, - ", saveAttempts?: number, ignoreErrors?: boolean, displayErrors?: boolean) => Promise; defaultDataViewExists: () => Promise; getDefaultDataViewLazy: () => Promise<", + "[]" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterGroupProps.controlsUrlState", + "type": "Array", + "tags": [], + "label": "controlsUrlState", + "description": [ + "\nThe controls configuration stored in the URL\n(takes precedence over the localStorage configuration)" + ], + "signature": [ { - "pluginId": "dataViews", + "pluginId": "@kbn/alerts-ui-shared", "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataViewLazy", - "text": "DataViewLazy" + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.FilterControlConfig", + "text": "FilterControlConfig" }, - " | null>; getDefaultDataView: (options?: { displayErrors?: boolean | undefined; refreshFields?: boolean | undefined; }) => Promise<", + "[] | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterGroupProps.setControlsUrlState", + "type": "Function", + "tags": [], + "label": "setControlsUrlState", + "description": [ + "\nSetter for the controls url state" + ], + "signature": [ + "((controls: ", { - "pluginId": "dataViews", + "pluginId": "@kbn/alerts-ui-shared", "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.FilterControlConfig", + "text": "FilterControlConfig" }, - " | null>; toDataView: (dataViewLazy: ", + "[]) => void) | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataViewLazy", - "text": "DataViewLazy" + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterGroupProps.setControlsUrlState.$1", + "type": "Array", + "tags": [], + "label": "controls", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.FilterControlConfig", + "text": "FilterControlConfig" + }, + "[]" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterGroupProps.onInit", + "type": "Function", + "tags": [], + "label": "onInit", + "description": [ + "\nInit callback" + ], + "signature": [ + "((controlGroupHandler: ", + { + "pluginId": "controls", + "scope": "public", + "docId": "kibControlsPluginApi", + "section": "def-public.ControlGroupContainer", + "text": "ControlGroupContainer" }, - ") => Promise<", + " | undefined) => void) | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterGroupProps.onInit.$1", + "type": "Object", + "tags": [], + "label": "controlGroupHandler", + "description": [], + "signature": [ + { + "pluginId": "controls", + "scope": "public", + "docId": "kibControlsPluginApi", + "section": "def-public.ControlGroupContainer", + "text": "ControlGroupContainer" + }, + " | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterGroupProps.maxControls", + "type": "number", + "tags": [], + "label": "maxControls", + "description": [ + "\nMaximum number of controls that can be added to the group" + ], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterGroupProps.ControlGroupRenderer", + "type": "Function", + "tags": [], + "label": "ControlGroupRenderer", + "description": [ + "\nThe control embeddable renderer" + ], + "signature": [ + "React.ForwardRefExoticComponent<", + { + "pluginId": "controls", + "scope": "public", + "docId": "kibControlsPluginApi", + "section": "def-public.ControlGroupRendererProps", + "text": "ControlGroupRendererProps" }, - ">; toDataViewLazy: (dataView: ", + " & React.RefAttributes<", { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" + "pluginId": "controls", + "scope": "public", + "docId": "kibControlsPluginApi", + "section": "def-public.AwaitingControlGroupAPI", + "text": "AwaitingControlGroupAPI" }, - ") => Promise<", + ">>" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataViewLazy", - "text": "DataViewLazy" - }, - ">; }" + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterGroupProps.ControlGroupRenderer.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterGroupProps.Storage", + "type": "Object", + "tags": [], + "label": "Storage", + "description": [], + "signature": [ + "typeof ", + { + "pluginId": "kibanaUtils", + "scope": "public", + "docId": "kibKibanaUtilsPluginApi", + "section": "def-public.Storage", + "text": "Storage" + } ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", "deprecated": false, "trackAdoption": false } @@ -1585,437 +3107,721 @@ }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterContextType", + "id": "def-common.GenericValidationResult", "type": "Interface", "tags": [], - "label": "FilterContextType", + "label": "GenericValidationResult", "description": [], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "signature": [ + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.GenericValidationResult", + "text": "GenericValidationResult" + }, + "" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterContextType.allControls", - "type": "Array", + "id": "def-common.GenericValidationResult.errors", + "type": "Object", "tags": [], - "label": "allControls", + "label": "errors", + "description": [], + "signature": [ + "{ [P in Extract]: unknown; }" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.MinimumScheduleInterval", + "type": "Interface", + "tags": [], + "label": "MinimumScheduleInterval", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.MinimumScheduleInterval.value", + "type": "string", + "tags": [], + "label": "value", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.MinimumScheduleInterval.enforce", + "type": "boolean", + "tags": [], + "label": "enforce", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleFormErrors", + "type": "Interface", + "tags": [], + "label": "RuleFormErrors", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleFormErrors.Unnamed", + "type": "IndexSignature", + "tags": [], + "label": "[key: string]: string | string[] | RuleFormErrors", "description": [], "signature": [ + "[key: string]: string | string[] | ", { "pluginId": "@kbn/alerts-ui-shared", "scope": "common", "docId": "kibKbnAlertsUiSharedPluginApi", - "section": "def-common.FilterControlConfig", - "text": "FilterControlConfig" + "section": "def-common.RuleFormErrors", + "text": "RuleFormErrors" + } + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeModel", + "type": "Interface", + "tags": [], + "label": "RuleTypeModel", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.RuleTypeModel", + "text": "RuleTypeModel" + }, + "" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeModel.id", + "type": "string", + "tags": [], + "label": "id", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeModel.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeModel.iconClass", + "type": "string", + "tags": [], + "label": "iconClass", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeModel.documentationUrl", + "type": "CompoundType", + "tags": [], + "label": "documentationUrl", + "description": [], + "signature": [ + "string | ((docLinks: ", + { + "pluginId": "@kbn/core-doc-links-browser", + "scope": "common", + "docId": "kibKbnCoreDocLinksBrowserPluginApi", + "section": "def-common.DocLinksStart", + "text": "DocLinksStart" }, - "[] | undefined" + ") => string) | null" ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterContextType.addControl", + "id": "def-common.RuleTypeModel.validate", "type": "Function", "tags": [], - "label": "addControl", + "label": "validate", "description": [], "signature": [ - "(controls: ", + "(ruleParams: Params, isServerless?: boolean | undefined) => ", { "pluginId": "@kbn/alerts-ui-shared", "scope": "common", "docId": "kibKbnAlertsUiSharedPluginApi", - "section": "def-common.FilterControlConfig", - "text": "FilterControlConfig" - }, - ") => void" + "section": "def-common.ValidationResult", + "text": "ValidationResult" + } ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterContextType.addControl.$1", - "type": "CompoundType", + "id": "def-common.RuleTypeModel.validate.$1", + "type": "Uncategorized", "tags": [], - "label": "controls", + "label": "ruleParams", "description": [], "signature": [ - { - "pluginId": "@kbn/alerts-ui-shared", - "scope": "common", - "docId": "kibKbnAlertsUiSharedPluginApi", - "section": "def-common.FilterControlConfig", - "text": "FilterControlConfig" - } + "Params" ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeModel.validate.$2", + "type": "CompoundType", + "tags": [], + "label": "isServerless", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false } ], - "returnComment": [] + "returnComment": [] + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeModel.ruleParamsExpression", + "type": "Function", + "tags": [], + "label": "ruleParamsExpression", + "description": [], + "signature": [ + "React.FunctionComponent | React.LazyExoticComponent, string>>>" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeModel.ruleParamsExpression.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeModel.requiresAppContext", + "type": "boolean", + "tags": [], + "label": "requiresAppContext", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeModel.defaultActionMessage", + "type": "string", + "tags": [], + "label": "defaultActionMessage", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeModel.defaultRecoveryMessage", + "type": "string", + "tags": [], + "label": "defaultRecoveryMessage", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeModel.defaultSummaryMessage", + "type": "string", + "tags": [], + "label": "defaultSummaryMessage", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeModel.alertDetailsAppSection", + "type": "CompoundType", + "tags": [], + "label": "alertDetailsAppSection", + "description": [], + "signature": [ + "React.FunctionComponent | React.LazyExoticComponent> | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterGroupProps", + "id": "def-common.RuleTypeParamsExpressionProps", "type": "Interface", "tags": [], - "label": "FilterGroupProps", + "label": "RuleTypeParamsExpressionProps", "description": [], "signature": [ { "pluginId": "@kbn/alerts-ui-shared", "scope": "common", "docId": "kibKbnAlertsUiSharedPluginApi", - "section": "def-common.FilterGroupProps", - "text": "FilterGroupProps" - }, - " extends Pick<", - { - "pluginId": "controls", - "scope": "common", - "docId": "kibControlsPluginApi", - "section": "def-common.ControlGroupInput", - "text": "ControlGroupInput" + "section": "def-common.RuleTypeParamsExpressionProps", + "text": "RuleTypeParamsExpressionProps" }, - ", \"query\" | \"filters\" | \"timeRange\" | \"chainingSystem\">" + "" ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterGroupProps.spaceId", + "id": "def-common.RuleTypeParamsExpressionProps.id", "type": "string", "tags": [], - "label": "spaceId", + "label": "id", "description": [], "signature": [ "string | undefined" ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterGroupProps.dataViewId", - "type": "CompoundType", + "id": "def-common.RuleTypeParamsExpressionProps.ruleParams", + "type": "Uncategorized", "tags": [], - "label": "dataViewId", + "label": "ruleParams", "description": [], "signature": [ - "string | null" + "Params" ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterGroupProps.featureIds", - "type": "Array", + "id": "def-common.RuleTypeParamsExpressionProps.ruleInterval", + "type": "string", "tags": [], - "label": "featureIds", + "label": "ruleInterval", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeParamsExpressionProps.ruleThrottle", + "type": "string", + "tags": [], + "label": "ruleThrottle", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeParamsExpressionProps.alertNotifyWhen", + "type": "CompoundType", + "tags": [], + "label": "alertNotifyWhen", "description": [], "signature": [ - { - "pluginId": "@kbn/rule-data-utils", - "scope": "common", - "docId": "kibKbnRuleDataUtilsPluginApi", - "section": "def-common.AlertConsumers", - "text": "AlertConsumers" - }, - "[]" + "\"onActionGroupChange\" | \"onActiveAlert\" | \"onThrottleInterval\"" ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterGroupProps.onFiltersChange", + "id": "def-common.RuleTypeParamsExpressionProps.setRuleParams", "type": "Function", "tags": [], - "label": "onFiltersChange", - "description": [ - "\nFilters changed callback" - ], + "label": "setRuleParams", + "description": [], "signature": [ - "((newFilters: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[]) => void) | undefined" + "(property: Key, value: Params[Key] | undefined) => void" ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterGroupProps.onFiltersChange.$1", - "type": "Array", + "id": "def-common.RuleTypeParamsExpressionProps.setRuleParams.$1", + "type": "Uncategorized", "tags": [], - "label": "newFilters", + "label": "property", "description": [], "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[]" + "Key" ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterGroupProps.defaultControls", - "type": "Array", - "tags": [], - "label": "defaultControls", - "description": [], - "signature": [ - { - "pluginId": "@kbn/alerts-ui-shared", - "scope": "common", - "docId": "kibKbnAlertsUiSharedPluginApi", - "section": "def-common.FilterControlConfig", - "text": "FilterControlConfig" }, - "[]" - ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterGroupProps.controlsUrlState", - "type": "Array", - "tags": [], - "label": "controlsUrlState", - "description": [ - "\nThe controls configuration stored in the URL\n(takes precedence over the localStorage configuration)" - ], - "signature": [ { - "pluginId": "@kbn/alerts-ui-shared", - "scope": "common", - "docId": "kibKbnAlertsUiSharedPluginApi", - "section": "def-common.FilterControlConfig", - "text": "FilterControlConfig" - }, - "[] | undefined" + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeParamsExpressionProps.setRuleParams.$2", + "type": "Uncategorized", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "Params[Key] | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", - "deprecated": false, - "trackAdoption": false + "returnComment": [] }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterGroupProps.setControlsUrlState", + "id": "def-common.RuleTypeParamsExpressionProps.setRuleProperty", "type": "Function", "tags": [], - "label": "setControlsUrlState", - "description": [ - "\nSetter for the controls url state" - ], + "label": "setRuleProperty", + "description": [], "signature": [ - "((controls: ", + "(key: Prop, value: ", { "pluginId": "@kbn/alerts-ui-shared", "scope": "common", "docId": "kibKbnAlertsUiSharedPluginApi", - "section": "def-common.FilterControlConfig", - "text": "FilterControlConfig" + "section": "def-common.SanitizedRule", + "text": "SanitizedRule" }, - "[]) => void) | undefined" + "[Prop] | null) => void" ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterGroupProps.setControlsUrlState.$1", - "type": "Array", + "id": "def-common.RuleTypeParamsExpressionProps.setRuleProperty.$1", + "type": "Uncategorized", "tags": [], - "label": "controls", + "label": "key", + "description": [], + "signature": [ + "Prop" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeParamsExpressionProps.setRuleProperty.$2", + "type": "CompoundType", + "tags": [], + "label": "value", "description": [], "signature": [ { "pluginId": "@kbn/alerts-ui-shared", "scope": "common", "docId": "kibKbnAlertsUiSharedPluginApi", - "section": "def-common.FilterControlConfig", - "text": "FilterControlConfig" + "section": "def-common.SanitizedRule", + "text": "SanitizedRule" }, - "[]" + "[Prop] | null" ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false, - "isRequired": true + "isRequired": false } ], "returnComment": [] }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterGroupProps.onInit", + "id": "def-common.RuleTypeParamsExpressionProps.onChangeMetaData", "type": "Function", "tags": [], - "label": "onInit", - "description": [ - "\nInit callback" - ], + "label": "onChangeMetaData", + "description": [], "signature": [ - "((controlGroupHandler: ", - { - "pluginId": "controls", - "scope": "public", - "docId": "kibControlsPluginApi", - "section": "def-public.ControlGroupContainer", - "text": "ControlGroupContainer" - }, - " | undefined) => void) | undefined" + "(metadata: MetaData) => void" ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterGroupProps.onInit.$1", - "type": "Object", + "id": "def-common.RuleTypeParamsExpressionProps.onChangeMetaData.$1", + "type": "Uncategorized", "tags": [], - "label": "controlGroupHandler", + "label": "metadata", "description": [], "signature": [ - { - "pluginId": "controls", - "scope": "public", - "docId": "kibControlsPluginApi", - "section": "def-public.ControlGroupContainer", - "text": "ControlGroupContainer" - }, - " | undefined" + "MetaData" ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false, - "isRequired": false + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeParamsExpressionProps.errors", + "type": "Object", + "tags": [], + "label": "errors", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.RuleFormErrors", + "text": "RuleFormErrors" } ], - "returnComment": [] + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeParamsExpressionProps.defaultActionGroupId", + "type": "string", + "tags": [], + "label": "defaultActionGroupId", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeParamsExpressionProps.actionGroups", + "type": "Array", + "tags": [], + "label": "actionGroups", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerting-types", + "scope": "common", + "docId": "kibKbnAlertingTypesPluginApi", + "section": "def-common.ActionGroup", + "text": "ActionGroup" + }, + "[]" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterGroupProps.maxControls", - "type": "number", + "id": "def-common.RuleTypeParamsExpressionProps.metadata", + "type": "Uncategorized", "tags": [], - "label": "maxControls", - "description": [ - "\nMaximum number of controls that can be added to the group" - ], + "label": "metadata", + "description": [], "signature": [ - "number | undefined" + "MetaData | undefined" ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterGroupProps.ControlGroupRenderer", - "type": "Function", + "id": "def-common.RuleTypeParamsExpressionProps.charts", + "type": "Object", "tags": [], - "label": "ControlGroupRenderer", - "description": [ - "\nThe control embeddable renderer" - ], + "label": "charts", + "description": [], "signature": [ - "React.ForwardRefExoticComponent<", { - "pluginId": "controls", + "pluginId": "charts", "scope": "public", - "docId": "kibControlsPluginApi", - "section": "def-public.ControlGroupRendererProps", - "text": "ControlGroupRendererProps" - }, - " & React.RefAttributes<", + "docId": "kibChartsPluginApi", + "section": "def-public.ChartsPluginSetup", + "text": "ChartsPluginSetup" + } + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeParamsExpressionProps.data", + "type": "Object", + "tags": [], + "label": "data", + "description": [], + "signature": [ { - "pluginId": "controls", + "pluginId": "data", "scope": "public", - "docId": "kibControlsPluginApi", - "section": "def-public.AwaitingControlGroupAPI", - "text": "AwaitingControlGroupAPI" - }, - ">>" + "docId": "kibDataPluginApi", + "section": "def-public.DataPublicPluginStart", + "text": "DataPublicPluginStart" + } ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeParamsExpressionProps.dataViews", + "type": "Object", + "tags": [], + "label": "dataViews", + "description": [], + "signature": [ { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterGroupProps.ControlGroupRenderer.$1", - "type": "Uncategorized", - "tags": [], - "label": "props", - "description": [], - "signature": [ - "P" - ], - "path": "node_modules/@types/react/index.d.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "dataViews", + "scope": "public", + "docId": "kibDataViewsPluginApi", + "section": "def-public.DataViewsServicePublic", + "text": "DataViewsServicePublic" } - ] + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterGroupProps.Storage", + "id": "def-common.RuleTypeParamsExpressionProps.unifiedSearch", "type": "Object", "tags": [], - "label": "Storage", + "label": "unifiedSearch", "description": [], "signature": [ - "typeof ", { - "pluginId": "kibanaUtils", + "pluginId": "unifiedSearch", "scope": "public", - "docId": "kibKibanaUtilsPluginApi", - "section": "def-public.Storage", - "text": "Storage" + "docId": "kibUnifiedSearchPluginApi", + "section": "def-public.UnifiedSearchPublicPluginStart", + "text": "UnifiedSearchPublicPluginStart" } ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false } @@ -2391,16 +4197,176 @@ "tags": [], "label": "toasts", "description": [], - "signature": [ - { - "pluginId": "@kbn/core-notifications-browser", - "scope": "common", - "docId": "kibKbnCoreNotificationsBrowserPluginApi", - "section": "def-common.IToasts", - "text": "IToasts" - } - ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_alert_data_view.ts", + "signature": [ + { + "pluginId": "@kbn/core-notifications-browser", + "scope": "common", + "docId": "kibKbnCoreNotificationsBrowserPluginApi", + "section": "def-common.IToasts", + "text": "IToasts" + } + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_alert_data_view.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.UseAlertDataViewResult", + "type": "Interface", + "tags": [], + "label": "UseAlertDataViewResult", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_alert_data_view.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.UseAlertDataViewResult.dataViews", + "type": "Array", + "tags": [], + "label": "dataViews", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + "[] | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_alert_data_view.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.UseAlertDataViewResult.loading", + "type": "boolean", + "tags": [], + "label": "loading", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_alert_data_view.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.UseRuleAADFieldsProps", + "type": "Interface", + "tags": [], + "label": "UseRuleAADFieldsProps", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_rule_aad_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.UseRuleAADFieldsProps.ruleTypeId", + "type": "string", + "tags": [], + "label": "ruleTypeId", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_rule_aad_fields.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.UseRuleAADFieldsProps.http", + "type": "Object", + "tags": [], + "label": "http", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-browser", + "scope": "common", + "docId": "kibKbnCoreHttpBrowserPluginApi", + "section": "def-common.HttpSetup", + "text": "HttpSetup" + } + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_rule_aad_fields.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.UseRuleAADFieldsProps.toasts", + "type": "Object", + "tags": [], + "label": "toasts", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-notifications-browser", + "scope": "common", + "docId": "kibKbnCoreNotificationsBrowserPluginApi", + "section": "def-common.IToasts", + "text": "IToasts" + } + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_rule_aad_fields.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.UseRuleAADFieldsResult", + "type": "Interface", + "tags": [], + "label": "UseRuleAADFieldsResult", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_rule_aad_fields.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.UseRuleAADFieldsResult.aadFields", + "type": "Array", + "tags": [], + "label": "aadFields", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataViewField", + "text": "DataViewField" + }, + "[]" + ], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_rule_aad_fields.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.UseRuleAADFieldsResult.loading", + "type": "boolean", + "tags": [], + "label": "loading", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_rule_aad_fields.ts", "deprecated": false, "trackAdoption": false } @@ -2409,326 +4375,534 @@ }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.UseAlertDataViewResult", + "id": "def-common.ValidationResult", "type": "Interface", "tags": [], - "label": "UseAlertDataViewResult", + "label": "ValidationResult", "description": [], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_alert_data_view.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.UseAlertDataViewResult.dataViews", - "type": "Array", + "id": "def-common.ValidationResult.errors", + "type": "Object", "tags": [], - "label": "dataViews", + "label": "errors", "description": [], "signature": [ - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" - }, - "[] | undefined" + "{ [x: string]: any; }" ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_alert_data_view.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionConnectorMode", + "type": "Enum", + "tags": [], + "label": "ActionConnectorMode", + "description": [], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "misc": [ + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionConnector", + "type": "Type", + "tags": [], + "label": "ActionConnector", + "description": [], + "signature": [ + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.PreConfiguredActionConnector", + "text": "PreConfiguredActionConnector" }, + " | ", { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.UseAlertDataViewResult.loading", - "type": "boolean", - "tags": [], - "label": "loading", - "description": [], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_alert_data_view.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.SystemAction", + "text": "SystemAction" + }, + " | ", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.UserConfiguredActionConnector", + "text": "UserConfiguredActionConnector" + }, + "" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ActionTypeRegistryContract", + "type": "Type", + "tags": [], + "label": "ActionTypeRegistryContract", + "description": [], + "signature": [ + "{ get: (id: string) => ", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", + "text": "ActionTypeModel" + }, + "; list: () => ", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", + "text": "ActionTypeModel" + }, + "[]; register: (objectType: ", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", + "text": "ActionTypeModel" + }, + ") => void; has: (id: string) => boolean; }" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ConnectorValidationFunc", + "type": "Type", + "tags": [], + "label": "ConnectorValidationFunc", + "description": [], + "signature": [ + "() => Promise" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterControlConfig", + "type": "Type", + "tags": [], + "label": "FilterControlConfig", + "description": [], + "signature": [ + "Omit<", + { + "pluginId": "controls", + "scope": "public", + "docId": "kibControlsPluginApi", + "section": "def-public.AddOptionsListControlProps", + "text": "AddOptionsListControlProps" + }, + ", \"dataViewId\" | \"controlId\"> & { persist?: boolean | undefined; }" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterGroupHandler", + "type": "Type", + "tags": [], + "label": "FilterGroupHandler", + "description": [], + "signature": [ + { + "pluginId": "controls", + "scope": "public", + "docId": "kibControlsPluginApi", + "section": "def-public.ControlGroupContainer", + "text": "ControlGroupContainer" } ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.FilterUrlFormat", + "type": "Type", + "tags": [], + "label": "FilterUrlFormat", + "description": [], + "signature": [ + "{ [x: string]: Pick<", + { + "pluginId": "controls", + "scope": "common", + "docId": "kibControlsPluginApi", + "section": "def-common.OptionsListEmbeddableInput", + "text": "OptionsListEmbeddableInput" + }, + ", \"selectedOptions\" | \"title\" | \"fieldName\" | \"existsSelected\" | \"exclude\">; }" + ], + "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.PreConfiguredActionConnector", + "type": "Type", + "tags": [], + "label": "PreConfiguredActionConnector", + "description": [], + "signature": [ + "Omit<", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionConnectorProps", + "text": "ActionConnectorProps" + }, + ", \"config\" | \"secrets\"> & { isPreconfigured: true; isSystemAction: false; }" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.ResolvedRule", + "type": "Type", + "tags": [], + "label": "ResolvedRule", + "description": [], + "signature": [ + "Omit<", + { + "pluginId": "@kbn/alerting-types", + "scope": "common", + "docId": "kibKbnAlertingTypesPluginApi", + "section": "def-common.ResolvedSanitizedRule", + "text": "ResolvedSanitizedRule" + }, + "<", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.RuleTypeParams", + "text": "RuleTypeParams" + }, + ">, \"actions\" | \"alertTypeId\" | \"systemActions\"> & { ruleTypeId: string; actions: ", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.RuleUiAction", + "text": "RuleUiAction" + }, + "[]; }" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.Rule", + "type": "Type", + "tags": [], + "label": "Rule", + "description": [], + "signature": [ + "Omit<", + { + "pluginId": "@kbn/alerting-types", + "scope": "common", + "docId": "kibKbnAlertingTypesPluginApi", + "section": "def-common.SanitizedRule", + "text": "SanitizedRule" + }, + ", \"actions\" | \"alertTypeId\" | \"systemActions\"> & { ruleTypeId: string; actions: ", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.RuleUiAction", + "text": "RuleUiAction" + }, + "[]; }" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeIndexWithDescriptions", + "type": "Type", + "tags": [], + "label": "RuleTypeIndexWithDescriptions", + "description": [], + "signature": [ + "Map" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.UseRuleAADFieldsProps", - "type": "Interface", + "id": "def-common.RuleTypeParams", + "type": "Type", "tags": [], - "label": "UseRuleAADFieldsProps", + "label": "RuleTypeParams", "description": [], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_rule_aad_fields.ts", + "signature": [ + "{ [x: string]: unknown; }" + ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false, - "children": [ + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/alerts-ui-shared", + "id": "def-common.RuleTypeRegistryContract", + "type": "Type", + "tags": [], + "label": "RuleTypeRegistryContract", + "description": [], + "signature": [ + "{ get: (id: string) => ", { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.UseRuleAADFieldsProps.ruleTypeId", - "type": "string", - "tags": [], - "label": "ruleTypeId", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_rule_aad_fields.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.RuleTypeModel", + "text": "RuleTypeModel" }, + "<", { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.UseRuleAADFieldsProps.http", - "type": "Object", - "tags": [], - "label": "http", - "description": [], - "signature": [ - { - "pluginId": "@kbn/core-http-browser", - "scope": "common", - "docId": "kibKbnCoreHttpBrowserPluginApi", - "section": "def-common.HttpSetup", - "text": "HttpSetup" - } - ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_rule_aad_fields.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.RuleTypeParams", + "text": "RuleTypeParams" }, + ">; list: () => ", { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.UseRuleAADFieldsProps.toasts", - "type": "Object", - "tags": [], - "label": "toasts", - "description": [], - "signature": [ - { - "pluginId": "@kbn/core-notifications-browser", - "scope": "common", - "docId": "kibKbnCoreNotificationsBrowserPluginApi", - "section": "def-common.IToasts", - "text": "IToasts" - } - ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_rule_aad_fields.ts", - "deprecated": false, - "trackAdoption": false - } + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.RuleTypeModel", + "text": "RuleTypeModel" + }, + "<", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.RuleTypeParams", + "text": "RuleTypeParams" + }, + ">[]; register: (objectType: ", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.RuleTypeModel", + "text": "RuleTypeModel" + }, + "<", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.RuleTypeParams", + "text": "RuleTypeParams" + }, + ">) => void; has: (id: string) => boolean; }" ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.UseRuleAADFieldsResult", - "type": "Interface", + "id": "def-common.RuleTypeWithDescription", + "type": "Type", "tags": [], - "label": "UseRuleAADFieldsResult", + "label": "RuleTypeWithDescription", "description": [], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_rule_aad_fields.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ + "signature": [ { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.UseRuleAADFieldsResult.aadFields", - "type": "Array", - "tags": [], - "label": "aadFields", - "description": [], - "signature": [ - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataViewField", - "text": "DataViewField" - }, - "[]" - ], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_rule_aad_fields.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "@kbn/triggers-actions-ui-types", + "scope": "common", + "docId": "kibKbnTriggersActionsUiTypesPluginApi", + "section": "def-common.RuleType", + "text": "RuleType" }, - { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.UseRuleAADFieldsResult.loading", - "type": "boolean", - "tags": [], - "label": "loading", - "description": [], - "path": "packages/kbn-alerts-ui-shared/src/alerts_search_bar/hooks/use_rule_aad_fields.ts", - "deprecated": false, - "trackAdoption": false - } + " & { description?: string | undefined; }" ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.UseRuleTypesProps", - "type": "Interface", + "id": "def-common.RuleUiAction", + "type": "Type", "tags": [], - "label": "UseRuleTypesProps", + "label": "RuleUiAction", "description": [], - "path": "packages/kbn-alerts-ui-shared/src/common/hooks/use_load_rule_types_query.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.UseRuleTypesProps.http", - "type": "Object", - "tags": [], - "label": "http", - "description": [], - "signature": [ - { - "pluginId": "@kbn/core-http-browser", - "scope": "common", - "docId": "kibKbnCoreHttpBrowserPluginApi", - "section": "def-common.HttpSetup", - "text": "HttpSetup" - } - ], - "path": "packages/kbn-alerts-ui-shared/src/common/hooks/use_load_rule_types_query.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.UseRuleTypesProps.toasts", - "type": "Object", - "tags": [], - "label": "toasts", - "description": [], - "signature": [ - { - "pluginId": "@kbn/core-notifications-browser", - "scope": "common", - "docId": "kibKbnCoreNotificationsBrowserPluginApi", - "section": "def-common.IToasts", - "text": "IToasts" - } - ], - "path": "packages/kbn-alerts-ui-shared/src/common/hooks/use_load_rule_types_query.ts", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.UseRuleTypesProps.filteredRuleTypes", - "type": "Array", - "tags": [], - "label": "filteredRuleTypes", - "description": [], - "signature": [ - "string[] | undefined" - ], - "path": "packages/kbn-alerts-ui-shared/src/common/hooks/use_load_rule_types_query.ts", - "deprecated": false, - "trackAdoption": false - }, + "signature": [ { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.UseRuleTypesProps.registeredRuleTypes", - "type": "Array", - "tags": [], - "label": "registeredRuleTypes", - "description": [], - "signature": [ - "{ id: string; description: string; }[] | undefined" - ], - "path": "packages/kbn-alerts-ui-shared/src/common/hooks/use_load_rule_types_query.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "@kbn/alerting-types", + "scope": "common", + "docId": "kibKbnAlertingTypesPluginApi", + "section": "def-common.SanitizedRuleAction", + "text": "SanitizedRuleAction" }, + " | ", { - "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.UseRuleTypesProps.enabled", - "type": "CompoundType", - "tags": [], - "label": "enabled", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "packages/kbn-alerts-ui-shared/src/common/hooks/use_load_rule_types_query.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "@kbn/alerting-types", + "scope": "common", + "docId": "kibKbnAlertingTypesPluginApi", + "section": "def-common.RuleSystemAction", + "text": "RuleSystemAction" } ], + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", + "deprecated": false, + "trackAdoption": false, "initialIsOpen": false - } - ], - "enums": [], - "misc": [ + }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterControlConfig", + "id": "def-common.SanitizedRule", "type": "Type", "tags": [], - "label": "FilterControlConfig", + "label": "SanitizedRule", "description": [], "signature": [ "Omit<", { - "pluginId": "controls", - "scope": "public", - "docId": "kibControlsPluginApi", - "section": "def-public.AddOptionsListControlProps", - "text": "AddOptionsListControlProps" + "pluginId": "@kbn/alerting-types", + "scope": "common", + "docId": "kibKbnAlertingTypesPluginApi", + "section": "def-common.SanitizedRule", + "text": "SanitizedRule" }, - ", \"dataViewId\" | \"controlId\"> & { persist?: boolean | undefined; }" + ", \"actions\" | \"alertTypeId\" | \"systemActions\"> & { ruleTypeId: string; actions: ", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.RuleUiAction", + "text": "RuleUiAction" + }, + "[]; }" ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterGroupHandler", + "id": "def-common.SystemAction", "type": "Type", "tags": [], - "label": "FilterGroupHandler", + "label": "SystemAction", "description": [], "signature": [ + "Omit<", { - "pluginId": "controls", - "scope": "public", - "docId": "kibControlsPluginApi", - "section": "def-public.ControlGroupContainer", - "text": "ControlGroupContainer" - } + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionConnectorProps", + "text": "ActionConnectorProps" + }, + ", \"config\" | \"secrets\"> & { isSystemAction: true; isPreconfigured: false; }" ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "@kbn/alerts-ui-shared", - "id": "def-common.FilterUrlFormat", + "id": "def-common.UserConfiguredActionConnector", "type": "Type", "tags": [], - "label": "FilterUrlFormat", + "label": "UserConfiguredActionConnector", "description": [], "signature": [ - "{ [x: string]: Pick<", { - "pluginId": "controls", + "pluginId": "@kbn/alerts-ui-shared", "scope": "common", - "docId": "kibControlsPluginApi", - "section": "def-common.OptionsListEmbeddableInput", - "text": "OptionsListEmbeddableInput" + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionConnectorProps", + "text": "ActionConnectorProps" }, - ", \"selectedOptions\" | \"title\" | \"fieldName\" | \"existsSelected\" | \"exclude\">; }" + " & { isPreconfigured: false; isSystemAction: false; }" ], - "path": "packages/kbn-alerts-ui-shared/src/alert_filter_controls/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/kbn_alerts_ui_shared.mdx b/api_docs/kbn_alerts_ui_shared.mdx index c4f5dc40e65ce..035f4691ada4a 100644 --- a/api_docs/kbn_alerts_ui_shared.mdx +++ b/api_docs/kbn_alerts_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts-ui-shared title: "@kbn/alerts-ui-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts-ui-shared plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts-ui-shared'] --- import kbnAlertsUiSharedObj from './kbn_alerts_ui_shared.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-o | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 116 | 0 | 104 | 3 | +| 237 | 0 | 223 | 2 | ## Common @@ -34,6 +34,9 @@ Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-o ### Interfaces +### Enums + + ### Consts, variables and types diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index c04b6d439e29b..a61eec4b3e02c 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2024-06-11 +date: 2024-06-19 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 44ac84f58b11d..d5245aeb10e6b 100644 --- a/api_docs/kbn_analytics_client.devdocs.json +++ b/api_docs/kbn_analytics_client.devdocs.json @@ -756,7 +756,7 @@ }, { "plugin": "observabilityAIAssistant", - "path": "x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/context.ts" + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/recall_and_score.ts" }, { "plugin": "observabilityAIAssistant", @@ -860,19 +860,11 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts" + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts" - }, - { - "plugin": "reporting", - "path": "x-pack/plugins/reporting/server/usage/event_tracker.ts" - }, - { - "plugin": "searchPlayground", - "path": "x-pack/plugins/search_playground/server/routes.ts" + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" }, { "plugin": "securitySolution", @@ -980,11 +972,19 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts" + }, + { + "plugin": "reporting", + "path": "x-pack/plugins/reporting/server/usage/event_tracker.ts" + }, + { + "plugin": "searchPlayground", + "path": "x-pack/plugins/search_playground/server/routes.ts" }, { "plugin": "securitySolutionServerless", diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index 0877025868909..45cdad52bc478 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2024-06-11 +date: 2024-06-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_collection_utils.mdx b/api_docs/kbn_analytics_collection_utils.mdx index 76d8b977afd52..143acf3a461c7 100644 --- a/api_docs/kbn_analytics_collection_utils.mdx +++ b/api_docs/kbn_analytics_collection_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-collection-utils title: "@kbn/analytics-collection-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-collection-utils plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-collection-utils'] --- import kbnAnalyticsCollectionUtilsObj from './kbn_analytics_collection_utils.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index 6c96e7d8c2b2d..a1fc062137334 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2024-06-11 +date: 2024-06-18 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 3310ec68cd328..530d29f8d6ee8 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2024-06-11 +date: 2024-06-18 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 9a32586b2d7df..536f741488ff9 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2024-06-11 +date: 2024-06-18 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 93eca338027dc..6d5fbaf58357b 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2024-06-11 +date: 2024-06-18 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index cb1f047f91418..f05c565560637 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2024-06-11 +date: 2024-06-19 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_data_view.mdx b/api_docs/kbn_apm_data_view.mdx index 25d1fa6f4f566..f414c8a5b9a24 100644 --- a/api_docs/kbn_apm_data_view.mdx +++ b/api_docs/kbn_apm_data_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-data-view title: "@kbn/apm-data-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-data-view plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-data-view'] --- import kbnApmDataViewObj from './kbn_apm_data_view.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 0d99f78ab6a58..ad357a4144d50 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace_client.mdx b/api_docs/kbn_apm_synthtrace_client.mdx index d0f084dafeafc..50ece00b8f43b 100644 --- a/api_docs/kbn_apm_synthtrace_client.mdx +++ b/api_docs/kbn_apm_synthtrace_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace-client title: "@kbn/apm-synthtrace-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace-client plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace-client'] --- import kbnApmSynthtraceClientObj from './kbn_apm_synthtrace_client.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index 11e06e92ec820..f7328138d0261 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index f4e9eb13b9a45..fc15dcbb32bfe 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_bfetch_error.mdx b/api_docs/kbn_bfetch_error.mdx index 7e6c93ea284f4..130c509e6e978 100644 --- a/api_docs/kbn_bfetch_error.mdx +++ b/api_docs/kbn_bfetch_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-bfetch-error title: "@kbn/bfetch-error" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/bfetch-error plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/bfetch-error'] --- import kbnBfetchErrorObj from './kbn_bfetch_error.devdocs.json'; diff --git a/api_docs/kbn_calculate_auto.mdx b/api_docs/kbn_calculate_auto.mdx index 574298632d923..4a92028ce2133 100644 --- a/api_docs/kbn_calculate_auto.mdx +++ b/api_docs/kbn_calculate_auto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-calculate-auto title: "@kbn/calculate-auto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/calculate-auto plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/calculate-auto'] --- import kbnCalculateAutoObj from './kbn_calculate_auto.devdocs.json'; diff --git a/api_docs/kbn_calculate_width_from_char_count.mdx b/api_docs/kbn_calculate_width_from_char_count.mdx index f6df5a3c3c5e3..4a731b72de36a 100644 --- a/api_docs/kbn_calculate_width_from_char_count.mdx +++ b/api_docs/kbn_calculate_width_from_char_count.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-calculate-width-from-char-count title: "@kbn/calculate-width-from-char-count" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/calculate-width-from-char-count plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/calculate-width-from-char-count'] --- import kbnCalculateWidthFromCharCountObj from './kbn_calculate_width_from_char_count.devdocs.json'; diff --git a/api_docs/kbn_cases_components.mdx b/api_docs/kbn_cases_components.mdx index 77d867efd61d8..3649b11cba127 100644 --- a/api_docs/kbn_cases_components.mdx +++ b/api_docs/kbn_cases_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cases-components title: "@kbn/cases-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cases-components plugin -date: 2024-06-11 +date: 2024-06-19 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 10c7200c4dd52..aa437b53ef5c5 100644 --- a/api_docs/kbn_cell_actions.mdx +++ b/api_docs/kbn_cell_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cell-actions title: "@kbn/cell-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cell-actions plugin -date: 2024-06-11 +date: 2024-06-19 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 33343022b7bbf..039d4cac81d55 100644 --- a/api_docs/kbn_chart_expressions_common.mdx +++ b/api_docs/kbn_chart_expressions_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-expressions-common title: "@kbn/chart-expressions-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-expressions-common plugin -date: 2024-06-11 +date: 2024-06-19 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 96fba2be43cf4..8d52987346013 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2024-06-11 +date: 2024-06-19 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 c112f47525362..c883c4ea501c6 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2024-06-11 +date: 2024-06-19 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 015ebc494e1d1..4d8d6b6f095db 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2024-06-11 +date: 2024-06-19 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 3b663e52acbd3..47a27d830b9b0 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2024-06-11 +date: 2024-06-19 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 a2a71767dbcd0..9b7d2a0d64912 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2024-06-11 +date: 2024-06-19 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 4a0dcf61a4942..0d42b35f0f7e6 100644 --- a/api_docs/kbn_code_editor.mdx +++ b/api_docs/kbn_code_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor title: "@kbn/code-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor'] --- import kbnCodeEditorObj from './kbn_code_editor.devdocs.json'; diff --git a/api_docs/kbn_code_editor_mock.mdx b/api_docs/kbn_code_editor_mock.mdx index 494044bb707a4..6c18c1361f88c 100644 --- a/api_docs/kbn_code_editor_mock.mdx +++ b/api_docs/kbn_code_editor_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-editor-mock title: "@kbn/code-editor-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-editor-mock plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor-mock'] --- import kbnCodeEditorMockObj from './kbn_code_editor_mock.devdocs.json'; diff --git a/api_docs/kbn_code_owners.mdx b/api_docs/kbn_code_owners.mdx index 0bff22bec25e1..4506e4957f745 100644 --- a/api_docs/kbn_code_owners.mdx +++ b/api_docs/kbn_code_owners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-code-owners title: "@kbn/code-owners" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/code-owners plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-owners'] --- import kbnCodeOwnersObj from './kbn_code_owners.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index 1e9dc27ad065e..f50fe7d50e1bc 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2024-06-11 +date: 2024-06-19 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 8db895a2de932..971b080bd7600 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2024-06-11 +date: 2024-06-19 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 c3879fe3a90a0..9ac2e856ef23d 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.devdocs.json b/api_docs/kbn_config_schema.devdocs.json index 6b5e7c68c2f26..a78058f053a21 100644 --- a/api_docs/kbn_config_schema.devdocs.json +++ b/api_docs/kbn_config_schema.devdocs.json @@ -1169,9 +1169,11 @@ "type": "Function", "tags": [], "label": "validate", - "description": [], + "description": [ + "\nValidates the provided value against this schema.\nIf valid, the resulting output will be returned, otherwise an exception will be thrown." + ], "signature": [ - "(value: any, context?: Record, namespace?: string | undefined) => V" + "(value: unknown, context?: Record, namespace?: string | undefined) => V" ], "path": "packages/kbn-config-schema/src/types/type.ts", "deprecated": false, @@ -1180,12 +1182,12 @@ { "parentPluginId": "@kbn/config-schema", "id": "def-common.Type.validate.$1", - "type": "Any", + "type": "Unknown", "tags": [], "label": "value", "description": [], "signature": [ - "any" + "unknown" ], "path": "packages/kbn-config-schema/src/types/type.ts", "deprecated": false, @@ -1200,7 +1202,7 @@ "label": "context", "description": [], "signature": [ - "Record" + "Record" ], "path": "packages/kbn-config-schema/src/types/type.ts", "deprecated": false, @@ -1668,213 +1670,185 @@ "label": "Schema", "description": [], "signature": [ - "{ any: (options?: ", - "TypeOptions", - " | undefined) => ", + "{ allOf: { (itemType: ", + ", B extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", options?: ", - "ArrayOptions", - " | undefined) => ", + ", C extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - "; boolean: (options?: ", - "TypeOptions", - " | undefined) => ", + ", D extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - "; buffer: (options?: ", - "TypeOptions", - " | undefined) => ", + ", E extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - "; byteSize: (options?: ", - "ByteSizeOptions", - " | undefined) => ", + ", F extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - "<", + ", G extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.ByteSizeValue", - "text": "ByteSizeValue" + "section": "def-common.Props", + "text": "Props" }, - ">; conditional: (leftOperand: ", - "Reference", - ", rightOperand: A | ", - "Reference", - " | ", + ", H extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", equalType: ", + ", I extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", notEqualType: ", + ", J extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", options?: ", - "TypeOptions", - " | undefined) => ", + ", K extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.ConditionalType", - "text": "ConditionalType" + "section": "def-common.Props", + "text": "Props" }, - "; contextRef: (key: string) => ", - "ContextReference", - "; duration: (options?: ", - "DurationOptions", - " | undefined) => ", + ">(types: [", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - "; ip: (options?: ", - "IpOptions", - " | undefined) => ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - "; lazy: (id: string) => ", - "Lazy", - "; literal: (value: T) => ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - "; mapOf: (keyType: ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - ", valueType: ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - ", options?: ", - "MapOfOptions", - " | undefined) => ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - ">; maybe: (type: ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - ") => ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - "; nullable: (type: ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - ") => ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - "; never: () => ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - "; number: (options?: ", - "NumberOptions", - " | undefined) => ", + "], options?: ", + "UnionTypeOptions", + " | undefined): ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -1882,205 +1856,185 @@ "section": "def-common.Type", "text": "Type" }, - "; object:

]?: ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Props", - "text": "Props" + "section": "def-common.TypeOf", + "text": "TypeOf" }, - ">(props: P, options?: ", - "ObjectTypeOptions", - "

| undefined) => ", + "<(A & B & C & D & E & F & G & H & I & J & K)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.ObjectType", - "text": "ObjectType" + "section": "def-common.TypeOf", + "text": "TypeOf" }, - "

; oneOf: { (types: [", + "<(A & B & C & D & E & F & G & H & I & J & K)[K]>; }>>; , ", + ", B extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ", C extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ", D extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ", E extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ", F extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ", G extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ", H extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ", I extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ", J extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ">(types: [", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - "], options?: ", - "UnionTypeOptions", - " | undefined): ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - "; (types: [", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - ", ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - ", ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - ", ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" - }, - ", ", - { - "pluginId": "@kbn/config-schema", - "scope": "common", - "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" - }, - ", ", - { - "pluginId": "@kbn/config-schema", - "scope": "common", - "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, "], options?: ", "UnionTypeOptions", - " | undefined): ", + " | undefined): ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -2088,163 +2042,169 @@ "section": "def-common.Type", "text": "Type" }, - "; (types: [", + "]?: ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.TypeOf", + "text": "TypeOf" }, - ", ", + "<(A & B & C & D & E & F & G & H & I & J)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.TypeOf", + "text": "TypeOf" }, - ", ", + "<(A & B & C & D & E & F & G & H & I & J)[K]>; }>>; , ", + ", B extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ", C extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ", D extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ", E extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ", F extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ", G extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - "], options?: ", - "UnionTypeOptions", - " | undefined): ", + ", H extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - "; (types: [", + ", I extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" + }, + ">(types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" }, ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - "], options?: ", - "UnionTypeOptions", - " | undefined): ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - "; (types: [", + "], options?: ", + "UnionTypeOptions", + " | undefined): ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -2252,155 +2212,153 @@ "section": "def-common.Type", "text": "Type" }, - ", ", + "]?: ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.TypeOf", + "text": "TypeOf" }, - ", ", + "<(A & B & C & D & E & F & G & H & I)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.TypeOf", + "text": "TypeOf" }, - ", ", + "<(A & B & C & D & E & F & G & H & I)[K]>; }>>; , ", + ", B extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ", C extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ", D extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - "], options?: ", - "UnionTypeOptions", - " | undefined): ", + ", E extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - "; (types: [", + ", F extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ", G extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ", H extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ">(types: [", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - ", ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - ", ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - "], options?: ", - "UnionTypeOptions", - " | undefined): ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - "; (types: [", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - ", ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - ", ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - ", ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - ", ", + "], options?: ", + "UnionTypeOptions", + " | undefined): ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -2408,85 +2366,137 @@ "section": "def-common.Type", "text": "Type" }, - "], options?: ", - "UnionTypeOptions", - " | undefined): ", + "]?: ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.TypeOf", + "text": "TypeOf" }, - "; (types: [", + "<(A & B & C & D & E & F & G & H)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.TypeOf", + "text": "TypeOf" }, - ", ", + "<(A & B & C & D & E & F & G & H)[K]>; }>>; , ", + "section": "def-common.Props", + "text": "Props" + }, + ", B extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", ", + ", C extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - "], options?: ", - "UnionTypeOptions", - " | undefined): ", + ", D extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - "; (types: [", + ", E extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" + }, + ", F extends ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Props", + "text": "Props" + }, + ", G extends ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Props", + "text": "Props" + }, + ">(types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" }, ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - "], options?: ", + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", "UnionTypeOptions", - " | undefined): ", + " | undefined): ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -2494,43 +2504,121 @@ "section": "def-common.Type", "text": "Type" }, - "; (types: [", + "]?: ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" }, ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - "], options?: ", - "UnionTypeOptions", - " | undefined): ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - "; (types: [", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - "], options?: ", + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", "UnionTypeOptions", - " | undefined): ", + " | undefined): ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -2538,257 +2626,5712 @@ "section": "def-common.Type", "text": "Type" }, - "; }; recordOf: (keyType: ", + "]?: ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.TypeOf", + "text": "TypeOf" }, - ", valueType: ", + "<(A & B & C & D & E & F)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.TypeOf", + "text": "TypeOf" }, - ", options?: ", - "RecordOfOptions", - " | undefined) => ", + "<(A & B & C & D & E & F)[K]>; }>>; >; stream: (options?: ", - "TypeOptions", - "<", - "Stream", - "> | undefined) => ", + ", B extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - "<", - "Stream", - ">; siblingRef: (key: string) => ", - "SiblingReference", - "; string: (options?: ", - "StringOptions", - " | undefined) => ", + ", C extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - "; uri: (options?: ", - "URIOptions", - " | undefined) => ", + ", D extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - "; }" - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.TypeOf", - "type": "Type", - "tags": [], - "label": "TypeOf", - "description": [], - "signature": [ - "RT extends () => ", + ", E extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - " ? ReturnType[\"type\"] : RT extends ", + ">(types: [", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - " ? RT[\"type\"] : never" - ], - "path": "packages/kbn-config-schema/src/types/object_type.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - } - ], - "objects": [ - { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.metaFields", - "type": "Object", - "tags": [], - "label": "metaFields", - "description": [], - "signature": [ - "{ readonly META_FIELD_X_OAS_ANY: \"x-oas-any-type\"; readonly META_FIELD_X_OAS_OPTIONAL: \"x-oas-optional\"; readonly META_FIELD_X_OAS_DEPRECATED: \"x-oas-deprecated\"; readonly META_FIELD_X_OAS_MAX_LENGTH: \"x-oas-max-length\"; readonly META_FIELD_X_OAS_MIN_LENGTH: \"x-oas-min-length\"; readonly META_FIELD_X_OAS_GET_ADDITIONAL_PROPERTIES: \"x-oas-get-additional-properties\"; }" - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema", - "type": "Object", - "tags": [], - "label": "schema", - "description": [], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ + ", ", { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema.any", - "type": "Function", - "tags": [], - "label": "any", - "description": [], - "signature": [ - "(options?: ", - "TypeOptions", - " | undefined) => ", + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + " | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "; }>>; }; any: (options?: ", + "TypeOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.AnyType", + "text": "AnyType" + }, + "; arrayOf: (itemType: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", options?: ", + "ArrayOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; boolean: (options?: ", + "TypeOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; buffer: (options?: ", + "TypeOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; byteSize: (options?: ", + "ByteSizeOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "<", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ByteSizeValue", + "text": "ByteSizeValue" + }, + ">; conditional: (leftOperand: ", + "Reference", + ", rightOperand: A | ", + "Reference", + " | ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", equalType: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", notEqualType: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", options?: ", + "TypeOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ConditionalType", + "text": "ConditionalType" + }, + "; contextRef: (key: string) => ", + "ContextReference", + "; duration: (options?: ", + "DurationOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; intersection: { (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H & I & J & K)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H & I & J & K)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H & I & J)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H & I & J)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H & I)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H & I)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + " | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "; }>>; }; ip: (options?: ", + "IpOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; lazy: (id: string) => ", + "Lazy", + "; literal: (value: T) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; mapOf: (keyType: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", valueType: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", options?: ", + "MapOfOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ">; maybe: (type: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ") => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; nullable: (type: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ") => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; never: () => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; number: (options?: ", + "NumberOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; object:

(props: P, options?: ", + "ObjectTypeOptions", + "

| undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "

; oneOf: { (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; }; recordOf: (keyType: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", valueType: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", options?: ", + "RecordOfOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ">; stream: (options?: ", + "TypeOptions", + "<", + "Stream", + "> | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "<", + "Stream", + ">; siblingRef: (key: string) => ", + "SiblingReference", + "; string: (options?: ", + "StringOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; uri: (options?: ", + "URIOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "; }" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.TypeOf", + "type": "Type", + "tags": [], + "label": "TypeOf", + "description": [], + "signature": [ + "RT extends () => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + " ? ReturnType[\"type\"] : RT extends ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + " ? RT[\"type\"] : never" + ], + "path": "packages/kbn-config-schema/src/types/object_type.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [ + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.metaFields", + "type": "Object", + "tags": [], + "label": "metaFields", + "description": [], + "signature": [ + "{ readonly META_FIELD_X_OAS_ANY: \"x-oas-any-type\"; readonly META_FIELD_X_OAS_OPTIONAL: \"x-oas-optional\"; readonly META_FIELD_X_OAS_DEPRECATED: \"x-oas-deprecated\"; readonly META_FIELD_X_OAS_MAX_LENGTH: \"x-oas-max-length\"; readonly META_FIELD_X_OAS_MIN_LENGTH: \"x-oas-min-length\"; readonly META_FIELD_X_OAS_GET_ADDITIONAL_PROPERTIES: \"x-oas-get-additional-properties\"; }" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.allOf", + "type": "Function", + "tags": [], + "label": "allOf", + "description": [], + "signature": [ + "{ (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H & I & J & K)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H & I & J & K)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H & I & J)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H & I & J)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H & I)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H & I)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + " | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "; }>>; }" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.any", + "type": "Function", + "tags": [], + "label": "any", + "description": [], + "signature": [ + "(options?: ", + "TypeOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.AnyType", + "text": "AnyType" + } + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.any.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "TypeOptions", + " | undefined" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.arrayOf", + "type": "Function", + "tags": [], + "label": "arrayOf", + "description": [], + "signature": [ + "(itemType: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", options?: ", + "ArrayOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.arrayOf.$1", + "type": "Object", + "tags": [], + "label": "itemType", + "description": [], + "signature": [ + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.arrayOf.$2", + "type": "CompoundType", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "ArrayOptions", + " | undefined" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.boolean", + "type": "Function", + "tags": [], + "label": "boolean", + "description": [], + "signature": [ + "(options?: ", + "TypeOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.boolean.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "TypeOptions", + " | undefined" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.buffer", + "type": "Function", + "tags": [], + "label": "buffer", + "description": [], + "signature": [ + "(options?: ", + "TypeOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.buffer.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "TypeOptions", + " | undefined" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.byteSize", + "type": "Function", + "tags": [], + "label": "byteSize", + "description": [], + "signature": [ + "(options?: ", + "ByteSizeOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "<", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ByteSizeValue", + "text": "ByteSizeValue" + }, + ">" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.byteSize.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "ByteSizeOptions", + " | undefined" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.conditional", + "type": "Function", + "tags": [], + "label": "conditional", + "description": [], + "signature": [ + "(leftOperand: ", + "Reference", + ", rightOperand: A | ", + "Reference", + " | ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", equalType: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", notEqualType: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + ", options?: ", + "TypeOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ConditionalType", + "text": "ConditionalType" + }, + "" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.conditional.$1", + "type": "Object", + "tags": [], + "label": "leftOperand", + "description": [], + "signature": [ + "Reference", + "" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.conditional.$2", + "type": "CompoundType", + "tags": [], + "label": "rightOperand", + "description": [], + "signature": [ + "A | ", + "Reference", + " | ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.conditional.$3", + "type": "Object", + "tags": [], + "label": "equalType", + "description": [], + "signature": [ + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.conditional.$4", + "type": "Object", + "tags": [], + "label": "notEqualType", + "description": [], + "signature": [ + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.conditional.$5", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "TypeOptions", + " | undefined" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.contextRef", + "type": "Function", + "tags": [], + "label": "contextRef", + "description": [], + "signature": [ + "(key: string) => ", + "ContextReference", + "" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.contextRef.$1", + "type": "string", + "tags": [], + "label": "key", + "description": [], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.duration", + "type": "Function", + "tags": [], + "label": "duration", + "description": [], + "signature": [ + "(options?: ", + "DurationOptions", + " | undefined) => ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.duration.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "DurationOptions", + " | undefined" + ], + "path": "packages/kbn-config-schema/index.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/config-schema", + "id": "def-common.schema.intersection", + "type": "Function", + "tags": [], + "label": "intersection", + "description": [], + "signature": [ + "{ (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H & I & J & K)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H & I & J & K)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H & I & J)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H & I & J)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H & I)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H & I)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G & H)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F & G)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E & F)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" + }, + "]?: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D & E)[K]>; }>>; | undefined" - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema.arrayOf", - "type": "Function", - "tags": [], - "label": "arrayOf", - "description": [], - "signature": [ - "(itemType: ", + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Props", + "text": "Props" + }, + ", D extends ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.Props", + "text": "Props" }, - ", options?: ", - "ArrayOptions", - " | undefined) => ", + ">(types: [", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - "" - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ + ", ", { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema.arrayOf.$1", - "type": "Object", - "tags": [], - "label": "itemType", - "description": [], - "signature": [ - { - "pluginId": "@kbn/config-schema", - "scope": "common", - "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" - }, - "" - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" }, + ", ", { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema.arrayOf.$2", - "type": "CompoundType", - "tags": [], - "label": "options", - "description": [], - "signature": [ - "ArrayOptions", - " | undefined" - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema.boolean", - "type": "Function", - "tags": [], - "label": "boolean", - "description": [], - "signature": [ - "(options?: ", - "TypeOptions", - " | undefined) => ", + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -2796,41 +8339,73 @@ "section": "def-common.Type", "text": "Type" }, - "" - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ + "]?: ", { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema.boolean.$1", - "type": "Object", - "tags": [], - "label": "options", - "description": [], - "signature": [ - "TypeOptions", - " | undefined" - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema.buffer", - "type": "Function", - "tags": [], - "label": "buffer", - "description": [], - "signature": [ - "(options?: ", - "TypeOptions", - " | undefined) => ", + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C & D)[K]>; }>>; (types: [", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + ", ", + { + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" + }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -2838,111 +8413,57 @@ "section": "def-common.Type", "text": "Type" }, - "" - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ + "]?: ", { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema.buffer.$1", - "type": "Object", - "tags": [], - "label": "options", - "description": [], - "signature": [ - "TypeOptions", - " | undefined" - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema.byteSize", - "type": "Function", - "tags": [], - "label": "byteSize", - "description": [], - "signature": [ - "(options?: ", - "ByteSizeOptions", - " | undefined) => ", + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + "<(A & B & C)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.TypeOf", + "text": "TypeOf" }, - "<", + "<(A & B & C)[K]>; }>>; " - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ + ", B extends ", { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema.byteSize.$1", - "type": "Object", - "tags": [], - "label": "options", - "description": [], - "signature": [ - "ByteSizeOptions", - " | undefined" - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema.conditional", - "type": "Function", - "tags": [], - "label": "conditional", - "description": [], - "signature": [ - "(leftOperand: ", - "Reference", - ", rightOperand: A | ", - "Reference", - " | ", + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Props", + "text": "Props" + }, + ">(types: [", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - ", equalType: ", + ", ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.ObjectType", + "text": "ObjectType" }, - ", notEqualType: ", + "], options?: ", + "UnionTypeOptions", + " | undefined): ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -2950,192 +8471,69 @@ "section": "def-common.Type", "text": "Type" }, - ", options?: ", - "TypeOptions", - " | undefined) => ", + "]?: ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.ConditionalType", - "text": "ConditionalType" + "section": "def-common.TypeOf", + "text": "TypeOf" }, - "" - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ + "<(A & B)[K]> | undefined; } & { [K in keyof RequiredProperties]: ", { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema.conditional.$1", - "type": "Object", - "tags": [], - "label": "leftOperand", - "description": [], - "signature": [ - "Reference", - "" - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" }, + "<(A & B)[K]>; }>>; | ", - { - "pluginId": "@kbn/config-schema", - "scope": "common", - "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" - }, - "" - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Props", + "text": "Props" }, + ">(types: [", { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema.conditional.$3", - "type": "Object", - "tags": [], - "label": "equalType", - "description": [], - "signature": [ - { - "pluginId": "@kbn/config-schema", - "scope": "common", - "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" - }, - "" - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.ObjectType", + "text": "ObjectType" }, + "], options?: ", + "UnionTypeOptions", + " | undefined): ", { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema.conditional.$4", - "type": "Object", - "tags": [], - "label": "notEqualType", - "description": [], - "signature": [ - { - "pluginId": "@kbn/config-schema", - "scope": "common", - "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" - }, - "" - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.Type", + "text": "Type" }, + "]?: ", { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema.conditional.$5", - "type": "Object", - "tags": [], - "label": "options", - "description": [], - "signature": [ - "TypeOptions", - " | undefined" - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema.contextRef", - "type": "Function", - "tags": [], - "label": "contextRef", - "description": [], - "signature": [ - "(key: string) => ", - "ContextReference", - "" - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema.contextRef.$1", - "type": "string", - "tags": [], - "label": "key", - "description": [], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false - } - ] - }, - { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema.duration", - "type": "Function", - "tags": [], - "label": "duration", - "description": [], - "signature": [ - "(options?: ", - "DurationOptions", - " | undefined) => ", + "pluginId": "@kbn/config-schema", + "scope": "common", + "docId": "kibKbnConfigSchemaPluginApi", + "section": "def-common.TypeOf", + "text": "TypeOf" + }, + " | undefined; } & { [K in keyof RequiredProperties]: ", { "pluginId": "@kbn/config-schema", "scope": "common", "docId": "kibKbnConfigSchemaPluginApi", - "section": "def-common.Type", - "text": "Type" + "section": "def-common.TypeOf", + "text": "TypeOf" }, - "" + "; }>>; }" ], "path": "packages/kbn-config-schema/index.ts", "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ - { - "parentPluginId": "@kbn/config-schema", - "id": "def-common.schema.duration.$1", - "type": "Object", - "tags": [], - "label": "options", - "description": [], - "signature": [ - "DurationOptions", - " | undefined" - ], - "path": "packages/kbn-config-schema/index.ts", - "deprecated": false, - "trackAdoption": false - } - ] + "trackAdoption": false }, { "parentPluginId": "@kbn/config-schema", diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index c14ae0e1bbf9c..7eb1ba384b302 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.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 | |-------------------|-----------|------------------------|-----------------| -| 144 | 3 | 141 | 20 | +| 146 | 2 | 142 | 20 | ## Common diff --git a/api_docs/kbn_content_management_content_editor.devdocs.json b/api_docs/kbn_content_management_content_editor.devdocs.json index b8650daee4eb8..c6144ff95f63b 100644 --- a/api_docs/kbn_content_management_content_editor.devdocs.json +++ b/api_docs/kbn_content_management_content_editor.devdocs.json @@ -186,7 +186,7 @@ "Item", "; isReadonly?: boolean | undefined; readonlyReason?: string | undefined; entityName: string; customValidators?: ", "CustomValidators", - " | undefined; }" + " | undefined; showActivityView?: boolean | undefined; }" ], "path": "packages/content-management/content_editor/src/open_content_editor.tsx", "deprecated": false, diff --git a/api_docs/kbn_content_management_content_editor.mdx b/api_docs/kbn_content_management_content_editor.mdx index 7945a26712f2e..11d11e8c99e80 100644 --- a/api_docs/kbn_content_management_content_editor.mdx +++ b/api_docs/kbn_content_management_content_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-content-editor title: "@kbn/content-management-content-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-content-editor plugin -date: 2024-06-11 +date: 2024-06-19 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 479f842babb58..b8c232f7bda68 100644 --- a/api_docs/kbn_content_management_tabbed_table_list_view.mdx +++ b/api_docs/kbn_content_management_tabbed_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-tabbed-table-list-view title: "@kbn/content-management-tabbed-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-tabbed-table-list-view plugin -date: 2024-06-11 +date: 2024-06-19 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 d71e92ae70012..8edb473e68481 100644 --- a/api_docs/kbn_content_management_table_list_view.mdx +++ b/api_docs/kbn_content_management_table_list_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view title: "@kbn/content-management-table-list-view" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view'] --- import kbnContentManagementTableListViewObj from './kbn_content_management_table_list_view.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list_view_common.devdocs.json b/api_docs/kbn_content_management_table_list_view_common.devdocs.json index 61f11d1c921f8..8bf94c542f624 100644 --- a/api_docs/kbn_content_management_table_list_view_common.devdocs.json +++ b/api_docs/kbn_content_management_table_list_view_common.devdocs.json @@ -53,6 +53,34 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/content-management-table-list-view-common", + "id": "def-common.UserContentCommonSchema.updatedBy", + "type": "string", + "tags": [], + "label": "updatedBy", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/content-management/table_list_view_common/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/content-management-table-list-view-common", + "id": "def-common.UserContentCommonSchema.createdAt", + "type": "string", + "tags": [], + "label": "createdAt", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/content-management/table_list_view_common/index.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/content-management-table-list-view-common", "id": "def-common.UserContentCommonSchema.createdBy", diff --git a/api_docs/kbn_content_management_table_list_view_common.mdx b/api_docs/kbn_content_management_table_list_view_common.mdx index c639bbaeb72f4..d318908661959 100644 --- a/api_docs/kbn_content_management_table_list_view_common.mdx +++ b/api_docs/kbn_content_management_table_list_view_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view-common title: "@kbn/content-management-table-list-view-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view-common plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-common'] --- import kbnContentManagementTableListViewCommonObj from './kbn_content_management_table_list_view_common.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sh | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 8 | 0 | 8 | 0 | +| 10 | 0 | 10 | 0 | ## Common 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 5a8d5b8f04610..0ac0ed64beb77 100644 --- a/api_docs/kbn_content_management_table_list_view_table.mdx +++ b/api_docs/kbn_content_management_table_list_view_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list-view-table title: "@kbn/content-management-table-list-view-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list-view-table plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list-view-table'] --- import kbnContentManagementTableListViewTableObj from './kbn_content_management_table_list_view_table.devdocs.json'; diff --git a/api_docs/kbn_content_management_user_profiles.devdocs.json b/api_docs/kbn_content_management_user_profiles.devdocs.json new file mode 100644 index 0000000000000..0107a179f3ac0 --- /dev/null +++ b/api_docs/kbn_content_management_user_profiles.devdocs.json @@ -0,0 +1,622 @@ +{ + "id": "@kbn/content-management-user-profiles", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.ManagedAvatarTip", + "type": "Function", + "tags": [], + "label": "ManagedAvatarTip", + "description": [], + "signature": [ + "({\n entityName = i18n.translate('contentManagement.userProfiles.managedAvatarTip.defaultEntityName', {\n defaultMessage: 'object',\n }),\n}: { entityName?: string | undefined; }) => JSX.Element" + ], + "path": "packages/content-management/user_profiles/src/components/managed_avatar_tip.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.ManagedAvatarTip.$1", + "type": "Object", + "tags": [], + "label": "{\n entityName = i18n.translate('contentManagement.userProfiles.managedAvatarTip.defaultEntityName', {\n defaultMessage: 'object',\n }),\n}", + "description": [], + "path": "packages/content-management/user_profiles/src/components/managed_avatar_tip.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.ManagedAvatarTip.$1.entityName", + "type": "string", + "tags": [], + "label": "entityName", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/content-management/user_profiles/src/components/managed_avatar_tip.tsx", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.NoCreatorTip", + "type": "Function", + "tags": [], + "label": "NoCreatorTip", + "description": [], + "signature": [ + "(props: { iconType?: ", + "IconType", + " | undefined; }) => JSX.Element" + ], + "path": "packages/content-management/user_profiles/src/components/user_missing_tip.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.NoCreatorTip.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "path": "packages/content-management/user_profiles/src/components/user_missing_tip.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.NoCreatorTip.$1.iconType", + "type": "CompoundType", + "tags": [], + "label": "iconType", + "description": [], + "signature": [ + "IconType", + " | undefined" + ], + "path": "packages/content-management/user_profiles/src/components/user_missing_tip.tsx", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.NoUpdaterTip", + "type": "Function", + "tags": [], + "label": "NoUpdaterTip", + "description": [], + "signature": [ + "(props: { iconType?: string | undefined; }) => JSX.Element" + ], + "path": "packages/content-management/user_profiles/src/components/user_missing_tip.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.NoUpdaterTip.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "path": "packages/content-management/user_profiles/src/components/user_missing_tip.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.NoUpdaterTip.$1.iconType", + "type": "string", + "tags": [], + "label": "iconType", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/content-management/user_profiles/src/components/user_missing_tip.tsx", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.UserAvatarTip", + "type": "Function", + "tags": [], + "label": "UserAvatarTip", + "description": [], + "signature": [ + "(props: { uid: string; }) => JSX.Element | null" + ], + "path": "packages/content-management/user_profiles/src/components/user_avatar_tip.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.UserAvatarTip.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "path": "packages/content-management/user_profiles/src/components/user_avatar_tip.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.UserAvatarTip.$1.uid", + "type": "string", + "tags": [], + "label": "uid", + "description": [], + "path": "packages/content-management/user_profiles/src/components/user_avatar_tip.tsx", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.UserProfilesKibanaProvider", + "type": "Function", + "tags": [], + "label": "UserProfilesKibanaProvider", + "description": [], + "signature": [ + "({ children, core, }: React.PropsWithChildren>) => JSX.Element" + ], + "path": "packages/content-management/user_profiles/src/services.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.UserProfilesKibanaProvider.$1", + "type": "CompoundType", + "tags": [], + "label": "{\n children,\n core,\n}", + "description": [], + "signature": [ + "React.PropsWithChildren>" + ], + "path": "packages/content-management/user_profiles/src/services.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.UserProfilesProvider", + "type": "Function", + "tags": [], + "label": "UserProfilesProvider", + "description": [], + "signature": [ + "({ children, ...services }: React.PropsWithChildren>) => JSX.Element" + ], + "path": "packages/content-management/user_profiles/src/services.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.UserProfilesProvider.$1", + "type": "CompoundType", + "tags": [], + "label": "{\n children,\n ...services\n}", + "description": [], + "signature": [ + "React.PropsWithChildren>" + ], + "path": "packages/content-management/user_profiles/src/services.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.useUserProfile", + "type": "Function", + "tags": [], + "label": "useUserProfile", + "description": [], + "signature": [ + "(uid: string) => ", + "UseQueryResult", + "<", + { + "pluginId": "@kbn/user-profile-components", + "scope": "common", + "docId": "kibKbnUserProfileComponentsPluginApi", + "section": "def-common.UserProfile", + "text": "UserProfile" + }, + "<", + { + "pluginId": "@kbn/user-profile-components", + "scope": "common", + "docId": "kibKbnUserProfileComponentsPluginApi", + "section": "def-common.UserProfileData", + "text": "UserProfileData" + }, + ">, unknown>" + ], + "path": "packages/content-management/user_profiles/src/queries.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.useUserProfile.$1", + "type": "string", + "tags": [], + "label": "uid", + "description": [], + "signature": [ + "string" + ], + "path": "packages/content-management/user_profiles/src/queries.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.useUserProfiles", + "type": "Function", + "tags": [], + "label": "useUserProfiles", + "description": [], + "signature": [ + "(uids: string[], opts?: { enabled?: boolean | undefined; } | undefined) => ", + "UseQueryResult", + "<", + { + "pluginId": "@kbn/user-profile-components", + "scope": "common", + "docId": "kibKbnUserProfileComponentsPluginApi", + "section": "def-common.UserProfile", + "text": "UserProfile" + }, + "<", + { + "pluginId": "@kbn/user-profile-components", + "scope": "common", + "docId": "kibKbnUserProfileComponentsPluginApi", + "section": "def-common.UserProfileData", + "text": "UserProfileData" + }, + ">[], unknown>" + ], + "path": "packages/content-management/user_profiles/src/queries.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.useUserProfiles.$1", + "type": "Array", + "tags": [], + "label": "uids", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/content-management/user_profiles/src/queries.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.useUserProfiles.$2", + "type": "Object", + "tags": [], + "label": "opts", + "description": [], + "path": "packages/content-management/user_profiles/src/queries.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.useUserProfiles.$2.enabled", + "type": "CompoundType", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/content-management/user_profiles/src/queries.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.useUserProfilesServices", + "type": "Function", + "tags": [], + "label": "useUserProfilesServices", + "description": [], + "signature": [ + "() => ", + { + "pluginId": "@kbn/content-management-user-profiles", + "scope": "common", + "docId": "kibKbnContentManagementUserProfilesPluginApi", + "section": "def-common.UserProfilesServices", + "text": "UserProfilesServices" + } + ], + "path": "packages/content-management/user_profiles/src/services.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.UserProfilesKibanaDependencies", + "type": "Interface", + "tags": [], + "label": "UserProfilesKibanaDependencies", + "description": [], + "path": "packages/content-management/user_profiles/src/services.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.UserProfilesKibanaDependencies.core", + "type": "Object", + "tags": [], + "label": "core", + "description": [], + "signature": [ + "{ userProfile: { bulkGet: (params: ", + { + "pluginId": "@kbn/core-user-profile-browser", + "scope": "common", + "docId": "kibKbnCoreUserProfileBrowserPluginApi", + "section": "def-common.UserProfileBulkGetParams", + "text": "UserProfileBulkGetParams" + }, + ") => Promise<", + { + "pluginId": "@kbn/core-user-profile-common", + "scope": "common", + "docId": "kibKbnCoreUserProfileCommonPluginApi", + "section": "def-common.UserProfile", + "text": "UserProfile" + }, + "[]>; }; }" + ], + "path": "packages/content-management/user_profiles/src/services.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.UserProfilesServices", + "type": "Interface", + "tags": [], + "label": "UserProfilesServices", + "description": [], + "path": "packages/content-management/user_profiles/src/services.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.UserProfilesServices.bulkGetUserProfiles", + "type": "Function", + "tags": [], + "label": "bulkGetUserProfiles", + "description": [], + "signature": [ + "(uids: string[]) => Promise<", + { + "pluginId": "@kbn/user-profile-components", + "scope": "common", + "docId": "kibKbnUserProfileComponentsPluginApi", + "section": "def-common.UserProfile", + "text": "UserProfile" + }, + "<", + { + "pluginId": "@kbn/user-profile-components", + "scope": "common", + "docId": "kibKbnUserProfileComponentsPluginApi", + "section": "def-common.UserProfileData", + "text": "UserProfileData" + }, + ">[]>" + ], + "path": "packages/content-management/user_profiles/src/services.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.UserProfilesServices.bulkGetUserProfiles.$1", + "type": "Array", + "tags": [], + "label": "uids", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/content-management/user_profiles/src/services.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.UserProfilesServices.getUserProfile", + "type": "Function", + "tags": [], + "label": "getUserProfile", + "description": [], + "signature": [ + "(uid: string) => Promise<", + { + "pluginId": "@kbn/user-profile-components", + "scope": "common", + "docId": "kibKbnUserProfileComponentsPluginApi", + "section": "def-common.UserProfile", + "text": "UserProfile" + }, + "<", + { + "pluginId": "@kbn/user-profile-components", + "scope": "common", + "docId": "kibKbnUserProfileComponentsPluginApi", + "section": "def-common.UserProfileData", + "text": "UserProfileData" + }, + ">>" + ], + "path": "packages/content-management/user_profiles/src/services.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/content-management-user-profiles", + "id": "def-common.UserProfilesServices.getUserProfile.$1", + "type": "string", + "tags": [], + "label": "uid", + "description": [], + "signature": [ + "string" + ], + "path": "packages/content-management/user_profiles/src/services.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_content_management_user_profiles.mdx b/api_docs/kbn_content_management_user_profiles.mdx new file mode 100644 index 0000000000000..8c7fdd49f0c66 --- /dev/null +++ b/api_docs/kbn_content_management_user_profiles.mdx @@ -0,0 +1,33 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnContentManagementUserProfilesPluginApi +slug: /kibana-dev-docs/api/kbn-content-management-user-profiles +title: "@kbn/content-management-user-profiles" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/content-management-user-profiles plugin +date: 2024-06-19 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-user-profiles'] +--- +import kbnContentManagementUserProfilesObj from './kbn_content_management_user_profiles.devdocs.json'; + + + +Contact [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 30 | 0 | 30 | 0 | + +## Common + +### Functions + + +### Interfaces + + diff --git a/api_docs/kbn_content_management_utils.mdx b/api_docs/kbn_content_management_utils.mdx index ef404b8458e75..5e36fe54ede76 100644 --- a/api_docs/kbn_content_management_utils.mdx +++ b/api_docs/kbn_content_management_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-utils title: "@kbn/content-management-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-utils plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-utils'] --- import kbnContentManagementUtilsObj from './kbn_content_management_utils.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.devdocs.json b/api_docs/kbn_core_analytics_browser.devdocs.json index 1f9e8beb3c160..7f19ef23a3e2d 100644 --- a/api_docs/kbn_core_analytics_browser.devdocs.json +++ b/api_docs/kbn_core_analytics_browser.devdocs.json @@ -20,36 +20,2867 @@ "classes": [], "functions": [], "interfaces": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.AnalyticsClientInitContext", + "type": "Interface", + "tags": [], + "label": "AnalyticsClientInitContext", + "description": [ + "\nGeneral settings of the analytics client" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.AnalyticsClientInitContext.isDev", + "type": "boolean", + "tags": [], + "label": "isDev", + "description": [ + "\nBoolean indicating if it's running in developer mode." + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.AnalyticsClientInitContext.sendTo", + "type": "CompoundType", + "tags": [], + "label": "sendTo", + "description": [ + "\nSpecify if the shippers should send their data to the production or staging environments." + ], + "signature": [ + "\"production\" | \"staging\"" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.AnalyticsClientInitContext.logger", + "type": "Object", + "tags": [], + "label": "logger", + "description": [ + "\nApplication-provided logger." + ], + "signature": [ + { + "pluginId": "@kbn/logging", + "scope": "common", + "docId": "kibKbnLoggingPluginApi", + "section": "def-common.Logger", + "text": "Logger" + } + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.ContextProviderOpts", + "type": "Interface", + "tags": [], + "label": "ContextProviderOpts", + "description": [ + "\nDefinition of a context provider" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ContextProviderOpts", + "text": "ContextProviderOpts" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.ContextProviderOpts.name", + "type": "string", + "tags": [], + "label": "name", + "description": [ + "\nThe name of the provider." + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.ContextProviderOpts.context$", + "type": "Object", + "tags": [], + "label": "context$", + "description": [ + "\nObservable that emits the custom context." + ], + "signature": [ + "Observable", + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.ContextProviderOpts.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [ + "\nSchema declaring and documenting the expected output in the context$\n" + ], + "signature": [ + "{ [Key in keyof Required]: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaValue", + "text": "SchemaValue" + }, + "; }" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.Event", + "type": "Interface", + "tags": [], + "label": "Event", + "description": [ + "\nDefinition of the full event structure" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.Event", + "text": "Event" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.Event.timestamp", + "type": "string", + "tags": [], + "label": "timestamp", + "description": [ + "\nThe time the event was generated in ISO format." + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.Event.event_type", + "type": "string", + "tags": [], + "label": "event_type", + "description": [ + "\nThe event type." + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.Event.properties", + "type": "Uncategorized", + "tags": [], + "label": "properties", + "description": [ + "\nThe specific properties of the event type." + ], + "signature": [ + "Properties" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.Event.context", + "type": "Object", + "tags": [], + "label": "context", + "description": [ + "\nThe {@link EventContext} enriched during the processing pipeline." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventContext", + "text": "EventContext" + } + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.EventContext", + "type": "Interface", + "tags": [], + "label": "EventContext", + "description": [ + "\nDefinition of the context that can be appended to the events through the {@link IAnalyticsClient.registerContextProvider}." + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.EventContext.cluster_uuid", + "type": "string", + "tags": [], + "label": "cluster_uuid", + "description": [ + "\nThe UUID of the cluster" + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.EventContext.cluster_name", + "type": "string", + "tags": [], + "label": "cluster_name", + "description": [ + "\nThe name of the cluster." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.EventContext.license_id", + "type": "string", + "tags": [], + "label": "license_id", + "description": [ + "\nThe license ID." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.EventContext.userId", + "type": "string", + "tags": [], + "label": "userId", + "description": [ + "\nThe unique user ID." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.EventContext.cloudId", + "type": "string", + "tags": [], + "label": "cloudId", + "description": [ + "\nThe Cloud ID." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.EventContext.isElasticCloudUser", + "type": "CompoundType", + "tags": [], + "label": "isElasticCloudUser", + "description": [ + "\n`true` if the user is logged in via the Elastic Cloud authentication provider." + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.EventContext.version", + "type": "string", + "tags": [], + "label": "version", + "description": [ + "\nThe product's version." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.EventContext.pageName", + "type": "string", + "tags": [], + "label": "pageName", + "description": [ + "\nThe name of the current page." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.EventContext.applicationId", + "type": "string", + "tags": [], + "label": "applicationId", + "description": [ + "\nThe current application ID." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.EventContext.entityId", + "type": "string", + "tags": [], + "label": "entityId", + "description": [ + "\nThe current entity ID (dashboard ID, visualization ID, etc.)." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.EventContext.Unnamed", + "type": "IndexSignature", + "tags": [], + "label": "[key: string]: unknown", + "description": [ + "\nAdditional keys are allowed." + ], + "signature": [ + "[key: string]: unknown" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.EventTypeOpts", + "type": "Interface", + "tags": [], + "label": "EventTypeOpts", + "description": [ + "\nDefinition of an Event Type." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventTypeOpts", + "text": "EventTypeOpts" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.EventTypeOpts.eventType", + "type": "string", + "tags": [], + "label": "eventType", + "description": [ + "\nThe event type's unique name." + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.EventTypeOpts.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [ + "\nSchema declaring and documenting the expected structure of this event type.\n" + ], + "signature": [ + "{ [Key in keyof Required]: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaValue", + "text": "SchemaValue" + }, + "; }" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IAnalyticsClient", + "type": "Interface", + "tags": [], + "label": "IAnalyticsClient", + "description": [ + "\nAnalytics client's public APIs" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IAnalyticsClient.reportEvent", + "type": "Function", + "tags": [ + "track-adoption" + ], + "label": "reportEvent", + "description": [ + "\nReports a telemetry event." + ], + "signature": [ + "(eventType: string, eventData: EventTypeData) => void" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": true, + "references": [ + { + "plugin": "@kbn/core-notifications-browser-internal", + "path": "packages/core/notifications/core-notifications-browser-internal/src/toasts/telemetry/event_reporter.ts" + }, + { + "plugin": "@kbn/core-notifications-browser-internal", + "path": "packages/core/notifications/core-notifications-browser-internal/src/toasts/telemetry/event_reporter.ts" + }, + { + "plugin": "@kbn/ebt-tools", + "path": "packages/kbn-ebt-tools/src/performance_metric_events/helpers.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-root-server-internal", + "path": "packages/core/root/core-root-server-internal/src/events/kibana_started.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.ts" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "dashboard", + "path": "src/plugins/dashboard/public/services/analytics/types.ts" + }, + { + "plugin": "dashboard", + "path": "src/plugins/dashboard/public/services/analytics/analytics_service.ts" + }, + { + "plugin": "observabilityAIAssistant", + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/recall_and_score.ts" + }, + { + "plugin": "observabilityAIAssistant", + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/analytics/index.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" + }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/lib/langchain/elasticsearch_store/elasticsearch_store.ts" + }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/lib/langchain/elasticsearch_store/elasticsearch_store.ts" + }, + { + "plugin": "globalSearchBar", + "path": "x-pack/plugins/global_search_bar/public/telemetry/event_reporter.ts" + }, + { + "plugin": "globalSearchBar", + "path": "x-pack/plugins/global_search_bar/public/telemetry/event_reporter.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts" + }, + { + "plugin": "reporting", + "path": "x-pack/plugins/reporting/server/usage/event_tracker.ts" + }, + { + "plugin": "searchPlayground", + "path": "x-pack/plugins/search_playground/server/routes.ts" + }, + { + "plugin": "securitySolutionServerless", + "path": "x-pack/plugins/security_solution_serverless/server/task_manager/nlp_cleanup_task/nlp_cleanup_task.ts" + }, + { + "plugin": "securitySolutionServerless", + "path": "x-pack/plugins/security_solution_serverless/server/task_manager/nlp_cleanup_task/nlp_cleanup_task.ts" + }, + { + "plugin": "apm", + "path": "x-pack/plugins/observability_solution/apm/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "observabilityLogsExplorer", + "path": "x-pack/plugins/observability_solution/observability_logs_explorer/public/state_machines/observability_logs_explorer/src/telemetry_events.ts" + }, + { + "plugin": "observabilityOnboarding", + "path": "x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_flow_progress_telemetry.ts" + }, + { + "plugin": "observabilityOnboarding", + "path": "x-pack/plugins/observability_solution/observability_onboarding/public/application/app.tsx" + }, + { + "plugin": "observabilityAIAssistant", + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_chat_service.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-mocks", + "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-mocks", + "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "dashboard", + "path": "src/plugins/dashboard/public/services/analytics/analytics.stub.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-analytics-server-mocks", + "path": "packages/core/analytics/core-analytics-server-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "@kbn/core-analytics-server-mocks", + "path": "packages/core/analytics/core-analytics-server-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.test.ts" + }, + { + "plugin": "apm", + "path": "x-pack/plugins/observability_solution/apm/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "apm", + "path": "x-pack/plugins/observability_solution/apm/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.test.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.test.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.test.ts" + }, + { + "plugin": "@kbn/core-analytics-server-mocks", + "path": "packages/core/analytics/core-analytics-server-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/mocks.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.mocks.ts" + } + ], + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IAnalyticsClient.reportEvent.$1", + "type": "string", + "tags": [], + "label": "eventType", + "description": [ + "The event type registered via the `registerEventType` API." + ], + "signature": [ + "string" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IAnalyticsClient.reportEvent.$2", + "type": "Uncategorized", + "tags": [], + "label": "eventData", + "description": [ + "The properties matching the schema declared in the `registerEventType` API." + ], + "signature": [ + "EventTypeData" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IAnalyticsClient.registerEventType", + "type": "Function", + "tags": [], + "label": "registerEventType", + "description": [ + "\nRegisters the event type that will be emitted via the reportEvent API." + ], + "signature": [ + "(eventTypeOps: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventTypeOpts", + "text": "EventTypeOpts" + }, + ") => void" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IAnalyticsClient.registerEventType.$1", + "type": "Object", + "tags": [], + "label": "eventTypeOps", + "description": [ + "The definition of the event type {@link EventTypeOpts }." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventTypeOpts", + "text": "EventTypeOpts" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IAnalyticsClient.registerShipper", + "type": "Function", + "tags": [], + "label": "registerShipper", + "description": [ + "\nSet up the shipper that will be used to report the telemetry events." + ], + "signature": [ + "(Shipper: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ShipperClassConstructor", + "text": "ShipperClassConstructor" + }, + ", shipperConfig: ShipperConfig, opts?: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.RegisterShipperOpts", + "text": "RegisterShipperOpts" + }, + " | undefined) => void" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IAnalyticsClient.registerShipper.$1", + "type": "Object", + "tags": [], + "label": "Shipper", + "description": [ + "The {@link IShipper } class to instantiate the shipper." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ShipperClassConstructor", + "text": "ShipperClassConstructor" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IAnalyticsClient.registerShipper.$2", + "type": "Uncategorized", + "tags": [], + "label": "shipperConfig", + "description": [ + "The config specific to the Shipper to instantiate." + ], + "signature": [ + "ShipperConfig" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IAnalyticsClient.registerShipper.$3", + "type": "Object", + "tags": [], + "label": "opts", + "description": [ + "Additional options to register the shipper {@link RegisterShipperOpts }." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.RegisterShipperOpts", + "text": "RegisterShipperOpts" + }, + " | undefined" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IAnalyticsClient.optIn", + "type": "Function", + "tags": [], + "label": "optIn", + "description": [ + "\nUsed to control the user's consent to report the data.\nIn the advanced mode, it allows to \"cherry-pick\" which events and shippers are enabled/disabled." + ], + "signature": [ + "(optInConfig: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.OptInConfig", + "text": "OptInConfig" + }, + ") => void" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IAnalyticsClient.optIn.$1", + "type": "Object", + "tags": [], + "label": "optInConfig", + "description": [ + "{@link OptInConfig }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.OptInConfig", + "text": "OptInConfig" + } + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IAnalyticsClient.registerContextProvider", + "type": "Function", + "tags": [ + "track-adoption" + ], + "label": "registerContextProvider", + "description": [ + "\nRegisters the context provider to enrich any reported events." + ], + "signature": [ + "(contextProviderOpts: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ContextProviderOpts", + "text": "ContextProviderOpts" + }, + ") => void" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": true, + "references": [ + { + "plugin": "@kbn/core-execution-context-browser-internal", + "path": "packages/core/execution-context/core-execution-context-browser-internal/src/execution_context_service.ts" + }, + { + "plugin": "@kbn/core-application-browser-internal", + "path": "packages/core/application/core-application-browser-internal/src/register_analytics_context_provider.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-chrome-browser-internal", + "path": "packages/core/chrome/core-chrome-browser-internal/src/register_analytics_context_provider.ts" + }, + { + "plugin": "@kbn/core-elasticsearch-server-internal", + "path": "packages/core/elasticsearch/core-elasticsearch-server-internal/src/register_analytics_context_provider.ts" + }, + { + "plugin": "@kbn/core-environment-server-internal", + "path": "packages/core/environment/core-environment-server-internal/src/environment_service.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "cloud", + "path": "x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts" + }, + { + "plugin": "licensing", + "path": "x-pack/plugins/licensing/common/register_analytics_context_provider.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.ts" + }, + { + "plugin": "telemetry", + "path": "src/plugins/telemetry/server/plugin.ts" + }, + { + "plugin": "telemetry", + "path": "src/plugins/telemetry/public/plugin.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-mocks", + "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "@kbn/core-application-browser-internal", + "path": "packages/core/application/core-application-browser-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-application-browser-internal", + "path": "packages/core/application/core-application-browser-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-application-browser-internal", + "path": "packages/core/application/core-application-browser-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-chrome-browser-internal", + "path": "packages/core/chrome/core-chrome-browser-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-chrome-browser-internal", + "path": "packages/core/chrome/core-chrome-browser-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-chrome-browser-internal", + "path": "packages/core/chrome/core-chrome-browser-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-execution-context-browser-internal", + "path": "packages/core/execution-context/core-execution-context-browser-internal/src/execution_context_service.test.ts" + }, + { + "plugin": "@kbn/core-execution-context-browser-internal", + "path": "packages/core/execution-context/core-execution-context-browser-internal/src/execution_context_service.test.ts" + }, + { + "plugin": "@kbn/core-analytics-server-mocks", + "path": "packages/core/analytics/core-analytics-server-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "@kbn/core-analytics-server-mocks", + "path": "packages/core/analytics/core-analytics-server-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "@kbn/core-elasticsearch-server-internal", + "path": "packages/core/elasticsearch/core-elasticsearch-server-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.test.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/mocks.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.mocks.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.ts" + } + ], + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IAnalyticsClient.registerContextProvider.$1", + "type": "Object", + "tags": [], + "label": "contextProviderOpts", + "description": [ + "{@link ContextProviderOpts }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ContextProviderOpts", + "text": "ContextProviderOpts" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IAnalyticsClient.removeContextProvider", + "type": "Function", + "tags": [], + "label": "removeContextProvider", + "description": [ + "\nRemoves the context provider and stop enriching the events from its context." + ], + "signature": [ + "(contextProviderName: string) => void" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IAnalyticsClient.removeContextProvider.$1", + "type": "string", + "tags": [], + "label": "contextProviderName", + "description": [ + "The name of the context provider to remove." + ], + "signature": [ + "string" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IAnalyticsClient.telemetryCounter$", + "type": "Object", + "tags": [], + "label": "telemetryCounter$", + "description": [ + "\nObservable to emit the stats of the processed events." + ], + "signature": [ + "Observable", + "<", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.TelemetryCounter", + "text": "TelemetryCounter" + }, + ">" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IAnalyticsClient.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nForces all shippers to send all their enqueued events and fulfills the returned promise." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IAnalyticsClient.shutdown", + "type": "Function", + "tags": [], + "label": "shutdown", + "description": [ + "\nStops the client. Flushing any pending events in the process." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IShipper", + "type": "Interface", + "tags": [], + "label": "IShipper", + "description": [ + "\nBasic structure of a Shipper" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IShipper.reportEvents", + "type": "Function", + "tags": [], + "label": "reportEvents", + "description": [ + "\nAdapts and ships the event to the persisting/analytics solution." + ], + "signature": [ + "(events: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.Event", + "text": "Event" + }, + ">[]) => void" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IShipper.reportEvents.$1", + "type": "Array", + "tags": [], + "label": "events", + "description": [ + "batched events {@link Event }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.Event", + "text": "Event" + }, + ">[]" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IShipper.optIn", + "type": "Function", + "tags": [], + "label": "optIn", + "description": [ + "\nStops/restarts the shipping mechanism based on the value of isOptedIn" + ], + "signature": [ + "(isOptedIn: boolean) => void" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IShipper.optIn.$1", + "type": "boolean", + "tags": [], + "label": "isOptedIn", + "description": [ + "`true` for resume sending events. `false` to stop." + ], + "signature": [ + "boolean" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IShipper.extendContext", + "type": "Function", + "tags": [], + "label": "extendContext", + "description": [ + "\nPerform any necessary calls to the persisting/analytics solution to set the event's context." + ], + "signature": [ + "((newContext: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventContext", + "text": "EventContext" + }, + ") => void) | undefined" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IShipper.extendContext.$1", + "type": "Object", + "tags": [], + "label": "newContext", + "description": [ + "The full new context to set {@link EventContext }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventContext", + "text": "EventContext" + } + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IShipper.telemetryCounter$", + "type": "Object", + "tags": [], + "label": "telemetryCounter$", + "description": [ + "\nObservable to emit the stats of the processed events." + ], + "signature": [ + "Observable", + "<", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.TelemetryCounter", + "text": "TelemetryCounter" + }, + "> | undefined" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IShipper.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nSends all the enqueued events and fulfills the returned promise." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.IShipper.shutdown", + "type": "Function", + "tags": [], + "label": "shutdown", + "description": [ + "\nShutdown the shipper." + ], + "signature": [ + "() => void" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-analytics-browser", "id": "def-common.KbnAnalyticsWindowApi", "type": "Interface", "tags": [], - "label": "KbnAnalyticsWindowApi", + "label": "KbnAnalyticsWindowApi", + "description": [ + "\nAPI exposed through `window.__kbnAnalytics`" + ], + "path": "packages/core/analytics/core-analytics-browser/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.KbnAnalyticsWindowApi.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nReturns a promise that resolves when all the events in the queue have been sent." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/core/analytics/core-analytics-browser/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.OptInConfig", + "type": "Interface", + "tags": [], + "label": "OptInConfig", + "description": [ + "\nOptions for the optIn API" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.OptInConfig.global", + "type": "Object", + "tags": [], + "label": "global", + "description": [ + "\nControls the global enabled/disabled behaviour of the client and shippers." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.OptInConfigPerType", + "text": "OptInConfigPerType" + } + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.OptInConfig.event_types", + "type": "Object", + "tags": [], + "label": "event_types", + "description": [ + "\nControls if an event type should be disabled for a specific type of shipper." + ], + "signature": [ + "Record | undefined" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.OptInConfigPerType", + "type": "Interface", + "tags": [], + "label": "OptInConfigPerType", + "description": [ + "\nSets whether a type of event is enabled/disabled globally or per shipper." + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.OptInConfigPerType.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [ + "\nThe event type is globally enabled." + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.OptInConfigPerType.shippers", + "type": "Object", + "tags": [], + "label": "shippers", + "description": [ + "\nControls if an event type should be disabled for a specific type of shipper." + ], + "signature": [ + "Record | undefined" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.RegisterShipperOpts", + "type": "Interface", + "tags": [], + "label": "RegisterShipperOpts", + "description": [ + "\nOptional options to register a shipper" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.SchemaArray", + "type": "Interface", + "tags": [], + "label": "SchemaArray", + "description": [ + "\nSchema to represent an array" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaArray", + "text": "SchemaArray" + }, + " extends ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMeta", + "text": "SchemaMeta" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.SchemaArray.type", + "type": "string", + "tags": [], + "label": "type", + "description": [ + "The type must be an array" + ], + "signature": [ + "\"array\"" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.SchemaArray.items", + "type": "CompoundType", + "tags": [], + "label": "items", + "description": [ + "The schema of the items in the array is defined in the `items` property" + ], + "signature": [ + "{ type: \"pass_through\"; _meta: { description: string; } & ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMetaOptional", + "text": "SchemaMetaOptional" + }, + "; } | (unknown extends Value ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaArray", + "text": "SchemaArray" + }, + " | ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaObject", + "text": "SchemaObject" + }, + " | ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + " : NonNullable extends (infer U)[] | readonly (infer U)[] ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaArray", + "text": "SchemaArray" + }, + " : NonNullable extends Date ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + " : NonNullable extends object ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaObject", + "text": "SchemaObject" + }, + " : ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + ")" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.SchemaChildValue", + "type": "Interface", + "tags": [], + "label": "SchemaChildValue", + "description": [ + "\nSchema to define a primitive value" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.SchemaChildValue.type", + "type": "Uncategorized", + "tags": [], + "label": "type", + "description": [ + "The type of the value" + ], + "signature": [ + "NonNullable extends string | Date ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AllowedSchemaStringTypes", + "text": "AllowedSchemaStringTypes" + }, + " : NonNullable extends number ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AllowedSchemaNumberTypes", + "text": "AllowedSchemaNumberTypes" + }, + " : NonNullable extends boolean ? \"boolean\" : ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AllowedSchemaTypes", + "text": "AllowedSchemaTypes" + } + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.SchemaChildValue._meta", + "type": "CompoundType", + "tags": [], + "label": "_meta", + "description": [ + "Meta properties of the value: description and is optional" + ], + "signature": [ + "{ description: string; } & ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMetaOptional", + "text": "SchemaMetaOptional" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.SchemaMeta", + "type": "Interface", + "tags": [], + "label": "SchemaMeta", + "description": [ + "\nSchema meta with optional description" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMeta", + "text": "SchemaMeta" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.SchemaMeta._meta", + "type": "CompoundType", + "tags": [], + "label": "_meta", + "description": [ + "Meta properties of the pass through: description and is optional" + ], + "signature": [ + "({ description?: string | undefined; } & ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMetaOptional", + "text": "SchemaMetaOptional" + }, + ") | undefined" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.SchemaObject", + "type": "Interface", + "tags": [], + "label": "SchemaObject", + "description": [ + "\nSchema to represent an object" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaObject", + "text": "SchemaObject" + }, + " extends ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMeta", + "text": "SchemaMeta" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.SchemaObject.properties", + "type": "Object", + "tags": [], + "label": "properties", + "description": [ + "\nThe schemas of the keys of the object are defined in the `properties` object." + ], + "signature": [ + "{ [Key in keyof Required]: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaValue", + "text": "SchemaValue" + }, + "; }" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.ShipperClassConstructor", + "type": "Interface", + "tags": [], + "label": "ShipperClassConstructor", "description": [ - "\nAPI exposed through `window.__kbnAnalytics`" + "\nConstructor of a {@link IShipper}" ], - "path": "packages/core/analytics/core-analytics-browser/src/types.ts", + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ShipperClassConstructor", + "text": "ShipperClassConstructor" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/core-analytics-browser", - "id": "def-common.KbnAnalyticsWindowApi.flush", + "id": "def-common.ShipperClassConstructor.shipperName", + "type": "string", + "tags": [], + "label": "shipperName", + "description": [ + "\nThe shipper's unique name" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.ShipperClassConstructor.new", "type": "Function", "tags": [], - "label": "flush", + "label": "new", "description": [ - "\nReturns a promise that resolves when all the events in the queue have been sent." + "\nThe constructor" ], "signature": [ - "() => Promise" + "any" ], - "path": "packages/core/analytics/core-analytics-browser/src/types.ts", + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", "deprecated": false, "trackAdoption": false, - "returnComment": [], - "children": [] + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.ShipperClassConstructor.new.$1", + "type": "Uncategorized", + "tags": [], + "label": "config", + "description": [ + "The shipper's custom config" + ], + "signature": [ + "Config" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.ShipperClassConstructor.new.$2", + "type": "Object", + "tags": [], + "label": "initContext", + "description": [ + "Common context {@link AnalyticsClientInitContext }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AnalyticsClientInitContext", + "text": "AnalyticsClientInitContext" + } + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.TelemetryCounter", + "type": "Interface", + "tags": [], + "label": "TelemetryCounter", + "description": [ + "\nShape of the events emitted by the telemetryCounter$ observable" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.TelemetryCounter.type", + "type": "CompoundType", + "tags": [], + "label": "type", + "description": [ + "\n{@link TelemetryCounterType}" + ], + "signature": [ + "\"succeeded\" | \"failed\" | \"enqueued\" | \"sent_to_shipper\" | \"dropped\"" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.TelemetryCounter.source", + "type": "string", + "tags": [], + "label": "source", + "description": [ + "\nWho emitted the event? It can be \"client\" or the name of the shipper." + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.TelemetryCounter.event_type", + "type": "string", + "tags": [], + "label": "event_type", + "description": [ + "\nThe event type the success/failure/drop event refers to." + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.TelemetryCounter.code", + "type": "string", + "tags": [], + "label": "code", + "description": [ + "\nCode to provide additional information about the success or failure. Examples are 200/400/504/ValidationError/UnknownError" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.TelemetryCounter.count", + "type": "number", + "tags": [], + "label": "count", + "description": [ + "\nThe number of events that this counter refers to." + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -57,6 +2888,74 @@ ], "enums": [], "misc": [ + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.AllowedSchemaBooleanTypes", + "type": "Type", + "tags": [], + "label": "AllowedSchemaBooleanTypes", + "description": [ + "Types matching boolean values" + ], + "signature": [ + "\"boolean\"" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.AllowedSchemaNumberTypes", + "type": "Type", + "tags": [], + "label": "AllowedSchemaNumberTypes", + "description": [ + "Types matching number values" + ], + "signature": [ + "\"date\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"double\"" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.AllowedSchemaStringTypes", + "type": "Type", + "tags": [], + "label": "AllowedSchemaStringTypes", + "description": [ + "Types matching string values" + ], + "signature": [ + "\"keyword\" | \"text\" | \"date\"" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.AllowedSchemaTypes", + "type": "Type", + "tags": [], + "label": "AllowedSchemaTypes", + "description": [ + "\nPossible type values in the schema" + ], + "signature": [ + "\"boolean\" | \"keyword\" | \"text\" | \"date\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"double\"" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-analytics-browser", "id": "def-common.AnalyticsServiceSetup", @@ -69,9 +2968,9 @@ "signature": [ "{ optIn: (optInConfig: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.OptInConfig", "text": "OptInConfig" }, @@ -79,49 +2978,49 @@ "Observable", "<", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.TelemetryCounter", "text": "TelemetryCounter" }, ">; registerEventType: (eventTypeOps: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.EventTypeOpts", "text": "EventTypeOpts" }, ") => void; registerShipper: (Shipper: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.ShipperClassConstructor", "text": "ShipperClassConstructor" }, ", shipperConfig: ShipperConfig, opts?: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.RegisterShipperOpts", "text": "RegisterShipperOpts" }, " | undefined) => void; registerContextProvider: (contextProviderOpts: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.ContextProviderOpts", "text": "ContextProviderOpts" }, @@ -144,9 +3043,9 @@ "signature": [ "{ optIn: (optInConfig: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.OptInConfig", "text": "OptInConfig" }, @@ -154,9 +3053,9 @@ "Observable", "<", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.TelemetryCounter", "text": "TelemetryCounter" }, @@ -166,6 +3065,237 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.ContextProviderName", + "type": "Type", + "tags": [], + "label": "ContextProviderName", + "description": [ + "\nContextProviderName used for indexed structures. Only used to improve the readability of the types" + ], + "signature": [ + "string" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.EventType", + "type": "Type", + "tags": [], + "label": "EventType", + "description": [ + "\nEvent Type used for indexed structures. Only used to improve the readability of the types" + ], + "signature": [ + "string" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.PossibleSchemaTypes", + "type": "Type", + "tags": [], + "label": "PossibleSchemaTypes", + "description": [ + "\nHelper to ensure the declared types match the schema types" + ], + "signature": [ + "Value extends string | Date ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AllowedSchemaStringTypes", + "text": "AllowedSchemaStringTypes" + }, + " : Value extends number ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AllowedSchemaNumberTypes", + "text": "AllowedSchemaNumberTypes" + }, + " : Value extends boolean ? \"boolean\" : ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AllowedSchemaTypes", + "text": "AllowedSchemaTypes" + } + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.RootSchema", + "type": "Type", + "tags": [], + "label": "RootSchema", + "description": [ + "\nSchema definition to match the structure of the properties provided.\n" + ], + "signature": [ + "{ [Key in keyof Required]: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaValue", + "text": "SchemaValue" + }, + "; }" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.SchemaMetaOptional", + "type": "Type", + "tags": [], + "label": "SchemaMetaOptional", + "description": [ + "\nEnforces { optional: true } if the value can be undefined" + ], + "signature": [ + "unknown extends Value ? { optional?: boolean | undefined; } : undefined extends Value ? { optional: true; } : { optional?: false | undefined; }" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.SchemaValue", + "type": "Type", + "tags": [], + "label": "SchemaValue", + "description": [ + "\nType that defines all the possible values that the Schema accepts.\nThese types definitions are helping to identify earlier the possible missing `properties` nesting when\nmanually defining the schemas." + ], + "signature": [ + "{ type: \"pass_through\"; _meta: { description: string; } & ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMetaOptional", + "text": "SchemaMetaOptional" + }, + "; } | (unknown extends Value ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaArray", + "text": "SchemaArray" + }, + " | ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaObject", + "text": "SchemaObject" + }, + " | ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + " : NonNullable extends (infer U)[] | readonly (infer U)[] ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaArray", + "text": "SchemaArray" + }, + " : NonNullable extends Date ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + " : NonNullable extends object ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaObject", + "text": "SchemaObject" + }, + " : ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + ")" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.ShipperName", + "type": "Type", + "tags": [], + "label": "ShipperName", + "description": [ + "\nShipper Name used for indexed structures. Only used to improve the readability of the types" + ], + "signature": [ + "string" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-browser", + "id": "def-common.TelemetryCounterType", + "type": "Type", + "tags": [], + "label": "TelemetryCounterType", + "description": [ + "\nIndicates if the event contains data about succeeded, failed or dropped events:\n- enqueued: The event was accepted and will be sent to the shippers when they become available (and opt-in === true).\n- sent_to_shipper: The event was sent to at least one shipper.\n- succeeded: The event was successfully sent by the shipper.\n- failed: There was an error when processing/shipping the event. Refer to the Telemetry Counter's code for the reason.\n- dropped: The event was dropped from the queue. Refer to the Telemetry Counter's code for the reason." + ], + "signature": [ + "\"succeeded\" | \"failed\" | \"enqueued\" | \"sent_to_shipper\" | \"dropped\"" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false } ], "objects": [] diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index ad08d7c5c45ce..c8ff98d0fd1d8 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 4 | 0 | 0 | 0 | +| 101 | 0 | 0 | 0 | ## Common diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 83f6e39da7b27..a8a42992594af 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 1d8948f01c6b4..f6911a5bcdb2b 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.devdocs.json b/api_docs/kbn_core_analytics_server.devdocs.json index 9f116d542418f..5c4760eb2c883 100644 --- a/api_docs/kbn_core_analytics_server.devdocs.json +++ b/api_docs/kbn_core_analytics_server.devdocs.json @@ -19,9 +19,2909 @@ "common": { "classes": [], "functions": [], - "interfaces": [], + "interfaces": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.AnalyticsClientInitContext", + "type": "Interface", + "tags": [], + "label": "AnalyticsClientInitContext", + "description": [ + "\nGeneral settings of the analytics client" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.AnalyticsClientInitContext.isDev", + "type": "boolean", + "tags": [], + "label": "isDev", + "description": [ + "\nBoolean indicating if it's running in developer mode." + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.AnalyticsClientInitContext.sendTo", + "type": "CompoundType", + "tags": [], + "label": "sendTo", + "description": [ + "\nSpecify if the shippers should send their data to the production or staging environments." + ], + "signature": [ + "\"production\" | \"staging\"" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.AnalyticsClientInitContext.logger", + "type": "Object", + "tags": [], + "label": "logger", + "description": [ + "\nApplication-provided logger." + ], + "signature": [ + { + "pluginId": "@kbn/logging", + "scope": "common", + "docId": "kibKbnLoggingPluginApi", + "section": "def-common.Logger", + "text": "Logger" + } + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.ContextProviderOpts", + "type": "Interface", + "tags": [], + "label": "ContextProviderOpts", + "description": [ + "\nDefinition of a context provider" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ContextProviderOpts", + "text": "ContextProviderOpts" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.ContextProviderOpts.name", + "type": "string", + "tags": [], + "label": "name", + "description": [ + "\nThe name of the provider." + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.ContextProviderOpts.context$", + "type": "Object", + "tags": [], + "label": "context$", + "description": [ + "\nObservable that emits the custom context." + ], + "signature": [ + "Observable", + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.ContextProviderOpts.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [ + "\nSchema declaring and documenting the expected output in the context$\n" + ], + "signature": [ + "{ [Key in keyof Required]: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaValue", + "text": "SchemaValue" + }, + "; }" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.Event", + "type": "Interface", + "tags": [], + "label": "Event", + "description": [ + "\nDefinition of the full event structure" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.Event", + "text": "Event" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.Event.timestamp", + "type": "string", + "tags": [], + "label": "timestamp", + "description": [ + "\nThe time the event was generated in ISO format." + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.Event.event_type", + "type": "string", + "tags": [], + "label": "event_type", + "description": [ + "\nThe event type." + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.Event.properties", + "type": "Uncategorized", + "tags": [], + "label": "properties", + "description": [ + "\nThe specific properties of the event type." + ], + "signature": [ + "Properties" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.Event.context", + "type": "Object", + "tags": [], + "label": "context", + "description": [ + "\nThe {@link EventContext} enriched during the processing pipeline." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventContext", + "text": "EventContext" + } + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.EventContext", + "type": "Interface", + "tags": [], + "label": "EventContext", + "description": [ + "\nDefinition of the context that can be appended to the events through the {@link IAnalyticsClient.registerContextProvider}." + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.EventContext.cluster_uuid", + "type": "string", + "tags": [], + "label": "cluster_uuid", + "description": [ + "\nThe UUID of the cluster" + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.EventContext.cluster_name", + "type": "string", + "tags": [], + "label": "cluster_name", + "description": [ + "\nThe name of the cluster." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.EventContext.license_id", + "type": "string", + "tags": [], + "label": "license_id", + "description": [ + "\nThe license ID." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.EventContext.userId", + "type": "string", + "tags": [], + "label": "userId", + "description": [ + "\nThe unique user ID." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.EventContext.cloudId", + "type": "string", + "tags": [], + "label": "cloudId", + "description": [ + "\nThe Cloud ID." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.EventContext.isElasticCloudUser", + "type": "CompoundType", + "tags": [], + "label": "isElasticCloudUser", + "description": [ + "\n`true` if the user is logged in via the Elastic Cloud authentication provider." + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.EventContext.version", + "type": "string", + "tags": [], + "label": "version", + "description": [ + "\nThe product's version." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.EventContext.pageName", + "type": "string", + "tags": [], + "label": "pageName", + "description": [ + "\nThe name of the current page." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.EventContext.applicationId", + "type": "string", + "tags": [], + "label": "applicationId", + "description": [ + "\nThe current application ID." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.EventContext.entityId", + "type": "string", + "tags": [], + "label": "entityId", + "description": [ + "\nThe current entity ID (dashboard ID, visualization ID, etc.)." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.EventContext.Unnamed", + "type": "IndexSignature", + "tags": [], + "label": "[key: string]: unknown", + "description": [ + "\nAdditional keys are allowed." + ], + "signature": [ + "[key: string]: unknown" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.EventTypeOpts", + "type": "Interface", + "tags": [], + "label": "EventTypeOpts", + "description": [ + "\nDefinition of an Event Type." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventTypeOpts", + "text": "EventTypeOpts" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.EventTypeOpts.eventType", + "type": "string", + "tags": [], + "label": "eventType", + "description": [ + "\nThe event type's unique name." + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.EventTypeOpts.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [ + "\nSchema declaring and documenting the expected structure of this event type.\n" + ], + "signature": [ + "{ [Key in keyof Required]: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaValue", + "text": "SchemaValue" + }, + "; }" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IAnalyticsClient", + "type": "Interface", + "tags": [], + "label": "IAnalyticsClient", + "description": [ + "\nAnalytics client's public APIs" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IAnalyticsClient.reportEvent", + "type": "Function", + "tags": [ + "track-adoption" + ], + "label": "reportEvent", + "description": [ + "\nReports a telemetry event." + ], + "signature": [ + "(eventType: string, eventData: EventTypeData) => void" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": true, + "references": [ + { + "plugin": "@kbn/core-notifications-browser-internal", + "path": "packages/core/notifications/core-notifications-browser-internal/src/toasts/telemetry/event_reporter.ts" + }, + { + "plugin": "@kbn/core-notifications-browser-internal", + "path": "packages/core/notifications/core-notifications-browser-internal/src/toasts/telemetry/event_reporter.ts" + }, + { + "plugin": "@kbn/ebt-tools", + "path": "packages/kbn-ebt-tools/src/performance_metric_events/helpers.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-root-server-internal", + "path": "packages/core/root/core-root-server-internal/src/events/kibana_started.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.ts" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "dashboard", + "path": "src/plugins/dashboard/public/services/analytics/types.ts" + }, + { + "plugin": "dashboard", + "path": "src/plugins/dashboard/public/services/analytics/analytics_service.ts" + }, + { + "plugin": "observabilityAIAssistant", + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/recall_and_score.ts" + }, + { + "plugin": "observabilityAIAssistant", + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/analytics/index.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" + }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/lib/langchain/elasticsearch_store/elasticsearch_store.ts" + }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/lib/langchain/elasticsearch_store/elasticsearch_store.ts" + }, + { + "plugin": "globalSearchBar", + "path": "x-pack/plugins/global_search_bar/public/telemetry/event_reporter.ts" + }, + { + "plugin": "globalSearchBar", + "path": "x-pack/plugins/global_search_bar/public/telemetry/event_reporter.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts" + }, + { + "plugin": "reporting", + "path": "x-pack/plugins/reporting/server/usage/event_tracker.ts" + }, + { + "plugin": "searchPlayground", + "path": "x-pack/plugins/search_playground/server/routes.ts" + }, + { + "plugin": "securitySolutionServerless", + "path": "x-pack/plugins/security_solution_serverless/server/task_manager/nlp_cleanup_task/nlp_cleanup_task.ts" + }, + { + "plugin": "securitySolutionServerless", + "path": "x-pack/plugins/security_solution_serverless/server/task_manager/nlp_cleanup_task/nlp_cleanup_task.ts" + }, + { + "plugin": "apm", + "path": "x-pack/plugins/observability_solution/apm/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "observabilityLogsExplorer", + "path": "x-pack/plugins/observability_solution/observability_logs_explorer/public/state_machines/observability_logs_explorer/src/telemetry_events.ts" + }, + { + "plugin": "observabilityOnboarding", + "path": "x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_flow_progress_telemetry.ts" + }, + { + "plugin": "observabilityOnboarding", + "path": "x-pack/plugins/observability_solution/observability_onboarding/public/application/app.tsx" + }, + { + "plugin": "observabilityAIAssistant", + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_chat_service.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-mocks", + "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-mocks", + "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "dashboard", + "path": "src/plugins/dashboard/public/services/analytics/analytics.stub.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-analytics-server-mocks", + "path": "packages/core/analytics/core-analytics-server-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "@kbn/core-analytics-server-mocks", + "path": "packages/core/analytics/core-analytics-server-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.test.ts" + }, + { + "plugin": "apm", + "path": "x-pack/plugins/observability_solution/apm/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "apm", + "path": "x-pack/plugins/observability_solution/apm/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.test.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.test.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.test.ts" + }, + { + "plugin": "@kbn/core-analytics-server-mocks", + "path": "packages/core/analytics/core-analytics-server-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/mocks.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.mocks.ts" + } + ], + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IAnalyticsClient.reportEvent.$1", + "type": "string", + "tags": [], + "label": "eventType", + "description": [ + "The event type registered via the `registerEventType` API." + ], + "signature": [ + "string" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IAnalyticsClient.reportEvent.$2", + "type": "Uncategorized", + "tags": [], + "label": "eventData", + "description": [ + "The properties matching the schema declared in the `registerEventType` API." + ], + "signature": [ + "EventTypeData" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IAnalyticsClient.registerEventType", + "type": "Function", + "tags": [], + "label": "registerEventType", + "description": [ + "\nRegisters the event type that will be emitted via the reportEvent API." + ], + "signature": [ + "(eventTypeOps: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventTypeOpts", + "text": "EventTypeOpts" + }, + ") => void" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IAnalyticsClient.registerEventType.$1", + "type": "Object", + "tags": [], + "label": "eventTypeOps", + "description": [ + "The definition of the event type {@link EventTypeOpts }." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventTypeOpts", + "text": "EventTypeOpts" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IAnalyticsClient.registerShipper", + "type": "Function", + "tags": [], + "label": "registerShipper", + "description": [ + "\nSet up the shipper that will be used to report the telemetry events." + ], + "signature": [ + "(Shipper: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ShipperClassConstructor", + "text": "ShipperClassConstructor" + }, + ", shipperConfig: ShipperConfig, opts?: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.RegisterShipperOpts", + "text": "RegisterShipperOpts" + }, + " | undefined) => void" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IAnalyticsClient.registerShipper.$1", + "type": "Object", + "tags": [], + "label": "Shipper", + "description": [ + "The {@link IShipper } class to instantiate the shipper." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ShipperClassConstructor", + "text": "ShipperClassConstructor" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IAnalyticsClient.registerShipper.$2", + "type": "Uncategorized", + "tags": [], + "label": "shipperConfig", + "description": [ + "The config specific to the Shipper to instantiate." + ], + "signature": [ + "ShipperConfig" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IAnalyticsClient.registerShipper.$3", + "type": "Object", + "tags": [], + "label": "opts", + "description": [ + "Additional options to register the shipper {@link RegisterShipperOpts }." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.RegisterShipperOpts", + "text": "RegisterShipperOpts" + }, + " | undefined" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IAnalyticsClient.optIn", + "type": "Function", + "tags": [], + "label": "optIn", + "description": [ + "\nUsed to control the user's consent to report the data.\nIn the advanced mode, it allows to \"cherry-pick\" which events and shippers are enabled/disabled." + ], + "signature": [ + "(optInConfig: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.OptInConfig", + "text": "OptInConfig" + }, + ") => void" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IAnalyticsClient.optIn.$1", + "type": "Object", + "tags": [], + "label": "optInConfig", + "description": [ + "{@link OptInConfig }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.OptInConfig", + "text": "OptInConfig" + } + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IAnalyticsClient.registerContextProvider", + "type": "Function", + "tags": [ + "track-adoption" + ], + "label": "registerContextProvider", + "description": [ + "\nRegisters the context provider to enrich any reported events." + ], + "signature": [ + "(contextProviderOpts: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ContextProviderOpts", + "text": "ContextProviderOpts" + }, + ") => void" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": true, + "references": [ + { + "plugin": "@kbn/core-execution-context-browser-internal", + "path": "packages/core/execution-context/core-execution-context-browser-internal/src/execution_context_service.ts" + }, + { + "plugin": "@kbn/core-application-browser-internal", + "path": "packages/core/application/core-application-browser-internal/src/register_analytics_context_provider.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-chrome-browser-internal", + "path": "packages/core/chrome/core-chrome-browser-internal/src/register_analytics_context_provider.ts" + }, + { + "plugin": "@kbn/core-elasticsearch-server-internal", + "path": "packages/core/elasticsearch/core-elasticsearch-server-internal/src/register_analytics_context_provider.ts" + }, + { + "plugin": "@kbn/core-environment-server-internal", + "path": "packages/core/environment/core-environment-server-internal/src/environment_service.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "cloud", + "path": "x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts" + }, + { + "plugin": "licensing", + "path": "x-pack/plugins/licensing/common/register_analytics_context_provider.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.ts" + }, + { + "plugin": "telemetry", + "path": "src/plugins/telemetry/server/plugin.ts" + }, + { + "plugin": "telemetry", + "path": "src/plugins/telemetry/public/plugin.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-mocks", + "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "@kbn/core-application-browser-internal", + "path": "packages/core/application/core-application-browser-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-application-browser-internal", + "path": "packages/core/application/core-application-browser-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-application-browser-internal", + "path": "packages/core/application/core-application-browser-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-chrome-browser-internal", + "path": "packages/core/chrome/core-chrome-browser-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-chrome-browser-internal", + "path": "packages/core/chrome/core-chrome-browser-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-chrome-browser-internal", + "path": "packages/core/chrome/core-chrome-browser-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-execution-context-browser-internal", + "path": "packages/core/execution-context/core-execution-context-browser-internal/src/execution_context_service.test.ts" + }, + { + "plugin": "@kbn/core-execution-context-browser-internal", + "path": "packages/core/execution-context/core-execution-context-browser-internal/src/execution_context_service.test.ts" + }, + { + "plugin": "@kbn/core-analytics-server-mocks", + "path": "packages/core/analytics/core-analytics-server-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "@kbn/core-analytics-server-mocks", + "path": "packages/core/analytics/core-analytics-server-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "@kbn/core-elasticsearch-server-internal", + "path": "packages/core/elasticsearch/core-elasticsearch-server-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.test.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.test.ts" + }, + { + "plugin": "@kbn/ebt", + "path": "packages/analytics/ebt/client/src/analytics_client/mocks.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.mocks.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.ts" + } + ], + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IAnalyticsClient.registerContextProvider.$1", + "type": "Object", + "tags": [], + "label": "contextProviderOpts", + "description": [ + "{@link ContextProviderOpts }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ContextProviderOpts", + "text": "ContextProviderOpts" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IAnalyticsClient.removeContextProvider", + "type": "Function", + "tags": [], + "label": "removeContextProvider", + "description": [ + "\nRemoves the context provider and stop enriching the events from its context." + ], + "signature": [ + "(contextProviderName: string) => void" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IAnalyticsClient.removeContextProvider.$1", + "type": "string", + "tags": [], + "label": "contextProviderName", + "description": [ + "The name of the context provider to remove." + ], + "signature": [ + "string" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IAnalyticsClient.telemetryCounter$", + "type": "Object", + "tags": [], + "label": "telemetryCounter$", + "description": [ + "\nObservable to emit the stats of the processed events." + ], + "signature": [ + "Observable", + "<", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.TelemetryCounter", + "text": "TelemetryCounter" + }, + ">" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IAnalyticsClient.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nForces all shippers to send all their enqueued events and fulfills the returned promise." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IAnalyticsClient.shutdown", + "type": "Function", + "tags": [], + "label": "shutdown", + "description": [ + "\nStops the client. Flushing any pending events in the process." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IShipper", + "type": "Interface", + "tags": [], + "label": "IShipper", + "description": [ + "\nBasic structure of a Shipper" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IShipper.reportEvents", + "type": "Function", + "tags": [], + "label": "reportEvents", + "description": [ + "\nAdapts and ships the event to the persisting/analytics solution." + ], + "signature": [ + "(events: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.Event", + "text": "Event" + }, + ">[]) => void" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IShipper.reportEvents.$1", + "type": "Array", + "tags": [], + "label": "events", + "description": [ + "batched events {@link Event }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.Event", + "text": "Event" + }, + ">[]" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IShipper.optIn", + "type": "Function", + "tags": [], + "label": "optIn", + "description": [ + "\nStops/restarts the shipping mechanism based on the value of isOptedIn" + ], + "signature": [ + "(isOptedIn: boolean) => void" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IShipper.optIn.$1", + "type": "boolean", + "tags": [], + "label": "isOptedIn", + "description": [ + "`true` for resume sending events. `false` to stop." + ], + "signature": [ + "boolean" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IShipper.extendContext", + "type": "Function", + "tags": [], + "label": "extendContext", + "description": [ + "\nPerform any necessary calls to the persisting/analytics solution to set the event's context." + ], + "signature": [ + "((newContext: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventContext", + "text": "EventContext" + }, + ") => void) | undefined" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IShipper.extendContext.$1", + "type": "Object", + "tags": [], + "label": "newContext", + "description": [ + "The full new context to set {@link EventContext }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventContext", + "text": "EventContext" + } + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IShipper.telemetryCounter$", + "type": "Object", + "tags": [], + "label": "telemetryCounter$", + "description": [ + "\nObservable to emit the stats of the processed events." + ], + "signature": [ + "Observable", + "<", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.TelemetryCounter", + "text": "TelemetryCounter" + }, + "> | undefined" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IShipper.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nSends all the enqueued events and fulfills the returned promise." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.IShipper.shutdown", + "type": "Function", + "tags": [], + "label": "shutdown", + "description": [ + "\nShutdown the shipper." + ], + "signature": [ + "() => void" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.OptInConfig", + "type": "Interface", + "tags": [], + "label": "OptInConfig", + "description": [ + "\nOptions for the optIn API" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.OptInConfig.global", + "type": "Object", + "tags": [], + "label": "global", + "description": [ + "\nControls the global enabled/disabled behaviour of the client and shippers." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.OptInConfigPerType", + "text": "OptInConfigPerType" + } + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.OptInConfig.event_types", + "type": "Object", + "tags": [], + "label": "event_types", + "description": [ + "\nControls if an event type should be disabled for a specific type of shipper." + ], + "signature": [ + "Record | undefined" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.OptInConfigPerType", + "type": "Interface", + "tags": [], + "label": "OptInConfigPerType", + "description": [ + "\nSets whether a type of event is enabled/disabled globally or per shipper." + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.OptInConfigPerType.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [ + "\nThe event type is globally enabled." + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.OptInConfigPerType.shippers", + "type": "Object", + "tags": [], + "label": "shippers", + "description": [ + "\nControls if an event type should be disabled for a specific type of shipper." + ], + "signature": [ + "Record | undefined" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.RegisterShipperOpts", + "type": "Interface", + "tags": [], + "label": "RegisterShipperOpts", + "description": [ + "\nOptional options to register a shipper" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.SchemaArray", + "type": "Interface", + "tags": [], + "label": "SchemaArray", + "description": [ + "\nSchema to represent an array" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaArray", + "text": "SchemaArray" + }, + " extends ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMeta", + "text": "SchemaMeta" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.SchemaArray.type", + "type": "string", + "tags": [], + "label": "type", + "description": [ + "The type must be an array" + ], + "signature": [ + "\"array\"" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.SchemaArray.items", + "type": "CompoundType", + "tags": [], + "label": "items", + "description": [ + "The schema of the items in the array is defined in the `items` property" + ], + "signature": [ + "{ type: \"pass_through\"; _meta: { description: string; } & ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMetaOptional", + "text": "SchemaMetaOptional" + }, + "; } | (unknown extends Value ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaArray", + "text": "SchemaArray" + }, + " | ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaObject", + "text": "SchemaObject" + }, + " | ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + " : NonNullable extends (infer U)[] | readonly (infer U)[] ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaArray", + "text": "SchemaArray" + }, + " : NonNullable extends Date ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + " : NonNullable extends object ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaObject", + "text": "SchemaObject" + }, + " : ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + ")" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.SchemaChildValue", + "type": "Interface", + "tags": [], + "label": "SchemaChildValue", + "description": [ + "\nSchema to define a primitive value" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.SchemaChildValue.type", + "type": "Uncategorized", + "tags": [], + "label": "type", + "description": [ + "The type of the value" + ], + "signature": [ + "NonNullable extends string | Date ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AllowedSchemaStringTypes", + "text": "AllowedSchemaStringTypes" + }, + " : NonNullable extends number ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AllowedSchemaNumberTypes", + "text": "AllowedSchemaNumberTypes" + }, + " : NonNullable extends boolean ? \"boolean\" : ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AllowedSchemaTypes", + "text": "AllowedSchemaTypes" + } + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.SchemaChildValue._meta", + "type": "CompoundType", + "tags": [], + "label": "_meta", + "description": [ + "Meta properties of the value: description and is optional" + ], + "signature": [ + "{ description: string; } & ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMetaOptional", + "text": "SchemaMetaOptional" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.SchemaMeta", + "type": "Interface", + "tags": [], + "label": "SchemaMeta", + "description": [ + "\nSchema meta with optional description" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMeta", + "text": "SchemaMeta" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.SchemaMeta._meta", + "type": "CompoundType", + "tags": [], + "label": "_meta", + "description": [ + "Meta properties of the pass through: description and is optional" + ], + "signature": [ + "({ description?: string | undefined; } & ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMetaOptional", + "text": "SchemaMetaOptional" + }, + ") | undefined" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.SchemaObject", + "type": "Interface", + "tags": [], + "label": "SchemaObject", + "description": [ + "\nSchema to represent an object" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaObject", + "text": "SchemaObject" + }, + " extends ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMeta", + "text": "SchemaMeta" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.SchemaObject.properties", + "type": "Object", + "tags": [], + "label": "properties", + "description": [ + "\nThe schemas of the keys of the object are defined in the `properties` object." + ], + "signature": [ + "{ [Key in keyof Required]: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaValue", + "text": "SchemaValue" + }, + "; }" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.ShipperClassConstructor", + "type": "Interface", + "tags": [], + "label": "ShipperClassConstructor", + "description": [ + "\nConstructor of a {@link IShipper}" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ShipperClassConstructor", + "text": "ShipperClassConstructor" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.ShipperClassConstructor.shipperName", + "type": "string", + "tags": [], + "label": "shipperName", + "description": [ + "\nThe shipper's unique name" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.ShipperClassConstructor.new", + "type": "Function", + "tags": [], + "label": "new", + "description": [ + "\nThe constructor" + ], + "signature": [ + "any" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.ShipperClassConstructor.new.$1", + "type": "Uncategorized", + "tags": [], + "label": "config", + "description": [ + "The shipper's custom config" + ], + "signature": [ + "Config" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.ShipperClassConstructor.new.$2", + "type": "Object", + "tags": [], + "label": "initContext", + "description": [ + "Common context {@link AnalyticsClientInitContext }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AnalyticsClientInitContext", + "text": "AnalyticsClientInitContext" + } + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.TelemetryCounter", + "type": "Interface", + "tags": [], + "label": "TelemetryCounter", + "description": [ + "\nShape of the events emitted by the telemetryCounter$ observable" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.TelemetryCounter.type", + "type": "CompoundType", + "tags": [], + "label": "type", + "description": [ + "\n{@link TelemetryCounterType}" + ], + "signature": [ + "\"succeeded\" | \"failed\" | \"enqueued\" | \"sent_to_shipper\" | \"dropped\"" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.TelemetryCounter.source", + "type": "string", + "tags": [], + "label": "source", + "description": [ + "\nWho emitted the event? It can be \"client\" or the name of the shipper." + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.TelemetryCounter.event_type", + "type": "string", + "tags": [], + "label": "event_type", + "description": [ + "\nThe event type the success/failure/drop event refers to." + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.TelemetryCounter.code", + "type": "string", + "tags": [], + "label": "code", + "description": [ + "\nCode to provide additional information about the success or failure. Examples are 200/400/504/ValidationError/UnknownError" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.TelemetryCounter.count", + "type": "number", + "tags": [], + "label": "count", + "description": [ + "\nThe number of events that this counter refers to." + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], "enums": [], "misc": [ + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.AllowedSchemaBooleanTypes", + "type": "Type", + "tags": [], + "label": "AllowedSchemaBooleanTypes", + "description": [ + "Types matching boolean values" + ], + "signature": [ + "\"boolean\"" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.AllowedSchemaNumberTypes", + "type": "Type", + "tags": [], + "label": "AllowedSchemaNumberTypes", + "description": [ + "Types matching number values" + ], + "signature": [ + "\"date\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"double\"" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.AllowedSchemaStringTypes", + "type": "Type", + "tags": [], + "label": "AllowedSchemaStringTypes", + "description": [ + "Types matching string values" + ], + "signature": [ + "\"keyword\" | \"text\" | \"date\"" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.AllowedSchemaTypes", + "type": "Type", + "tags": [], + "label": "AllowedSchemaTypes", + "description": [ + "\nPossible type values in the schema" + ], + "signature": [ + "\"boolean\" | \"keyword\" | \"text\" | \"date\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"double\"" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-analytics-server", "id": "def-common.AnalyticsServicePreboot", @@ -34,9 +2934,9 @@ "signature": [ "{ optIn: (optInConfig: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.OptInConfig", "text": "OptInConfig" }, @@ -44,49 +2944,49 @@ "Observable", "<", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.TelemetryCounter", "text": "TelemetryCounter" }, ">; registerEventType: (eventTypeOps: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.EventTypeOpts", "text": "EventTypeOpts" }, ") => void; registerShipper: (Shipper: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.ShipperClassConstructor", "text": "ShipperClassConstructor" }, ", shipperConfig: ShipperConfig, opts?: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.RegisterShipperOpts", "text": "RegisterShipperOpts" }, " | undefined) => void; registerContextProvider: (contextProviderOpts: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.ContextProviderOpts", "text": "ContextProviderOpts" }, @@ -109,9 +3009,9 @@ "signature": [ "{ optIn: (optInConfig: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.OptInConfig", "text": "OptInConfig" }, @@ -119,49 +3019,49 @@ "Observable", "<", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.TelemetryCounter", "text": "TelemetryCounter" }, ">; registerEventType: (eventTypeOps: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.EventTypeOpts", "text": "EventTypeOpts" }, ") => void; registerShipper: (Shipper: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.ShipperClassConstructor", "text": "ShipperClassConstructor" }, ", shipperConfig: ShipperConfig, opts?: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.RegisterShipperOpts", "text": "RegisterShipperOpts" }, " | undefined) => void; registerContextProvider: (contextProviderOpts: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.ContextProviderOpts", "text": "ContextProviderOpts" }, @@ -184,9 +3084,9 @@ "signature": [ "{ optIn: (optInConfig: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.OptInConfig", "text": "OptInConfig" }, @@ -194,9 +3094,9 @@ "Observable", "<", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.TelemetryCounter", "text": "TelemetryCounter" }, @@ -206,6 +3106,237 @@ "deprecated": false, "trackAdoption": false, "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.ContextProviderName", + "type": "Type", + "tags": [], + "label": "ContextProviderName", + "description": [ + "\nContextProviderName used for indexed structures. Only used to improve the readability of the types" + ], + "signature": [ + "string" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.EventType", + "type": "Type", + "tags": [], + "label": "EventType", + "description": [ + "\nEvent Type used for indexed structures. Only used to improve the readability of the types" + ], + "signature": [ + "string" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.PossibleSchemaTypes", + "type": "Type", + "tags": [], + "label": "PossibleSchemaTypes", + "description": [ + "\nHelper to ensure the declared types match the schema types" + ], + "signature": [ + "Value extends string | Date ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AllowedSchemaStringTypes", + "text": "AllowedSchemaStringTypes" + }, + " : Value extends number ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AllowedSchemaNumberTypes", + "text": "AllowedSchemaNumberTypes" + }, + " : Value extends boolean ? \"boolean\" : ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AllowedSchemaTypes", + "text": "AllowedSchemaTypes" + } + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.RootSchema", + "type": "Type", + "tags": [], + "label": "RootSchema", + "description": [ + "\nSchema definition to match the structure of the properties provided.\n" + ], + "signature": [ + "{ [Key in keyof Required]: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaValue", + "text": "SchemaValue" + }, + "; }" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.SchemaMetaOptional", + "type": "Type", + "tags": [], + "label": "SchemaMetaOptional", + "description": [ + "\nEnforces { optional: true } if the value can be undefined" + ], + "signature": [ + "unknown extends Value ? { optional?: boolean | undefined; } : undefined extends Value ? { optional: true; } : { optional?: false | undefined; }" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.SchemaValue", + "type": "Type", + "tags": [], + "label": "SchemaValue", + "description": [ + "\nType that defines all the possible values that the Schema accepts.\nThese types definitions are helping to identify earlier the possible missing `properties` nesting when\nmanually defining the schemas." + ], + "signature": [ + "{ type: \"pass_through\"; _meta: { description: string; } & ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMetaOptional", + "text": "SchemaMetaOptional" + }, + "; } | (unknown extends Value ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaArray", + "text": "SchemaArray" + }, + " | ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaObject", + "text": "SchemaObject" + }, + " | ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + " : NonNullable extends (infer U)[] | readonly (infer U)[] ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaArray", + "text": "SchemaArray" + }, + " : NonNullable extends Date ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + " : NonNullable extends object ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaObject", + "text": "SchemaObject" + }, + " : ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + ")" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.ShipperName", + "type": "Type", + "tags": [], + "label": "ShipperName", + "description": [ + "\nShipper Name used for indexed structures. Only used to improve the readability of the types" + ], + "signature": [ + "string" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/core-analytics-server", + "id": "def-common.TelemetryCounterType", + "type": "Type", + "tags": [], + "label": "TelemetryCounterType", + "description": [ + "\nIndicates if the event contains data about succeeded, failed or dropped events:\n- enqueued: The event was accepted and will be sent to the shippers when they become available (and opt-in === true).\n- sent_to_shipper: The event was sent to at least one shipper.\n- succeeded: The event was successfully sent by the shipper.\n- failed: There was an error when processing/shipping the event. Refer to the Telemetry Counter's code for the reason.\n- dropped: The event was dropped from the queue. Refer to the Telemetry Counter's code for the reason." + ], + "signature": [ + "\"succeeded\" | \"failed\" | \"enqueued\" | \"sent_to_shipper\" | \"dropped\"" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false } ], "objects": [] diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index 16bd39f611331..e90a091217ad8 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; @@ -21,10 +21,13 @@ Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3 | 0 | 0 | 0 | +| 100 | 0 | 0 | 0 | ## Common +### Interfaces + + ### Consts, variables and types diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index 95fc615398fe0..0e60a56e9cfce 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 e97a828be5e24..1bbdd8746f8f8 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 24a11c16e645a..6b76538ad3b79 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 c04d76d3d853d..d83bda2ad887d 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 189a644f5df26..34097db0fd1ae 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 cd0d158d35faa..459d3ad7f0a1d 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.devdocs.json b/api_docs/kbn_core_apps_browser_internal.devdocs.json index 4f9ba443d8311..d243920c1c1f9 100644 --- a/api_docs/kbn_core_apps_browser_internal.devdocs.json +++ b/api_docs/kbn_core_apps_browser_internal.devdocs.json @@ -380,9 +380,9 @@ "signature": [ "{ optIn: (optInConfig: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.OptInConfig", "text": "OptInConfig" }, @@ -390,9 +390,9 @@ "Observable", "<", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.TelemetryCounter", "text": "TelemetryCounter" }, diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index e6bb5b84839f1..deebf2082410e 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 8bae90ff760bf..92c43aa8d20a8 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 7713f2cf44405..e3bfe07de819c 100644 --- a/api_docs/kbn_core_apps_server_internal.mdx +++ b/api_docs/kbn_core_apps_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-server-internal title: "@kbn/core-apps-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 842e7ddecef59..83361db92ba26 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 9a7ceb7c3bd73..2d4abb8658498 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2024-06-11 +date: 2024-06-19 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 18f4f80c0f23a..5730e672d8684 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 b40ce269eb1c0..0093286db5fb8 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 41382d5e9139d..2c0c272df5213 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 5bc0163fbe934..c9d891961606c 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index decdb3841b987..51221490afe8c 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index 0b2ae56152c5d..2175735f414c9 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.devdocs.json b/api_docs/kbn_core_chrome_browser.devdocs.json index 0cc31110a2fb6..66c2b4398c08c 100644 --- a/api_docs/kbn_core_chrome_browser.devdocs.json +++ b/api_docs/kbn_core_chrome_browser.devdocs.json @@ -3716,7 +3716,7 @@ "label": "AppDeepLinkId", "description": [], "signature": [ - "\"fleet\" | \"graph\" | \"ml\" | \"monitoring\" | \"metrics\" | \"management\" | \"synthetics\" | \"ux\" | \"apm\" | \"logs\" | \"profiling\" | \"dashboards\" | \"observabilityAIAssistant\" | \"home\" | \"canvas\" | \"integrations\" | \"discover\" | \"observability-overview\" | \"appSearch\" | \"dev_tools\" | \"maps\" | \"visualize\" | \"dev_tools:console\" | \"dev_tools:searchprofiler\" | \"dev_tools:painless_lab\" | \"dev_tools:grokdebugger\" | \"ml:notifications\" | \"ml:nodes\" | \"ml:overview\" | \"ml:memoryUsage\" | \"ml:settings\" | \"ml:dataVisualizer\" | \"ml:anomalyDetection\" | \"ml:anomalyExplorer\" | \"ml:singleMetricViewer\" | \"ml:dataDrift\" | \"ml:dataFrameAnalytics\" | \"ml:resultExplorer\" | \"ml:analyticsMap\" | \"ml:aiOps\" | \"ml:logRateAnalysis\" | \"ml:logPatternAnalysis\" | \"ml:changePointDetections\" | \"ml:modelManagement\" | \"ml:nodesOverview\" | \"ml:esqlDataVisualizer\" | \"ml:fileUpload\" | \"ml:indexDataVisualizer\" | \"ml:calendarSettings\" | \"ml:filterListsSettings\" | \"osquery\" | \"management:transform\" | \"management:watcher\" | \"management:cases\" | \"management:tags\" | \"management:maintenanceWindows\" | \"management:settings\" | \"management:dataViews\" | \"management:spaces\" | \"management:users\" | \"management:migrate_data\" | \"management:search_sessions\" | \"management:data_quality\" | \"management:filesManagement\" | \"management:roles\" | \"management:reporting\" | \"management:aiAssistantManagementSelection\" | \"management:securityAiAssistantManagement\" | \"management:observabilityAiAssistantManagement\" | \"management:api_keys\" | \"management:cross_cluster_replication\" | \"management:license_management\" | \"management:index_lifecycle_management\" | \"management:index_management\" | \"management:ingest_pipelines\" | \"management:jobsListLink\" | \"management:objects\" | \"management:pipelines\" | \"management:remote_clusters\" | \"management:role_mappings\" | \"management:rollup_jobs\" | \"management:snapshot_restore\" | \"management:triggersActions\" | \"management:triggersActionsConnectors\" | \"management:upgrade_assistant\" | \"enterpriseSearch\" | \"enterpriseSearchContent\" | \"enterpriseSearchApplications\" | \"enterpriseSearchAnalytics\" | \"workplaceSearch\" | \"serverlessElasticsearch\" | \"serverlessConnectors\" | \"searchPlayground\" | \"enterpriseSearchContent:connectors\" | \"enterpriseSearchContent:searchIndices\" | \"enterpriseSearchContent:webCrawlers\" | \"enterpriseSearchApplications:searchApplications\" | \"enterpriseSearchApplications:playground\" | \"appSearch:engines\" | \"observability-logs-explorer\" | \"observabilityOnboarding\" | \"slo\" | \"logs:settings\" | \"logs:stream\" | \"logs:log-categories\" | \"logs:anomalies\" | \"observability-overview:cases\" | \"observability-overview:alerts\" | \"observability-overview:rules\" | \"observability-overview:cases_create\" | \"observability-overview:cases_configure\" | \"metrics:settings\" | \"metrics:hosts\" | \"metrics:inventory\" | \"metrics:metrics-explorer\" | \"metrics:assetDetails\" | \"apm:traces\" | \"apm:dependencies\" | \"apm:service-map\" | \"apm:settings\" | \"apm:services\" | \"apm:service-groups-list\" | \"apm:storage-explorer\" | \"synthetics:overview\" | \"synthetics:certificates\" | \"profiling:stacktraces\" | \"profiling:flamegraphs\" | \"profiling:functions\" | \"securitySolutionUI\" | \"securitySolutionUI:\" | \"securitySolutionUI:cases\" | \"securitySolutionUI:alerts\" | \"securitySolutionUI:rules\" | \"securitySolutionUI:policy\" | \"securitySolutionUI:overview\" | \"securitySolutionUI:dashboards\" | \"securitySolutionUI:cases_create\" | \"securitySolutionUI:cases_configure\" | \"securitySolutionUI:hosts\" | \"securitySolutionUI:users\" | \"securitySolutionUI:cloud_defend-policies\" | \"securitySolutionUI:cloud_security_posture-dashboard\" | \"securitySolutionUI:cloud_security_posture-findings\" | \"securitySolutionUI:cloud_security_posture-benchmarks\" | \"securitySolutionUI:kubernetes\" | \"securitySolutionUI:network\" | \"securitySolutionUI:data_quality\" | \"securitySolutionUI:explore\" | \"securitySolutionUI:assets\" | \"securitySolutionUI:cloud_defend\" | \"securitySolutionUI:administration\" | \"securitySolutionUI:attack_discovery\" | \"securitySolutionUI:blocklist\" | \"securitySolutionUI:cloud_security_posture-rules\" | \"securitySolutionUI:detections\" | \"securitySolutionUI:detection_response\" | \"securitySolutionUI:endpoints\" | \"securitySolutionUI:event_filters\" | \"securitySolutionUI:exceptions\" | \"securitySolutionUI:host_isolation_exceptions\" | \"securitySolutionUI:hosts-all\" | \"securitySolutionUI:hosts-anomalies\" | \"securitySolutionUI:hosts-risk\" | \"securitySolutionUI:hosts-events\" | \"securitySolutionUI:hosts-sessions\" | \"securitySolutionUI:hosts-uncommon_processes\" | \"securitySolutionUI:investigations\" | \"securitySolutionUI:get_started\" | \"securitySolutionUI:machine_learning-landing\" | \"securitySolutionUI:network-anomalies\" | \"securitySolutionUI:network-dns\" | \"securitySolutionUI:network-events\" | \"securitySolutionUI:network-flows\" | \"securitySolutionUI:network-http\" | \"securitySolutionUI:network-tls\" | \"securitySolutionUI:response_actions_history\" | \"securitySolutionUI:rules-add\" | \"securitySolutionUI:rules-create\" | \"securitySolutionUI:rules-landing\" | \"securitySolutionUI:threat_intelligence\" | \"securitySolutionUI:timelines\" | \"securitySolutionUI:timelines-templates\" | \"securitySolutionUI:trusted_apps\" | \"securitySolutionUI:users-all\" | \"securitySolutionUI:users-anomalies\" | \"securitySolutionUI:users-authentications\" | \"securitySolutionUI:users-events\" | \"securitySolutionUI:users-risk\" | \"securitySolutionUI:entity_analytics\" | \"securitySolutionUI:entity_analytics-management\" | \"securitySolutionUI:entity_analytics-asset-classification\" | \"securitySolutionUI:coverage-overview\" | \"fleet:settings\" | \"fleet:policies\" | \"fleet:data_streams\" | \"fleet:enrollment_tokens\" | \"fleet:uninstall_tokens\" | \"fleet:agents\"" + "\"fleet\" | \"graph\" | \"ml\" | \"monitoring\" | \"metrics\" | \"management\" | \"synthetics\" | \"ux\" | \"apm\" | \"logs\" | \"profiling\" | \"dashboards\" | \"observabilityAIAssistant\" | \"home\" | \"canvas\" | \"integrations\" | \"discover\" | \"observability-overview\" | \"appSearch\" | \"dev_tools\" | \"maps\" | \"visualize\" | \"dev_tools:console\" | \"dev_tools:searchprofiler\" | \"dev_tools:painless_lab\" | \"dev_tools:grokdebugger\" | \"ml:notifications\" | \"ml:nodes\" | \"ml:overview\" | \"ml:memoryUsage\" | \"ml:settings\" | \"ml:dataVisualizer\" | \"ml:anomalyDetection\" | \"ml:anomalyExplorer\" | \"ml:singleMetricViewer\" | \"ml:dataDrift\" | \"ml:dataFrameAnalytics\" | \"ml:resultExplorer\" | \"ml:analyticsMap\" | \"ml:aiOps\" | \"ml:logRateAnalysis\" | \"ml:logPatternAnalysis\" | \"ml:changePointDetections\" | \"ml:modelManagement\" | \"ml:nodesOverview\" | \"ml:esqlDataVisualizer\" | \"ml:fileUpload\" | \"ml:indexDataVisualizer\" | \"ml:calendarSettings\" | \"ml:filterListsSettings\" | \"osquery\" | \"management:transform\" | \"management:watcher\" | \"management:cases\" | \"management:tags\" | \"management:maintenanceWindows\" | \"management:settings\" | \"management:dataViews\" | \"management:spaces\" | \"management:users\" | \"management:migrate_data\" | \"management:search_sessions\" | \"management:data_quality\" | \"management:filesManagement\" | \"management:roles\" | \"management:reporting\" | \"management:aiAssistantManagementSelection\" | \"management:securityAiAssistantManagement\" | \"management:observabilityAiAssistantManagement\" | \"management:api_keys\" | \"management:cross_cluster_replication\" | \"management:license_management\" | \"management:index_lifecycle_management\" | \"management:index_management\" | \"management:ingest_pipelines\" | \"management:jobsListLink\" | \"management:objects\" | \"management:pipelines\" | \"management:remote_clusters\" | \"management:role_mappings\" | \"management:rollup_jobs\" | \"management:snapshot_restore\" | \"management:triggersActions\" | \"management:triggersActionsConnectors\" | \"management:upgrade_assistant\" | \"enterpriseSearch\" | \"enterpriseSearchContent\" | \"enterpriseSearchApplications\" | \"enterpriseSearchAnalytics\" | \"workplaceSearch\" | \"serverlessElasticsearch\" | \"serverlessConnectors\" | \"searchPlayground\" | \"searchInferenceEndpoints\" | \"enterpriseSearchContent:connectors\" | \"enterpriseSearchContent:searchIndices\" | \"enterpriseSearchContent:webCrawlers\" | \"enterpriseSearchApplications:searchApplications\" | \"enterpriseSearchApplications:playground\" | \"appSearch:engines\" | \"observability-logs-explorer\" | \"observabilityOnboarding\" | \"slo\" | \"logs:settings\" | \"logs:stream\" | \"logs:log-categories\" | \"logs:anomalies\" | \"observability-overview:cases\" | \"observability-overview:alerts\" | \"observability-overview:rules\" | \"observability-overview:cases_create\" | \"observability-overview:cases_configure\" | \"metrics:settings\" | \"metrics:hosts\" | \"metrics:inventory\" | \"metrics:metrics-explorer\" | \"metrics:assetDetails\" | \"apm:traces\" | \"apm:dependencies\" | \"apm:service-map\" | \"apm:settings\" | \"apm:services\" | \"apm:service-groups-list\" | \"apm:storage-explorer\" | \"synthetics:overview\" | \"synthetics:certificates\" | \"profiling:stacktraces\" | \"profiling:flamegraphs\" | \"profiling:functions\" | \"securitySolutionUI\" | \"securitySolutionUI:\" | \"securitySolutionUI:cases\" | \"securitySolutionUI:alerts\" | \"securitySolutionUI:rules\" | \"securitySolutionUI:policy\" | \"securitySolutionUI:overview\" | \"securitySolutionUI:dashboards\" | \"securitySolutionUI:cases_create\" | \"securitySolutionUI:cases_configure\" | \"securitySolutionUI:hosts\" | \"securitySolutionUI:users\" | \"securitySolutionUI:cloud_defend-policies\" | \"securitySolutionUI:cloud_security_posture-dashboard\" | \"securitySolutionUI:cloud_security_posture-findings\" | \"securitySolutionUI:cloud_security_posture-benchmarks\" | \"securitySolutionUI:kubernetes\" | \"securitySolutionUI:network\" | \"securitySolutionUI:data_quality\" | \"securitySolutionUI:explore\" | \"securitySolutionUI:assets\" | \"securitySolutionUI:cloud_defend\" | \"securitySolutionUI:administration\" | \"securitySolutionUI:attack_discovery\" | \"securitySolutionUI:blocklist\" | \"securitySolutionUI:cloud_security_posture-rules\" | \"securitySolutionUI:detections\" | \"securitySolutionUI:detection_response\" | \"securitySolutionUI:endpoints\" | \"securitySolutionUI:event_filters\" | \"securitySolutionUI:exceptions\" | \"securitySolutionUI:host_isolation_exceptions\" | \"securitySolutionUI:hosts-all\" | \"securitySolutionUI:hosts-anomalies\" | \"securitySolutionUI:hosts-risk\" | \"securitySolutionUI:hosts-events\" | \"securitySolutionUI:hosts-sessions\" | \"securitySolutionUI:hosts-uncommon_processes\" | \"securitySolutionUI:investigations\" | \"securitySolutionUI:get_started\" | \"securitySolutionUI:machine_learning-landing\" | \"securitySolutionUI:network-anomalies\" | \"securitySolutionUI:network-dns\" | \"securitySolutionUI:network-events\" | \"securitySolutionUI:network-flows\" | \"securitySolutionUI:network-http\" | \"securitySolutionUI:network-tls\" | \"securitySolutionUI:response_actions_history\" | \"securitySolutionUI:rules-add\" | \"securitySolutionUI:rules-create\" | \"securitySolutionUI:rules-landing\" | \"securitySolutionUI:threat_intelligence\" | \"securitySolutionUI:timelines\" | \"securitySolutionUI:timelines-templates\" | \"securitySolutionUI:trusted_apps\" | \"securitySolutionUI:users-all\" | \"securitySolutionUI:users-anomalies\" | \"securitySolutionUI:users-authentications\" | \"securitySolutionUI:users-events\" | \"securitySolutionUI:users-risk\" | \"securitySolutionUI:entity_analytics\" | \"securitySolutionUI:entity_analytics-management\" | \"securitySolutionUI:entity_analytics-asset-classification\" | \"securitySolutionUI:coverage-overview\" | \"securitySolutionUI:notes-management\" | \"fleet:settings\" | \"fleet:policies\" | \"fleet:data_streams\" | \"fleet:enrollment_tokens\" | \"fleet:uninstall_tokens\" | \"fleet:agents\"" ], "path": "packages/core/chrome/core-chrome-browser/src/project_navigation.ts", "deprecated": false, diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index da428d381c926..71105415fb4ca 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 20703a2e24718..df950e030dec0 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 2759e2062f430..9eec1a1692c15 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 3452cdea43a6c..2e6eebc20e1ab 100644 --- a/api_docs/kbn_core_custom_branding_browser.mdx +++ b/api_docs/kbn_core_custom_branding_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser title: "@kbn/core-custom-branding-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 9e8fbf8f3f61c..c499b048c9344 100644 --- a/api_docs/kbn_core_custom_branding_browser_internal.mdx +++ b/api_docs/kbn_core_custom_branding_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-internal title: "@kbn/core-custom-branding-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 fcc2d3699c1dd..df9652c4f6d7e 100644 --- a/api_docs/kbn_core_custom_branding_browser_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-mocks title: "@kbn/core-custom-branding-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 f5a6e7f8fd57c..9ce3fd7b27906 100644 --- a/api_docs/kbn_core_custom_branding_common.mdx +++ b/api_docs/kbn_core_custom_branding_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-common title: "@kbn/core-custom-branding-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-common plugin -date: 2024-06-11 +date: 2024-06-19 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 7cd1141887a39..23632cdbbd9ac 100644 --- a/api_docs/kbn_core_custom_branding_server.mdx +++ b/api_docs/kbn_core_custom_branding_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server title: "@kbn/core-custom-branding-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server plugin -date: 2024-06-11 +date: 2024-06-19 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 50bf85b937a19..d23c897cecffa 100644 --- a/api_docs/kbn_core_custom_branding_server_internal.mdx +++ b/api_docs/kbn_core_custom_branding_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-internal title: "@kbn/core-custom-branding-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 4b1d3c77f846a..6f258fcfc8b48 100644 --- a/api_docs/kbn_core_custom_branding_server_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-mocks title: "@kbn/core-custom-branding-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 46e2c7fe8d6eb..90daf5fa734f3 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 068508e347d51..f657bd0dc847e 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 d36fa2217e875..e5f76f49825dd 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 9d3c68d81e0f2..9a20d9cb1f7fe 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2024-06-11 +date: 2024-06-19 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 d4dbf3c4dea2a..92b19b1819557 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2024-06-11 +date: 2024-06-19 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 243b0204be9d5..d5df05b865157 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 933a2e013ae8f..78e9a91330219 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 fa2d3216d7e53..8336c45fbe0c2 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 502f6fcf26f27..cda2ac5294b20 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 720a4d1c3fa84..612ce5f4084cb 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2024-06-11 +date: 2024-06-19 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 b8c9ed1ee85cf..a65bfc25d7e6e 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 753420594fdd1..674666ba6ebd2 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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.devdocs.json b/api_docs/kbn_core_elasticsearch_client_server_mocks.devdocs.json index 28d341d687694..bbbd204ac49d6 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.devdocs.json +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.devdocs.json @@ -3689,6 +3689,1133 @@ "path": "packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/mocks.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-elasticsearch-client-server-mocks", + "id": "def-common.ScopedClusterClientMock.asSecondaryAuthUser", + "type": "CompoundType", + "tags": [], + "label": "asSecondaryAuthUser", + "description": [], + "signature": [ + "{ create: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; update: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ">, [params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; get: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ">, [params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; delete: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; helpers: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; search: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ">, [params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined]>; name: string | symbol; [kAsyncSearch]: symbol | null; [kAutoscaling]: symbol | null; [kCat]: symbol | null; [kCcr]: symbol | null; [kCluster]: symbol | null; [kDanglingIndices]: symbol | null; [kEnrich]: symbol | null; [kEql]: symbol | null; [kEsql]: symbol | null; [kFeatures]: symbol | null; [kFleet]: symbol | null; [kGraph]: symbol | null; [kIlm]: symbol | null; [kIndices]: symbol | null; [kInference]: symbol | null; [kIngest]: symbol | null; [kLicense]: symbol | null; [kLogstash]: symbol | null; [kMigration]: symbol | null; [kMl]: symbol | null; [kMonitoring]: symbol | null; [kNodes]: symbol | null; [kQueryRuleset]: symbol | null; [kRollup]: symbol | null; [kSearchApplication]: symbol | null; [kSearchableSnapshots]: symbol | null; [kSecurity]: symbol | null; [kShutdown]: symbol | null; [kSlm]: symbol | null; [kSnapshot]: symbol | null; [kSql]: symbol | null; [kSsl]: symbol | null; [kSynonyms]: symbol | null; [kTasks]: symbol | null; [kTextStructure]: symbol | null; [kTransform]: symbol | null; [kWatcher]: symbol | null; [kXpack]: symbol | null; transport: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; child: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + "<", + "default", + ", [opts: ", + "ClientOptions", + "]>; asyncSearch: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; autoscaling: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; bulk: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; cat: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; ccr: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; clearScroll: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined]>; closePointInTime: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; cluster: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; count: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined]>; danglingIndices: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; deleteByQuery: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; deleteByQueryRethrottle: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; deleteScript: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; enrich: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; eql: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; esql: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; exists: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; existsSource: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; explain: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ">, [params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; features: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; fieldCaps: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params?: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined]>; fleet: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; getScript: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; getScriptContext: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined]>; getScriptLanguages: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined]>; getSource: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; graph: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; healthReport: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params?: ", + "HealthReportRequest", + " | ", + "HealthReportRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined]>; ilm: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; index: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; indices: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; inference: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; info: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined]>; ingest: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; knnSearch: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ">, [params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; license: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; logstash: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; mget: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ">, [params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined]>; migration: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; ml: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; monitoring: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; msearch: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ">, [params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; msearchTemplate: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ">, [params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; mtermvectors: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined]>; nodes: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; openPointInTime: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; ping: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined]>; putScript: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; queryRuleset: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; rankEval: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; reindex: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; reindexRethrottle: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; renderSearchTemplate: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined]>; rollup: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; scriptsPainlessExecute: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ">, [params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined]>; scroll: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ">, [params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; searchApplication: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; searchMvt: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; searchShards: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined]>; searchTemplate: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ">, [params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined]>; searchableSnapshots: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; security: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; shutdown: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; slm: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; snapshot: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; sql: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; ssl: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; synonyms: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; tasks: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; termsEnum: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; termvectors: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; textStructure: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; transform: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; updateByQuery: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; updateByQueryRethrottle: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.ClientApiMockInstance", + "text": "ClientApiMockInstance" + }, + ", [params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined]>; watcher: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; xpack: ", + { + "pluginId": "@kbn/core-elasticsearch-client-server-mocks", + "scope": "common", + "docId": "kibKbnCoreElasticsearchClientServerMocksPluginApi", + "section": "def-common.DeeplyMockedApi", + "text": "DeeplyMockedApi" + }, + "<", + "default", + ">; } & ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + } + ], + "path": "packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/mocks.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index 53e330287ed09..7904772ed2ae2 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.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 | |-------------------|-----------|------------------------|-----------------| -| 37 | 1 | 33 | 0 | +| 38 | 1 | 34 | 0 | ## Common diff --git a/api_docs/kbn_core_elasticsearch_server.devdocs.json b/api_docs/kbn_core_elasticsearch_server.devdocs.json index 7bd196a627743..7f68422b77181 100644 --- a/api_docs/kbn_core_elasticsearch_server.devdocs.json +++ b/api_docs/kbn_core_elasticsearch_server.devdocs.json @@ -377,11 +377,14 @@ }, { "parentPluginId": "@kbn/core-elasticsearch-server", - "id": "def-common.ElasticsearchClientConfig.dnsCacheTtlInSeconds", - "type": "number", + "id": "def-common.ElasticsearchClientConfig.dnsCacheTtl", + "type": "Object", "tags": [], - "label": "dnsCacheTtlInSeconds", + "label": "dnsCacheTtl", "description": [], + "signature": [ + "moment.Duration" + ], "path": "packages/core/elasticsearch/core-elasticsearch-server/src/client/client_config.ts", "deprecated": false, "trackAdoption": false @@ -2773,12 +2776,15 @@ }, { "parentPluginId": "@kbn/core-elasticsearch-server", - "id": "def-common.IElasticsearchConfig.dnsCacheTtlInSeconds", - "type": "number", + "id": "def-common.IElasticsearchConfig.dnsCacheTtl", + "type": "Object", "tags": [], - "label": "dnsCacheTtlInSeconds", + "label": "dnsCacheTtl", "description": [ - "\nThe maximum number of seconds to retain the DNS lookup resolutions.\nSet to 0 to disable the cache (default Node.js behavior)" + "\nThe maximum time to retain the DNS lookup resolutions.\nSet to 0 to disable the cache (default Node.js behavior)" + ], + "signature": [ + "moment.Duration" ], "path": "packages/core/elasticsearch/core-elasticsearch-server/src/elasticsearch_config.ts", "deprecated": false, @@ -4038,6 +4044,1244 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "@kbn/core-elasticsearch-server", + "id": "def-common.IScopedClusterClient.asSecondaryAuthUser", + "type": "Object", + "tags": [], + "label": "asSecondaryAuthUser", + "description": [ + "\nA {@link ElasticsearchClient | client} to be used to query the elasticsearch cluster\nwith the internal Kibana user as primary auth and the current user as secondary auth\n(using the `es-secondary-authorization` header).\n\nNote that only a subset of Elasticsearch APIs support secondary authentication, and that only those endpoints\nshould be called with this client." + ], + "signature": [ + "{ create: { (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; update: { (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateResponse", + ">; (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateResponse", + ", unknown>>; (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateResponse", + ">; }; get: { (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetResponse", + ">; (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetResponse", + ", unknown>>; (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetResponse", + ">; }; delete: { (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; helpers: ", + "default", + "; search: { >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchResponse", + ">; >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchResponse", + ", unknown>>; >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchResponse", + ">; }; name: string | symbol; [kAsyncSearch]: symbol | null; [kAutoscaling]: symbol | null; [kCat]: symbol | null; [kCcr]: symbol | null; [kCluster]: symbol | null; [kDanglingIndices]: symbol | null; [kEnrich]: symbol | null; [kEql]: symbol | null; [kEsql]: symbol | null; [kFeatures]: symbol | null; [kFleet]: symbol | null; [kGraph]: symbol | null; [kIlm]: symbol | null; [kIndices]: symbol | null; [kInference]: symbol | null; [kIngest]: symbol | null; [kLicense]: symbol | null; [kLogstash]: symbol | null; [kMigration]: symbol | null; [kMl]: symbol | null; [kMonitoring]: symbol | null; [kNodes]: symbol | null; [kQueryRuleset]: symbol | null; [kRollup]: symbol | null; [kSearchApplication]: symbol | null; [kSearchableSnapshots]: symbol | null; [kSecurity]: symbol | null; [kShutdown]: symbol | null; [kSlm]: symbol | null; [kSnapshot]: symbol | null; [kSql]: symbol | null; [kSsl]: symbol | null; [kSynonyms]: symbol | null; [kTasks]: symbol | null; [kTextStructure]: symbol | null; [kTransform]: symbol | null; [kWatcher]: symbol | null; [kXpack]: symbol | null; transport: ", + "default", + "; child: (opts: ", + "ClientOptions", + ") => ", + "default", + "; asyncSearch: ", + "default", + "; autoscaling: ", + "default", + "; bulk: { (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "BulkResponse", + ">; (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "BulkResponse", + ", unknown>>; (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "BulkResponse", + ">; }; cat: ", + "default", + "; ccr: ", + "default", + "; clearScroll: { (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ClearScrollResponse", + ">; (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ClearScrollResponse", + ", unknown>>; (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ClearScrollResponse", + ">; }; closePointInTime: { (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ClosePointInTimeResponse", + ">; (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ClosePointInTimeResponse", + ", unknown>>; (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ClosePointInTimeResponse", + ">; }; cluster: ", + "default", + "; count: { (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "CountResponse", + ">; (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "CountResponse", + ", unknown>>; (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "CountResponse", + ">; }; danglingIndices: ", + "default", + "; deleteByQuery: { (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "DeleteByQueryResponse", + ">; (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "DeleteByQueryResponse", + ", unknown>>; (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "DeleteByQueryResponse", + ">; }; deleteByQueryRethrottle: { (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TasksTaskListResponseBase", + ">; (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TasksTaskListResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TasksTaskListResponseBase", + ">; }; deleteScript: { (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "AcknowledgedResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; }; enrich: ", + "default", + "; eql: ", + "default", + "; esql: ", + "default", + "; exists: { (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; existsSource: { (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; explain: { (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ExplainResponse", + ">; (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ExplainResponse", + ", unknown>>; (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ExplainResponse", + ">; }; features: ", + "default", + "; fieldCaps: { (this: That, params?: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "FieldCapsResponse", + ">; (this: That, params?: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "FieldCapsResponse", + ", unknown>>; (this: That, params?: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "FieldCapsResponse", + ">; }; fleet: ", + "default", + "; getScript: { (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptResponse", + ">; (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptResponse", + ", unknown>>; (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptResponse", + ">; }; getScriptContext: { (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptContextResponse", + ">; (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptContextResponse", + ", unknown>>; (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptContextResponse", + ">; }; getScriptLanguages: { (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptLanguagesResponse", + ">; (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptLanguagesResponse", + ", unknown>>; (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptLanguagesResponse", + ">; }; getSource: { (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; graph: ", + "default", + "; healthReport: { (this: That, params?: ", + "HealthReportRequest", + " | ", + "HealthReportRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "HealthReportResponse", + ">; (this: That, params?: ", + "HealthReportRequest", + " | ", + "HealthReportRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "HealthReportResponse", + ", unknown>>; (this: That, params?: ", + "HealthReportRequest", + " | ", + "HealthReportRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "HealthReportResponse", + ">; }; ilm: ", + "default", + "; index: { (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; indices: ", + "default", + "; inference: ", + "default", + "; info: { (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "InfoResponse", + ">; (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "InfoResponse", + ", unknown>>; (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "InfoResponse", + ">; }; ingest: ", + "default", + "; knnSearch: { (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "KnnSearchResponse", + ">; (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "KnnSearchResponse", + ", unknown>>; (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "KnnSearchResponse", + ">; }; license: ", + "default", + "; logstash: ", + "default", + "; mget: { (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MgetResponse", + ">; (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MgetResponse", + ", unknown>>; (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MgetResponse", + ">; }; migration: ", + "default", + "; ml: ", + "default", + "; monitoring: ", + "default", + "; msearch: { >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MsearchResponse", + ">; >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MsearchResponse", + ", unknown>>; >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MsearchResponse", + ">; }; msearchTemplate: { >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MsearchTemplateResponse", + ">; >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MsearchTemplateResponse", + ", unknown>>; >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MsearchTemplateResponse", + ">; }; mtermvectors: { (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MtermvectorsResponse", + ">; (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MtermvectorsResponse", + ", unknown>>; (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MtermvectorsResponse", + ">; }; nodes: ", + "default", + "; openPointInTime: { (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "OpenPointInTimeResponse", + ">; (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "OpenPointInTimeResponse", + ", unknown>>; (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "OpenPointInTimeResponse", + ">; }; ping: { (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; putScript: { (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "AcknowledgedResponseBase", + ", unknown>>; (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; }; queryRuleset: ", + "default", + "; rankEval: { (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "RankEvalResponse", + ">; (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "RankEvalResponse", + ", unknown>>; (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "RankEvalResponse", + ">; }; reindex: { (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ReindexResponse", + ">; (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ReindexResponse", + ", unknown>>; (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ReindexResponse", + ">; }; reindexRethrottle: { (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ReindexRethrottleResponse", + ">; (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ReindexRethrottleResponse", + ", unknown>>; (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ReindexRethrottleResponse", + ">; }; renderSearchTemplate: { (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "RenderSearchTemplateResponse", + ">; (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "RenderSearchTemplateResponse", + ", unknown>>; (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "RenderSearchTemplateResponse", + ">; }; rollup: ", + "default", + "; scriptsPainlessExecute: { (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ScriptsPainlessExecuteResponse", + ">; (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ScriptsPainlessExecuteResponse", + ", unknown>>; (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ScriptsPainlessExecuteResponse", + ">; }; scroll: { >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ScrollResponse", + ">; >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ScrollResponse", + ", unknown>>; >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ScrollResponse", + ">; }; searchApplication: ", + "default", + "; searchMvt: { (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; searchShards: { (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchShardsResponse", + ">; (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchShardsResponse", + ", unknown>>; (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchShardsResponse", + ">; }; searchTemplate: { (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchTemplateResponse", + ">; (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchTemplateResponse", + ", unknown>>; (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchTemplateResponse", + ">; }; searchableSnapshots: ", + "default", + "; security: ", + "default", + "; shutdown: ", + "default", + "; slm: ", + "default", + "; snapshot: ", + "default", + "; sql: ", + "default", + "; ssl: ", + "default", + "; synonyms: ", + "default", + "; tasks: ", + "default", + "; termsEnum: { (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TermsEnumResponse", + ">; (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TermsEnumResponse", + ", unknown>>; (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TermsEnumResponse", + ">; }; termvectors: { (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TermvectorsResponse", + ">; (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TermvectorsResponse", + ", unknown>>; (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TermvectorsResponse", + ">; }; textStructure: ", + "default", + "; transform: ", + "default", + "; updateByQuery: { (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateByQueryResponse", + ">; (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateByQueryResponse", + ", unknown>>; (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateByQueryResponse", + ">; }; updateByQueryRethrottle: { (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateByQueryRethrottleResponse", + ">; (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateByQueryRethrottleResponse", + ", unknown>>; (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateByQueryRethrottleResponse", + ">; }; watcher: ", + "default", + "; xpack: ", + "default", + "; }" + ], + "path": "packages/core/elasticsearch/core-elasticsearch-server/src/client/scoped_cluster_client.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/core-elasticsearch-server", "id": "def-common.IScopedClusterClient.asCurrentUser", diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index 3b874a18ae042..e7d4c7a2ac357 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_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 | |-------------------|-----------|------------------------|-----------------| -| 114 | 0 | 55 | 0 | +| 115 | 0 | 55 | 0 | ## Common diff --git a/api_docs/kbn_core_elasticsearch_server_internal.devdocs.json b/api_docs/kbn_core_elasticsearch_server_internal.devdocs.json index 807861bea6db1..81fc1bdadb72e 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.devdocs.json +++ b/api_docs/kbn_core_elasticsearch_server_internal.devdocs.json @@ -3114,7 +3114,7 @@ "label": "ElasticsearchConfigType", "description": [], "signature": [ - "{ readonly username?: string | undefined; readonly password?: string | undefined; readonly serviceAccountToken?: string | undefined; readonly ssl: Readonly<{ key?: string | undefined; certificateAuthorities?: string | string[] | undefined; certificate?: string | undefined; keyPassphrase?: string | undefined; } & { verificationMode: \"none\" | \"full\" | \"certificate\"; keystore: Readonly<{ password?: string | undefined; path?: string | undefined; } & {}>; truststore: Readonly<{ password?: string | undefined; path?: string | undefined; } & {}>; alwaysPresentCertificate: boolean; }>; readonly healthCheck: Readonly<{} & { delay: moment.Duration; startupDelay: moment.Duration; }>; readonly hosts: string | string[]; readonly apiVersion: string; readonly customHeaders: Record; readonly sniffOnStart: boolean; readonly sniffInterval: false | moment.Duration; readonly sniffOnConnectionFault: boolean; readonly maxSockets: number; readonly maxIdleSockets: number; readonly idleSocketTimeout: moment.Duration; readonly compression: boolean; readonly requestHeadersWhitelist: string | string[]; readonly shardTimeout: moment.Duration; readonly requestTimeout: moment.Duration; readonly pingTimeout: moment.Duration; readonly logQueries: boolean; readonly ignoreVersionMismatch: boolean; readonly skipStartupConnectionCheck: boolean; readonly apisToRedactInLogs: Readonly<{ method?: string | undefined; } & { path: string; }>[]; readonly dnsCacheTtlInSeconds: number; }" + "{ readonly username?: string | undefined; readonly password?: string | undefined; readonly serviceAccountToken?: string | undefined; readonly ssl: Readonly<{ key?: string | undefined; certificateAuthorities?: string | string[] | undefined; certificate?: string | undefined; keyPassphrase?: string | undefined; } & { verificationMode: \"none\" | \"full\" | \"certificate\"; keystore: Readonly<{ password?: string | undefined; path?: string | undefined; } & {}>; truststore: Readonly<{ password?: string | undefined; path?: string | undefined; } & {}>; alwaysPresentCertificate: boolean; }>; readonly healthCheck: Readonly<{} & { delay: moment.Duration; startupDelay: moment.Duration; }>; readonly hosts: string | string[]; readonly apiVersion: string; readonly customHeaders: Record; readonly sniffOnStart: boolean; readonly sniffInterval: false | moment.Duration; readonly sniffOnConnectionFault: boolean; readonly maxSockets: number; readonly maxIdleSockets: number; readonly idleSocketTimeout: moment.Duration; readonly compression: boolean; readonly requestHeadersWhitelist: string | string[]; readonly shardTimeout: moment.Duration; readonly requestTimeout: moment.Duration; readonly pingTimeout: moment.Duration; readonly logQueries: boolean; readonly ignoreVersionMismatch: boolean; readonly skipStartupConnectionCheck: boolean; readonly apisToRedactInLogs: Readonly<{ method?: string | undefined; } & { path: string; }>[]; readonly dnsCacheTtl: moment.Duration; }" ], "path": "packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.ts", "deprecated": false, @@ -3436,7 +3436,7 @@ "section": "def-common.Type", "text": "Type" }, - "[]>; dnsCacheTtlInSeconds: ", + "[]>; dnsCacheTtl: ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -3444,7 +3444,7 @@ "section": "def-common.Type", "text": "Type" }, - "; }>" + "; }>" ], "path": "packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.ts", "deprecated": false, diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index 510fa2568f6f0..5e73f0659d04b 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 b1d64a9d2833c..75bcf0def4b57 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 04e38ac841b72..5eb9304aac317 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 edf2a2e770c00..8ba4a516a391b 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 cdf767b331f6e..2770e6e0bf0cb 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 c335985036017..338cfc8f178ab 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 3651da7838fb3..7bd144d8c9668 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 e07a07e522c7a..2943e8c03296f 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2024-06-11 +date: 2024-06-19 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 e656cd0dfeeef..5778a1ec26183 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2024-06-11 +date: 2024-06-19 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 a282e5b14e3e1..485e7e1e79c36 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 58f190e9538c2..1dc6be712cf05 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 98f383e1cb450..8dd268062f9c7 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 aef1701e929be..2b593020653fd 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 e4435fb6919fe..cf2c050db360b 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 88287f7d96890..77688dedbcd7a 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 07d520cc7449a..565afd6b084f1 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 2ded33383b8ae..0558d5a0eed69 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2024-06-11 +date: 2024-06-19 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 b68fe6f32b7af..5f7488eb7179f 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 0217aa0e4c39f..afd1291384dd2 100644 --- a/api_docs/kbn_core_http_request_handler_context_server.mdx +++ b/api_docs/kbn_core_http_request_handler_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-request-handler-context-server title: "@kbn/core-http-request-handler-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-request-handler-context-server plugin -date: 2024-06-11 +date: 2024-06-19 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 6777856941788..a0f4412ee86e1 100644 --- a/api_docs/kbn_core_http_resources_server.mdx +++ b/api_docs/kbn_core_http_resources_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server title: "@kbn/core-http-resources-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server plugin -date: 2024-06-11 +date: 2024-06-19 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 e8ff25f02bf98..94f41e2228c56 100644 --- a/api_docs/kbn_core_http_resources_server_internal.mdx +++ b/api_docs/kbn_core_http_resources_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-internal title: "@kbn/core-http-resources-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 d2a704e4dd6f1..aded1566dcffd 100644 --- a/api_docs/kbn_core_http_resources_server_mocks.mdx +++ b/api_docs/kbn_core_http_resources_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-mocks title: "@kbn/core-http-resources-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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.devdocs.json b/api_docs/kbn_core_http_router_server_internal.devdocs.json index bd6d96ce1e1d2..3debd264e8662 100644 --- a/api_docs/kbn_core_http_router_server_internal.devdocs.json +++ b/api_docs/kbn_core_http_router_server_internal.devdocs.json @@ -187,7 +187,15 @@ }, "<", "Method", - ">, \"description\" | \"access\"> | undefined; access: \"internal\" | \"public\"; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; }" + ">, \"description\" | \"deprecated\" | \"access\"> | undefined; access: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.RouteAccess", + "text": "RouteAccess" + }, + "; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; deprecated?: boolean | undefined; }" ], "path": "packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_router.ts", "deprecated": false, @@ -258,7 +266,15 @@ }, "<", "Method", - ">, \"description\" | \"access\"> | undefined; access: \"internal\" | \"public\"; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; }" + ">, \"description\" | \"deprecated\" | \"access\"> | undefined; access: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.RouteAccess", + "text": "RouteAccess" + }, + "; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; deprecated?: boolean | undefined; }" ], "path": "packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_router.ts", "deprecated": false, @@ -329,7 +345,15 @@ }, "<", "Method", - ">, \"description\" | \"access\"> | undefined; access: \"internal\" | \"public\"; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; }" + ">, \"description\" | \"deprecated\" | \"access\"> | undefined; access: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.RouteAccess", + "text": "RouteAccess" + }, + "; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; deprecated?: boolean | undefined; }" ], "path": "packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_router.ts", "deprecated": false, @@ -400,7 +424,15 @@ }, "<", "Method", - ">, \"description\" | \"access\"> | undefined; access: \"internal\" | \"public\"; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; }" + ">, \"description\" | \"deprecated\" | \"access\"> | undefined; access: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.RouteAccess", + "text": "RouteAccess" + }, + "; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; deprecated?: boolean | undefined; }" ], "path": "packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_router.ts", "deprecated": false, @@ -471,7 +503,15 @@ }, "<", "Method", - ">, \"description\" | \"access\"> | undefined; access: \"internal\" | \"public\"; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; }" + ">, \"description\" | \"deprecated\" | \"access\"> | undefined; access: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.RouteAccess", + "text": "RouteAccess" + }, + "; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; deprecated?: boolean | undefined; }" ], "path": "packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_router.ts", "deprecated": false, diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index 8615045dcce94..dcdcbc84d6529 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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.devdocs.json b/api_docs/kbn_core_http_router_server_mocks.devdocs.json index 068d2f989e0d7..de2f1580a0cb2 100644 --- a/api_docs/kbn_core_http_router_server_mocks.devdocs.json +++ b/api_docs/kbn_core_http_router_server_mocks.devdocs.json @@ -80,7 +80,15 @@ "section": "def-common.RouteConfigOptions", "text": "RouteConfigOptions" }, - ", \"description\" | \"access\"> | undefined; access: \"internal\" | \"public\"; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; }" + ", \"description\" | \"deprecated\" | \"access\"> | undefined; access: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.RouteAccess", + "text": "RouteAccess" + }, + "; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; deprecated?: boolean | undefined; }" ], "path": "packages/core/http/core-http-router-server-mocks/src/versioned_router.mock.ts", "deprecated": false, diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index 66488f2bfa5cb..f5d32c20ea099 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 7fbe9f21461e0..6efa1875aa8c9 100644 --- a/api_docs/kbn_core_http_server.devdocs.json +++ b/api_docs/kbn_core_http_server.devdocs.json @@ -3510,6 +3510,10 @@ "plugin": "@kbn/core-apps-server-internal", "path": "packages/core/apps/core-apps-server-internal/src/core_app.ts" }, + { + "plugin": "usageCollection", + "path": "src/plugins/usage_collection/server/routes/stats/stats.ts" + }, { "plugin": "licensing", "path": "x-pack/plugins/licensing/server/routes/info.ts" @@ -3522,10 +3526,6 @@ "plugin": "features", "path": "x-pack/plugins/features/server/routes/index.ts" }, - { - "plugin": "usageCollection", - "path": "src/plugins/usage_collection/server/routes/stats/stats.ts" - }, { "plugin": "customIntegrations", "path": "src/plugins/custom_integrations/server/routes/define_routes.ts" @@ -4526,6 +4526,10 @@ "plugin": "rollup", "path": "x-pack/plugins/rollup/server/routes/api/jobs/register_get_route.ts" }, + { + "plugin": "searchInferenceEndpoints", + "path": "x-pack/plugins/search_inference_endpoints/server/routes.ts" + }, { "plugin": "searchNotebooks", "path": "x-pack/plugins/search_notebooks/server/routes/index.ts" @@ -6176,6 +6180,10 @@ "plugin": "@kbn/core-capabilities-server-internal", "path": "packages/core/capabilities/core-capabilities-server-internal/src/routes/resolve_capabilities.ts" }, + { + "plugin": "usageCollection", + "path": "src/plugins/usage_collection/server/routes/ui_counters.ts" + }, { "plugin": "licensing", "path": "x-pack/plugins/licensing/server/routes/internal/notify_feature_usage.ts" @@ -6184,10 +6192,6 @@ "plugin": "licensing", "path": "x-pack/plugins/licensing/server/routes/internal/register_feature.ts" }, - { - "plugin": "usageCollection", - "path": "src/plugins/usage_collection/server/routes/ui_counters.ts" - }, { "plugin": "home", "path": "src/plugins/home/server/services/sample_data/routes/install.ts" @@ -12506,7 +12510,14 @@ "\nDefines intended request origin of the route:\n- public. The route is public, declared stable and intended for external access.\n In the future, may require an incomming request to contain a specified header.\n- internal. The route is internal and intended for internal access only.\n\nDefaults to 'internal' If not declared," ], "signature": [ - "\"internal\" | \"public\" | undefined" + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.RouteAccess", + "text": "RouteAccess" + }, + " | undefined" ], "path": "packages/core/http/core-http-server/src/router/route.ts", "deprecated": false, @@ -12599,6 +12610,22 @@ "path": "packages/core/http/core-http-server/src/router/route.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-http-server", + "id": "def-common.RouteConfigOptions.deprecated", + "type": "CompoundType", + "tags": [], + "label": "deprecated", + "description": [ + "\nSetting this to `true` declares this route to be deprecated. Consumers SHOULD\nrefrain from usage of this route.\n" + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/core/http/core-http-server/src/router/route.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -14128,6 +14155,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" @@ -14312,6 +14343,10 @@ "plugin": "elasticAssistant", "path": "x-pack/plugins/elastic_assistant/server/routes/anonymization_fields/find_route.ts" }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/find_route.ts" + }, { "plugin": "logsShared", "path": "x-pack/plugins/observability_solution/logs_shared/server/lib/adapters/framework/kibana_framework_adapter.ts" @@ -14604,6 +14639,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/privileges.ts" @@ -14891,7 +14930,15 @@ "section": "def-common.RouteConfigOptions", "text": "RouteConfigOptions" }, - ", \"description\" | \"access\"> | undefined; access: \"internal\" | \"public\"; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; }" + ", \"description\" | \"deprecated\" | \"access\"> | undefined; access: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.RouteAccess", + "text": "RouteAccess" + }, + "; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; deprecated?: boolean | undefined; }" ], "path": "packages/core/http/core-http-server/src/versioning/types.ts", "deprecated": false, @@ -15174,7 +15221,15 @@ "section": "def-common.RouteConfigOptions", "text": "RouteConfigOptions" }, - ", \"description\" | \"access\"> | undefined; access: \"internal\" | \"public\"; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; }" + ", \"description\" | \"deprecated\" | \"access\"> | undefined; access: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.RouteAccess", + "text": "RouteAccess" + }, + "; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; deprecated?: boolean | undefined; }" ], "path": "packages/core/http/core-http-server/src/versioning/types.ts", "deprecated": false, @@ -15690,6 +15745,14 @@ "plugin": "elasticAssistant", "path": "x-pack/plugins/elastic_assistant/server/routes/anonymization_fields/bulk_actions_route.ts" }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/bulk_actions_route.ts" + }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/create_route.ts" + }, { "plugin": "logsShared", "path": "x-pack/plugins/observability_solution/logs_shared/server/lib/adapters/framework/kibana_framework_adapter.ts" @@ -15714,6 +15777,26 @@ "plugin": "fileUpload", "path": "x-pack/plugins/file_upload/server/routes.ts" }, + { + "plugin": "integrationAssistant", + "path": "x-pack/plugins/integration_assistant/server/routes/ecs_routes.ts" + }, + { + "plugin": "integrationAssistant", + "path": "x-pack/plugins/integration_assistant/server/routes/build_integration_routes.ts" + }, + { + "plugin": "integrationAssistant", + "path": "x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts" + }, + { + "plugin": "integrationAssistant", + "path": "x-pack/plugins/integration_assistant/server/routes/related_routes.ts" + }, + { + "plugin": "integrationAssistant", + "path": "x-pack/plugins/integration_assistant/server/routes/pipeline_routes.ts" + }, { "plugin": "lists", "path": "x-pack/plugins/lists/server/routes/create_endpoint_list_item_route.ts" @@ -15954,6 +16037,14 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts" @@ -16074,10 +16165,6 @@ "plugin": "synthetics", "path": "x-pack/plugins/observability_solution/synthetics/server/server.ts" }, - { - "plugin": "elasticAssistant", - "path": "x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/create_route.ts" - }, { "plugin": "controls", "path": "src/plugins/controls/server/options_list/options_list_suggestions_route.ts" @@ -16185,7 +16272,15 @@ "section": "def-common.RouteConfigOptions", "text": "RouteConfigOptions" }, - ", \"description\" | \"access\"> | undefined; access: \"internal\" | \"public\"; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; }" + ", \"description\" | \"deprecated\" | \"access\"> | undefined; access: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.RouteAccess", + "text": "RouteAccess" + }, + "; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; deprecated?: boolean | undefined; }" ], "path": "packages/core/http/core-http-server/src/versioning/types.ts", "deprecated": false, @@ -16312,7 +16407,15 @@ "section": "def-common.RouteConfigOptions", "text": "RouteConfigOptions" }, - ", \"description\" | \"access\"> | undefined; access: \"internal\" | \"public\"; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; }" + ", \"description\" | \"deprecated\" | \"access\"> | undefined; access: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.RouteAccess", + "text": "RouteAccess" + }, + "; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; deprecated?: boolean | undefined; }" ], "path": "packages/core/http/core-http-server/src/versioning/types.ts", "deprecated": false, @@ -16516,6 +16619,10 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts" + }, { "plugin": "synthetics", "path": "x-pack/plugins/observability_solution/synthetics/server/server.ts" @@ -16571,7 +16678,15 @@ "section": "def-common.RouteConfigOptions", "text": "RouteConfigOptions" }, - ", \"description\" | \"access\"> | undefined; access: \"internal\" | \"public\"; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; }" + ", \"description\" | \"deprecated\" | \"access\"> | undefined; access: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.RouteAccess", + "text": "RouteAccess" + }, + "; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; deprecated?: boolean | undefined; }" ], "path": "packages/core/http/core-http-server/src/versioning/types.ts", "deprecated": false, @@ -18707,6 +18822,23 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/core-http-server", + "id": "def-common.RouteAccess", + "type": "Type", + "tags": [], + "label": "RouteAccess", + "description": [ + "\nRoute access level.\n\nPublic routes are stable and intended for external access and are subject to\nstricter change management and have long term maintenance windows.\n" + ], + "signature": [ + "\"internal\" | \"public\"" + ], + "path": "packages/core/http/core-http-server/src/router/route.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/core-http-server", "id": "def-common.RouteContentType", @@ -19249,7 +19381,15 @@ "section": "def-common.RouteConfigOptions", "text": "RouteConfigOptions" }, - ", \"description\" | \"access\"> | undefined; access: \"internal\" | \"public\"; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; }" + ", \"description\" | \"deprecated\" | \"access\"> | undefined; access: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.RouteAccess", + "text": "RouteAccess" + }, + "; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; deprecated?: boolean | undefined; }" ], "path": "packages/core/http/core-http-server/src/versioning/types.ts", "deprecated": false, @@ -19317,7 +19457,15 @@ "section": "def-common.RouteConfigOptions", "text": "RouteConfigOptions" }, - ", \"description\" | \"access\"> | undefined; access: \"internal\" | \"public\"; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; }" + ", \"description\" | \"deprecated\" | \"access\"> | undefined; access: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.RouteAccess", + "text": "RouteAccess" + }, + "; enableQueryVersion?: boolean | undefined; summary?: string | undefined; description?: string | undefined; deprecated?: boolean | undefined; }" ], "path": "packages/core/http/core-http-server/src/versioning/types.ts", "deprecated": false, diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index 3c069f800cbb8..3d4ca4db80615 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_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 | |-------------------|-----------|------------------------|-----------------| -| 487 | 2 | 193 | 0 | +| 489 | 2 | 193 | 0 | ## Common diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index ca1dac8c4c2d4..403fc01ea0b4d 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 2be07105265e9..2c11130958d72 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 3f42084487ca7..293fa964ee4a5 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 300d2413000c2..b65b47da9991e 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 cd59e73330710..1078b9422550d 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2024-06-11 +date: 2024-06-19 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 0bcd4b9a25240..6e63af728c086 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 7af9e1b08f497..399f8f707f150 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 12e228c52862c..e09f4d57977fd 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 0316e8b642247..53e799d45f330 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 40a5668df6327..742fb3ae3e247 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.devdocs.json b/api_docs/kbn_core_lifecycle_browser.devdocs.json index 249fb73e18a74..ad2d7ba96eb1f 100644 --- a/api_docs/kbn_core_lifecycle_browser.devdocs.json +++ b/api_docs/kbn_core_lifecycle_browser.devdocs.json @@ -55,9 +55,9 @@ "signature": [ "{ optIn: (optInConfig: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.OptInConfig", "text": "OptInConfig" }, @@ -65,49 +65,49 @@ "Observable", "<", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.TelemetryCounter", "text": "TelemetryCounter" }, ">; registerEventType: (eventTypeOps: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.EventTypeOpts", "text": "EventTypeOpts" }, ") => void; registerShipper: (Shipper: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.ShipperClassConstructor", "text": "ShipperClassConstructor" }, ", shipperConfig: ShipperConfig, opts?: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.RegisterShipperOpts", "text": "RegisterShipperOpts" }, " | undefined) => void; registerContextProvider: (contextProviderOpts: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.ContextProviderOpts", "text": "ContextProviderOpts" }, @@ -437,9 +437,9 @@ "signature": [ "{ optIn: (optInConfig: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.OptInConfig", "text": "OptInConfig" }, @@ -447,9 +447,9 @@ "Observable", "<", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.TelemetryCounter", "text": "TelemetryCounter" }, diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 35e26216852f2..bfeb9a0c6fa54 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 dd37a5082262b..3e3d6f9103061 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server.devdocs.json b/api_docs/kbn_core_lifecycle_server.devdocs.json index 39e4ac4c5f031..b52260d08327c 100644 --- a/api_docs/kbn_core_lifecycle_server.devdocs.json +++ b/api_docs/kbn_core_lifecycle_server.devdocs.json @@ -45,9 +45,9 @@ "signature": [ "{ optIn: (optInConfig: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.OptInConfig", "text": "OptInConfig" }, @@ -55,49 +55,49 @@ "Observable", "<", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.TelemetryCounter", "text": "TelemetryCounter" }, ">; registerEventType: (eventTypeOps: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.EventTypeOpts", "text": "EventTypeOpts" }, ") => void; registerShipper: (Shipper: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.ShipperClassConstructor", "text": "ShipperClassConstructor" }, ", shipperConfig: ShipperConfig, opts?: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.RegisterShipperOpts", "text": "RegisterShipperOpts" }, " | undefined) => void; registerContextProvider: (contextProviderOpts: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.ContextProviderOpts", "text": "ContextProviderOpts" }, @@ -220,9 +220,9 @@ "signature": [ "{ optIn: (optInConfig: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.OptInConfig", "text": "OptInConfig" }, @@ -230,49 +230,49 @@ "Observable", "<", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.TelemetryCounter", "text": "TelemetryCounter" }, ">; registerEventType: (eventTypeOps: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.EventTypeOpts", "text": "EventTypeOpts" }, ") => void; registerShipper: (Shipper: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.ShipperClassConstructor", "text": "ShipperClassConstructor" }, ", shipperConfig: ShipperConfig, opts?: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.RegisterShipperOpts", "text": "RegisterShipperOpts" }, " | undefined) => void; registerContextProvider: (contextProviderOpts: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.ContextProviderOpts", "text": "ContextProviderOpts" }, @@ -727,9 +727,9 @@ "signature": [ "{ optIn: (optInConfig: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.OptInConfig", "text": "OptInConfig" }, @@ -737,9 +737,9 @@ "Observable", "<", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.TelemetryCounter", "text": "TelemetryCounter" }, diff --git a/api_docs/kbn_core_lifecycle_server.mdx b/api_docs/kbn_core_lifecycle_server.mdx index e9cea9c7a545d..540a363e5f08a 100644 --- a/api_docs/kbn_core_lifecycle_server.mdx +++ b/api_docs/kbn_core_lifecycle_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server title: "@kbn/core-lifecycle-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server plugin -date: 2024-06-11 +date: 2024-06-19 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 78eedafe659c9..bcd4440d8c427 100644 --- a/api_docs/kbn_core_lifecycle_server_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server-mocks title: "@kbn/core-lifecycle-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 bb93f573719f7..ebe1d709f3626 100644 --- a/api_docs/kbn_core_logging_browser_mocks.mdx +++ b/api_docs/kbn_core_logging_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-browser-mocks title: "@kbn/core-logging-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 c459b4ab0c907..81ced9b396fd3 100644 --- a/api_docs/kbn_core_logging_common_internal.mdx +++ b/api_docs/kbn_core_logging_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-common-internal title: "@kbn/core-logging-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-common-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 5a0dc4da7f807..462ee206a17ea 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2024-06-11 +date: 2024-06-19 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 b90a7fff8edc0..547d8e1f274c3 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 e5bafa1248398..46007186a4cf7 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 f9714351f8777..18e66be6b7a3c 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 831ecb4426312..464491aa5691d 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 ed2ac2e92f1ab..e01424863afb0 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2024-06-11 +date: 2024-06-19 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 4d3257103b778..ab8dd8c9df904 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 3f9d10b056b7e..3ec6efa109800 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 bb33bdb9239f6..61f1267a833b8 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 e23b0c94f8ae2..83f6a6541d425 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2024-06-11 +date: 2024-06-19 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 29d8560eb136e..380f12a5efa54 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 c09b66133a2cc..7f2fd98b2ad18 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 4bec911843930..260678120bf7b 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 f2f6a4d9aa9c4..19a018e82aa93 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 297923c5bc542..a37cddaa83ce9 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 09527d44068c1..4e07e307cd3ef 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 da82d6303d824..ce21d35d70b63 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 35cd3023b4ae7..3ffde560b9cb8 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index eb95dfca5a1be..394a55506fc30 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 acee255a1d4a6..0f2265927f8e9 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 0fdd2d0515f02..2f74db77bc206 100644 --- a/api_docs/kbn_core_plugins_contracts_browser.mdx +++ b/api_docs/kbn_core_plugins_contracts_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-contracts-browser title: "@kbn/core-plugins-contracts-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-contracts-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 cd0b9a3d74814..36ded0cd67d6c 100644 --- a/api_docs/kbn_core_plugins_contracts_server.mdx +++ b/api_docs/kbn_core_plugins_contracts_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-contracts-server title: "@kbn/core-plugins-contracts-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-contracts-server plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-contracts-server'] --- import kbnCorePluginsContractsServerObj from './kbn_core_plugins_contracts_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server.mdx b/api_docs/kbn_core_plugins_server.mdx index adf375d8b2fc2..568715e2a0baa 100644 --- a/api_docs/kbn_core_plugins_server.mdx +++ b/api_docs/kbn_core_plugins_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server title: "@kbn/core-plugins-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server plugin -date: 2024-06-11 +date: 2024-06-19 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 baf5fb23c8b56..f0ffacb8de493 100644 --- a/api_docs/kbn_core_plugins_server_mocks.mdx +++ b/api_docs/kbn_core_plugins_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server-mocks title: "@kbn/core-plugins-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 79c88f8d753e3..ae491b22583cf 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2024-06-11 +date: 2024-06-19 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 db8ad202b157d..42345a0163255 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 801713926be9a..8880edcde44d2 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 db5b20425a5cc..f50b1493d6bc0 100644 --- a/api_docs/kbn_core_rendering_server_internal.mdx +++ b/api_docs/kbn_core_rendering_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-internal title: "@kbn/core-rendering-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 5036d7b5404d8..c602f636b9874 100644 --- a/api_docs/kbn_core_rendering_server_mocks.mdx +++ b/api_docs/kbn_core_rendering_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-mocks title: "@kbn/core-rendering-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 46016824a0c19..7cb65d62fa0d1 100644 --- a/api_docs/kbn_core_root_server_internal.mdx +++ b/api_docs/kbn_core_root_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-root-server-internal title: "@kbn/core-root-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-root-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-root-server-internal'] --- import kbnCoreRootServerInternalObj from './kbn_core_root_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json index 6686d9eec0265..80e7b392cbaae 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json @@ -139,19 +139,19 @@ }, { "plugin": "maps", - "path": "x-pack/plugins/maps/public/map_attribute_service.ts" + "path": "x-pack/plugins/maps/public/routes/map_page/saved_map/load_from_library.ts" }, { "plugin": "maps", - "path": "x-pack/plugins/maps/public/map_attribute_service.ts" + "path": "x-pack/plugins/maps/public/routes/map_page/saved_map/load_from_library.ts" }, { "plugin": "maps", - "path": "x-pack/plugins/maps/public/map_attribute_service.ts" + "path": "x-pack/plugins/maps/public/routes/map_page/saved_map/load_from_library.ts" }, { "plugin": "maps", - "path": "x-pack/plugins/maps/public/map_attribute_service.ts" + "path": "x-pack/plugins/maps/public/routes/map_page/saved_map/load_from_library.ts" }, { "plugin": "canvas", diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 42ffe20d466c7..9f91e8923304d 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 8d1f63689b676..874e3e0bdd0ea 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2024-06-11 +date: 2024-06-19 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 7dedf452d7acd..ecffad213e4ff 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 eedfd378013d7..e0cc5522906d0 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 a4513c08d8576..0c5b314f1f660 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 bb2570492a611..752ecbf87d7e6 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 c4e422ea7247e..c433502eed430 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 3b6551a91886b..35a7a834c69bc 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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.devdocs.json b/api_docs/kbn_core_saved_objects_common.devdocs.json index e072a4e7e6a2b..12f98a813006b 100644 --- a/api_docs/kbn_core_saved_objects_common.devdocs.json +++ b/api_docs/kbn_core_saved_objects_common.devdocs.json @@ -2146,6 +2146,22 @@ "plugin": "lens", "path": "x-pack/plugins/lens/public/types.ts" }, + { + "plugin": "maps", + "path": "x-pack/plugins/maps/public/routes/map_page/saved_map/load_from_library.ts" + }, + { + "plugin": "maps", + "path": "x-pack/plugins/maps/public/routes/map_page/saved_map/load_from_library.ts" + }, + { + "plugin": "maps", + "path": "x-pack/plugins/maps/public/routes/map_page/saved_map/save_to_library.ts" + }, + { + "plugin": "maps", + "path": "x-pack/plugins/maps/public/routes/map_page/saved_map/save_to_library.ts" + }, { "plugin": "graph", "path": "x-pack/plugins/graph/public/services/persistence/saved_workspace_references.ts" @@ -2542,14 +2558,6 @@ "plugin": "maps", "path": "x-pack/plugins/maps/common/migrations/references.ts" }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/map_attribute_service.ts" - }, - { - "plugin": "maps", - "path": "x-pack/plugins/maps/public/map_attribute_service.ts" - }, { "plugin": "canvas", "path": "x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts" diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 24c7b7e2873b4..68d6a67769611 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2024-06-11 +date: 2024-06-19 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 55355b701c921..4a1337ba6374f 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 86fea67b7f37a..8e6a4bbac0003 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 008beedd3a030..80a214f8e8bcc 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 a0cf84ee7a64c..e4c15b37732ab 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 c68a8442ccf37..78665054e2d51 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2024-06-11 +date: 2024-06-19 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.devdocs.json b/api_docs/kbn_core_saved_objects_server_internal.devdocs.json index bf0bc606f0add..4f60034ae8d37 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.devdocs.json +++ b/api_docs/kbn_core_saved_objects_server_internal.devdocs.json @@ -180,7 +180,7 @@ "signature": [ "(router: ", "InternalSavedObjectRouter", - ", { config, coreUsageData, logger }: RouteDependencies) => void" + ", { config, coreUsageData, logger, access }: RouteDependencies) => void" ], "path": "packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_create.ts", "deprecated": false, @@ -206,7 +206,7 @@ "id": "def-common.registerBulkCreateRoute.$2", "type": "Object", "tags": [], - "label": "{ config, coreUsageData, logger }", + "label": "{ config, coreUsageData, logger, access }", "description": [], "signature": [ "RouteDependencies" @@ -230,7 +230,7 @@ "signature": [ "(router: ", "InternalSavedObjectRouter", - ", { config, coreUsageData, logger }: RouteDependencies) => void" + ", { config, coreUsageData, logger, access }: RouteDependencies) => void" ], "path": "packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_delete.ts", "deprecated": false, @@ -256,7 +256,7 @@ "id": "def-common.registerBulkDeleteRoute.$2", "type": "Object", "tags": [], - "label": "{ config, coreUsageData, logger }", + "label": "{ config, coreUsageData, logger, access }", "description": [], "signature": [ "RouteDependencies" @@ -280,7 +280,7 @@ "signature": [ "(router: ", "InternalSavedObjectRouter", - ", { config, coreUsageData, logger }: RouteDependencies) => void" + ", { config, coreUsageData, logger, access }: RouteDependencies) => void" ], "path": "packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_get.ts", "deprecated": false, @@ -306,7 +306,7 @@ "id": "def-common.registerBulkGetRoute.$2", "type": "Object", "tags": [], - "label": "{ config, coreUsageData, logger }", + "label": "{ config, coreUsageData, logger, access }", "description": [], "signature": [ "RouteDependencies" @@ -330,7 +330,7 @@ "signature": [ "(router: ", "InternalSavedObjectRouter", - ", { config, coreUsageData, logger }: RouteDependencies) => void" + ", { config, coreUsageData, logger, access }: RouteDependencies) => void" ], "path": "packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_resolve.ts", "deprecated": false, @@ -356,7 +356,7 @@ "id": "def-common.registerBulkResolveRoute.$2", "type": "Object", "tags": [], - "label": "{ config, coreUsageData, logger }", + "label": "{ config, coreUsageData, logger, access }", "description": [], "signature": [ "RouteDependencies" @@ -380,7 +380,7 @@ "signature": [ "(router: ", "InternalSavedObjectRouter", - ", { config, coreUsageData, logger }: RouteDependencies) => void" + ", { config, coreUsageData, logger, access }: RouteDependencies) => void" ], "path": "packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_update.ts", "deprecated": false, @@ -406,7 +406,7 @@ "id": "def-common.registerBulkUpdateRoute.$2", "type": "Object", "tags": [], - "label": "{ config, coreUsageData, logger }", + "label": "{ config, coreUsageData, logger, access }", "description": [], "signature": [ "RouteDependencies" @@ -430,7 +430,7 @@ "signature": [ "(router: ", "InternalSavedObjectRouter", - ", { config, coreUsageData, logger }: RouteDependencies) => void" + ", { config, coreUsageData, logger, access }: RouteDependencies) => void" ], "path": "packages/core/saved-objects/core-saved-objects-server-internal/src/routes/create.ts", "deprecated": false, @@ -456,7 +456,7 @@ "id": "def-common.registerCreateRoute.$2", "type": "Object", "tags": [], - "label": "{ config, coreUsageData, logger }", + "label": "{ config, coreUsageData, logger, access }", "description": [], "signature": [ "RouteDependencies" @@ -480,7 +480,7 @@ "signature": [ "(router: ", "InternalSavedObjectRouter", - ", { config, coreUsageData, logger }: RouteDependencies) => void" + ", { config, coreUsageData, logger, access }: RouteDependencies) => void" ], "path": "packages/core/saved-objects/core-saved-objects-server-internal/src/routes/delete.ts", "deprecated": false, @@ -506,7 +506,7 @@ "id": "def-common.registerDeleteRoute.$2", "type": "Object", "tags": [], - "label": "{ config, coreUsageData, logger }", + "label": "{ config, coreUsageData, logger, access }", "description": [], "signature": [ "RouteDependencies" @@ -630,7 +630,7 @@ "signature": [ "(router: ", "InternalSavedObjectRouter", - ", { config, coreUsageData, logger }: RouteDependencies) => void" + ", { config, coreUsageData, logger, access }: RouteDependencies) => void" ], "path": "packages/core/saved-objects/core-saved-objects-server-internal/src/routes/find.ts", "deprecated": false, @@ -656,7 +656,7 @@ "id": "def-common.registerFindRoute.$2", "type": "Object", "tags": [], - "label": "{ config, coreUsageData, logger }", + "label": "{ config, coreUsageData, logger, access }", "description": [], "signature": [ "RouteDependencies" @@ -680,7 +680,7 @@ "signature": [ "(router: ", "InternalSavedObjectRouter", - ", { config, coreUsageData, logger }: RouteDependencies) => void" + ", { config, coreUsageData, logger, access }: RouteDependencies) => void" ], "path": "packages/core/saved-objects/core-saved-objects-server-internal/src/routes/get.ts", "deprecated": false, @@ -706,7 +706,7 @@ "id": "def-common.registerGetRoute.$2", "type": "Object", "tags": [], - "label": "{ config, coreUsageData, logger }", + "label": "{ config, coreUsageData, logger, access }", "description": [], "signature": [ "RouteDependencies" @@ -1090,7 +1090,7 @@ "signature": [ "(router: ", "InternalSavedObjectRouter", - ", { config, coreUsageData, logger }: RouteDependencies) => void" + ", { config, coreUsageData, logger, access }: RouteDependencies) => void" ], "path": "packages/core/saved-objects/core-saved-objects-server-internal/src/routes/resolve.ts", "deprecated": false, @@ -1116,7 +1116,7 @@ "id": "def-common.registerResolveRoute.$2", "type": "Object", "tags": [], - "label": "{ config, coreUsageData, logger }", + "label": "{ config, coreUsageData, logger, access }", "description": [], "signature": [ "RouteDependencies" @@ -1140,7 +1140,7 @@ "signature": [ "(router: ", "InternalSavedObjectRouter", - ", { config, coreUsageData, logger }: RouteDependencies) => void" + ", { config, coreUsageData, logger, access }: RouteDependencies) => void" ], "path": "packages/core/saved-objects/core-saved-objects-server-internal/src/routes/update.ts", "deprecated": false, @@ -1166,7 +1166,7 @@ "id": "def-common.registerUpdateRoute.$2", "type": "Object", "tags": [], - "label": "{ config, coreUsageData, logger }", + "label": "{ config, coreUsageData, logger, access }", "description": [], "signature": [ "RouteDependencies" diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index a8a6eb293b187..98fc56e33289f 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 2f261131b2893..19151bb337527 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 a7f13f31337ac..ff2d640d3dff1 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_security_browser.mdx b/api_docs/kbn_core_security_browser.mdx index 180205cc3ff2d..0918bd4435b48 100644 --- a/api_docs/kbn_core_security_browser.mdx +++ b/api_docs/kbn_core_security_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser title: "@kbn/core-security-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser'] --- import kbnCoreSecurityBrowserObj from './kbn_core_security_browser.devdocs.json'; diff --git a/api_docs/kbn_core_security_browser_internal.mdx b/api_docs/kbn_core_security_browser_internal.mdx index 2018fc5b09a5b..bb1ed61f82173 100644 --- a/api_docs/kbn_core_security_browser_internal.mdx +++ b/api_docs/kbn_core_security_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser-internal title: "@kbn/core-security-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser-internal plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser-internal'] --- import kbnCoreSecurityBrowserInternalObj from './kbn_core_security_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_security_browser_mocks.mdx b/api_docs/kbn_core_security_browser_mocks.mdx index e274a5f5d6c5b..300d673be9d5c 100644 --- a/api_docs/kbn_core_security_browser_mocks.mdx +++ b/api_docs/kbn_core_security_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-browser-mocks title: "@kbn/core-security-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-browser-mocks'] --- import kbnCoreSecurityBrowserMocksObj from './kbn_core_security_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_security_common.mdx b/api_docs/kbn_core_security_common.mdx index d368d960bb087..5078e7aac7003 100644 --- a/api_docs/kbn_core_security_common.mdx +++ b/api_docs/kbn_core_security_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-common title: "@kbn/core-security-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-common plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-common'] --- import kbnCoreSecurityCommonObj from './kbn_core_security_common.devdocs.json'; diff --git a/api_docs/kbn_core_security_server.mdx b/api_docs/kbn_core_security_server.mdx index c899f9afbf94f..84c9098187bbb 100644 --- a/api_docs/kbn_core_security_server.mdx +++ b/api_docs/kbn_core_security_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server title: "@kbn/core-security-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server'] --- import kbnCoreSecurityServerObj from './kbn_core_security_server.devdocs.json'; diff --git a/api_docs/kbn_core_security_server_internal.mdx b/api_docs/kbn_core_security_server_internal.mdx index 662209c9bd2f6..2bd601031c0c3 100644 --- a/api_docs/kbn_core_security_server_internal.mdx +++ b/api_docs/kbn_core_security_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server-internal title: "@kbn/core-security-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server-internal'] --- import kbnCoreSecurityServerInternalObj from './kbn_core_security_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_security_server_mocks.mdx b/api_docs/kbn_core_security_server_mocks.mdx index 0b0974a15fcda..7c7d458c61b64 100644 --- a/api_docs/kbn_core_security_server_mocks.mdx +++ b/api_docs/kbn_core_security_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-security-server-mocks title: "@kbn/core-security-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-security-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-security-server-mocks'] --- import kbnCoreSecurityServerMocksObj from './kbn_core_security_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 94d60e1e89a47..b2be2d573a383 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2024-06-11 +date: 2024-06-19 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 6f9aeb24ceaa3..a4e723953e857 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 1f62e7da6912f..226d66fd93b4c 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.devdocs.json b/api_docs/kbn_core_status_server_internal.devdocs.json index 107828999c77f..5a27f026247ba 100644 --- a/api_docs/kbn_core_status_server_internal.devdocs.json +++ b/api_docs/kbn_core_status_server_internal.devdocs.json @@ -296,9 +296,9 @@ "signature": [ "{ optIn: (optInConfig: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.OptInConfig", "text": "OptInConfig" }, @@ -306,49 +306,49 @@ "Observable", "<", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.TelemetryCounter", "text": "TelemetryCounter" }, ">; registerEventType: (eventTypeOps: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.EventTypeOpts", "text": "EventTypeOpts" }, ") => void; registerShipper: (Shipper: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.ShipperClassConstructor", "text": "ShipperClassConstructor" }, ", shipperConfig: ShipperConfig, opts?: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.RegisterShipperOpts", "text": "RegisterShipperOpts" }, " | undefined) => void; registerContextProvider: (contextProviderOpts: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.ContextProviderOpts", "text": "ContextProviderOpts" }, diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index 8cf3583a2173d..fc208bda2f92b 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 b960e83584a81..da52f2f53ee6d 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 55abc57d57622..57d54bc469e26 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2024-06-11 +date: 2024-06-19 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 bacb46eb025d7..7dbb268d367bc 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 f8131f01c2d77..1bceadbf6500e 100644 --- a/api_docs/kbn_core_test_helpers_kbn_server.mdx +++ b/api_docs/kbn_core_test_helpers_kbn_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-kbn-server title: "@kbn/core-test-helpers-kbn-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-kbn-server plugin -date: 2024-06-11 +date: 2024-06-19 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 cd2eb7750bc18..80dfcec841e4f 100644 --- a/api_docs/kbn_core_test_helpers_model_versions.mdx +++ b/api_docs/kbn_core_test_helpers_model_versions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-model-versions title: "@kbn/core-test-helpers-model-versions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-model-versions plugin -date: 2024-06-11 +date: 2024-06-19 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 ad791c3562c99..b5397439e4a13 100644 --- a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx +++ b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-so-type-serializer title: "@kbn/core-test-helpers-so-type-serializer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-so-type-serializer plugin -date: 2024-06-11 +date: 2024-06-19 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 cfc877be73a52..08ee975d23ec6 100644 --- a/api_docs/kbn_core_test_helpers_test_utils.mdx +++ b/api_docs/kbn_core_test_helpers_test_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-test-utils title: "@kbn/core-test-helpers-test-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-test-utils plugin -date: 2024-06-11 +date: 2024-06-19 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 6b91031a75b36..3a8b9886f7f8a 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 944f5eb6b4c78..888ce7638b70b 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 a4d11f8b3dcf2..d2fe1dfd87a1b 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2024-06-11 +date: 2024-06-19 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 bfb3eea937e6c..a80cfcb497d84 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 fd1cd93e53292..2f7cad133b4cd 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 aa94501f6d31a..fd3819d87139b 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2024-06-11 +date: 2024-06-19 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 0c26a79847796..776cd00c33ba9 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2024-06-11 +date: 2024-06-19 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 2c038c50653a2..66a0252c0743c 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 9b779cf76d000..44ffdbee19eed 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 a3242c6f2e31d..8c86d7d90a81d 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2024-06-11 +date: 2024-06-19 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 2676ff3da5513..167711a91f528 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 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 1003d2c3ffa2a..91b5b01cc15bf 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_browser.mdx b/api_docs/kbn_core_user_profile_browser.mdx index c30d14dcfa07e..eb95315c988d1 100644 --- a/api_docs/kbn_core_user_profile_browser.mdx +++ b/api_docs/kbn_core_user_profile_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser title: "@kbn/core-user-profile-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser'] --- import kbnCoreUserProfileBrowserObj from './kbn_core_user_profile_browser.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_browser_internal.mdx b/api_docs/kbn_core_user_profile_browser_internal.mdx index e0f0782c84f5a..c843246a61a82 100644 --- a/api_docs/kbn_core_user_profile_browser_internal.mdx +++ b/api_docs/kbn_core_user_profile_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser-internal title: "@kbn/core-user-profile-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser-internal plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser-internal'] --- import kbnCoreUserProfileBrowserInternalObj from './kbn_core_user_profile_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_browser_mocks.mdx b/api_docs/kbn_core_user_profile_browser_mocks.mdx index 0bcea913557d8..befa003474b26 100644 --- a/api_docs/kbn_core_user_profile_browser_mocks.mdx +++ b/api_docs/kbn_core_user_profile_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-browser-mocks title: "@kbn/core-user-profile-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-browser-mocks plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-browser-mocks'] --- import kbnCoreUserProfileBrowserMocksObj from './kbn_core_user_profile_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_common.mdx b/api_docs/kbn_core_user_profile_common.mdx index ecb978e96053a..f66ad47be6122 100644 --- a/api_docs/kbn_core_user_profile_common.mdx +++ b/api_docs/kbn_core_user_profile_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-common title: "@kbn/core-user-profile-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-common plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-common'] --- import kbnCoreUserProfileCommonObj from './kbn_core_user_profile_common.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server.mdx b/api_docs/kbn_core_user_profile_server.mdx index da6cb17ffb8e6..0e15c25f306cb 100644 --- a/api_docs/kbn_core_user_profile_server.mdx +++ b/api_docs/kbn_core_user_profile_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server title: "@kbn/core-user-profile-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server'] --- import kbnCoreUserProfileServerObj from './kbn_core_user_profile_server.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server_internal.mdx b/api_docs/kbn_core_user_profile_server_internal.mdx index 1c7e9c979a7c7..41d57e14eb86d 100644 --- a/api_docs/kbn_core_user_profile_server_internal.mdx +++ b/api_docs/kbn_core_user_profile_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server-internal title: "@kbn/core-user-profile-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server-internal plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server-internal'] --- import kbnCoreUserProfileServerInternalObj from './kbn_core_user_profile_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_user_profile_server_mocks.mdx b/api_docs/kbn_core_user_profile_server_mocks.mdx index 11c08c8a6fece..b86eb701b873f 100644 --- a/api_docs/kbn_core_user_profile_server_mocks.mdx +++ b/api_docs/kbn_core_user_profile_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-profile-server-mocks title: "@kbn/core-user-profile-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-profile-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-profile-server-mocks'] --- import kbnCoreUserProfileServerMocksObj from './kbn_core_user_profile_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server.mdx b/api_docs/kbn_core_user_settings_server.mdx index ceabe6f441fa5..795d69dbb69e3 100644 --- a/api_docs/kbn_core_user_settings_server.mdx +++ b/api_docs/kbn_core_user_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server title: "@kbn/core-user-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-user-settings-server'] --- import kbnCoreUserSettingsServerObj from './kbn_core_user_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_user_settings_server_mocks.mdx b/api_docs/kbn_core_user_settings_server_mocks.mdx index 76c9304c0a492..a3aa62ecb7342 100644 --- a/api_docs/kbn_core_user_settings_server_mocks.mdx +++ b/api_docs/kbn_core_user_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-user-settings-server-mocks title: "@kbn/core-user-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-user-settings-server-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 bb0486e89d44b..0738623f9c4cd 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2024-06-11 +date: 2024-06-19 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 bdd5f0661be9b..a222beae12053 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_custom_icons.mdx b/api_docs/kbn_custom_icons.mdx index d48295e72480a..6bb317fa407d4 100644 --- a/api_docs/kbn_custom_icons.mdx +++ b/api_docs/kbn_custom_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-custom-icons title: "@kbn/custom-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/custom-icons plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-icons'] --- import kbnCustomIconsObj from './kbn_custom_icons.devdocs.json'; diff --git a/api_docs/kbn_custom_integrations.mdx b/api_docs/kbn_custom_integrations.mdx index e0bc654d8dcf9..368d1d7209818 100644 --- a/api_docs/kbn_custom_integrations.mdx +++ b/api_docs/kbn_custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-custom-integrations title: "@kbn/custom-integrations" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/custom-integrations plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/custom-integrations'] --- import kbnCustomIntegrationsObj from './kbn_custom_integrations.devdocs.json'; diff --git a/api_docs/kbn_cypress_config.mdx b/api_docs/kbn_cypress_config.mdx index 0a820b3e5e432..e3da1a3651de5 100644 --- a/api_docs/kbn_cypress_config.mdx +++ b/api_docs/kbn_cypress_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cypress-config title: "@kbn/cypress-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cypress-config plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cypress-config'] --- import kbnCypressConfigObj from './kbn_cypress_config.devdocs.json'; diff --git a/api_docs/kbn_data_forge.mdx b/api_docs/kbn_data_forge.mdx index fb94fc957fdba..aef3c64f67757 100644 --- a/api_docs/kbn_data_forge.mdx +++ b/api_docs/kbn_data_forge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-forge title: "@kbn/data-forge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-forge plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-forge'] --- import kbnDataForgeObj from './kbn_data_forge.devdocs.json'; diff --git a/api_docs/kbn_data_service.mdx b/api_docs/kbn_data_service.mdx index 4c42af0ea1a44..932436e20308e 100644 --- a/api_docs/kbn_data_service.mdx +++ b/api_docs/kbn_data_service.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-service title: "@kbn/data-service" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-service plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-service'] --- import kbnDataServiceObj from './kbn_data_service.devdocs.json'; diff --git a/api_docs/kbn_data_stream_adapter.mdx b/api_docs/kbn_data_stream_adapter.mdx index db268f8d22a96..50998407ddbfe 100644 --- a/api_docs/kbn_data_stream_adapter.mdx +++ b/api_docs/kbn_data_stream_adapter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-stream-adapter title: "@kbn/data-stream-adapter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-stream-adapter plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-stream-adapter'] --- import kbnDataStreamAdapterObj from './kbn_data_stream_adapter.devdocs.json'; diff --git a/api_docs/kbn_data_view_utils.devdocs.json b/api_docs/kbn_data_view_utils.devdocs.json index 0bacfe7a0aa46..bbef9faf79cd5 100644 --- a/api_docs/kbn_data_view_utils.devdocs.json +++ b/api_docs/kbn_data_view_utils.devdocs.json @@ -18,7 +18,74 @@ }, "common": { "classes": [], - "functions": [], + "functions": [ + { + "parentPluginId": "@kbn/data-view-utils", + "id": "def-common.createRegExpPatternFrom", + "type": "Function", + "tags": [], + "label": "createRegExpPatternFrom", + "description": [], + "signature": [ + "(basePatterns: string | string[]) => RegExp" + ], + "path": "packages/kbn-data-view-utils/src/utils/create_regexp_pattern_from.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/data-view-utils", + "id": "def-common.createRegExpPatternFrom.$1", + "type": "CompoundType", + "tags": [], + "label": "basePatterns", + "description": [], + "signature": [ + "string | string[]" + ], + "path": "packages/kbn-data-view-utils/src/utils/create_regexp_pattern_from.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/data-view-utils", + "id": "def-common.testPatternAgainstAllowedList", + "type": "Function", + "tags": [], + "label": "testPatternAgainstAllowedList", + "description": [], + "signature": [ + "(allowedList: (string | RegExp)[]) => (value: string) => boolean" + ], + "path": "packages/kbn-data-view-utils/src/utils/test_pattern_against_allowed_list.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/data-view-utils", + "id": "def-common.testPatternAgainstAllowedList.$1", + "type": "Array", + "tags": [], + "label": "allowedList", + "description": [], + "signature": [ + "(string | RegExp)[]" + ], + "path": "packages/kbn-data-view-utils/src/utils/test_pattern_against_allowed_list.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], "interfaces": [], "enums": [], "misc": [ diff --git a/api_docs/kbn_data_view_utils.mdx b/api_docs/kbn_data_view_utils.mdx index bc7df441da5f8..e03b8bc0f9617 100644 --- a/api_docs/kbn_data_view_utils.mdx +++ b/api_docs/kbn_data_view_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-data-view-utils title: "@kbn/data-view-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/data-view-utils plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/data-view-utils'] --- import kbnDataViewUtilsObj from './kbn_data_view_utils.devdocs.json'; @@ -21,10 +21,13 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 1 | 0 | 0 | 0 | +| 5 | 0 | 4 | 0 | ## Common +### Functions + + ### Consts, variables and types diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index f0dd2abca8f26..e90b3a3e04981 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2024-06-11 +date: 2024-06-19 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 12ddda4ba21b7..3db3b8868e8a1 100644 --- a/api_docs/kbn_deeplinks_analytics.mdx +++ b/api_docs/kbn_deeplinks_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-analytics title: "@kbn/deeplinks-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-analytics plugin -date: 2024-06-11 +date: 2024-06-19 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 e97fd6c8e04c8..08891eda96b5f 100644 --- a/api_docs/kbn_deeplinks_devtools.mdx +++ b/api_docs/kbn_deeplinks_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-devtools title: "@kbn/deeplinks-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-devtools plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-devtools'] --- import kbnDeeplinksDevtoolsObj from './kbn_deeplinks_devtools.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_fleet.mdx b/api_docs/kbn_deeplinks_fleet.mdx index 30f019129e75d..2b6f29f578e49 100644 --- a/api_docs/kbn_deeplinks_fleet.mdx +++ b/api_docs/kbn_deeplinks_fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-fleet title: "@kbn/deeplinks-fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-fleet plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-fleet'] --- import kbnDeeplinksFleetObj from './kbn_deeplinks_fleet.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_management.mdx b/api_docs/kbn_deeplinks_management.mdx index 70b52f07e5d5f..31971c1739ba7 100644 --- a/api_docs/kbn_deeplinks_management.mdx +++ b/api_docs/kbn_deeplinks_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-management title: "@kbn/deeplinks-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-management plugin -date: 2024-06-11 +date: 2024-06-19 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 0fc8dad56ea48..7a2fe8066cfa9 100644 --- a/api_docs/kbn_deeplinks_ml.mdx +++ b/api_docs/kbn_deeplinks_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-ml title: "@kbn/deeplinks-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-ml plugin -date: 2024-06-11 +date: 2024-06-19 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 2d678b2ebae04..672f113f92ac8 100644 --- a/api_docs/kbn_deeplinks_observability.mdx +++ b/api_docs/kbn_deeplinks_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-observability title: "@kbn/deeplinks-observability" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-observability plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-observability'] --- import kbnDeeplinksObservabilityObj from './kbn_deeplinks_observability.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_search.devdocs.json b/api_docs/kbn_deeplinks_search.devdocs.json index 6bb61ceeb91a7..d1c71948b3f00 100644 --- a/api_docs/kbn_deeplinks_search.devdocs.json +++ b/api_docs/kbn_deeplinks_search.devdocs.json @@ -30,7 +30,7 @@ "label": "DeepLinkId", "description": [], "signature": [ - "\"appSearch\" | \"enterpriseSearch\" | \"enterpriseSearchContent\" | \"enterpriseSearchApplications\" | \"enterpriseSearchAnalytics\" | \"workplaceSearch\" | \"serverlessElasticsearch\" | \"serverlessConnectors\" | \"searchPlayground\" | \"enterpriseSearchContent:connectors\" | \"enterpriseSearchContent:searchIndices\" | \"enterpriseSearchContent:webCrawlers\" | \"enterpriseSearchApplications:searchApplications\" | \"enterpriseSearchApplications:playground\" | \"appSearch:engines\"" + "\"appSearch\" | \"enterpriseSearch\" | \"enterpriseSearchContent\" | \"enterpriseSearchApplications\" | \"enterpriseSearchAnalytics\" | \"workplaceSearch\" | \"serverlessElasticsearch\" | \"serverlessConnectors\" | \"searchPlayground\" | \"searchInferenceEndpoints\" | \"enterpriseSearchContent:connectors\" | \"enterpriseSearchContent:searchIndices\" | \"enterpriseSearchContent:webCrawlers\" | \"enterpriseSearchApplications:searchApplications\" | \"enterpriseSearchApplications:playground\" | \"appSearch:engines\"" ], "path": "packages/deeplinks/search/deep_links.ts", "deprecated": false, @@ -112,6 +112,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/deeplinks-search", + "id": "def-common.ENTERPRISE_SEARCH_INFERENCE_ENDPOINTS_APP_ID", + "type": "string", + "tags": [], + "label": "ENTERPRISE_SEARCH_INFERENCE_ENDPOINTS_APP_ID", + "description": [], + "signature": [ + "\"enterpriseSearchInferenceEndpoints\"" + ], + "path": "packages/deeplinks/search/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/deeplinks-search", "id": "def-common.ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID", diff --git a/api_docs/kbn_deeplinks_search.mdx b/api_docs/kbn_deeplinks_search.mdx index 6fde510e06214..43a1345190c31 100644 --- a/api_docs/kbn_deeplinks_search.mdx +++ b/api_docs/kbn_deeplinks_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-search title: "@kbn/deeplinks-search" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-search plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-search'] --- import kbnDeeplinksSearchObj from './kbn_deeplinks_search.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-ki | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 16 | 0 | 16 | 0 | +| 17 | 0 | 17 | 0 | ## Common diff --git a/api_docs/kbn_deeplinks_security.devdocs.json b/api_docs/kbn_deeplinks_security.devdocs.json index 1e9a52bf2b368..5eec00a0d9340 100644 --- a/api_docs/kbn_deeplinks_security.devdocs.json +++ b/api_docs/kbn_deeplinks_security.devdocs.json @@ -58,7 +58,7 @@ "label": "DeepLinkId", "description": [], "signature": [ - "\"securitySolutionUI\" | \"securitySolutionUI:\" | \"securitySolutionUI:cases\" | \"securitySolutionUI:alerts\" | \"securitySolutionUI:rules\" | \"securitySolutionUI:policy\" | \"securitySolutionUI:overview\" | \"securitySolutionUI:dashboards\" | \"securitySolutionUI:cases_create\" | \"securitySolutionUI:cases_configure\" | \"securitySolutionUI:hosts\" | \"securitySolutionUI:users\" | \"securitySolutionUI:cloud_defend-policies\" | \"securitySolutionUI:cloud_security_posture-dashboard\" | \"securitySolutionUI:cloud_security_posture-findings\" | \"securitySolutionUI:cloud_security_posture-benchmarks\" | \"securitySolutionUI:kubernetes\" | \"securitySolutionUI:network\" | \"securitySolutionUI:data_quality\" | \"securitySolutionUI:explore\" | \"securitySolutionUI:assets\" | \"securitySolutionUI:cloud_defend\" | \"securitySolutionUI:administration\" | \"securitySolutionUI:attack_discovery\" | \"securitySolutionUI:blocklist\" | \"securitySolutionUI:cloud_security_posture-rules\" | \"securitySolutionUI:detections\" | \"securitySolutionUI:detection_response\" | \"securitySolutionUI:endpoints\" | \"securitySolutionUI:event_filters\" | \"securitySolutionUI:exceptions\" | \"securitySolutionUI:host_isolation_exceptions\" | \"securitySolutionUI:hosts-all\" | \"securitySolutionUI:hosts-anomalies\" | \"securitySolutionUI:hosts-risk\" | \"securitySolutionUI:hosts-events\" | \"securitySolutionUI:hosts-sessions\" | \"securitySolutionUI:hosts-uncommon_processes\" | \"securitySolutionUI:investigations\" | \"securitySolutionUI:get_started\" | \"securitySolutionUI:machine_learning-landing\" | \"securitySolutionUI:network-anomalies\" | \"securitySolutionUI:network-dns\" | \"securitySolutionUI:network-events\" | \"securitySolutionUI:network-flows\" | \"securitySolutionUI:network-http\" | \"securitySolutionUI:network-tls\" | \"securitySolutionUI:response_actions_history\" | \"securitySolutionUI:rules-add\" | \"securitySolutionUI:rules-create\" | \"securitySolutionUI:rules-landing\" | \"securitySolutionUI:threat_intelligence\" | \"securitySolutionUI:timelines\" | \"securitySolutionUI:timelines-templates\" | \"securitySolutionUI:trusted_apps\" | \"securitySolutionUI:users-all\" | \"securitySolutionUI:users-anomalies\" | \"securitySolutionUI:users-authentications\" | \"securitySolutionUI:users-events\" | \"securitySolutionUI:users-risk\" | \"securitySolutionUI:entity_analytics\" | \"securitySolutionUI:entity_analytics-management\" | \"securitySolutionUI:entity_analytics-asset-classification\" | \"securitySolutionUI:coverage-overview\"" + "\"securitySolutionUI\" | \"securitySolutionUI:\" | \"securitySolutionUI:cases\" | \"securitySolutionUI:alerts\" | \"securitySolutionUI:rules\" | \"securitySolutionUI:policy\" | \"securitySolutionUI:overview\" | \"securitySolutionUI:dashboards\" | \"securitySolutionUI:cases_create\" | \"securitySolutionUI:cases_configure\" | \"securitySolutionUI:hosts\" | \"securitySolutionUI:users\" | \"securitySolutionUI:cloud_defend-policies\" | \"securitySolutionUI:cloud_security_posture-dashboard\" | \"securitySolutionUI:cloud_security_posture-findings\" | \"securitySolutionUI:cloud_security_posture-benchmarks\" | \"securitySolutionUI:kubernetes\" | \"securitySolutionUI:network\" | \"securitySolutionUI:data_quality\" | \"securitySolutionUI:explore\" | \"securitySolutionUI:assets\" | \"securitySolutionUI:cloud_defend\" | \"securitySolutionUI:administration\" | \"securitySolutionUI:attack_discovery\" | \"securitySolutionUI:blocklist\" | \"securitySolutionUI:cloud_security_posture-rules\" | \"securitySolutionUI:detections\" | \"securitySolutionUI:detection_response\" | \"securitySolutionUI:endpoints\" | \"securitySolutionUI:event_filters\" | \"securitySolutionUI:exceptions\" | \"securitySolutionUI:host_isolation_exceptions\" | \"securitySolutionUI:hosts-all\" | \"securitySolutionUI:hosts-anomalies\" | \"securitySolutionUI:hosts-risk\" | \"securitySolutionUI:hosts-events\" | \"securitySolutionUI:hosts-sessions\" | \"securitySolutionUI:hosts-uncommon_processes\" | \"securitySolutionUI:investigations\" | \"securitySolutionUI:get_started\" | \"securitySolutionUI:machine_learning-landing\" | \"securitySolutionUI:network-anomalies\" | \"securitySolutionUI:network-dns\" | \"securitySolutionUI:network-events\" | \"securitySolutionUI:network-flows\" | \"securitySolutionUI:network-http\" | \"securitySolutionUI:network-tls\" | \"securitySolutionUI:response_actions_history\" | \"securitySolutionUI:rules-add\" | \"securitySolutionUI:rules-create\" | \"securitySolutionUI:rules-landing\" | \"securitySolutionUI:threat_intelligence\" | \"securitySolutionUI:timelines\" | \"securitySolutionUI:timelines-templates\" | \"securitySolutionUI:trusted_apps\" | \"securitySolutionUI:users-all\" | \"securitySolutionUI:users-anomalies\" | \"securitySolutionUI:users-authentications\" | \"securitySolutionUI:users-events\" | \"securitySolutionUI:users-risk\" | \"securitySolutionUI:entity_analytics\" | \"securitySolutionUI:entity_analytics-management\" | \"securitySolutionUI:entity_analytics-asset-classification\" | \"securitySolutionUI:coverage-overview\" | \"securitySolutionUI:notes-management\"" ], "path": "packages/deeplinks/security/index.ts", "deprecated": false, @@ -73,7 +73,7 @@ "label": "LinkId", "description": [], "signature": [ - "\"\" | \"cases\" | \"alerts\" | \"rules\" | \"policy\" | \"overview\" | \"dashboards\" | \"cases_create\" | \"cases_configure\" | \"hosts\" | \"users\" | \"cloud_defend-policies\" | \"cloud_security_posture-dashboard\" | \"cloud_security_posture-findings\" | \"cloud_security_posture-benchmarks\" | \"kubernetes\" | \"network\" | \"data_quality\" | \"explore\" | \"assets\" | \"cloud_defend\" | \"administration\" | \"attack_discovery\" | \"blocklist\" | \"cloud_security_posture-rules\" | \"detections\" | \"detection_response\" | \"endpoints\" | \"event_filters\" | \"exceptions\" | \"host_isolation_exceptions\" | \"hosts-all\" | \"hosts-anomalies\" | \"hosts-risk\" | \"hosts-events\" | \"hosts-sessions\" | \"hosts-uncommon_processes\" | \"investigations\" | \"get_started\" | \"machine_learning-landing\" | \"network-anomalies\" | \"network-dns\" | \"network-events\" | \"network-flows\" | \"network-http\" | \"network-tls\" | \"response_actions_history\" | \"rules-add\" | \"rules-create\" | \"rules-landing\" | \"threat_intelligence\" | \"timelines\" | \"timelines-templates\" | \"trusted_apps\" | \"users-all\" | \"users-anomalies\" | \"users-authentications\" | \"users-events\" | \"users-risk\" | \"entity_analytics\" | \"entity_analytics-management\" | \"entity_analytics-asset-classification\" | \"coverage-overview\"" + "\"\" | \"cases\" | \"alerts\" | \"rules\" | \"policy\" | \"overview\" | \"dashboards\" | \"cases_create\" | \"cases_configure\" | \"hosts\" | \"users\" | \"cloud_defend-policies\" | \"cloud_security_posture-dashboard\" | \"cloud_security_posture-findings\" | \"cloud_security_posture-benchmarks\" | \"kubernetes\" | \"network\" | \"data_quality\" | \"explore\" | \"assets\" | \"cloud_defend\" | \"administration\" | \"attack_discovery\" | \"blocklist\" | \"cloud_security_posture-rules\" | \"detections\" | \"detection_response\" | \"endpoints\" | \"event_filters\" | \"exceptions\" | \"host_isolation_exceptions\" | \"hosts-all\" | \"hosts-anomalies\" | \"hosts-risk\" | \"hosts-events\" | \"hosts-sessions\" | \"hosts-uncommon_processes\" | \"investigations\" | \"get_started\" | \"machine_learning-landing\" | \"network-anomalies\" | \"network-dns\" | \"network-events\" | \"network-flows\" | \"network-http\" | \"network-tls\" | \"response_actions_history\" | \"rules-add\" | \"rules-create\" | \"rules-landing\" | \"threat_intelligence\" | \"timelines\" | \"timelines-templates\" | \"trusted_apps\" | \"users-all\" | \"users-anomalies\" | \"users-authentications\" | \"users-events\" | \"users-risk\" | \"entity_analytics\" | \"entity_analytics-management\" | \"entity_analytics-asset-classification\" | \"coverage-overview\" | \"notes-management\"" ], "path": "packages/deeplinks/security/index.ts", "deprecated": false, diff --git a/api_docs/kbn_deeplinks_security.mdx b/api_docs/kbn_deeplinks_security.mdx index 11f956ea79df6..1eb07631bd8ae 100644 --- a/api_docs/kbn_deeplinks_security.mdx +++ b/api_docs/kbn_deeplinks_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-security title: "@kbn/deeplinks-security" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-security plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-security'] --- import kbnDeeplinksSecurityObj from './kbn_deeplinks_security.devdocs.json'; diff --git a/api_docs/kbn_deeplinks_shared.mdx b/api_docs/kbn_deeplinks_shared.mdx index 32157f98f0f62..c79f2f054e5e6 100644 --- a/api_docs/kbn_deeplinks_shared.mdx +++ b/api_docs/kbn_deeplinks_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-deeplinks-shared title: "@kbn/deeplinks-shared" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/deeplinks-shared plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/deeplinks-shared'] --- import kbnDeeplinksSharedObj from './kbn_deeplinks_shared.devdocs.json'; diff --git a/api_docs/kbn_default_nav_analytics.mdx b/api_docs/kbn_default_nav_analytics.mdx index a696f2993541d..4cc118f0038c3 100644 --- a/api_docs/kbn_default_nav_analytics.mdx +++ b/api_docs/kbn_default_nav_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-analytics title: "@kbn/default-nav-analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-analytics plugin -date: 2024-06-11 +date: 2024-06-19 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 6eced6ea2baf0..4e4c7a5e1cbbe 100644 --- a/api_docs/kbn_default_nav_devtools.mdx +++ b/api_docs/kbn_default_nav_devtools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-devtools title: "@kbn/default-nav-devtools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-devtools plugin -date: 2024-06-11 +date: 2024-06-19 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 f524715f375ec..752e3fdbd17ed 100644 --- a/api_docs/kbn_default_nav_management.mdx +++ b/api_docs/kbn_default_nav_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-management title: "@kbn/default-nav-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-management plugin -date: 2024-06-11 +date: 2024-06-19 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 fc176a52cb510..0619209c5133d 100644 --- a/api_docs/kbn_default_nav_ml.mdx +++ b/api_docs/kbn_default_nav_ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-default-nav-ml title: "@kbn/default-nav-ml" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/default-nav-ml plugin -date: 2024-06-11 +date: 2024-06-19 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 941b4224721ce..6dcc7055b3f00 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2024-06-11 +date: 2024-06-19 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 b6ed009dce3ef..5382194ebec16 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2024-06-11 +date: 2024-06-19 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 4a9f927260f3a..99148ba4dc8fd 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2024-06-11 +date: 2024-06-19 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 88b7e208ffd1c..f5414936849fc 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_discover_utils.devdocs.json b/api_docs/kbn_discover_utils.devdocs.json index 5f3f7110f3a6b..6777a3f093a66 100644 --- a/api_docs/kbn_discover_utils.devdocs.json +++ b/api_docs/kbn_discover_utils.devdocs.json @@ -136,7 +136,7 @@ "\nHelper to build multiple DataTableRecords at once, saved a bit of testing code lines" ], "signature": [ - "(docs: ", + "({\n records,\n dataView,\n processRecord,\n}: { records: ", { "pluginId": "@kbn/discover-utils", "scope": "common", @@ -144,7 +144,7 @@ "section": "def-common.EsHitRecord", "text": "EsHitRecord" }, - "[], dataView: ", + "[]; dataView?: ", { "pluginId": "dataViews", "scope": "common", @@ -152,7 +152,7 @@ "section": "def-common.DataView", "text": "DataView" }, - " | undefined, { processRecord }: { processRecord?: ((record: ", + " | undefined; processRecord?: ((record: ", { "pluginId": "@kbn/discover-utils", "scope": "common", @@ -177,57 +177,9 @@ { "parentPluginId": "@kbn/discover-utils", "id": "def-common.buildDataTableRecordList.$1", - "type": "Array", - "tags": [], - "label": "docs", - "description": [ - "Array of documents returned from Elasticsearch" - ], - "signature": [ - { - "pluginId": "@kbn/discover-utils", - "scope": "common", - "docId": "kibKbnDiscoverUtilsPluginApi", - "section": "def-common.EsHitRecord", - "text": "EsHitRecord" - }, - "[]" - ], - "path": "packages/kbn-discover-utils/src/utils/build_data_record.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "@kbn/discover-utils", - "id": "def-common.buildDataTableRecordList.$2", - "type": "Object", - "tags": [], - "label": "dataView", - "description": [ - "this current data view" - ], - "signature": [ - { - "pluginId": "dataViews", - "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" - }, - " | undefined" - ], - "path": "packages/kbn-discover-utils/src/utils/build_data_record.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - }, - { - "parentPluginId": "@kbn/discover-utils", - "id": "def-common.buildDataTableRecordList.$3", "type": "Object", "tags": [], - "label": "{ processRecord }", + "label": "{\n records,\n dataView,\n processRecord,\n}", "description": [], "path": "packages/kbn-discover-utils/src/utils/build_data_record.ts", "deprecated": false, @@ -235,7 +187,49 @@ "children": [ { "parentPluginId": "@kbn/discover-utils", - "id": "def-common.buildDataTableRecordList.$3.processRecord", + "id": "def-common.buildDataTableRecordList.$1.records", + "type": "Array", + "tags": [], + "label": "records", + "description": [], + "signature": [ + { + "pluginId": "@kbn/discover-utils", + "scope": "common", + "docId": "kibKbnDiscoverUtilsPluginApi", + "section": "def-common.EsHitRecord", + "text": "EsHitRecord" + }, + "[]" + ], + "path": "packages/kbn-discover-utils/src/utils/build_data_record.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/discover-utils", + "id": "def-common.buildDataTableRecordList.$1.dataView", + "type": "Object", + "tags": [], + "label": "dataView", + "description": [], + "signature": [ + { + "pluginId": "dataViews", + "scope": "common", + "docId": "kibDataViewsPluginApi", + "section": "def-common.DataView", + "text": "DataView" + }, + " | undefined" + ], + "path": "packages/kbn-discover-utils/src/utils/build_data_record.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/discover-utils", + "id": "def-common.buildDataTableRecordList.$1.processRecord", "type": "Function", "tags": [], "label": "processRecord", @@ -257,7 +251,7 @@ "children": [ { "parentPluginId": "@kbn/discover-utils", - "id": "def-common.buildDataTableRecordList.$3.processRecord.$1", + "id": "def-common.buildDataTableRecordList.$1.processRecord.$1", "type": "Object", "tags": [], "label": "record", @@ -285,6 +279,41 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/discover-utils", + "id": "def-common.createLogsContextService", + "type": "Function", + "tags": [], + "label": "createLogsContextService", + "description": [], + "signature": [ + "(_deps?: ", + "LogsContextServiceDeps", + ") => { isLogsIndexPattern: (indexPattern: unknown) => boolean; }" + ], + "path": "packages/kbn-discover-utils/src/data_types/logs/logs_context_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/discover-utils", + "id": "def-common.createLogsContextService.$1", + "type": "Object", + "tags": [], + "label": "_deps", + "description": [], + "signature": [ + "LogsContextServiceDeps" + ], + "path": "packages/kbn-discover-utils/src/data_types/logs/logs_context_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/discover-utils", "id": "def-common.formatFieldValue", @@ -296,8 +325,14 @@ ], "signature": [ "(value: unknown, hit: ", - "SearchHit", - ", fieldFormats: ", + { + "pluginId": "@kbn/discover-utils", + "scope": "common", + "docId": "kibKbnDiscoverUtilsPluginApi", + "section": "def-common.EsHitRecord", + "text": "EsHitRecord" + }, + ", fieldFormats: ", { "pluginId": "fieldFormats", "scope": "public", @@ -378,8 +413,13 @@ "The actual search hit (required to get highlight information from)" ], "signature": [ - "SearchHit", - "" + { + "pluginId": "@kbn/discover-utils", + "scope": "common", + "docId": "kibKbnDiscoverUtilsPluginApi", + "section": "def-common.EsHitRecord", + "text": "EsHitRecord" + } ], "path": "packages/kbn-discover-utils/src/utils/format_value.ts", "deprecated": false, @@ -1357,14 +1397,40 @@ "section": "def-common.EsHitRecord", "text": "EsHitRecord" }, - " extends Omit<", - "SearchHit", - ", \"_source\">" + " extends Omit" ], "path": "packages/kbn-discover-utils/src/types.ts", "deprecated": false, "trackAdoption": false, "children": [ + { + "parentPluginId": "@kbn/discover-utils", + "id": "def-common.EsHitRecord._index", + "type": "string", + "tags": [], + "label": "_index", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-discover-utils/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/discover-utils", + "id": "def-common.EsHitRecord._id", + "type": "string", + "tags": [], + "label": "_id", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-discover-utils/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "@kbn/discover-utils", "id": "def-common.EsHitRecord._source", @@ -1770,6 +1836,52 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/discover-utils", + "id": "def-common.LogsContextService", + "type": "Interface", + "tags": [], + "label": "LogsContextService", + "description": [], + "path": "packages/kbn-discover-utils/src/data_types/logs/logs_context_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/discover-utils", + "id": "def-common.LogsContextService.isLogsIndexPattern", + "type": "Function", + "tags": [], + "label": "isLogsIndexPattern", + "description": [], + "signature": [ + "(indexPattern: unknown) => boolean" + ], + "path": "packages/kbn-discover-utils/src/data_types/logs/logs_context_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/discover-utils", + "id": "def-common.LogsContextService.isLogsIndexPattern.$1", + "type": "Unknown", + "tags": [], + "label": "indexPattern", + "description": [], + "signature": [ + "unknown" + ], + "path": "packages/kbn-discover-utils/src/data_types/logs/logs_context_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/discover-utils", "id": "def-common.LogStackTraceFields", @@ -1887,6 +1999,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/discover-utils", + "id": "def-common.DEFAULT_ALLOWED_LOGS_BASE_PATTERNS", + "type": "Array", + "tags": [], + "label": "DEFAULT_ALLOWED_LOGS_BASE_PATTERNS", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-discover-utils/src/data_types/logs/logs_context_service.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/discover-utils", "id": "def-common.DEFAULT_COLUMNS_SETTING", diff --git a/api_docs/kbn_discover_utils.mdx b/api_docs/kbn_discover_utils.mdx index 81855b3950f86..100bd04b1e3aa 100644 --- a/api_docs/kbn_discover_utils.mdx +++ b/api_docs/kbn_discover_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-discover-utils title: "@kbn/discover-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/discover-utils plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/discover-utils'] --- import kbnDiscoverUtilsObj from './kbn_discover_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/k | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 112 | 0 | 84 | 0 | +| 120 | 0 | 94 | 1 | ## Common diff --git a/api_docs/kbn_doc_links.devdocs.json b/api_docs/kbn_doc_links.devdocs.json index 84badfab59a72..a0be6948d2da6 100644 --- a/api_docs/kbn_doc_links.devdocs.json +++ b/api_docs/kbn_doc_links.devdocs.json @@ -840,7 +840,7 @@ "label": "fleet", "description": [], "signature": [ - "{ readonly beatsAgentComparison: string; readonly guide: string; readonly fleetServer: string; readonly fleetServerAddFleetServer: string; readonly esSettings: string; readonly settings: string; readonly logstashSettings: string; readonly kafkaSettings: string; readonly settingsFleetServerHostSettings: string; readonly settingsFleetServerProxySettings: string; readonly troubleshooting: string; readonly elasticAgent: string; readonly datastreams: string; readonly datastreamsILM: string; readonly datastreamsNamingScheme: string; readonly datastreamsManualRollover: string; readonly datastreamsTSDS: string; readonly datastreamsTSDSMetrics: string; readonly installElasticAgent: string; readonly installElasticAgentStandalone: string; readonly packageSignatures: string; readonly upgradeElasticAgent: string; readonly learnMoreBlog: string; readonly apiKeysLearnMore: string; readonly onPremRegistry: string; readonly secureLogstash: string; readonly agentPolicy: string; readonly api: string; readonly uninstallAgent: string; readonly installAndUninstallIntegrationAssets: string; readonly elasticAgentInputConfiguration: string; readonly policySecrets: string; readonly remoteESOoutput: string; readonly performancePresets: string; readonly scalingKubernetesResourcesAndLimits: string; readonly roleAndPrivileges: string; readonly proxiesSettings: string; }" + "{ readonly beatsAgentComparison: string; readonly guide: string; readonly fleetServer: string; readonly fleetServerAddFleetServer: string; readonly esSettings: string; readonly settings: string; readonly logstashSettings: string; readonly kafkaSettings: string; readonly settingsFleetServerHostSettings: string; readonly settingsFleetServerProxySettings: string; readonly troubleshooting: string; readonly elasticAgent: string; readonly datastreams: string; readonly datastreamsILM: string; readonly datastreamsNamingScheme: string; readonly datastreamsManualRollover: string; readonly datastreamsTSDS: string; readonly datastreamsTSDSMetrics: string; readonly datastreamsDownsampling: string; readonly installElasticAgent: string; readonly installElasticAgentStandalone: string; readonly packageSignatures: string; readonly upgradeElasticAgent: string; readonly learnMoreBlog: string; readonly apiKeysLearnMore: string; readonly onPremRegistry: string; readonly secureLogstash: string; readonly agentPolicy: string; readonly api: string; readonly uninstallAgent: string; readonly installAndUninstallIntegrationAssets: string; readonly elasticAgentInputConfiguration: string; readonly policySecrets: string; readonly remoteESOoutput: string; readonly performancePresets: string; readonly scalingKubernetesResourcesAndLimits: string; readonly roleAndPrivileges: string; readonly proxiesSettings: string; }" ], "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, @@ -854,7 +854,7 @@ "label": "ecs", "description": [], "signature": [ - "{ readonly guide: string; }" + "{ readonly guide: string; readonly dataStreams: string; }" ], "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 7db29ead76c9e..949b00b86eed2 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2024-06-11 +date: 2024-06-19 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 fa3c5dada0849..b709c1656cde5 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2024-06-11 +date: 2024-06-19 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 905102e020b6e..20f8a3b4caecd 100644 --- a/api_docs/kbn_dom_drag_drop.mdx +++ b/api_docs/kbn_dom_drag_drop.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dom-drag-drop title: "@kbn/dom-drag-drop" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dom-drag-drop plugin -date: 2024-06-11 +date: 2024-06-19 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.devdocs.json b/api_docs/kbn_ebt.devdocs.json new file mode 100644 index 0000000000000..82e6770af4fac --- /dev/null +++ b/api_docs/kbn_ebt.devdocs.json @@ -0,0 +1,4130 @@ +{ + "id": "@kbn/ebt", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3BrowserShipper", + "type": "Class", + "tags": [], + "label": "ElasticV3BrowserShipper", + "description": [ + "\nElastic V3 shipper to use in the browser." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ElasticV3BrowserShipper", + "text": "ElasticV3BrowserShipper" + }, + " implements ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.IShipper", + "text": "IShipper" + } + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3BrowserShipper.shipperName", + "type": "string", + "tags": [], + "label": "shipperName", + "description": [ + "Shipper's unique name" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3BrowserShipper.telemetryCounter$", + "type": "Object", + "tags": [], + "label": "telemetryCounter$", + "description": [ + "Observable to emit the stats of the processed events." + ], + "signature": [ + "Subject", + "<", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.TelemetryCounter", + "text": "TelemetryCounter" + }, + ">" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3BrowserShipper.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [ + "\nCreates a new instance of the {@link ElasticV3BrowserShipper}." + ], + "signature": [ + "any" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3BrowserShipper.Unnamed.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [ + "{@link ElasticV3ShipperOptions }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ElasticV3ShipperOptions", + "text": "ElasticV3ShipperOptions" + } + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3BrowserShipper.Unnamed.$2", + "type": "Object", + "tags": [], + "label": "initContext", + "description": [ + "{@link AnalyticsClientInitContext }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AnalyticsClientInitContext", + "text": "AnalyticsClientInitContext" + } + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3BrowserShipper.extendContext", + "type": "Function", + "tags": [], + "label": "extendContext", + "description": [ + "\nUses the `cluster_uuid` and `license_id` from the context to hold them in memory for the generation of the headers\nused later on in the HTTP request." + ], + "signature": [ + "(newContext: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventContext", + "text": "EventContext" + }, + ") => void" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3BrowserShipper.extendContext.$1", + "type": "Object", + "tags": [], + "label": "newContext", + "description": [ + "The full new context to set {@link EventContext }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventContext", + "text": "EventContext" + } + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3BrowserShipper.optIn", + "type": "Function", + "tags": [], + "label": "optIn", + "description": [ + "\nWhen `false`, it flushes the internal queue and stops sending events." + ], + "signature": [ + "(isOptedIn: boolean) => void" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3BrowserShipper.optIn.$1", + "type": "boolean", + "tags": [], + "label": "isOptedIn", + "description": [ + "`true` for resume sending events. `false` to stop." + ], + "signature": [ + "boolean" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3BrowserShipper.reportEvents", + "type": "Function", + "tags": [], + "label": "reportEvents", + "description": [ + "\nEnqueues the events to be sent to in a batched approach." + ], + "signature": [ + "(events: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.Event", + "text": "Event" + }, + ">[]) => void" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3BrowserShipper.reportEvents.$1", + "type": "Array", + "tags": [], + "label": "events", + "description": [ + "batched events {@link Event }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.Event", + "text": "Event" + }, + ">[]" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3BrowserShipper.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nTriggers a flush of the internal queue to attempt to send any events held in the queue\nand resolves the returned promise once the queue is emptied." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3BrowserShipper.shutdown", + "type": "Function", + "tags": [], + "label": "shutdown", + "description": [ + "\nShuts down the shipper.\nTriggers a flush of the internal queue to attempt to send any events held in the queue." + ], + "signature": [ + "() => void" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3ServerShipper", + "type": "Class", + "tags": [], + "label": "ElasticV3ServerShipper", + "description": [ + "\nElastic V3 shipper to use on the server side." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ElasticV3ServerShipper", + "text": "ElasticV3ServerShipper" + }, + " implements ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.IShipper", + "text": "IShipper" + } + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3ServerShipper.shipperName", + "type": "string", + "tags": [], + "label": "shipperName", + "description": [ + "Shipper's unique name" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3ServerShipper.telemetryCounter$", + "type": "Object", + "tags": [], + "label": "telemetryCounter$", + "description": [ + "Observable to emit the stats of the processed events." + ], + "signature": [ + "Subject", + "<", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.TelemetryCounter", + "text": "TelemetryCounter" + }, + ">" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3ServerShipper.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [ + "\nCreates a new instance of the {@link ElasticV3ServerShipper}." + ], + "signature": [ + "any" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3ServerShipper.Unnamed.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [ + "{@link ElasticV3ShipperOptions }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ElasticV3ShipperOptions", + "text": "ElasticV3ShipperOptions" + } + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3ServerShipper.Unnamed.$2", + "type": "Object", + "tags": [], + "label": "initContext", + "description": [ + "{@link AnalyticsClientInitContext }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AnalyticsClientInitContext", + "text": "AnalyticsClientInitContext" + } + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3ServerShipper.extendContext", + "type": "Function", + "tags": [], + "label": "extendContext", + "description": [ + "\nUses the `cluster_uuid` and `license_id` from the context to hold them in memory for the generation of the headers\nused later on in the HTTP request." + ], + "signature": [ + "(newContext: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventContext", + "text": "EventContext" + }, + ") => void" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3ServerShipper.extendContext.$1", + "type": "Object", + "tags": [], + "label": "newContext", + "description": [ + "The full new context to set {@link EventContext }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventContext", + "text": "EventContext" + } + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3ServerShipper.optIn", + "type": "Function", + "tags": [], + "label": "optIn", + "description": [ + "\nWhen `false`, it flushes the internal queue and stops sending events." + ], + "signature": [ + "(isOptedIn: boolean) => void" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3ServerShipper.optIn.$1", + "type": "boolean", + "tags": [], + "label": "isOptedIn", + "description": [ + "`true` for resume sending events. `false` to stop." + ], + "signature": [ + "boolean" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3ServerShipper.reportEvents", + "type": "Function", + "tags": [], + "label": "reportEvents", + "description": [ + "\nEnqueues the events to be sent via the leaky bucket algorithm." + ], + "signature": [ + "(events: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.Event", + "text": "Event" + }, + ">[]) => void" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3ServerShipper.reportEvents.$1", + "type": "Array", + "tags": [], + "label": "events", + "description": [ + "batched events {@link Event }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.Event", + "text": "Event" + }, + ">[]" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3ServerShipper.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nTriggers a flush of the internal queue to attempt to send any events held in the queue\nand resolves the returned promise once the queue is emptied." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3ServerShipper.shutdown", + "type": "Function", + "tags": [], + "label": "shutdown", + "description": [ + "\nShuts down the shipper.\nTriggers a flush of the internal queue to attempt to send any events held in the queue." + ], + "signature": [ + "() => void" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStoryShipper", + "type": "Class", + "tags": [], + "label": "FullStoryShipper", + "description": [ + "\nFullStory shipper." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.FullStoryShipper", + "text": "FullStoryShipper" + }, + " implements ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.IShipper", + "text": "IShipper" + } + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStoryShipper.shipperName", + "type": "string", + "tags": [], + "label": "shipperName", + "description": [ + "Shipper's unique name" + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStoryShipper.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [ + "\nCreates a new instance of the FullStoryShipper." + ], + "signature": [ + "any" + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStoryShipper.Unnamed.$1", + "type": "Object", + "tags": [], + "label": "config", + "description": [ + "{@link FullStoryShipperConfig }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.FullStoryShipperConfig", + "text": "FullStoryShipperConfig" + } + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStoryShipper.Unnamed.$2", + "type": "Object", + "tags": [], + "label": "initContext", + "description": [ + "{@link AnalyticsClientInitContext }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AnalyticsClientInitContext", + "text": "AnalyticsClientInitContext" + } + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStoryShipper.extendContext", + "type": "Function", + "tags": [], + "label": "extendContext", + "description": [ + "\nCalls `fs.identify`, `fs.setUserVars` and `fs.setVars` depending on the fields provided in the newContext." + ], + "signature": [ + "(newContext: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventContext", + "text": "EventContext" + }, + ") => void" + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStoryShipper.extendContext.$1", + "type": "Object", + "tags": [], + "label": "newContext", + "description": [ + "The full new context to set {@link EventContext }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventContext", + "text": "EventContext" + } + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStoryShipper.optIn", + "type": "Function", + "tags": [], + "label": "optIn", + "description": [ + "\nStops/restarts the shipping mechanism based on the value of isOptedIn" + ], + "signature": [ + "(isOptedIn: boolean) => void" + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStoryShipper.optIn.$1", + "type": "boolean", + "tags": [], + "label": "isOptedIn", + "description": [ + "`true` for resume sending events. `false` to stop." + ], + "signature": [ + "boolean" + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStoryShipper.reportEvents", + "type": "Function", + "tags": [], + "label": "reportEvents", + "description": [ + "\nFilters the events by the eventTypesAllowlist from the config.\nThen it transforms the event into a FS valid format and calls `fs.event`." + ], + "signature": [ + "(events: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.Event", + "text": "Event" + }, + ">[]) => void" + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStoryShipper.reportEvents.$1", + "type": "Array", + "tags": [], + "label": "events", + "description": [ + "batched events {@link Event }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.Event", + "text": "Event" + }, + ">[]" + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStoryShipper.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nFlushes all internal queues of the shipper.\nIt doesn't really do anything inside because this shipper doesn't hold any internal queues." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStoryShipper.shutdown", + "type": "Function", + "tags": [], + "label": "shutdown", + "description": [ + "\nShuts down the shipper." + ], + "signature": [ + "() => void" + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], + "functions": [], + "interfaces": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.AnalyticsClientInitContext", + "type": "Interface", + "tags": [], + "label": "AnalyticsClientInitContext", + "description": [ + "\nGeneral settings of the analytics client" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.AnalyticsClientInitContext.isDev", + "type": "boolean", + "tags": [], + "label": "isDev", + "description": [ + "\nBoolean indicating if it's running in developer mode." + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.AnalyticsClientInitContext.sendTo", + "type": "CompoundType", + "tags": [], + "label": "sendTo", + "description": [ + "\nSpecify if the shippers should send their data to the production or staging environments." + ], + "signature": [ + "\"production\" | \"staging\"" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.AnalyticsClientInitContext.logger", + "type": "Object", + "tags": [], + "label": "logger", + "description": [ + "\nApplication-provided logger." + ], + "signature": [ + { + "pluginId": "@kbn/logging", + "scope": "common", + "docId": "kibKbnLoggingPluginApi", + "section": "def-common.Logger", + "text": "Logger" + } + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ContextProviderOpts", + "type": "Interface", + "tags": [], + "label": "ContextProviderOpts", + "description": [ + "\nDefinition of a context provider" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ContextProviderOpts", + "text": "ContextProviderOpts" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ContextProviderOpts.name", + "type": "string", + "tags": [], + "label": "name", + "description": [ + "\nThe name of the provider." + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ContextProviderOpts.context$", + "type": "Object", + "tags": [], + "label": "context$", + "description": [ + "\nObservable that emits the custom context." + ], + "signature": [ + "Observable", + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ContextProviderOpts.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [ + "\nSchema declaring and documenting the expected output in the context$\n" + ], + "signature": [ + "{ [Key in keyof Required]: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaValue", + "text": "SchemaValue" + }, + "; }" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3ShipperOptions", + "type": "Interface", + "tags": [], + "label": "ElasticV3ShipperOptions", + "description": [ + "\nOptions for the Elastic V3 shipper" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/common/src/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3ShipperOptions.channelName", + "type": "string", + "tags": [], + "label": "channelName", + "description": [ + "\nThe name of the channel to stream all the events to." + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/common/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3ShipperOptions.version", + "type": "string", + "tags": [], + "label": "version", + "description": [ + "\nThe product's version." + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/common/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3ShipperOptions.sendTo", + "type": "CompoundType", + "tags": [], + "label": "sendTo", + "description": [ + "\nProvide it to override the Analytics client's default configuration." + ], + "signature": [ + "\"production\" | \"staging\" | undefined" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/common/src/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ElasticV3ShipperOptions.debug", + "type": "CompoundType", + "tags": [], + "label": "debug", + "description": [ + "\nShould show debug information about the requests it makes to the V3 API." + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/analytics/ebt/shippers/elastic_v3/common/src/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.Event", + "type": "Interface", + "tags": [], + "label": "Event", + "description": [ + "\nDefinition of the full event structure" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.Event", + "text": "Event" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.Event.timestamp", + "type": "string", + "tags": [], + "label": "timestamp", + "description": [ + "\nThe time the event was generated in ISO format." + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.Event.event_type", + "type": "string", + "tags": [], + "label": "event_type", + "description": [ + "\nThe event type." + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.Event.properties", + "type": "Uncategorized", + "tags": [], + "label": "properties", + "description": [ + "\nThe specific properties of the event type." + ], + "signature": [ + "Properties" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.Event.context", + "type": "Object", + "tags": [], + "label": "context", + "description": [ + "\nThe {@link EventContext} enriched during the processing pipeline." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventContext", + "text": "EventContext" + } + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.EventContext", + "type": "Interface", + "tags": [], + "label": "EventContext", + "description": [ + "\nDefinition of the context that can be appended to the events through the {@link IAnalyticsClient.registerContextProvider}." + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.EventContext.cluster_uuid", + "type": "string", + "tags": [], + "label": "cluster_uuid", + "description": [ + "\nThe UUID of the cluster" + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.EventContext.cluster_name", + "type": "string", + "tags": [], + "label": "cluster_name", + "description": [ + "\nThe name of the cluster." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.EventContext.license_id", + "type": "string", + "tags": [], + "label": "license_id", + "description": [ + "\nThe license ID." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.EventContext.userId", + "type": "string", + "tags": [], + "label": "userId", + "description": [ + "\nThe unique user ID." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.EventContext.cloudId", + "type": "string", + "tags": [], + "label": "cloudId", + "description": [ + "\nThe Cloud ID." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.EventContext.isElasticCloudUser", + "type": "CompoundType", + "tags": [], + "label": "isElasticCloudUser", + "description": [ + "\n`true` if the user is logged in via the Elastic Cloud authentication provider." + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.EventContext.version", + "type": "string", + "tags": [], + "label": "version", + "description": [ + "\nThe product's version." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.EventContext.pageName", + "type": "string", + "tags": [], + "label": "pageName", + "description": [ + "\nThe name of the current page." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.EventContext.applicationId", + "type": "string", + "tags": [], + "label": "applicationId", + "description": [ + "\nThe current application ID." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.EventContext.entityId", + "type": "string", + "tags": [], + "label": "entityId", + "description": [ + "\nThe current entity ID (dashboard ID, visualization ID, etc.)." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.EventContext.Unnamed", + "type": "IndexSignature", + "tags": [], + "label": "[key: string]: unknown", + "description": [ + "\nAdditional keys are allowed." + ], + "signature": [ + "[key: string]: unknown" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.EventTypeOpts", + "type": "Interface", + "tags": [], + "label": "EventTypeOpts", + "description": [ + "\nDefinition of an Event Type." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventTypeOpts", + "text": "EventTypeOpts" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.EventTypeOpts.eventType", + "type": "string", + "tags": [], + "label": "eventType", + "description": [ + "\nThe event type's unique name." + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.EventTypeOpts.schema", + "type": "Object", + "tags": [], + "label": "schema", + "description": [ + "\nSchema declaring and documenting the expected structure of this event type.\n" + ], + "signature": [ + "{ [Key in keyof Required]: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaValue", + "text": "SchemaValue" + }, + "; }" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStoryShipperConfig", + "type": "Interface", + "tags": [], + "label": "FullStoryShipperConfig", + "description": [ + "\nFullStory shipper configuration." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.FullStoryShipperConfig", + "text": "FullStoryShipperConfig" + }, + " extends ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.FullStorySnippetConfig", + "text": "FullStorySnippetConfig" + } + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStoryShipperConfig.eventTypesAllowlist", + "type": "Array", + "tags": [], + "label": "eventTypesAllowlist", + "description": [ + "\nFullStory's custom events rate limit is very aggressive.\nIf this setting is provided, it'll only send the event types specified in this list." + ], + "signature": [ + "string[] | undefined" + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStoryShipperConfig.pageVarsDebounceTimeMs", + "type": "number", + "tags": [], + "label": "pageVarsDebounceTimeMs", + "description": [ + "\nFullStory only allows calling setVars('page') once per navigation.\nThis setting defines how much time to hold from calling the API while additional lazy context is being resolved." + ], + "signature": [ + "number | undefined" + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStorySnippetConfig", + "type": "Interface", + "tags": [], + "label": "FullStorySnippetConfig", + "description": [ + "\nFullStory basic configuration." + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/load_snippet.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStorySnippetConfig.fullStoryOrgId", + "type": "string", + "tags": [], + "label": "fullStoryOrgId", + "description": [ + "\nThe FullStory account id." + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/load_snippet.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStorySnippetConfig.host", + "type": "string", + "tags": [], + "label": "host", + "description": [ + "\nThe host to send the data to. Used to overcome AdBlockers by using custom DNSs.\nIf not specified, it defaults to `fullstory.com`." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/load_snippet.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStorySnippetConfig.scriptUrl", + "type": "string", + "tags": [], + "label": "scriptUrl", + "description": [ + "\nThe URL to load the FullStory client from. Falls back to `edge.fullstory.com/s/fs.js` if not specified." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/load_snippet.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStorySnippetConfig.debug", + "type": "CompoundType", + "tags": [], + "label": "debug", + "description": [ + "\nWhether the debug logs should be printed to the console." + ], + "signature": [ + "boolean | undefined" + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/load_snippet.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.FullStorySnippetConfig.namespace", + "type": "string", + "tags": [], + "label": "namespace", + "description": [ + "\nThe name of the variable where the API is stored: `window[namespace]`. Defaults to `FS`." + ], + "signature": [ + "string | undefined" + ], + "path": "packages/analytics/ebt/shippers/fullstory/src/load_snippet.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IAnalyticsClient", + "type": "Interface", + "tags": [], + "label": "IAnalyticsClient", + "description": [ + "\nAnalytics client's public APIs" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IAnalyticsClient.reportEvent", + "type": "Function", + "tags": [ + "track-adoption" + ], + "label": "reportEvent", + "description": [ + "\nReports a telemetry event." + ], + "signature": [ + "(eventType: string, eventData: EventTypeData) => void" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": true, + "references": [ + { + "plugin": "@kbn/core-notifications-browser-internal", + "path": "packages/core/notifications/core-notifications-browser-internal/src/toasts/telemetry/event_reporter.ts" + }, + { + "plugin": "@kbn/core-notifications-browser-internal", + "path": "packages/core/notifications/core-notifications-browser-internal/src/toasts/telemetry/event_reporter.ts" + }, + { + "plugin": "@kbn/ebt-tools", + "path": "packages/kbn-ebt-tools/src/performance_metric_events/helpers.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-root-server-internal", + "path": "packages/core/root/core-root-server-internal/src/events/kibana_started.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.ts" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "@kbn/cloud", + "path": "packages/cloud/connection_details/kibana/kibana_connection_details_provider.tsx" + }, + { + "plugin": "dashboard", + "path": "src/plugins/dashboard/public/services/analytics/types.ts" + }, + { + "plugin": "dashboard", + "path": "src/plugins/dashboard/public/services/analytics/analytics_service.ts" + }, + { + "plugin": "observabilityAIAssistant", + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/recall_and_score.ts" + }, + { + "plugin": "observabilityAIAssistant", + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/analytics/index.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" + }, + { + "plugin": "fleet", + "path": "x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts" + }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/lib/langchain/elasticsearch_store/elasticsearch_store.ts" + }, + { + "plugin": "elasticAssistant", + "path": "x-pack/plugins/elastic_assistant/server/lib/langchain/elasticsearch_store/elasticsearch_store.ts" + }, + { + "plugin": "globalSearchBar", + "path": "x-pack/plugins/global_search_bar/public/telemetry/event_reporter.ts" + }, + { + "plugin": "globalSearchBar", + "path": "x-pack/plugins/global_search_bar/public/telemetry/event_reporter.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "osquery", + "path": "x-pack/plugins/osquery/server/lib/telemetry/sender.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_client.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts" + }, + { + "plugin": "reporting", + "path": "x-pack/plugins/reporting/server/usage/event_tracker.ts" + }, + { + "plugin": "searchPlayground", + "path": "x-pack/plugins/search_playground/server/routes.ts" + }, + { + "plugin": "securitySolutionServerless", + "path": "x-pack/plugins/security_solution_serverless/server/task_manager/nlp_cleanup_task/nlp_cleanup_task.ts" + }, + { + "plugin": "securitySolutionServerless", + "path": "x-pack/plugins/security_solution_serverless/server/task_manager/nlp_cleanup_task/nlp_cleanup_task.ts" + }, + { + "plugin": "apm", + "path": "x-pack/plugins/observability_solution/apm/public/services/telemetry/telemetry_client.ts" + }, + { + "plugin": "observabilityLogsExplorer", + "path": "x-pack/plugins/observability_solution/observability_logs_explorer/public/state_machines/observability_logs_explorer/src/telemetry_events.ts" + }, + { + "plugin": "observabilityOnboarding", + "path": "x-pack/plugins/observability_solution/observability_onboarding/public/hooks/use_flow_progress_telemetry.ts" + }, + { + "plugin": "observabilityOnboarding", + "path": "x-pack/plugins/observability_solution/observability_onboarding/public/application/app.tsx" + }, + { + "plugin": "observabilityAIAssistant", + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_chat_service.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-mocks", + "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-mocks", + "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "dashboard", + "path": "src/plugins/dashboard/public/services/analytics/analytics.stub.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-root-browser-internal", + "path": "packages/core/root/core-root-browser-internal/src/core_system.test.ts" + }, + { + "plugin": "@kbn/core-analytics-server-mocks", + "path": "packages/core/analytics/core-analytics-server-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "@kbn/core-analytics-server-mocks", + "path": "packages/core/analytics/core-analytics-server-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/server/analytics/analytics_service.test.ts" + }, + { + "plugin": "apm", + "path": "x-pack/plugins/observability_solution/apm/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "apm", + "path": "x-pack/plugins/observability_solution/apm/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/observability_solution/infra/public/services/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.test.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.test.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.test.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.test.ts" + }, + { + "plugin": "@kbn/core-analytics-server-mocks", + "path": "packages/core/analytics/core-analytics-server-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_clicks.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.mocks.ts" + } + ], + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IAnalyticsClient.reportEvent.$1", + "type": "string", + "tags": [], + "label": "eventType", + "description": [ + "The event type registered via the `registerEventType` API." + ], + "signature": [ + "string" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IAnalyticsClient.reportEvent.$2", + "type": "Uncategorized", + "tags": [], + "label": "eventData", + "description": [ + "The properties matching the schema declared in the `registerEventType` API." + ], + "signature": [ + "EventTypeData" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IAnalyticsClient.registerEventType", + "type": "Function", + "tags": [], + "label": "registerEventType", + "description": [ + "\nRegisters the event type that will be emitted via the reportEvent API." + ], + "signature": [ + "(eventTypeOps: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventTypeOpts", + "text": "EventTypeOpts" + }, + ") => void" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IAnalyticsClient.registerEventType.$1", + "type": "Object", + "tags": [], + "label": "eventTypeOps", + "description": [ + "The definition of the event type {@link EventTypeOpts }." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventTypeOpts", + "text": "EventTypeOpts" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IAnalyticsClient.registerShipper", + "type": "Function", + "tags": [], + "label": "registerShipper", + "description": [ + "\nSet up the shipper that will be used to report the telemetry events." + ], + "signature": [ + "(Shipper: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ShipperClassConstructor", + "text": "ShipperClassConstructor" + }, + ", shipperConfig: ShipperConfig, opts?: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.RegisterShipperOpts", + "text": "RegisterShipperOpts" + }, + " | undefined) => void" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IAnalyticsClient.registerShipper.$1", + "type": "Object", + "tags": [], + "label": "Shipper", + "description": [ + "The {@link IShipper } class to instantiate the shipper." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ShipperClassConstructor", + "text": "ShipperClassConstructor" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IAnalyticsClient.registerShipper.$2", + "type": "Uncategorized", + "tags": [], + "label": "shipperConfig", + "description": [ + "The config specific to the Shipper to instantiate." + ], + "signature": [ + "ShipperConfig" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IAnalyticsClient.registerShipper.$3", + "type": "Object", + "tags": [], + "label": "opts", + "description": [ + "Additional options to register the shipper {@link RegisterShipperOpts }." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.RegisterShipperOpts", + "text": "RegisterShipperOpts" + }, + " | undefined" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IAnalyticsClient.optIn", + "type": "Function", + "tags": [], + "label": "optIn", + "description": [ + "\nUsed to control the user's consent to report the data.\nIn the advanced mode, it allows to \"cherry-pick\" which events and shippers are enabled/disabled." + ], + "signature": [ + "(optInConfig: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.OptInConfig", + "text": "OptInConfig" + }, + ") => void" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IAnalyticsClient.optIn.$1", + "type": "Object", + "tags": [], + "label": "optInConfig", + "description": [ + "{@link OptInConfig }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.OptInConfig", + "text": "OptInConfig" + } + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IAnalyticsClient.registerContextProvider", + "type": "Function", + "tags": [ + "track-adoption" + ], + "label": "registerContextProvider", + "description": [ + "\nRegisters the context provider to enrich any reported events." + ], + "signature": [ + "(contextProviderOpts: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ContextProviderOpts", + "text": "ContextProviderOpts" + }, + ") => void" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": true, + "references": [ + { + "plugin": "@kbn/core-execution-context-browser-internal", + "path": "packages/core/execution-context/core-execution-context-browser-internal/src/execution_context_service.ts" + }, + { + "plugin": "@kbn/core-application-browser-internal", + "path": "packages/core/application/core-application-browser-internal/src/register_analytics_context_provider.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-chrome-browser-internal", + "path": "packages/core/chrome/core-chrome-browser-internal/src/register_analytics_context_provider.ts" + }, + { + "plugin": "@kbn/core-elasticsearch-server-internal", + "path": "packages/core/elasticsearch/core-elasticsearch-server-internal/src/register_analytics_context_provider.ts" + }, + { + "plugin": "@kbn/core-environment-server-internal", + "path": "packages/core/environment/core-environment-server-internal/src/environment_service.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-plugins-server-internal", + "path": "packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts" + }, + { + "plugin": "cloud", + "path": "x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts" + }, + { + "plugin": "licensing", + "path": "x-pack/plugins/licensing/common/register_analytics_context_provider.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.ts" + }, + { + "plugin": "telemetry", + "path": "src/plugins/telemetry/server/plugin.ts" + }, + { + "plugin": "telemetry", + "path": "src/plugins/telemetry/public/plugin.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/common/lib/telemetry/telemetry_service.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-mocks", + "path": "packages/core/analytics/core-analytics-browser-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/analytics_service.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "security", + "path": "x-pack/plugins/security/public/analytics/register_user_context.test.ts" + }, + { + "plugin": "@kbn/core-application-browser-internal", + "path": "packages/core/application/core-application-browser-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-application-browser-internal", + "path": "packages/core/application/core-application-browser-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-application-browser-internal", + "path": "packages/core/application/core-application-browser-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-chrome-browser-internal", + "path": "packages/core/chrome/core-chrome-browser-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-chrome-browser-internal", + "path": "packages/core/chrome/core-chrome-browser-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-chrome-browser-internal", + "path": "packages/core/chrome/core-chrome-browser-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-execution-context-browser-internal", + "path": "packages/core/execution-context/core-execution-context-browser-internal/src/execution_context_service.test.ts" + }, + { + "plugin": "@kbn/core-execution-context-browser-internal", + "path": "packages/core/execution-context/core-execution-context-browser-internal/src/execution_context_service.test.ts" + }, + { + "plugin": "@kbn/core-analytics-server-mocks", + "path": "packages/core/analytics/core-analytics-server-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "@kbn/core-analytics-server-mocks", + "path": "packages/core/analytics/core-analytics-server-mocks/src/analytics_service.mock.ts" + }, + { + "plugin": "@kbn/core-elasticsearch-server-internal", + "path": "packages/core/elasticsearch/core-elasticsearch-server-internal/src/register_analytics_context_provider.test.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.test.ts" + }, + { + "plugin": "@kbn/core-status-server-internal", + "path": "packages/core/status/core-status-server-internal/src/status_service.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-browser-internal", + "path": "packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.test.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.mocks.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.ts" + }, + { + "plugin": "@kbn/core-analytics-server-internal", + "path": "packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.ts" + } + ], + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IAnalyticsClient.registerContextProvider.$1", + "type": "Object", + "tags": [], + "label": "contextProviderOpts", + "description": [ + "{@link ContextProviderOpts }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ContextProviderOpts", + "text": "ContextProviderOpts" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IAnalyticsClient.removeContextProvider", + "type": "Function", + "tags": [], + "label": "removeContextProvider", + "description": [ + "\nRemoves the context provider and stop enriching the events from its context." + ], + "signature": [ + "(contextProviderName: string) => void" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IAnalyticsClient.removeContextProvider.$1", + "type": "string", + "tags": [], + "label": "contextProviderName", + "description": [ + "The name of the context provider to remove." + ], + "signature": [ + "string" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IAnalyticsClient.telemetryCounter$", + "type": "Object", + "tags": [], + "label": "telemetryCounter$", + "description": [ + "\nObservable to emit the stats of the processed events." + ], + "signature": [ + "Observable", + "<", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.TelemetryCounter", + "text": "TelemetryCounter" + }, + ">" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IAnalyticsClient.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nForces all shippers to send all their enqueued events and fulfills the returned promise." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IAnalyticsClient.shutdown", + "type": "Function", + "tags": [], + "label": "shutdown", + "description": [ + "\nStops the client. Flushing any pending events in the process." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IShipper", + "type": "Interface", + "tags": [], + "label": "IShipper", + "description": [ + "\nBasic structure of a Shipper" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IShipper.reportEvents", + "type": "Function", + "tags": [], + "label": "reportEvents", + "description": [ + "\nAdapts and ships the event to the persisting/analytics solution." + ], + "signature": [ + "(events: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.Event", + "text": "Event" + }, + ">[]) => void" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IShipper.reportEvents.$1", + "type": "Array", + "tags": [], + "label": "events", + "description": [ + "batched events {@link Event }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.Event", + "text": "Event" + }, + ">[]" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IShipper.optIn", + "type": "Function", + "tags": [], + "label": "optIn", + "description": [ + "\nStops/restarts the shipping mechanism based on the value of isOptedIn" + ], + "signature": [ + "(isOptedIn: boolean) => void" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IShipper.optIn.$1", + "type": "boolean", + "tags": [], + "label": "isOptedIn", + "description": [ + "`true` for resume sending events. `false` to stop." + ], + "signature": [ + "boolean" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IShipper.extendContext", + "type": "Function", + "tags": [], + "label": "extendContext", + "description": [ + "\nPerform any necessary calls to the persisting/analytics solution to set the event's context." + ], + "signature": [ + "((newContext: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventContext", + "text": "EventContext" + }, + ") => void) | undefined" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IShipper.extendContext.$1", + "type": "Object", + "tags": [], + "label": "newContext", + "description": [ + "The full new context to set {@link EventContext }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.EventContext", + "text": "EventContext" + } + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IShipper.telemetryCounter$", + "type": "Object", + "tags": [], + "label": "telemetryCounter$", + "description": [ + "\nObservable to emit the stats of the processed events." + ], + "signature": [ + "Observable", + "<", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.TelemetryCounter", + "text": "TelemetryCounter" + }, + "> | undefined" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IShipper.flush", + "type": "Function", + "tags": [], + "label": "flush", + "description": [ + "\nSends all the enqueued events and fulfills the returned promise." + ], + "signature": [ + "() => Promise" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.IShipper.shutdown", + "type": "Function", + "tags": [], + "label": "shutdown", + "description": [ + "\nShutdown the shipper." + ], + "signature": [ + "() => void" + ], + "path": "packages/analytics/ebt/client/src/shippers/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.OptInConfig", + "type": "Interface", + "tags": [], + "label": "OptInConfig", + "description": [ + "\nOptions for the optIn API" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.OptInConfig.global", + "type": "Object", + "tags": [], + "label": "global", + "description": [ + "\nControls the global enabled/disabled behaviour of the client and shippers." + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.OptInConfigPerType", + "text": "OptInConfigPerType" + } + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.OptInConfig.event_types", + "type": "Object", + "tags": [], + "label": "event_types", + "description": [ + "\nControls if an event type should be disabled for a specific type of shipper." + ], + "signature": [ + "Record | undefined" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.OptInConfigPerType", + "type": "Interface", + "tags": [], + "label": "OptInConfigPerType", + "description": [ + "\nSets whether a type of event is enabled/disabled globally or per shipper." + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.OptInConfigPerType.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [ + "\nThe event type is globally enabled." + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.OptInConfigPerType.shippers", + "type": "Object", + "tags": [], + "label": "shippers", + "description": [ + "\nControls if an event type should be disabled for a specific type of shipper." + ], + "signature": [ + "Record | undefined" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.RegisterShipperOpts", + "type": "Interface", + "tags": [], + "label": "RegisterShipperOpts", + "description": [ + "\nOptional options to register a shipper" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.SchemaArray", + "type": "Interface", + "tags": [], + "label": "SchemaArray", + "description": [ + "\nSchema to represent an array" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaArray", + "text": "SchemaArray" + }, + " extends ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMeta", + "text": "SchemaMeta" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.SchemaArray.type", + "type": "string", + "tags": [], + "label": "type", + "description": [ + "The type must be an array" + ], + "signature": [ + "\"array\"" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.SchemaArray.items", + "type": "CompoundType", + "tags": [], + "label": "items", + "description": [ + "The schema of the items in the array is defined in the `items` property" + ], + "signature": [ + "{ type: \"pass_through\"; _meta: { description: string; } & ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMetaOptional", + "text": "SchemaMetaOptional" + }, + "; } | (unknown extends Value ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaArray", + "text": "SchemaArray" + }, + " | ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaObject", + "text": "SchemaObject" + }, + " | ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + " : NonNullable extends (infer U)[] | readonly (infer U)[] ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaArray", + "text": "SchemaArray" + }, + " : NonNullable extends Date ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + " : NonNullable extends object ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaObject", + "text": "SchemaObject" + }, + " : ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + ")" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.SchemaChildValue", + "type": "Interface", + "tags": [], + "label": "SchemaChildValue", + "description": [ + "\nSchema to define a primitive value" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.SchemaChildValue.type", + "type": "Uncategorized", + "tags": [], + "label": "type", + "description": [ + "The type of the value" + ], + "signature": [ + "NonNullable extends string | Date ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AllowedSchemaStringTypes", + "text": "AllowedSchemaStringTypes" + }, + " : NonNullable extends number ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AllowedSchemaNumberTypes", + "text": "AllowedSchemaNumberTypes" + }, + " : NonNullable extends boolean ? \"boolean\" : ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AllowedSchemaTypes", + "text": "AllowedSchemaTypes" + } + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.SchemaChildValue._meta", + "type": "CompoundType", + "tags": [], + "label": "_meta", + "description": [ + "Meta properties of the value: description and is optional" + ], + "signature": [ + "{ description: string; } & ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMetaOptional", + "text": "SchemaMetaOptional" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.SchemaMeta", + "type": "Interface", + "tags": [], + "label": "SchemaMeta", + "description": [ + "\nSchema meta with optional description" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMeta", + "text": "SchemaMeta" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.SchemaMeta._meta", + "type": "CompoundType", + "tags": [], + "label": "_meta", + "description": [ + "Meta properties of the pass through: description and is optional" + ], + "signature": [ + "({ description?: string | undefined; } & ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMetaOptional", + "text": "SchemaMetaOptional" + }, + ") | undefined" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.SchemaObject", + "type": "Interface", + "tags": [], + "label": "SchemaObject", + "description": [ + "\nSchema to represent an object" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaObject", + "text": "SchemaObject" + }, + " extends ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMeta", + "text": "SchemaMeta" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.SchemaObject.properties", + "type": "Object", + "tags": [], + "label": "properties", + "description": [ + "\nThe schemas of the keys of the object are defined in the `properties` object." + ], + "signature": [ + "{ [Key in keyof Required]: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaValue", + "text": "SchemaValue" + }, + "; }" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ShipperClassConstructor", + "type": "Interface", + "tags": [], + "label": "ShipperClassConstructor", + "description": [ + "\nConstructor of a {@link IShipper}" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.ShipperClassConstructor", + "text": "ShipperClassConstructor" + }, + "" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ShipperClassConstructor.shipperName", + "type": "string", + "tags": [], + "label": "shipperName", + "description": [ + "\nThe shipper's unique name" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ShipperClassConstructor.new", + "type": "Function", + "tags": [], + "label": "new", + "description": [ + "\nThe constructor" + ], + "signature": [ + "any" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ShipperClassConstructor.new.$1", + "type": "Uncategorized", + "tags": [], + "label": "config", + "description": [ + "The shipper's custom config" + ], + "signature": [ + "Config" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ShipperClassConstructor.new.$2", + "type": "Object", + "tags": [], + "label": "initContext", + "description": [ + "Common context {@link AnalyticsClientInitContext }" + ], + "signature": [ + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AnalyticsClientInitContext", + "text": "AnalyticsClientInitContext" + } + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.TelemetryCounter", + "type": "Interface", + "tags": [], + "label": "TelemetryCounter", + "description": [ + "\nShape of the events emitted by the telemetryCounter$ observable" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.TelemetryCounter.type", + "type": "CompoundType", + "tags": [], + "label": "type", + "description": [ + "\n{@link TelemetryCounterType}" + ], + "signature": [ + "\"succeeded\" | \"failed\" | \"enqueued\" | \"sent_to_shipper\" | \"dropped\"" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.TelemetryCounter.source", + "type": "string", + "tags": [], + "label": "source", + "description": [ + "\nWho emitted the event? It can be \"client\" or the name of the shipper." + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.TelemetryCounter.event_type", + "type": "string", + "tags": [], + "label": "event_type", + "description": [ + "\nThe event type the success/failure/drop event refers to." + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.TelemetryCounter.code", + "type": "string", + "tags": [], + "label": "code", + "description": [ + "\nCode to provide additional information about the success or failure. Examples are 200/400/504/ValidationError/UnknownError" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.TelemetryCounter.count", + "type": "number", + "tags": [], + "label": "count", + "description": [ + "\nThe number of events that this counter refers to." + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.AllowedSchemaBooleanTypes", + "type": "Type", + "tags": [], + "label": "AllowedSchemaBooleanTypes", + "description": [ + "Types matching boolean values" + ], + "signature": [ + "\"boolean\"" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.AllowedSchemaNumberTypes", + "type": "Type", + "tags": [], + "label": "AllowedSchemaNumberTypes", + "description": [ + "Types matching number values" + ], + "signature": [ + "\"date\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"double\"" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.AllowedSchemaStringTypes", + "type": "Type", + "tags": [], + "label": "AllowedSchemaStringTypes", + "description": [ + "Types matching string values" + ], + "signature": [ + "\"keyword\" | \"text\" | \"date\"" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.AllowedSchemaTypes", + "type": "Type", + "tags": [], + "label": "AllowedSchemaTypes", + "description": [ + "\nPossible type values in the schema" + ], + "signature": [ + "\"boolean\" | \"keyword\" | \"text\" | \"date\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"double\"" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ContextProviderName", + "type": "Type", + "tags": [], + "label": "ContextProviderName", + "description": [ + "\nContextProviderName used for indexed structures. Only used to improve the readability of the types" + ], + "signature": [ + "string" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.EventType", + "type": "Type", + "tags": [], + "label": "EventType", + "description": [ + "\nEvent Type used for indexed structures. Only used to improve the readability of the types" + ], + "signature": [ + "string" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.PossibleSchemaTypes", + "type": "Type", + "tags": [], + "label": "PossibleSchemaTypes", + "description": [ + "\nHelper to ensure the declared types match the schema types" + ], + "signature": [ + "Value extends string | Date ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AllowedSchemaStringTypes", + "text": "AllowedSchemaStringTypes" + }, + " : Value extends number ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AllowedSchemaNumberTypes", + "text": "AllowedSchemaNumberTypes" + }, + " : Value extends boolean ? \"boolean\" : ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.AllowedSchemaTypes", + "text": "AllowedSchemaTypes" + } + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.RootSchema", + "type": "Type", + "tags": [], + "label": "RootSchema", + "description": [ + "\nSchema definition to match the structure of the properties provided.\n" + ], + "signature": [ + "{ [Key in keyof Required]: ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaValue", + "text": "SchemaValue" + }, + "; }" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.SchemaMetaOptional", + "type": "Type", + "tags": [], + "label": "SchemaMetaOptional", + "description": [ + "\nEnforces { optional: true } if the value can be undefined" + ], + "signature": [ + "unknown extends Value ? { optional?: boolean | undefined; } : undefined extends Value ? { optional: true; } : { optional?: false | undefined; }" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.SchemaValue", + "type": "Type", + "tags": [], + "label": "SchemaValue", + "description": [ + "\nType that defines all the possible values that the Schema accepts.\nThese types definitions are helping to identify earlier the possible missing `properties` nesting when\nmanually defining the schemas." + ], + "signature": [ + "{ type: \"pass_through\"; _meta: { description: string; } & ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaMetaOptional", + "text": "SchemaMetaOptional" + }, + "; } | (unknown extends Value ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaArray", + "text": "SchemaArray" + }, + " | ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaObject", + "text": "SchemaObject" + }, + " | ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + " : NonNullable extends (infer U)[] | readonly (infer U)[] ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaArray", + "text": "SchemaArray" + }, + " : NonNullable extends Date ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + " : NonNullable extends object ? ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaObject", + "text": "SchemaObject" + }, + " : ", + { + "pluginId": "@kbn/ebt", + "scope": "common", + "docId": "kibKbnEbtPluginApi", + "section": "def-common.SchemaChildValue", + "text": "SchemaChildValue" + }, + ")" + ], + "path": "packages/analytics/ebt/client/src/schema/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.ShipperName", + "type": "Type", + "tags": [], + "label": "ShipperName", + "description": [ + "\nShipper Name used for indexed structures. Only used to improve the readability of the types" + ], + "signature": [ + "string" + ], + "path": "packages/analytics/ebt/client/src/analytics_client/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/ebt", + "id": "def-common.TelemetryCounterType", + "type": "Type", + "tags": [], + "label": "TelemetryCounterType", + "description": [ + "\nIndicates if the event contains data about succeeded, failed or dropped events:\n- enqueued: The event was accepted and will be sent to the shippers when they become available (and opt-in === true).\n- sent_to_shipper: The event was sent to at least one shipper.\n- succeeded: The event was successfully sent by the shipper.\n- failed: There was an error when processing/shipping the event. Refer to the Telemetry Counter's code for the reason.\n- dropped: The event was dropped from the queue. Refer to the Telemetry Counter's code for the reason." + ], + "signature": [ + "\"succeeded\" | \"failed\" | \"enqueued\" | \"sent_to_shipper\" | \"dropped\"" + ], + "path": "packages/analytics/ebt/client/src/events/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_ebt.mdx b/api_docs/kbn_ebt.mdx new file mode 100644 index 0000000000000..eff48712195b2 --- /dev/null +++ b/api_docs/kbn_ebt.mdx @@ -0,0 +1,36 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnEbtPluginApi +slug: /kibana-dev-docs/api/kbn-ebt +title: "@kbn/ebt" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/ebt plugin +date: 2024-06-19 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt'] +--- +import kbnEbtObj from './kbn_ebt.devdocs.json'; + + + +Contact [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 152 | 0 | 0 | 0 | + +## Common + +### Classes + + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_ebt_tools.devdocs.json b/api_docs/kbn_ebt_tools.devdocs.json index ed143ac7aeec1..44363fefececf 100644 --- a/api_docs/kbn_ebt_tools.devdocs.json +++ b/api_docs/kbn_ebt_tools.devdocs.json @@ -65,9 +65,9 @@ "signature": [ "(analytics: Pick<", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.IAnalyticsClient", "text": "IAnalyticsClient" }, @@ -89,9 +89,9 @@ "signature": [ "Pick<", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.IAnalyticsClient", "text": "IAnalyticsClient" }, @@ -118,9 +118,9 @@ "signature": [ "(analytics: Pick<", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.IAnalyticsClient", "text": "IAnalyticsClient" }, @@ -150,9 +150,9 @@ "signature": [ "Pick<", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.IAnalyticsClient", "text": "IAnalyticsClient" }, diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index dd709e3c8a67e..ecb6ea6043d71 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_ecs_data_quality_dashboard.mdx b/api_docs/kbn_ecs_data_quality_dashboard.mdx index e4e3ce52b251b..3966867b26154 100644 --- a/api_docs/kbn_ecs_data_quality_dashboard.mdx +++ b/api_docs/kbn_ecs_data_quality_dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs-data-quality-dashboard title: "@kbn/ecs-data-quality-dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs-data-quality-dashboard plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs-data-quality-dashboard'] --- import kbnEcsDataQualityDashboardObj from './kbn_ecs_data_quality_dashboard.devdocs.json'; diff --git a/api_docs/kbn_elastic_agent_utils.mdx b/api_docs/kbn_elastic_agent_utils.mdx index 11173898a83dc..bc3aade98a916 100644 --- a/api_docs/kbn_elastic_agent_utils.mdx +++ b/api_docs/kbn_elastic_agent_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-agent-utils title: "@kbn/elastic-agent-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-agent-utils plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-agent-utils'] --- import kbnElasticAgentUtilsObj from './kbn_elastic_agent_utils.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant.mdx b/api_docs/kbn_elastic_assistant.mdx index aadfdb6649327..376443426f3db 100644 --- a/api_docs/kbn_elastic_assistant.mdx +++ b/api_docs/kbn_elastic_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-assistant title: "@kbn/elastic-assistant" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-assistant plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant'] --- import kbnElasticAssistantObj from './kbn_elastic_assistant.devdocs.json'; diff --git a/api_docs/kbn_elastic_assistant_common.devdocs.json b/api_docs/kbn_elastic_assistant_common.devdocs.json index 99c152889d5a3..7f5b1cccc8780 100644 --- a/api_docs/kbn_elastic_assistant_common.devdocs.json +++ b/api_docs/kbn_elastic_assistant_common.devdocs.json @@ -1607,6 +1607,18 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_FIND", + "type": "string", + "tags": [], + "label": "ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_FIND", + "description": [], + "path": "x-pack/packages/kbn-elastic-assistant-common/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/elastic-assistant-common", "id": "def-common.ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_URL", @@ -1880,6 +1892,81 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.FindKnowledgeBaseEntriesRequestQuery", + "type": "Type", + "tags": [], + "label": "FindKnowledgeBaseEntriesRequestQuery", + "description": [], + "signature": [ + "{ per_page: number; page: number; fields?: string[] | undefined; filter?: string | undefined; sort_field?: \"title\" | \"updated_at\" | \"created_at\" | \"is_default\" | undefined; sort_order?: \"asc\" | \"desc\" | undefined; }" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/find_knowledge_base_entries_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.FindKnowledgeBaseEntriesRequestQueryInput", + "type": "Type", + "tags": [], + "label": "FindKnowledgeBaseEntriesRequestQueryInput", + "description": [], + "signature": [ + "{ fields?: unknown; filter?: string | undefined; sort_field?: \"title\" | \"updated_at\" | \"created_at\" | \"is_default\" | undefined; sort_order?: \"asc\" | \"desc\" | undefined; page?: number | undefined; per_page?: number | undefined; }" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/find_knowledge_base_entries_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.FindKnowledgeBaseEntriesResponse", + "type": "Type", + "tags": [], + "label": "FindKnowledgeBaseEntriesResponse", + "description": [], + "signature": [ + "{ page: number; perPage: number; total: number; data: { id: string; namespace: string; text: string; createdAt: string; users: { id?: string | undefined; name?: string | undefined; }[]; timestamp?: string | undefined; createdBy?: string | undefined; updatedAt?: string | undefined; updatedBy?: string | undefined; metadata?: { source: string; required: boolean; kbResource: string; } | undefined; vector?: { modelId: string; tokens: {} & { [k: string]: number; }; } | undefined; }[]; }" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/find_knowledge_base_entries_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.FindKnowledgeBaseEntriesSortField", + "type": "Type", + "tags": [], + "label": "FindKnowledgeBaseEntriesSortField", + "description": [], + "signature": [ + "\"title\" | \"updated_at\" | \"created_at\" | \"is_default\"" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/find_knowledge_base_entries_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.FindKnowledgeBaseEntriesSortFieldEnum", + "type": "Type", + "tags": [], + "label": "FindKnowledgeBaseEntriesSortFieldEnum", + "description": [], + "signature": [ + "{ title: \"title\"; updated_at: \"updated_at\"; created_at: \"created_at\"; is_default: \"is_default\"; }" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/find_knowledge_base_entries_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/elastic-assistant-common", "id": "def-common.GetCapabilitiesResponse", @@ -2639,7 +2726,7 @@ "signature": [ "\"asc\" | \"desc\"" ], - "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.gen.ts", + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -2654,7 +2741,7 @@ "signature": [ "{ asc: \"asc\"; desc: \"desc\"; }" ], - "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.gen.ts", + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -3663,6 +3750,66 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.FindKnowledgeBaseEntriesRequestQuery", + "type": "Object", + "tags": [], + "label": "FindKnowledgeBaseEntriesRequestQuery", + "description": [], + "signature": [ + "Zod.ZodObject<{ fields: Zod.ZodOptional, string[], unknown>>; filter: Zod.ZodOptional; sort_field: Zod.ZodOptional>; sort_order: Zod.ZodOptional>; page: Zod.ZodDefault>; per_page: Zod.ZodDefault>; }, \"strip\", Zod.ZodTypeAny, { per_page: number; page: number; fields?: string[] | undefined; filter?: string | undefined; sort_field?: \"title\" | \"updated_at\" | \"created_at\" | \"is_default\" | undefined; sort_order?: \"asc\" | \"desc\" | undefined; }, { fields?: unknown; filter?: string | undefined; sort_field?: \"title\" | \"updated_at\" | \"created_at\" | \"is_default\" | undefined; sort_order?: \"asc\" | \"desc\" | undefined; page?: number | undefined; per_page?: number | undefined; }>" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/find_knowledge_base_entries_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.FindKnowledgeBaseEntriesResponse", + "type": "Object", + "tags": [], + "label": "FindKnowledgeBaseEntriesResponse", + "description": [], + "signature": [ + "Zod.ZodObject<{ page: Zod.ZodNumber; perPage: Zod.ZodNumber; total: Zod.ZodNumber; data: Zod.ZodArray; id: Zod.ZodString; createdAt: Zod.ZodString; createdBy: Zod.ZodOptional; updatedAt: Zod.ZodOptional; updatedBy: Zod.ZodOptional; users: Zod.ZodArray; name: Zod.ZodOptional; }, \"strip\", Zod.ZodTypeAny, { id?: string | undefined; name?: string | undefined; }, { id?: string | undefined; name?: string | undefined; }>, \"many\">; metadata: Zod.ZodOptional>; namespace: Zod.ZodString; text: Zod.ZodString; vector: Zod.ZodOptional, Zod.objectInputType<{}, Zod.ZodNumber, \"strip\">>; }, \"strip\", Zod.ZodTypeAny, { modelId: string; tokens: {} & { [k: string]: number; }; }, { modelId: string; tokens: {} & { [k: string]: number; }; }>>; }, \"strip\", Zod.ZodTypeAny, { id: string; namespace: string; text: string; createdAt: string; users: { id?: string | undefined; name?: string | undefined; }[]; timestamp?: string | undefined; createdBy?: string | undefined; updatedAt?: string | undefined; updatedBy?: string | undefined; metadata?: { source: string; required: boolean; kbResource: string; } | undefined; vector?: { modelId: string; tokens: {} & { [k: string]: number; }; } | undefined; }, { id: string; namespace: string; text: string; createdAt: string; users: { id?: string | undefined; name?: string | undefined; }[]; timestamp?: string | undefined; createdBy?: string | undefined; updatedAt?: string | undefined; updatedBy?: string | undefined; metadata?: { source: string; required: boolean; kbResource: string; } | undefined; vector?: { modelId: string; tokens: {} & { [k: string]: number; }; } | undefined; }>, \"many\">; }, \"strip\", Zod.ZodTypeAny, { page: number; perPage: number; total: number; data: { id: string; namespace: string; text: string; createdAt: string; users: { id?: string | undefined; name?: string | undefined; }[]; timestamp?: string | undefined; createdBy?: string | undefined; updatedAt?: string | undefined; updatedBy?: string | undefined; metadata?: { source: string; required: boolean; kbResource: string; } | undefined; vector?: { modelId: string; tokens: {} & { [k: string]: number; }; } | undefined; }[]; }, { page: number; perPage: number; total: number; data: { id: string; namespace: string; text: string; createdAt: string; users: { id?: string | undefined; name?: string | undefined; }[]; timestamp?: string | undefined; createdBy?: string | undefined; updatedAt?: string | undefined; updatedBy?: string | undefined; metadata?: { source: string; required: boolean; kbResource: string; } | undefined; vector?: { modelId: string; tokens: {} & { [k: string]: number; }; } | undefined; }[]; }>" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/find_knowledge_base_entries_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.FindKnowledgeBaseEntriesSortField", + "type": "Object", + "tags": [], + "label": "FindKnowledgeBaseEntriesSortField", + "description": [], + "signature": [ + "Zod.ZodEnum<[\"created_at\", \"is_default\", \"title\", \"updated_at\"]>" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/find_knowledge_base_entries_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/elastic-assistant-common", + "id": "def-common.FindKnowledgeBaseEntriesSortFieldEnum", + "type": "Object", + "tags": [], + "label": "FindKnowledgeBaseEntriesSortFieldEnum", + "description": [], + "signature": [ + "{ title: \"title\"; updated_at: \"updated_at\"; created_at: \"created_at\"; is_default: \"is_default\"; }" + ], + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/find_knowledge_base_entries_route.gen.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/elastic-assistant-common", "id": "def-common.GetCapabilitiesResponse", @@ -4273,7 +4420,7 @@ "signature": [ "Zod.ZodEnum<[\"asc\", \"desc\"]>" ], - "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.gen.ts", + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -4288,7 +4435,7 @@ "signature": [ "{ asc: \"asc\"; desc: \"desc\"; }" ], - "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.gen.ts", + "path": "x-pack/packages/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/kbn_elastic_assistant_common.mdx b/api_docs/kbn_elastic_assistant_common.mdx index eb04d1e751631..8c2dc5cdf2b7c 100644 --- a/api_docs/kbn_elastic_assistant_common.mdx +++ b/api_docs/kbn_elastic_assistant_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-elastic-assistant-common title: "@kbn/elastic-assistant-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/elastic-assistant-common plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/elastic-assistant-common'] --- import kbnElasticAssistantCommonObj from './kbn_elastic_assistant_common.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/ | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 295 | 0 | 276 | 0 | +| 305 | 0 | 286 | 0 | ## Common diff --git a/api_docs/kbn_entities_schema.mdx b/api_docs/kbn_entities_schema.mdx index d2bb0299d77f5..68ab25caaed79 100644 --- a/api_docs/kbn_entities_schema.mdx +++ b/api_docs/kbn_entities_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-entities-schema title: "@kbn/entities-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/entities-schema plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/entities-schema'] --- import kbnEntitiesSchemaObj from './kbn_entities_schema.devdocs.json'; diff --git a/api_docs/kbn_es.mdx b/api_docs/kbn_es.mdx index 712d2b3911a04..1065242e2ce20 100644 --- a/api_docs/kbn_es.mdx +++ b/api_docs/kbn_es.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es title: "@kbn/es" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es plugin -date: 2024-06-11 +date: 2024-06-19 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 6cb72895ba61d..534d4fed39a36 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2024-06-11 +date: 2024-06-19 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 3eac85e394e88..4900cf0b98c7c 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2024-06-11 +date: 2024-06-19 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 8be85a45dfd8c..1b19fa053acc1 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2024-06-11 +date: 2024-06-19 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 b1b2bdd8561df..feb5fad5b9042 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2024-06-11 +date: 2024-06-19 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 04c9fd0019529..47d8c928be47e 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_esql_ast.devdocs.json b/api_docs/kbn_esql_ast.devdocs.json index 870bb8da9db78..8449e60698bff 100644 --- a/api_docs/kbn_esql_ast.devdocs.json +++ b/api_docs/kbn_esql_ast.devdocs.json @@ -1302,7 +1302,19 @@ "docId": "kibKbnEsqlAstPluginApi", "section": "def-common.ESQLCommandMode", "text": "ESQLCommandMode" - } + }, + " | ", + "ESQLInlineCast", + "<", + { + "pluginId": "@kbn/esql-ast", + "scope": "common", + "docId": "kibKbnEsqlAstPluginApi", + "section": "def-common.ESQLAstItem", + "text": "ESQLAstItem" + }, + "> | ", + "ESQLUnknownItem" ], "path": "packages/kbn-esql-ast/src/types.ts", "deprecated": false, diff --git a/api_docs/kbn_esql_ast.mdx b/api_docs/kbn_esql_ast.mdx index 2d5c5b742ea39..2a28cd4a0e37a 100644 --- a/api_docs/kbn_esql_ast.mdx +++ b/api_docs/kbn_esql_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-ast title: "@kbn/esql-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-ast plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-ast'] --- import kbnEsqlAstObj from './kbn_esql_ast.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 68 | 1 | 68 | 7 | +| 68 | 1 | 68 | 9 | ## Common diff --git a/api_docs/kbn_esql_utils.mdx b/api_docs/kbn_esql_utils.mdx index 7fcc194969936..05bc1416a8a73 100644 --- a/api_docs/kbn_esql_utils.mdx +++ b/api_docs/kbn_esql_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-utils title: "@kbn/esql-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-utils plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-utils'] --- import kbnEsqlUtilsObj from './kbn_esql_utils.devdocs.json'; diff --git a/api_docs/kbn_esql_validation_autocomplete.devdocs.json b/api_docs/kbn_esql_validation_autocomplete.devdocs.json index 75d1a0843c498..ea46ea3ba35da 100644 --- a/api_docs/kbn_esql_validation_autocomplete.devdocs.json +++ b/api_docs/kbn_esql_validation_autocomplete.devdocs.json @@ -19,6 +19,106 @@ "common": { "classes": [], "functions": [ + { + "parentPluginId": "@kbn/esql-validation-autocomplete", + "id": "def-common.checkFunctionArgMatchesDefinition", + "type": "Function", + "tags": [], + "label": "checkFunctionArgMatchesDefinition", + "description": [ + "\nChecks if an AST function argument is of the correct type\ngiven the definition." + ], + "signature": [ + "(arg: ", + { + "pluginId": "@kbn/esql-ast", + "scope": "common", + "docId": "kibKbnEsqlAstPluginApi", + "section": "def-common.ESQLSingleAstItem", + "text": "ESQLSingleAstItem" + }, + ", parameterDefinition: { name: string; type: ", + "FunctionParameterType", + "; optional?: boolean | undefined; noNestingFunctions?: boolean | undefined; supportsWildcard?: boolean | undefined; constantOnly?: boolean | undefined; literalOptions?: string[] | undefined; literalSuggestions?: string[] | undefined; }, references: ", + "ReferenceMaps", + ", parentCommand: string | undefined) => boolean | undefined" + ], + "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/esql-validation-autocomplete", + "id": "def-common.checkFunctionArgMatchesDefinition.$1", + "type": "CompoundType", + "tags": [], + "label": "arg", + "description": [], + "signature": [ + { + "pluginId": "@kbn/esql-ast", + "scope": "common", + "docId": "kibKbnEsqlAstPluginApi", + "section": "def-common.ESQLSingleAstItem", + "text": "ESQLSingleAstItem" + } + ], + "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/esql-validation-autocomplete", + "id": "def-common.checkFunctionArgMatchesDefinition.$2", + "type": "Object", + "tags": [], + "label": "parameterDefinition", + "description": [], + "signature": [ + "{ name: string; type: ", + "FunctionParameterType", + "; optional?: boolean | undefined; noNestingFunctions?: boolean | undefined; supportsWildcard?: boolean | undefined; constantOnly?: boolean | undefined; literalOptions?: string[] | undefined; literalSuggestions?: string[] | undefined; }" + ], + "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/esql-validation-autocomplete", + "id": "def-common.checkFunctionArgMatchesDefinition.$3", + "type": "Object", + "tags": [], + "label": "references", + "description": [], + "signature": [ + "ReferenceMaps" + ], + "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/esql-validation-autocomplete", + "id": "def-common.checkFunctionArgMatchesDefinition.$4", + "type": "string", + "tags": [], + "label": "parentCommand", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/esql-validation-autocomplete", "id": "def-common.collectVariables", @@ -121,72 +221,6 @@ "returnComment": [], "initialIsOpen": false }, - { - "parentPluginId": "@kbn/esql-validation-autocomplete", - "id": "def-common.columnExists", - "type": "Function", - "tags": [], - "label": "columnExists", - "description": [], - "signature": [ - "(column: ", - { - "pluginId": "@kbn/esql-ast", - "scope": "common", - "docId": "kibKbnEsqlAstPluginApi", - "section": "def-common.ESQLColumn", - "text": "ESQLColumn" - }, - ", { fields, variables }: Pick<", - "ReferenceMaps", - ", \"fields\" | \"variables\">) => { hit: boolean; nameHit: string; } | { hit: boolean; nameHit?: undefined; }" - ], - "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/esql-validation-autocomplete", - "id": "def-common.columnExists.$1", - "type": "Object", - "tags": [], - "label": "column", - "description": [], - "signature": [ - { - "pluginId": "@kbn/esql-ast", - "scope": "common", - "docId": "kibKbnEsqlAstPluginApi", - "section": "def-common.ESQLColumn", - "text": "ESQLColumn" - } - ], - "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "@kbn/esql-validation-autocomplete", - "id": "def-common.columnExists.$2", - "type": "Object", - "tags": [], - "label": "{ fields, variables }", - "description": [], - "signature": [ - "Pick<", - "ReferenceMaps", - ", \"fields\" | \"variables\">" - ], - "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/esql-validation-autocomplete", "id": "def-common.getActions", @@ -601,6 +635,18 @@ "section": "def-common.ESQLCommandMode", "text": "ESQLCommandMode" }, + " | ", + "ESQLInlineCast", + "<", + { + "pluginId": "@kbn/esql-ast", + "scope": "common", + "docId": "kibKbnEsqlAstPluginApi", + "section": "def-common.ESQLAstItem", + "text": "ESQLAstItem" + }, + "> | ", + "ESQLUnknownItem", "; option: ", { "pluginId": "@kbn/esql-ast", @@ -699,6 +745,18 @@ "section": "def-common.ESQLCommandMode", "text": "ESQLCommandMode" }, + " | ", + "ESQLInlineCast", + "<", + { + "pluginId": "@kbn/esql-ast", + "scope": "common", + "docId": "kibKbnEsqlAstPluginApi", + "section": "def-common.ESQLAstItem", + "text": "ESQLAstItem" + }, + "> | ", + "ESQLUnknownItem", " | undefined; option: ", { "pluginId": "@kbn/esql-ast", @@ -781,6 +839,18 @@ "section": "def-common.ESQLCommandMode", "text": "ESQLCommandMode" }, + " | ", + "ESQLInlineCast", + "<", + { + "pluginId": "@kbn/esql-ast", + "scope": "common", + "docId": "kibKbnEsqlAstPluginApi", + "section": "def-common.ESQLAstItem", + "text": "ESQLAstItem" + }, + "> | ", + "ESQLUnknownItem", " | undefined; option: ", { "pluginId": "@kbn/esql-ast", @@ -871,6 +941,18 @@ "section": "def-common.ESQLCommandMode", "text": "ESQLCommandMode" }, + " | ", + "ESQLInlineCast", + "<", + { + "pluginId": "@kbn/esql-ast", + "scope": "common", + "docId": "kibKbnEsqlAstPluginApi", + "section": "def-common.ESQLAstItem", + "text": "ESQLAstItem" + }, + "> | ", + "ESQLUnknownItem", " | undefined; setting: ", { "pluginId": "@kbn/esql-ast", @@ -940,89 +1022,6 @@ "returnComment": [], "initialIsOpen": false }, - { - "parentPluginId": "@kbn/esql-validation-autocomplete", - "id": "def-common.getColumnHit", - "type": "Function", - "tags": [], - "label": "getColumnHit", - "description": [], - "signature": [ - "(columnName: string, { fields, variables }: Pick<", - "ReferenceMaps", - ", \"fields\" | \"variables\">, position: number | undefined) => ", - { - "pluginId": "@kbn/esql-validation-autocomplete", - "scope": "common", - "docId": "kibKbnEsqlValidationAutocompletePluginApi", - "section": "def-common.ESQLVariable", - "text": "ESQLVariable" - }, - " | ", - { - "pluginId": "@kbn/esql-validation-autocomplete", - "scope": "common", - "docId": "kibKbnEsqlValidationAutocompletePluginApi", - "section": "def-common.ESQLRealField", - "text": "ESQLRealField" - }, - " | undefined" - ], - "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/esql-validation-autocomplete", - "id": "def-common.getColumnHit.$1", - "type": "string", - "tags": [], - "label": "columnName", - "description": [], - "signature": [ - "string" - ], - "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "@kbn/esql-validation-autocomplete", - "id": "def-common.getColumnHit.$2", - "type": "Object", - "tags": [], - "label": "{ fields, variables }", - "description": [], - "signature": [ - "Pick<", - "ReferenceMaps", - ", \"fields\" | \"variables\">" - ], - "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "@kbn/esql-validation-autocomplete", - "id": "def-common.getColumnHit.$3", - "type": "number", - "tags": [], - "label": "position", - "description": [], - "signature": [ - "number | undefined" - ], - "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } - ], - "returnComment": [], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/esql-validation-autocomplete", "id": "def-common.getCommandDefinition", @@ -1353,7 +1352,7 @@ "section": "def-common.ESQLCallbacks", "text": "ESQLCallbacks" }, - " | undefined) => () => Promise<{ name: string; hidden: boolean; }[]>" + " | undefined) => () => Promise<{ name: string; hidden: boolean; title?: string | undefined; dataStreams?: { name: string; title?: string | undefined; }[] | undefined; }[]>" ], "path": "packages/kbn-esql-validation-autocomplete/src/shared/resources_helpers.ts", "deprecated": false, @@ -1527,121 +1526,6 @@ "returnComment": [], "initialIsOpen": false }, - { - "parentPluginId": "@kbn/esql-validation-autocomplete", - "id": "def-common.isEqualType", - "type": "Function", - "tags": [], - "label": "isEqualType", - "description": [ - "\nChecks if an AST argument is of the correct type\ngiven the definition." - ], - "signature": [ - "(arg: ", - { - "pluginId": "@kbn/esql-ast", - "scope": "common", - "docId": "kibKbnEsqlAstPluginApi", - "section": "def-common.ESQLSingleAstItem", - "text": "ESQLSingleAstItem" - }, - ", argDef: { name: string; type: ", - "FunctionParameterType", - "; optional?: boolean | undefined; noNestingFunctions?: boolean | undefined; supportsWildcard?: boolean | undefined; constantOnly?: boolean | undefined; literalOptions?: string[] | undefined; literalSuggestions?: string[] | undefined; } | { name: string; type: string; optional?: boolean | undefined; innerType?: string | undefined; values?: string[] | undefined; valueDescriptions?: string[] | undefined; constantOnly?: boolean | undefined; wildcards?: boolean | undefined; }, references: ", - "ReferenceMaps", - ", parentCommand: string | undefined, nameHit: string | undefined) => boolean | undefined" - ], - "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/esql-validation-autocomplete", - "id": "def-common.isEqualType.$1", - "type": "CompoundType", - "tags": [], - "label": "arg", - "description": [], - "signature": [ - { - "pluginId": "@kbn/esql-ast", - "scope": "common", - "docId": "kibKbnEsqlAstPluginApi", - "section": "def-common.ESQLSingleAstItem", - "text": "ESQLSingleAstItem" - } - ], - "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "@kbn/esql-validation-autocomplete", - "id": "def-common.isEqualType.$2", - "type": "CompoundType", - "tags": [], - "label": "argDef", - "description": [], - "signature": [ - "{ name: string; type: ", - "FunctionParameterType", - "; optional?: boolean | undefined; noNestingFunctions?: boolean | undefined; supportsWildcard?: boolean | undefined; constantOnly?: boolean | undefined; literalOptions?: string[] | undefined; literalSuggestions?: string[] | undefined; } | { name: string; type: string; optional?: boolean | undefined; innerType?: string | undefined; values?: string[] | undefined; valueDescriptions?: string[] | undefined; constantOnly?: boolean | undefined; wildcards?: boolean | undefined; }" - ], - "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "@kbn/esql-validation-autocomplete", - "id": "def-common.isEqualType.$3", - "type": "Object", - "tags": [], - "label": "references", - "description": [], - "signature": [ - "ReferenceMaps" - ], - "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "@kbn/esql-validation-autocomplete", - "id": "def-common.isEqualType.$4", - "type": "string", - "tags": [], - "label": "parentCommand", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - }, - { - "parentPluginId": "@kbn/esql-validation-autocomplete", - "id": "def-common.isEqualType.$5", - "type": "string", - "tags": [], - "label": "nameHit", - "description": [], - "signature": [ - "string | undefined" - ], - "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } - ], - "returnComment": [], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/esql-validation-autocomplete", "id": "def-common.isExpression", @@ -2083,6 +1967,90 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/esql-validation-autocomplete", + "id": "def-common.lookupColumn", + "type": "Function", + "tags": [], + "label": "lookupColumn", + "description": [ + "\nThis function returns the variable or field matching a column" + ], + "signature": [ + "(column: ", + { + "pluginId": "@kbn/esql-ast", + "scope": "common", + "docId": "kibKbnEsqlAstPluginApi", + "section": "def-common.ESQLColumn", + "text": "ESQLColumn" + }, + ", { fields, variables }: Pick<", + "ReferenceMaps", + ", \"fields\" | \"variables\">) => ", + { + "pluginId": "@kbn/esql-validation-autocomplete", + "scope": "common", + "docId": "kibKbnEsqlValidationAutocompletePluginApi", + "section": "def-common.ESQLVariable", + "text": "ESQLVariable" + }, + " | ", + { + "pluginId": "@kbn/esql-validation-autocomplete", + "scope": "common", + "docId": "kibKbnEsqlValidationAutocompletePluginApi", + "section": "def-common.ESQLRealField", + "text": "ESQLRealField" + }, + " | undefined" + ], + "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/esql-validation-autocomplete", + "id": "def-common.lookupColumn.$1", + "type": "Object", + "tags": [], + "label": "column", + "description": [], + "signature": [ + { + "pluginId": "@kbn/esql-ast", + "scope": "common", + "docId": "kibKbnEsqlAstPluginApi", + "section": "def-common.ESQLColumn", + "text": "ESQLColumn" + } + ], + "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/esql-validation-autocomplete", + "id": "def-common.lookupColumn.$2", + "type": "Object", + "tags": [], + "label": "{ fields, variables }", + "description": [], + "signature": [ + "Pick<", + "ReferenceMaps", + ", \"fields\" | \"variables\">" + ], + "path": "packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/esql-validation-autocomplete", "id": "def-common.printFunctionSignature", @@ -2977,7 +2945,7 @@ "label": "getSources", "description": [], "signature": [ - "CallbackFn<{}, { name: string; hidden: boolean; }> | undefined" + "CallbackFn<{}, { name: string; hidden: boolean; title?: string | undefined; dataStreams?: { name: string; title?: string | undefined; }[] | undefined; }> | undefined" ], "path": "packages/kbn-esql-validation-autocomplete/src/shared/types.ts", "deprecated": false, diff --git a/api_docs/kbn_esql_validation_autocomplete.mdx b/api_docs/kbn_esql_validation_autocomplete.mdx index 360878b3fc86b..f326d93d5c638 100644 --- a/api_docs/kbn_esql_validation_autocomplete.mdx +++ b/api_docs/kbn_esql_validation_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-esql-validation-autocomplete title: "@kbn/esql-validation-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/esql-validation-autocomplete plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/esql-validation-autocomplete'] --- import kbnEsqlValidationAutocompleteObj from './kbn_esql_validation_autocomplete.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 194 | 0 | 184 | 10 | +| 189 | 0 | 178 | 10 | ## Common diff --git a/api_docs/kbn_event_annotation_common.mdx b/api_docs/kbn_event_annotation_common.mdx index 262500cf1ab90..93754c21b806c 100644 --- a/api_docs/kbn_event_annotation_common.mdx +++ b/api_docs/kbn_event_annotation_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-common title: "@kbn/event-annotation-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-common plugin -date: 2024-06-11 +date: 2024-06-19 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 3361ef8c2f175..7b083d3bdcdcb 100644 --- a/api_docs/kbn_event_annotation_components.mdx +++ b/api_docs/kbn_event_annotation_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-event-annotation-components title: "@kbn/event-annotation-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/event-annotation-components plugin -date: 2024-06-11 +date: 2024-06-19 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 73900e0d0496a..97773962b0371 100644 --- a/api_docs/kbn_expandable_flyout.mdx +++ b/api_docs/kbn_expandable_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-expandable-flyout title: "@kbn/expandable-flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/expandable-flyout plugin -date: 2024-06-11 +date: 2024-06-19 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 312f64b56813e..a0cab91e0732e 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2024-06-11 +date: 2024-06-19 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 20c052666f89a..61c5a7cc45dbe 100644 --- a/api_docs/kbn_field_utils.mdx +++ b/api_docs/kbn_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-utils title: "@kbn/field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-utils plugin -date: 2024-06-11 +date: 2024-06-19 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 46b90e8fe908a..516e28902c830 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_formatters.mdx b/api_docs/kbn_formatters.mdx index d23a6c32e0303..55ad163620e2a 100644 --- a/api_docs/kbn_formatters.mdx +++ b/api_docs/kbn_formatters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-formatters title: "@kbn/formatters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/formatters plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/formatters'] --- import kbnFormattersObj from './kbn_formatters.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index 6b638ef4d6df7..76a7e2562c5f3 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_ui_services.mdx b/api_docs/kbn_ftr_common_functional_ui_services.mdx index eff706ca81e6d..eb5e6081a592f 100644 --- a/api_docs/kbn_ftr_common_functional_ui_services.mdx +++ b/api_docs/kbn_ftr_common_functional_ui_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-ui-services title: "@kbn/ftr-common-functional-ui-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-ui-services plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-ui-services'] --- import kbnFtrCommonFunctionalUiServicesObj from './kbn_ftr_common_functional_ui_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index 4a602b97049d2..377d3f28132a8 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2024-06-11 +date: 2024-06-19 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 b563554e3deaa..aeb4254520b6a 100644 --- a/api_docs/kbn_generate_console_definitions.mdx +++ b/api_docs/kbn_generate_console_definitions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-console-definitions title: "@kbn/generate-console-definitions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-console-definitions plugin -date: 2024-06-11 +date: 2024-06-19 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 286c9d3c0d0fc..0dfb7c3ed231d 100644 --- a/api_docs/kbn_generate_csv.mdx +++ b/api_docs/kbn_generate_csv.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate-csv title: "@kbn/generate-csv" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate-csv plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate-csv'] --- import kbnGenerateCsvObj from './kbn_generate_csv.devdocs.json'; diff --git a/api_docs/kbn_grouping.mdx b/api_docs/kbn_grouping.mdx index adfe502caa00f..abe0caab761c9 100644 --- a/api_docs/kbn_grouping.mdx +++ b/api_docs/kbn_grouping.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-grouping title: "@kbn/grouping" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/grouping plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/grouping'] --- import kbnGroupingObj from './kbn_grouping.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index 80edce889220e..d77bf48458c7c 100644 --- a/api_docs/kbn_guided_onboarding.mdx +++ b/api_docs/kbn_guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-guided-onboarding title: "@kbn/guided-onboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/guided-onboarding plugin -date: 2024-06-11 +date: 2024-06-19 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 6fd1e2a6b6662..2b379b261a2a2 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2024-06-11 +date: 2024-06-19 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 46fee7d7f7bb8..6246e2e784241 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 f7b1b6f97d7a6..bd4698775dfcd 100644 --- a/api_docs/kbn_health_gateway_server.mdx +++ b/api_docs/kbn_health_gateway_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-health-gateway-server title: "@kbn/health-gateway-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/health-gateway-server plugin -date: 2024-06-11 +date: 2024-06-19 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 c6d5a44ea6aff..3bdc220beb3a5 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2024-06-11 +date: 2024-06-19 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 59b2e6bf8acaa..0fac54d1c4056 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2024-06-11 +date: 2024-06-19 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 d121221fb0896..1be4bfe0b0f67 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2024-06-11 +date: 2024-06-19 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 858c1d83657c8..aac51775cf05d 100644 --- a/api_docs/kbn_i18n_react.mdx +++ b/api_docs/kbn_i18n_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n-react title: "@kbn/i18n-react" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n-react plugin -date: 2024-06-11 +date: 2024-06-19 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 289c2bb667710..758d716f10029 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_index_management.mdx b/api_docs/kbn_index_management.mdx index 4f8c798442110..c2daabbf703aa 100644 --- a/api_docs/kbn_index_management.mdx +++ b/api_docs/kbn_index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-index-management title: "@kbn/index-management" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/index-management plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/index-management'] --- import kbnIndexManagementObj from './kbn_index_management.devdocs.json'; diff --git a/api_docs/kbn_inference_integration_flyout.mdx b/api_docs/kbn_inference_integration_flyout.mdx index 06abd729589e0..95cae23bb1c3f 100644 --- a/api_docs/kbn_inference_integration_flyout.mdx +++ b/api_docs/kbn_inference_integration_flyout.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-inference_integration_flyout title: "@kbn/inference_integration_flyout" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/inference_integration_flyout plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/inference_integration_flyout'] --- import kbnInferenceIntegrationFlyoutObj from './kbn_inference_integration_flyout.devdocs.json'; diff --git a/api_docs/kbn_infra_forge.mdx b/api_docs/kbn_infra_forge.mdx index e752255180146..91815af1a7baf 100644 --- a/api_docs/kbn_infra_forge.mdx +++ b/api_docs/kbn_infra_forge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-infra-forge title: "@kbn/infra-forge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/infra-forge plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/infra-forge'] --- import kbnInfraForgeObj from './kbn_infra_forge.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 2cf5358700e1c..5bd2f82a476b5 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2024-06-11 +date: 2024-06-19 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 a76ff8b20a2e5..7806977a5ff63 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_ipynb.mdx b/api_docs/kbn_ipynb.mdx index 2e52870cd565f..0f55e6da22faa 100644 --- a/api_docs/kbn_ipynb.mdx +++ b/api_docs/kbn_ipynb.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ipynb title: "@kbn/ipynb" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ipynb plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ipynb'] --- import kbnIpynbObj from './kbn_ipynb.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index bba4d47b98e39..e2e126ace151e 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2024-06-11 +date: 2024-06-19 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 47d65c76e59e0..f92880e152af6 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2024-06-11 +date: 2024-06-19 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 f56828fdb6363..f7a970eade436 100644 --- a/api_docs/kbn_json_ast.mdx +++ b/api_docs/kbn_json_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-ast title: "@kbn/json-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-ast plugin -date: 2024-06-11 +date: 2024-06-19 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 b4295dc856741..719fe13148e43 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2024-06-11 +date: 2024-06-19 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 50a6dbbd59529..edcf7b993d47c 100644 --- a/api_docs/kbn_language_documentation_popover.mdx +++ b/api_docs/kbn_language_documentation_popover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-language-documentation-popover title: "@kbn/language-documentation-popover" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/language-documentation-popover plugin -date: 2024-06-11 +date: 2024-06-19 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 de2d017bad4db..b8b7b8305838e 100644 --- a/api_docs/kbn_lens_embeddable_utils.mdx +++ b/api_docs/kbn_lens_embeddable_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-lens-embeddable-utils title: "@kbn/lens-embeddable-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/lens-embeddable-utils plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-embeddable-utils'] --- import kbnLensEmbeddableUtilsObj from './kbn_lens_embeddable_utils.devdocs.json'; diff --git a/api_docs/kbn_lens_formula_docs.mdx b/api_docs/kbn_lens_formula_docs.mdx index 5ca72e7962432..dcc7887e2c389 100644 --- a/api_docs/kbn_lens_formula_docs.mdx +++ b/api_docs/kbn_lens_formula_docs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-lens-formula-docs title: "@kbn/lens-formula-docs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/lens-formula-docs plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/lens-formula-docs'] --- import kbnLensFormulaDocsObj from './kbn_lens_formula_docs.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 29922183497b8..0b3284ccaff5b 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2024-06-11 +date: 2024-06-19 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 9cd4fe2f6ff26..e7296264e4221 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_content_badge.mdx b/api_docs/kbn_managed_content_badge.mdx index 9f550c01799a7..ade696d52c11e 100644 --- a/api_docs/kbn_managed_content_badge.mdx +++ b/api_docs/kbn_managed_content_badge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-content-badge title: "@kbn/managed-content-badge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-content-badge plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-content-badge'] --- import kbnManagedContentBadgeObj from './kbn_managed_content_badge.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index 6e5e7411cd1ba..9ea1627776fdd 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2024-06-11 +date: 2024-06-19 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 c95d5545babbb..e57cd5e7ed8fa 100644 --- a/api_docs/kbn_management_cards_navigation.mdx +++ b/api_docs/kbn_management_cards_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-cards-navigation title: "@kbn/management-cards-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-cards-navigation plugin -date: 2024-06-11 +date: 2024-06-19 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 a738aeac58796..bfe63ca555d66 100644 --- a/api_docs/kbn_management_settings_application.mdx +++ b/api_docs/kbn_management_settings_application.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-application title: "@kbn/management-settings-application" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-application plugin -date: 2024-06-11 +date: 2024-06-19 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 32a8c407b173a..679ecc142d451 100644 --- a/api_docs/kbn_management_settings_components_field_category.mdx +++ b/api_docs/kbn_management_settings_components_field_category.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-category title: "@kbn/management-settings-components-field-category" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-category plugin -date: 2024-06-11 +date: 2024-06-19 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 6e656ebac294e..5466ac9f12882 100644 --- a/api_docs/kbn_management_settings_components_field_input.mdx +++ b/api_docs/kbn_management_settings_components_field_input.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-input title: "@kbn/management-settings-components-field-input" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-input plugin -date: 2024-06-11 +date: 2024-06-19 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 e72fd7564f720..389e2027f424a 100644 --- a/api_docs/kbn_management_settings_components_field_row.mdx +++ b/api_docs/kbn_management_settings_components_field_row.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-field-row title: "@kbn/management-settings-components-field-row" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-field-row plugin -date: 2024-06-11 +date: 2024-06-19 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 0774ae77ba654..ad499b3c90f10 100644 --- a/api_docs/kbn_management_settings_components_form.mdx +++ b/api_docs/kbn_management_settings_components_form.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-components-form title: "@kbn/management-settings-components-form" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-components-form plugin -date: 2024-06-11 +date: 2024-06-19 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 e15456827d1a9..2886e76ab1137 100644 --- a/api_docs/kbn_management_settings_field_definition.mdx +++ b/api_docs/kbn_management_settings_field_definition.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-field-definition title: "@kbn/management-settings-field-definition" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-field-definition plugin -date: 2024-06-11 +date: 2024-06-19 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 3ca9e308a59d4..d4d30d03b2e60 100644 --- a/api_docs/kbn_management_settings_ids.mdx +++ b/api_docs/kbn_management_settings_ids.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-ids title: "@kbn/management-settings-ids" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-ids plugin -date: 2024-06-11 +date: 2024-06-19 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 f75a5a4502960..d386b31cc29bb 100644 --- a/api_docs/kbn_management_settings_section_registry.mdx +++ b/api_docs/kbn_management_settings_section_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-section-registry title: "@kbn/management-settings-section-registry" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-section-registry plugin -date: 2024-06-11 +date: 2024-06-19 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 cff52d4dad46c..960d7c89e444c 100644 --- a/api_docs/kbn_management_settings_types.mdx +++ b/api_docs/kbn_management_settings_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-types title: "@kbn/management-settings-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-types plugin -date: 2024-06-11 +date: 2024-06-19 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 ed5b49384bc63..f846a138ad72d 100644 --- a/api_docs/kbn_management_settings_utilities.mdx +++ b/api_docs/kbn_management_settings_utilities.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-settings-utilities title: "@kbn/management-settings-utilities" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-settings-utilities plugin -date: 2024-06-11 +date: 2024-06-19 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 07e8a3b501797..cc94cfcc45577 100644 --- a/api_docs/kbn_management_storybook_config.mdx +++ b/api_docs/kbn_management_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-management-storybook-config title: "@kbn/management-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/management-storybook-config plugin -date: 2024-06-11 +date: 2024-06-19 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 89aaae69b68dd..7f8b965b1d5c2 100644 --- a/api_docs/kbn_mapbox_gl.devdocs.json +++ b/api_docs/kbn_mapbox_gl.devdocs.json @@ -9592,7 +9592,7 @@ "label": "MapEvent", "description": [], "signature": [ - "\"error\" | \"data\" | \"render\" | \"remove\" | \"rotate\" | \"resize\" | \"idle\" | \"zoom\" | \"load\" | \"move\" | \"mousedown\" | \"mouseup\" | \"mouseover\" | \"mousemove\" | \"click\" | \"dblclick\" | \"mouseenter\" | \"mouseleave\" | \"mouseout\" | \"contextmenu\" | \"wheel\" | \"touchstart\" | \"touchend\" | \"touchmove\" | \"touchcancel\" | \"movestart\" | \"moveend\" | \"dragstart\" | \"drag\" | \"dragend\" | \"zoomstart\" | \"zoomend\" | \"rotatestart\" | \"rotateend\" | \"pitchstart\" | \"pitch\" | \"pitchend\" | \"boxzoomstart\" | \"boxzoomend\" | \"boxzoomcancel\" | \"webglcontextlost\" | \"webglcontextrestored\" | \"styledata\" | \"sourcedata\" | \"dataloading\" | \"styledataloading\" | \"sourcedataloading\" | \"styleimagemissing\" | \"style.load\" | \"terrain\" | \"dataabort\" | \"sourcedataabort\"" + "\"error\" | \"data\" | \"move\" | \"render\" | \"remove\" | \"rotate\" | \"resize\" | \"idle\" | \"zoom\" | \"load\" | \"mousedown\" | \"mouseup\" | \"mouseover\" | \"mousemove\" | \"click\" | \"dblclick\" | \"mouseenter\" | \"mouseleave\" | \"mouseout\" | \"contextmenu\" | \"wheel\" | \"touchstart\" | \"touchend\" | \"touchmove\" | \"touchcancel\" | \"movestart\" | \"moveend\" | \"dragstart\" | \"drag\" | \"dragend\" | \"zoomstart\" | \"zoomend\" | \"rotatestart\" | \"rotateend\" | \"pitchstart\" | \"pitch\" | \"pitchend\" | \"boxzoomstart\" | \"boxzoomend\" | \"boxzoomcancel\" | \"webglcontextlost\" | \"webglcontextrestored\" | \"styledata\" | \"sourcedata\" | \"dataloading\" | \"styledataloading\" | \"sourcedataloading\" | \"styleimagemissing\" | \"style.load\" | \"terrain\" | \"dataabort\" | \"sourcedataabort\"" ], "path": "node_modules/maplibre-gl/dist/maplibre-gl.d.ts", "deprecated": false, diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 77e0207625ce7..29fe8a26cc807 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_maps_vector_tile_utils.mdx b/api_docs/kbn_maps_vector_tile_utils.mdx index 4cd68f6b6b670..6b6ab7809564d 100644 --- a/api_docs/kbn_maps_vector_tile_utils.mdx +++ b/api_docs/kbn_maps_vector_tile_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-maps-vector-tile-utils title: "@kbn/maps-vector-tile-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/maps-vector-tile-utils plugin -date: 2024-06-11 +date: 2024-06-19 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 82ab6213ec688..c682754234786 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2024-06-11 +date: 2024-06-19 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 2e6ae1f43f178..f94006b655dbf 100644 --- a/api_docs/kbn_ml_anomaly_utils.mdx +++ b/api_docs/kbn_ml_anomaly_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-anomaly-utils title: "@kbn/ml-anomaly-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-anomaly-utils plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-anomaly-utils'] --- import kbnMlAnomalyUtilsObj from './kbn_ml_anomaly_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_cancellable_search.mdx b/api_docs/kbn_ml_cancellable_search.mdx index 333e78587c604..bd45f8ad1a08b 100644 --- a/api_docs/kbn_ml_cancellable_search.mdx +++ b/api_docs/kbn_ml_cancellable_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-cancellable-search title: "@kbn/ml-cancellable-search" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-cancellable-search plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-cancellable-search'] --- import kbnMlCancellableSearchObj from './kbn_ml_cancellable_search.devdocs.json'; diff --git a/api_docs/kbn_ml_category_validator.mdx b/api_docs/kbn_ml_category_validator.mdx index fcf1f475f5c7e..5f6b80c05feb8 100644 --- a/api_docs/kbn_ml_category_validator.mdx +++ b/api_docs/kbn_ml_category_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-category-validator title: "@kbn/ml-category-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-category-validator plugin -date: 2024-06-11 +date: 2024-06-19 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 ecdc4732bd6d2..5869e96f4d4a2 100644 --- a/api_docs/kbn_ml_chi2test.mdx +++ b/api_docs/kbn_ml_chi2test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-chi2test title: "@kbn/ml-chi2test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-chi2test plugin -date: 2024-06-11 +date: 2024-06-19 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 ed4f2cbd9b3ae..dfdc3b08cbacf 100644 --- a/api_docs/kbn_ml_data_frame_analytics_utils.mdx +++ b/api_docs/kbn_ml_data_frame_analytics_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-frame-analytics-utils title: "@kbn/ml-data-frame-analytics-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-frame-analytics-utils plugin -date: 2024-06-11 +date: 2024-06-19 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 d5a46ec8632fc..8a1cd3f0ad4e7 100644 --- a/api_docs/kbn_ml_data_grid.mdx +++ b/api_docs/kbn_ml_data_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-data-grid title: "@kbn/ml-data-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-data-grid plugin -date: 2024-06-11 +date: 2024-06-19 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 2432223452511..c0466967360b1 100644 --- a/api_docs/kbn_ml_date_picker.mdx +++ b/api_docs/kbn_ml_date_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-picker title: "@kbn/ml-date-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-picker plugin -date: 2024-06-11 +date: 2024-06-19 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 49c80459f640f..da283ea5b2848 100644 --- a/api_docs/kbn_ml_date_utils.mdx +++ b/api_docs/kbn_ml_date_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-utils title: "@kbn/ml-date-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-utils plugin -date: 2024-06-11 +date: 2024-06-19 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 05fbaf0e91940..52e09fb58f798 100644 --- a/api_docs/kbn_ml_error_utils.mdx +++ b/api_docs/kbn_ml_error_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-error-utils title: "@kbn/ml-error-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-error-utils plugin -date: 2024-06-11 +date: 2024-06-19 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 855736009b8cc..7f261994b433c 100644 --- a/api_docs/kbn_ml_in_memory_table.mdx +++ b/api_docs/kbn_ml_in_memory_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-in-memory-table title: "@kbn/ml-in-memory-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-in-memory-table plugin -date: 2024-06-11 +date: 2024-06-19 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 c4535ea14bf16..f60bb413a2382 100644 --- a/api_docs/kbn_ml_is_defined.mdx +++ b/api_docs/kbn_ml_is_defined.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-defined title: "@kbn/ml-is-defined" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-defined plugin -date: 2024-06-11 +date: 2024-06-19 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 f25fe536d3f0c..80a615093e7f0 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2024-06-11 +date: 2024-06-19 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 8f957cf79d29a..d6cb7980ec60d 100644 --- a/api_docs/kbn_ml_kibana_theme.mdx +++ b/api_docs/kbn_ml_kibana_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-kibana-theme title: "@kbn/ml-kibana-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-kibana-theme plugin -date: 2024-06-11 +date: 2024-06-19 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 5df2336d17003..f5bce0abc160d 100644 --- a/api_docs/kbn_ml_local_storage.mdx +++ b/api_docs/kbn_ml_local_storage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-local-storage title: "@kbn/ml-local-storage" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-local-storage plugin -date: 2024-06-11 +date: 2024-06-19 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 8b8b63473c8cb..2925c0b6db445 100644 --- a/api_docs/kbn_ml_nested_property.mdx +++ b/api_docs/kbn_ml_nested_property.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-nested-property title: "@kbn/ml-nested-property" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-nested-property plugin -date: 2024-06-11 +date: 2024-06-19 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 9b2076c76534a..4bfee361313a4 100644 --- a/api_docs/kbn_ml_number_utils.mdx +++ b/api_docs/kbn_ml_number_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-number-utils title: "@kbn/ml-number-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-number-utils plugin -date: 2024-06-11 +date: 2024-06-19 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 f43798e8064b9..4b5f183519c84 100644 --- a/api_docs/kbn_ml_query_utils.mdx +++ b/api_docs/kbn_ml_query_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-query-utils title: "@kbn/ml-query-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-query-utils plugin -date: 2024-06-11 +date: 2024-06-19 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 8e332a634f005..f5fb6b5a066d8 100644 --- a/api_docs/kbn_ml_random_sampler_utils.mdx +++ b/api_docs/kbn_ml_random_sampler_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-random-sampler-utils title: "@kbn/ml-random-sampler-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-random-sampler-utils plugin -date: 2024-06-11 +date: 2024-06-19 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 4f11466a9cb2d..a85bf8421e6c0 100644 --- a/api_docs/kbn_ml_route_utils.mdx +++ b/api_docs/kbn_ml_route_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-route-utils title: "@kbn/ml-route-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-route-utils plugin -date: 2024-06-11 +date: 2024-06-19 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 b0c8c479a24af..ee9cc56bc047a 100644 --- a/api_docs/kbn_ml_runtime_field_utils.mdx +++ b/api_docs/kbn_ml_runtime_field_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-runtime-field-utils title: "@kbn/ml-runtime-field-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-runtime-field-utils plugin -date: 2024-06-11 +date: 2024-06-19 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 18a154dbc7670..7100105fa361a 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_ml_time_buckets.mdx b/api_docs/kbn_ml_time_buckets.mdx index 678f8c1fcb18a..7a1829d634b78 100644 --- a/api_docs/kbn_ml_time_buckets.mdx +++ b/api_docs/kbn_ml_time_buckets.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-time-buckets title: "@kbn/ml-time-buckets" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-time-buckets plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-time-buckets'] --- import kbnMlTimeBucketsObj from './kbn_ml_time_buckets.devdocs.json'; diff --git a/api_docs/kbn_ml_trained_models_utils.mdx b/api_docs/kbn_ml_trained_models_utils.mdx index 2b7e8eee414f1..34e51866932da 100644 --- a/api_docs/kbn_ml_trained_models_utils.mdx +++ b/api_docs/kbn_ml_trained_models_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-trained-models-utils title: "@kbn/ml-trained-models-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-trained-models-utils plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-trained-models-utils'] --- import kbnMlTrainedModelsUtilsObj from './kbn_ml_trained_models_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_ui_actions.mdx b/api_docs/kbn_ml_ui_actions.mdx index 9fbbd74209972..88be9a7227836 100644 --- a/api_docs/kbn_ml_ui_actions.mdx +++ b/api_docs/kbn_ml_ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-ui-actions title: "@kbn/ml-ui-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-ui-actions plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-ui-actions'] --- import kbnMlUiActionsObj from './kbn_ml_ui_actions.devdocs.json'; diff --git a/api_docs/kbn_ml_url_state.mdx b/api_docs/kbn_ml_url_state.mdx index c7a02f8a313bf..68f724104cb2c 100644 --- a/api_docs/kbn_ml_url_state.mdx +++ b/api_docs/kbn_ml_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-url-state title: "@kbn/ml-url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-url-state plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-url-state'] --- import kbnMlUrlStateObj from './kbn_ml_url_state.devdocs.json'; diff --git a/api_docs/kbn_mock_idp_utils.mdx b/api_docs/kbn_mock_idp_utils.mdx index 0595d93a84998..d56e817f32cfe 100644 --- a/api_docs/kbn_mock_idp_utils.mdx +++ b/api_docs/kbn_mock_idp_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mock-idp-utils title: "@kbn/mock-idp-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mock-idp-utils plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mock-idp-utils'] --- import kbnMockIdpUtilsObj from './kbn_mock_idp_utils.devdocs.json'; diff --git a/api_docs/kbn_monaco.devdocs.json b/api_docs/kbn_monaco.devdocs.json index e8cc403b3ee30..a432c6389bae2 100644 --- a/api_docs/kbn_monaco.devdocs.json +++ b/api_docs/kbn_monaco.devdocs.json @@ -482,7 +482,7 @@ "label": "getSources", "description": [], "signature": [ - "CallbackFn<{}, { name: string; hidden: boolean; }> | undefined" + "CallbackFn<{}, { name: string; hidden: boolean; title?: string | undefined; dataStreams?: { name: string; title?: string | undefined; }[] | undefined; }> | undefined" ], "path": "packages/kbn-esql-validation-autocomplete/src/shared/types.ts", "deprecated": false, diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 27c0a097b1170..23ac7c7f51194 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2024-06-11 +date: 2024-06-19 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 76664d1bf8ef2..bce22ad4b56e8 100644 --- a/api_docs/kbn_object_versioning.mdx +++ b/api_docs/kbn_object_versioning.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-object-versioning title: "@kbn/object-versioning" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/object-versioning plugin -date: 2024-06-11 +date: 2024-06-19 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 f81353a56c643..ab2abedacdfb7 100644 --- a/api_docs/kbn_observability_alert_details.mdx +++ b/api_docs/kbn_observability_alert_details.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alert-details title: "@kbn/observability-alert-details" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alert-details plugin -date: 2024-06-11 +date: 2024-06-19 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 0944b4a2ff6a3..6f86bf5ff4e02 100644 --- a/api_docs/kbn_observability_alerting_test_data.mdx +++ b/api_docs/kbn_observability_alerting_test_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-alerting-test-data title: "@kbn/observability-alerting-test-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-alerting-test-data plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-alerting-test-data'] --- import kbnObservabilityAlertingTestDataObj from './kbn_observability_alerting_test_data.devdocs.json'; diff --git a/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx b/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx index 8a7e405d7203c..2cefb2e5feaa8 100644 --- a/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx +++ b/api_docs/kbn_observability_get_padded_alert_time_range_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-observability-get-padded-alert-time-range-util title: "@kbn/observability-get-padded-alert-time-range-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/observability-get-padded-alert-time-range-util plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/observability-get-padded-alert-time-range-util'] --- import kbnObservabilityGetPaddedAlertTimeRangeUtilObj from './kbn_observability_get_padded_alert_time_range_util.devdocs.json'; diff --git a/api_docs/kbn_openapi_bundler.mdx b/api_docs/kbn_openapi_bundler.mdx index cd7abbb4b98dd..6f7bcb21157f8 100644 --- a/api_docs/kbn_openapi_bundler.mdx +++ b/api_docs/kbn_openapi_bundler.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-openapi-bundler title: "@kbn/openapi-bundler" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/openapi-bundler plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/openapi-bundler'] --- import kbnOpenapiBundlerObj from './kbn_openapi_bundler.devdocs.json'; diff --git a/api_docs/kbn_openapi_generator.mdx b/api_docs/kbn_openapi_generator.mdx index c54da64332a3c..a3843270d5540 100644 --- a/api_docs/kbn_openapi_generator.mdx +++ b/api_docs/kbn_openapi_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-openapi-generator title: "@kbn/openapi-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/openapi-generator plugin -date: 2024-06-11 +date: 2024-06-19 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 44f4ec87fbb08..32c15b1d0581a 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2024-06-11 +date: 2024-06-19 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 954eb26baf431..0d8c3816185a8 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2024-06-11 +date: 2024-06-19 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 c04ec602e7906..d27bd79efae83 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_panel_loader.mdx b/api_docs/kbn_panel_loader.mdx index 2eb6759dbd30b..998c16ed1affb 100644 --- a/api_docs/kbn_panel_loader.mdx +++ b/api_docs/kbn_panel_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-panel-loader title: "@kbn/panel-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/panel-loader plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/panel-loader'] --- import kbnPanelLoaderObj from './kbn_panel_loader.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 552529ecc9e49..4c5416e26ed0f 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_check.mdx b/api_docs/kbn_plugin_check.mdx index 5f5bd1619b8ae..b27b01dcff788 100644 --- a/api_docs/kbn_plugin_check.mdx +++ b/api_docs/kbn_plugin_check.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-check title: "@kbn/plugin-check" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-check plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-check'] --- import kbnPluginCheckObj from './kbn_plugin_check.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index cad19be50f46f..586db8494bb0c 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2024-06-11 +date: 2024-06-19 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 da1988b6f9139..6ef469883b64e 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_presentation_containers.mdx b/api_docs/kbn_presentation_containers.mdx index 8761c9037da7f..86b3132502f3c 100644 --- a/api_docs/kbn_presentation_containers.mdx +++ b/api_docs/kbn_presentation_containers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-presentation-containers title: "@kbn/presentation-containers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/presentation-containers plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/presentation-containers'] --- import kbnPresentationContainersObj from './kbn_presentation_containers.devdocs.json'; diff --git a/api_docs/kbn_presentation_publishing.mdx b/api_docs/kbn_presentation_publishing.mdx index ae28d7998adcd..0e021d0ca307c 100644 --- a/api_docs/kbn_presentation_publishing.mdx +++ b/api_docs/kbn_presentation_publishing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-presentation-publishing title: "@kbn/presentation-publishing" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/presentation-publishing plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/presentation-publishing'] --- import kbnPresentationPublishingObj from './kbn_presentation_publishing.devdocs.json'; diff --git a/api_docs/kbn_profiling_utils.mdx b/api_docs/kbn_profiling_utils.mdx index 74de6a8fcc2be..68813cccd6190 100644 --- a/api_docs/kbn_profiling_utils.mdx +++ b/api_docs/kbn_profiling_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-profiling-utils title: "@kbn/profiling-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/profiling-utils plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/profiling-utils'] --- import kbnProfilingUtilsObj from './kbn_profiling_utils.devdocs.json'; diff --git a/api_docs/kbn_random_sampling.mdx b/api_docs/kbn_random_sampling.mdx index 32c974c547dc6..b29ce64a87355 100644 --- a/api_docs/kbn_random_sampling.mdx +++ b/api_docs/kbn_random_sampling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-random-sampling title: "@kbn/random-sampling" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/random-sampling plugin -date: 2024-06-11 +date: 2024-06-19 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 678c39d86f521..f411673525943 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_react_hooks.mdx b/api_docs/kbn_react_hooks.mdx index 5b00883949005..a7443008e4121 100644 --- a/api_docs/kbn_react_hooks.mdx +++ b/api_docs/kbn_react_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-hooks title: "@kbn/react-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-hooks plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-hooks'] --- import kbnReactHooksObj from './kbn_react_hooks.devdocs.json'; diff --git a/api_docs/kbn_react_kibana_context_common.mdx b/api_docs/kbn_react_kibana_context_common.mdx index 4a825f4c6920c..0f8bb09a45811 100644 --- a/api_docs/kbn_react_kibana_context_common.mdx +++ b/api_docs/kbn_react_kibana_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-common title: "@kbn/react-kibana-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-common plugin -date: 2024-06-11 +date: 2024-06-19 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 afc25d48bc6aa..201f21fd2072d 100644 --- a/api_docs/kbn_react_kibana_context_render.mdx +++ b/api_docs/kbn_react_kibana_context_render.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-kibana-context-render title: "@kbn/react-kibana-context-render" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-kibana-context-render plugin -date: 2024-06-11 +date: 2024-06-19 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.devdocs.json b/api_docs/kbn_react_kibana_context_root.devdocs.json index fdef8f2de7d2f..400c634fcfb41 100644 --- a/api_docs/kbn_react_kibana_context_root.devdocs.json +++ b/api_docs/kbn_react_kibana_context_root.devdocs.json @@ -29,7 +29,7 @@ "\nPrepares and returns a configured `EuiProvider` for use in Kibana roots. In most cases, this utility context\nshould not be used. Instead, refer to `KibanaRootContextProvider` to set up the root of Kibana." ], "signature": [ - "({ theme: { theme$ }, globalStyles: globalStylesProp, colorMode: colorModeProp, children, }: React.PropsWithChildren(featureFlags: T[]) => FeatureFlag" + ], + "path": "packages/response-ops/feature_flag_service/feature_flag_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/response-ops-feature-flag-service", + "id": "def-common.createFeatureFlagService.$1", + "type": "Array", + "tags": [], + "label": "featureFlags", + "description": [], + "signature": [ + "T[]" + ], + "path": "packages/response-ops/feature_flag_service/feature_flag_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/response-ops-feature-flag-service", + "id": "def-common.FeatureFlagService", + "type": "Type", + "tags": [], + "label": "FeatureFlagService", + "description": [], + "signature": [ + "FeatureFlag" + ], + "path": "packages/response-ops/feature_flag_service/feature_flag_service.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_response_ops_feature_flag_service.mdx b/api_docs/kbn_response_ops_feature_flag_service.mdx new file mode 100644 index 0000000000000..832f03fdfeb36 --- /dev/null +++ b/api_docs/kbn_response_ops_feature_flag_service.mdx @@ -0,0 +1,33 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnResponseOpsFeatureFlagServicePluginApi +slug: /kibana-dev-docs/api/kbn-response-ops-feature-flag-service +title: "@kbn/response-ops-feature-flag-service" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/response-ops-feature-flag-service plugin +date: 2024-06-19 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/response-ops-feature-flag-service'] +--- +import kbnResponseOpsFeatureFlagServiceObj from './kbn_response_ops_feature_flag_service.devdocs.json'; + + + +Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 3 | 0 | 3 | 0 | + +## Common + +### Functions + + +### Consts, variables and types + + diff --git a/api_docs/kbn_rison.mdx b/api_docs/kbn_rison.mdx index 9d459a062b736..02fe8a3f84e65 100644 --- a/api_docs/kbn_rison.mdx +++ b/api_docs/kbn_rison.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rison title: "@kbn/rison" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rison plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_router_to_openapispec.mdx b/api_docs/kbn_router_to_openapispec.mdx index 7301ec678d553..3a36a6cad5926 100644 --- a/api_docs/kbn_router_to_openapispec.mdx +++ b/api_docs/kbn_router_to_openapispec.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-router-to-openapispec title: "@kbn/router-to-openapispec" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/router-to-openapispec plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/router-to-openapispec'] --- import kbnRouterToOpenapispecObj from './kbn_router_to_openapispec.devdocs.json'; diff --git a/api_docs/kbn_router_utils.mdx b/api_docs/kbn_router_utils.mdx index 7f86a68869417..ff48953d6b839 100644 --- a/api_docs/kbn_router_utils.mdx +++ b/api_docs/kbn_router_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-router-utils title: "@kbn/router-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/router-utils plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/router-utils'] --- import kbnRouterUtilsObj from './kbn_router_utils.devdocs.json'; diff --git a/api_docs/kbn_rrule.mdx b/api_docs/kbn_rrule.mdx index 52ab4d264435f..2938c97001f60 100644 --- a/api_docs/kbn_rrule.mdx +++ b/api_docs/kbn_rrule.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rrule title: "@kbn/rrule" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rrule plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rrule'] --- import kbnRruleObj from './kbn_rrule.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index cd4351890d289..bcc4f8825b62b 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_saved_objects_settings.mdx b/api_docs/kbn_saved_objects_settings.mdx index 7490ca3f0f246..3f3dd6ae50a6d 100644 --- a/api_docs/kbn_saved_objects_settings.mdx +++ b/api_docs/kbn_saved_objects_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-saved-objects-settings title: "@kbn/saved-objects-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/saved-objects-settings plugin -date: 2024-06-11 +date: 2024-06-19 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 b2bc44ce92385..180f6feaa00b6 100644 --- a/api_docs/kbn_search_api_panels.mdx +++ b/api_docs/kbn_search_api_panels.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-api-panels title: "@kbn/search-api-panels" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-api-panels plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-api-panels'] --- import kbnSearchApiPanelsObj from './kbn_search_api_panels.devdocs.json'; diff --git a/api_docs/kbn_search_connectors.devdocs.json b/api_docs/kbn_search_connectors.devdocs.json index 6e88d1a9bde29..3cf388d6f1656 100644 --- a/api_docs/kbn_search_connectors.devdocs.json +++ b/api_docs/kbn_search_connectors.devdocs.json @@ -6499,23 +6499,23 @@ "section": "def-common.ConnectorStatus", "text": "ConnectorStatus" }, - "; description: string | null; language: string | null; configuration: ", + "; description: string | null; language: string | null; pipeline?: ", { "pluginId": "@kbn/search-connectors", "scope": "common", "docId": "kibKbnSearchConnectorsPluginApi", - "section": "def-common.ConnectorConfiguration", - "text": "ConnectorConfiguration" + "section": "def-common.IngestPipelineParams", + "text": "IngestPipelineParams" }, - "; index_name: string | null; pipeline?: ", + " | null | undefined; configuration: ", { "pluginId": "@kbn/search-connectors", "scope": "common", "docId": "kibKbnSearchConnectorsPluginApi", - "section": "def-common.IngestPipelineParams", - "text": "IngestPipelineParams" + "section": "def-common.ConnectorConfiguration", + "text": "ConnectorConfiguration" }, - " | null | undefined; api_key_id: string | null; api_key_secret_id: string | null; custom_scheduling: ", + "; index_name: string | null; api_key_id: string | null; api_key_secret_id: string | null; custom_scheduling: ", { "pluginId": "@kbn/search-connectors", "scope": "common", @@ -15154,6 +15154,406 @@ "trackAdoption": false } ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.use_document_level_security", + "type": "Object", + "tags": [], + "label": "use_document_level_security", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.use_document_level_security.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.use_document_level_security.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.use_document_level_security.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TOGGLE" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.use_document_level_security.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.use_document_level_security.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.use_document_level_security.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.use_document_level_security.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.use_document_level_security.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.use_document_level_security.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.use_document_level_security.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".BOOLEAN" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.use_document_level_security.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.use_document_level_security.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.use_document_level_security.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.include_inherited_users_and_groups", + "type": "Object", + "tags": [], + "label": "include_inherited_users_and_groups", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.include_inherited_users_and_groups.default_value", + "type": "Uncategorized", + "tags": [], + "label": "default_value", + "description": [], + "signature": [ + "null" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.include_inherited_users_and_groups.depends_on", + "type": "Array", + "tags": [], + "label": "depends_on", + "description": [], + "signature": [ + "{ field: string; value: true; }[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.include_inherited_users_and_groups.display", + "type": "string", + "tags": [], + "label": "display", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.DisplayType", + "text": "DisplayType" + }, + ".TOGGLE" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.include_inherited_users_and_groups.label", + "type": "string", + "tags": [], + "label": "label", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.include_inherited_users_and_groups.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.include_inherited_users_and_groups.order", + "type": "number", + "tags": [], + "label": "order", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.include_inherited_users_and_groups.required", + "type": "boolean", + "tags": [], + "label": "required", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.include_inherited_users_and_groups.sensitive", + "type": "boolean", + "tags": [], + "label": "sensitive", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.include_inherited_users_and_groups.tooltip", + "type": "string", + "tags": [], + "label": "tooltip", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.include_inherited_users_and_groups.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "signature": [ + { + "pluginId": "@kbn/search-connectors", + "scope": "common", + "docId": "kibKbnSearchConnectorsPluginApi", + "section": "def-common.FieldType", + "text": "FieldType" + }, + ".BOOLEAN" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.include_inherited_users_and_groups.ui_restrictions", + "type": "Array", + "tags": [], + "label": "ui_restrictions", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.include_inherited_users_and_groups.validations", + "type": "Array", + "tags": [], + "label": "validations", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.configuration.include_inherited_users_and_groups.value", + "type": "boolean", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "false" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] } ] }, @@ -15168,6 +15568,33 @@ "deprecated": false, "trackAdoption": false, "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.features.FeatureName.DOCUMENT_LEVEL_SECURITY", + "type": "Object", + "tags": [], + "label": "[FeatureName.DOCUMENT_LEVEL_SECURITY]", + "description": [], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/search-connectors", + "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.features.FeatureName.DOCUMENT_LEVEL_SECURITY.enabled", + "type": "boolean", + "tags": [], + "label": "enabled", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-search-connectors/types/native_connectors.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, { "parentPluginId": "@kbn/search-connectors", "id": "def-common.NATIVE_CONNECTOR_DEFINITIONS.dropbox.features.FeatureName.SYNC_RULES", diff --git a/api_docs/kbn_search_connectors.mdx b/api_docs/kbn_search_connectors.mdx index 120f2d64a47e4..b9c9c36ee0cc4 100644 --- a/api_docs/kbn_search_connectors.mdx +++ b/api_docs/kbn_search_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-connectors title: "@kbn/search-connectors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-connectors plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-connectors'] --- import kbnSearchConnectorsObj from './kbn_search_connectors.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-ki | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 3687 | 0 | 3687 | 0 | +| 3717 | 0 | 3717 | 0 | ## Common diff --git a/api_docs/kbn_search_errors.mdx b/api_docs/kbn_search_errors.mdx index f9fc9843dace3..95d9c212126d9 100644 --- a/api_docs/kbn_search_errors.mdx +++ b/api_docs/kbn_search_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-errors title: "@kbn/search-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-errors plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-errors'] --- import kbnSearchErrorsObj from './kbn_search_errors.devdocs.json'; diff --git a/api_docs/kbn_search_index_documents.mdx b/api_docs/kbn_search_index_documents.mdx index 500851eb7e2e7..af6487fc4bd74 100644 --- a/api_docs/kbn_search_index_documents.mdx +++ b/api_docs/kbn_search_index_documents.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-index-documents title: "@kbn/search-index-documents" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-index-documents plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-index-documents'] --- import kbnSearchIndexDocumentsObj from './kbn_search_index_documents.devdocs.json'; diff --git a/api_docs/kbn_search_response_warnings.mdx b/api_docs/kbn_search_response_warnings.mdx index a72271bd15daf..164bc07bfde00 100644 --- a/api_docs/kbn_search_response_warnings.mdx +++ b/api_docs/kbn_search_response_warnings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-response-warnings title: "@kbn/search-response-warnings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-response-warnings plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-response-warnings'] --- import kbnSearchResponseWarningsObj from './kbn_search_response_warnings.devdocs.json'; diff --git a/api_docs/kbn_search_types.mdx b/api_docs/kbn_search_types.mdx index 3861f702d4784..bde8f9b48769a 100644 --- a/api_docs/kbn_search_types.mdx +++ b/api_docs/kbn_search_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-search-types title: "@kbn/search-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/search-types plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/search-types'] --- import kbnSearchTypesObj from './kbn_search_types.devdocs.json'; diff --git a/api_docs/kbn_security_hardening.mdx b/api_docs/kbn_security_hardening.mdx index 28e849eba640d..d77972c92b169 100644 --- a/api_docs/kbn_security_hardening.mdx +++ b/api_docs/kbn_security_hardening.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-hardening title: "@kbn/security-hardening" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-hardening plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-hardening'] --- import kbnSecurityHardeningObj from './kbn_security_hardening.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_common.mdx b/api_docs/kbn_security_plugin_types_common.mdx index 62331ae58eaf1..6af26b6b71fbf 100644 --- a/api_docs/kbn_security_plugin_types_common.mdx +++ b/api_docs/kbn_security_plugin_types_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-common title: "@kbn/security-plugin-types-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-common plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-common'] --- import kbnSecurityPluginTypesCommonObj from './kbn_security_plugin_types_common.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_public.devdocs.json b/api_docs/kbn_security_plugin_types_public.devdocs.json index 6c39e67266318..637e988957f0e 100644 --- a/api_docs/kbn_security_plugin_types_public.devdocs.json +++ b/api_docs/kbn_security_plugin_types_public.devdocs.json @@ -651,26 +651,6 @@ "plugin": "imageEmbeddable", "path": "src/plugins/image_embeddable/public/components/image_editor/open_image_editor.tsx" }, - { - "plugin": "enterpriseSearch", - "path": "x-pack/plugins/enterprise_search/public/applications/index.tsx" - }, - { - "plugin": "enterpriseSearch", - "path": "x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.tsx" - }, - { - "plugin": "enterpriseSearch", - "path": "x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.tsx" - }, - { - "plugin": "enterpriseSearch", - "path": "x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.tsx" - }, - { - "plugin": "enterpriseSearch", - "path": "x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.tsx" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/management/links.ts" diff --git a/api_docs/kbn_security_plugin_types_public.mdx b/api_docs/kbn_security_plugin_types_public.mdx index e5c52bb8790f9..e9c43ae092a19 100644 --- a/api_docs/kbn_security_plugin_types_public.mdx +++ b/api_docs/kbn_security_plugin_types_public.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-public title: "@kbn/security-plugin-types-public" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-public plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-public'] --- import kbnSecurityPluginTypesPublicObj from './kbn_security_plugin_types_public.devdocs.json'; diff --git a/api_docs/kbn_security_plugin_types_server.devdocs.json b/api_docs/kbn_security_plugin_types_server.devdocs.json index 3702b94894196..15aea85d3b059 100644 --- a/api_docs/kbn_security_plugin_types_server.devdocs.json +++ b/api_docs/kbn_security_plugin_types_server.devdocs.json @@ -110,7 +110,7 @@ "section": "def-common.Type", "text": "Type" }, - " | undefined; } & { spaces: string[] | \"*\"[]; }>[]; elasticsearch: Readonly<{ cluster?: string[] | undefined; indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; run_as?: string[] | undefined; } & {}>; }>>>; }>" + " | undefined; } & { spaces: string[] | \"*\"[]; }>[]; elasticsearch: Readonly<{ cluster?: string[] | undefined; indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; run_as?: string[] | undefined; } & {}>; }>>>; }>" ], "path": "x-pack/packages/security/plugin_types_server/src/authentication/api_keys/api_keys.ts", "deprecated": false, @@ -590,7 +590,7 @@ "section": "def-common.KibanaRequest", "text": "KibanaRequest" }, - ", createParams: Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; role_descriptors: Record>; }> | Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; kibana_role_descriptors: Record | undefined; } & { spaces: string[] | \"*\"[]; }>[]; elasticsearch: Readonly<{ cluster?: string[] | undefined; indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; run_as?: string[] | undefined; } & {}>; }>>; }>) => Promise<", + ", createParams: Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; role_descriptors: Record>; }> | Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; kibana_role_descriptors: Record | undefined; } & { spaces: string[] | \"*\"[]; }>[]; elasticsearch: Readonly<{ cluster?: string[] | undefined; indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; run_as?: string[] | undefined; } & {}>; }>>; }>) => Promise<", { "pluginId": "@kbn/security-plugin-types-server", "scope": "server", @@ -638,7 +638,7 @@ "Create operation parameters." ], "signature": [ - "Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; role_descriptors: Record>; }> | Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; kibana_role_descriptors: Record | undefined; } & { spaces: string[] | \"*\"[]; }>[]; elasticsearch: Readonly<{ cluster?: string[] | undefined; indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; run_as?: string[] | undefined; } & {}>; }>>; }>" + "Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; role_descriptors: Record>; }> | Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; kibana_role_descriptors: Record | undefined; } & { spaces: string[] | \"*\"[]; }>[]; elasticsearch: Readonly<{ cluster?: string[] | undefined; indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; run_as?: string[] | undefined; } & {}>; }>>; }>" ], "path": "x-pack/packages/security/plugin_types_server/src/authentication/api_keys/api_keys.ts", "deprecated": false, @@ -3193,83 +3193,83 @@ }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/security.ts" + "path": "x-pack/plugins/fleet/server/services/api_keys/security.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/api_keys/transform_api_keys.ts" + "path": "x-pack/plugins/fleet/server/services/security/security.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/api_keys/security.ts" + "path": "x-pack/plugins/fleet/server/services/api_keys/transform_api_keys.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/setup/fleet_server_policies_enrollment_keys.ts" + "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/setup/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/settings/index.ts" + "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts" + "path": "x-pack/plugins/fleet/server/services/setup/fleet_server_policies_enrollment_keys.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/setup/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/settings/index.ts" }, { "plugin": "cloudDefend", @@ -3291,10 +3291,6 @@ "plugin": "enterpriseSearch", "path": "x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts" }, - { - "plugin": "enterpriseSearch", - "path": "x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts" - }, { "plugin": "lists", "path": "x-pack/plugins/lists/server/get_user.ts" @@ -3355,14 +3351,6 @@ "plugin": "serverlessSearch", "path": "x-pack/plugins/serverless_search/server/routes/api_key_routes.ts" }, - { - "plugin": "serverlessSearch", - "path": "x-pack/plugins/serverless_search/server/routes/api_key_routes.ts" - }, - { - "plugin": "serverlessSearch", - "path": "x-pack/plugins/serverless_search/server/routes/indices_routes.ts" - }, { "plugin": "transform", "path": "x-pack/plugins/transform/server/routes/api/reauthorize_transforms/route_handler_factory.ts" @@ -4240,7 +4228,7 @@ "label": "CreateAPIKeyParams", "description": [], "signature": [ - "Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; role_descriptors: Record>; }> | Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; kibana_role_descriptors: Record | undefined; } & { spaces: string[] | \"*\"[]; }>[]; elasticsearch: Readonly<{ cluster?: string[] | undefined; indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; run_as?: string[] | undefined; } & {}>; }>>; }> | Readonly<{ metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { type: \"cross_cluster\"; name: string; access: Readonly<{ search?: Readonly<{ query?: any; field_security?: any; allow_restricted_indices?: boolean | undefined; } & { names: string[]; }>[] | undefined; replication?: Readonly<{} & { names: string[]; }>[] | undefined; } & {}>; }>" + "Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; role_descriptors: Record>; }> | Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; kibana_role_descriptors: Record | undefined; } & { spaces: string[] | \"*\"[]; }>[]; elasticsearch: Readonly<{ cluster?: string[] | undefined; indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; run_as?: string[] | undefined; } & {}>; }>>; }> | Readonly<{ metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { type: \"cross_cluster\"; name: string; access: Readonly<{ search?: Readonly<{ query?: any; field_security?: any; allow_restricted_indices?: boolean | undefined; } & { names: string[]; }>[] | undefined; replication?: Readonly<{} & { names: string[]; }>[] | undefined; } & {}>; }>" ], "path": "x-pack/packages/security/plugin_types_server/src/authentication/api_keys/api_keys.ts", "deprecated": false, @@ -4302,7 +4290,7 @@ "label": "CreateRestAPIKeyWithKibanaPrivilegesParams", "description": [], "signature": [ - "{ readonly type?: \"rest\" | undefined; readonly metadata?: Readonly<{} & {}> | undefined; readonly expiration?: string | undefined; readonly name: string; readonly kibana_role_descriptors: Record | undefined; } & { spaces: string[] | \"*\"[]; }>[]; elasticsearch: Readonly<{ cluster?: string[] | undefined; indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; run_as?: string[] | undefined; } & {}>; }>>; }" + "{ readonly type?: \"rest\" | undefined; readonly metadata?: Readonly<{} & {}> | undefined; readonly expiration?: string | undefined; readonly name: string; readonly kibana_role_descriptors: Record | undefined; } & { spaces: string[] | \"*\"[]; }>[]; elasticsearch: Readonly<{ cluster?: string[] | undefined; indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; run_as?: string[] | undefined; } & {}>; }>>; }" ], "path": "x-pack/packages/security/plugin_types_server/src/authentication/api_keys/api_keys.ts", "deprecated": false, @@ -4317,7 +4305,7 @@ "label": "ElasticsearchPrivilegesType", "description": [], "signature": [ - "{ readonly cluster?: string[] | undefined; readonly indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; readonly remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; readonly remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; readonly run_as?: string[] | undefined; }" + "{ readonly cluster?: string[] | undefined; readonly indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; readonly remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; readonly remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; readonly run_as?: string[] | undefined; }" ], "path": "x-pack/packages/security/plugin_types_server/src/authorization/role_schema.ts", "deprecated": false, @@ -4514,7 +4502,7 @@ "section": "def-common.Type", "text": "Type" }, - " | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined>; remote_indices: ", + " | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined>; remote_indices: ", { "pluginId": "@kbn/config-schema", "scope": "common", @@ -4522,7 +4510,7 @@ "section": "def-common.Type", "text": "Type" }, - " | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined>; run_as: ", + " | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined>; run_as: ", { "pluginId": "@kbn/config-schema", "scope": "common", diff --git a/api_docs/kbn_security_plugin_types_server.mdx b/api_docs/kbn_security_plugin_types_server.mdx index 2a7ee4796cf6e..1e2222e5ee35b 100644 --- a/api_docs/kbn_security_plugin_types_server.mdx +++ b/api_docs/kbn_security_plugin_types_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-plugin-types-server title: "@kbn/security-plugin-types-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-plugin-types-server plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/security-plugin-types-server'] --- import kbnSecurityPluginTypesServerObj from './kbn_security_plugin_types_server.devdocs.json'; diff --git a/api_docs/kbn_security_solution_features.mdx b/api_docs/kbn_security_solution_features.mdx index 5f1f2f4696d4f..9d91ab7470056 100644 --- a/api_docs/kbn_security_solution_features.mdx +++ b/api_docs/kbn_security_solution_features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-features title: "@kbn/security-solution-features" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-features plugin -date: 2024-06-11 +date: 2024-06-19 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 6e93674a698fe..6bf6254bc2591 100644 --- a/api_docs/kbn_security_solution_navigation.mdx +++ b/api_docs/kbn_security_solution_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-navigation title: "@kbn/security-solution-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-navigation plugin -date: 2024-06-11 +date: 2024-06-19 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 907ef0df9ca53..d011760047994 100644 --- a/api_docs/kbn_security_solution_side_nav.mdx +++ b/api_docs/kbn_security_solution_side_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-side-nav title: "@kbn/security-solution-side-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-side-nav plugin -date: 2024-06-11 +date: 2024-06-19 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 82bf409793461..168d289b799cd 100644 --- a/api_docs/kbn_security_solution_storybook_config.mdx +++ b/api_docs/kbn_security_solution_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-security-solution-storybook-config title: "@kbn/security-solution-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/security-solution-storybook-config plugin -date: 2024-06-11 +date: 2024-06-19 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 3f8cb118085d3..2ac141175ccf8 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2024-06-11 +date: 2024-06-19 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 ed8e7f8a9c020..d6a3696437744 100644 --- a/api_docs/kbn_securitysolution_data_table.mdx +++ b/api_docs/kbn_securitysolution_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-data-table title: "@kbn/securitysolution-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-data-table plugin -date: 2024-06-11 +date: 2024-06-19 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 8eb65c08e673f..8762cacb033b3 100644 --- a/api_docs/kbn_securitysolution_ecs.mdx +++ b/api_docs/kbn_securitysolution_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-ecs title: "@kbn/securitysolution-ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-ecs plugin -date: 2024-06-11 +date: 2024-06-19 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 1787dcd369d75..ff2f0609cc740 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2024-06-11 +date: 2024-06-19 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 7e28aff1ef627..09104e8d6febc 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.mdx +++ b/api_docs/kbn_securitysolution_exception_list_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-exception-list-components title: "@kbn/securitysolution-exception-list-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-exception-list-components plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 40529013d2b3c..873699f6b4092 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2024-06-11 +date: 2024-06-19 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 00e9bb07d45f2..68eaaad8c99b1 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2024-06-11 +date: 2024-06-19 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 00f1e348614f8..c78391812aced 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2024-06-11 +date: 2024-06-19 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 afffc02004eef..fdabec7085bb6 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2024-06-11 +date: 2024-06-19 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 bc085af476770..dd230ff4e75d2 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2024-06-11 +date: 2024-06-19 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 d3ad05962d01f..69cb2d0521c29 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2024-06-11 +date: 2024-06-19 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.devdocs.json b/api_docs/kbn_securitysolution_list_constants.devdocs.json index b58ba99b72179..889e28f5dc20c 100644 --- a/api_docs/kbn_securitysolution_list_constants.devdocs.json +++ b/api_docs/kbn_securitysolution_list_constants.devdocs.json @@ -77,39 +77,39 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/blocklist/constants.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts" + "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/blocklist/services/blocklists_api_client.ts" + "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts" }, { "plugin": "securitySolution", @@ -247,22 +247,6 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/fleet_integration/handlers/create_event_filters.ts" }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts" - }, - { - "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts" - }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts" @@ -315,6 +299,22 @@ "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/public/management/pages/event_filters/service/api_client.ts" }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts" + }, + { + "plugin": "securitySolution", + "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts" + }, { "plugin": "securitySolution", "path": "x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts" @@ -461,19 +461,19 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts" }, { "plugin": "securitySolution", @@ -481,11 +481,11 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts" + "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/host_isolation_exceptions_api_client.ts" + "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts" }, { "plugin": "lists", @@ -762,39 +762,39 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/trusted_apps/constants.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts" + "path": "x-pack/plugins/security_solution/public/management/pages/policy/view/policy_hooks.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts" + "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/management/pages/trusted_apps/service/api_client.ts" + "path": "x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts" }, { "plugin": "lists", diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index ca56649d40ded..6b3e626eccecf 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2024-06-11 +date: 2024-06-19 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 1ff5bae838a3b..a2a939321dd14 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2024-06-11 +date: 2024-06-19 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.devdocs.json b/api_docs/kbn_securitysolution_list_utils.devdocs.json index 6cd192ee0916e..5cf64af6019c8 100644 --- a/api_docs/kbn_securitysolution_list_utils.devdocs.json +++ b/api_docs/kbn_securitysolution_list_utils.devdocs.json @@ -2254,6 +2254,56 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/securitysolution-list-utils", + "id": "def-common.hasWrongOperatorWithWildcard", + "type": "Function", + "tags": [], + "label": "hasWrongOperatorWithWildcard", + "description": [ + "\nGiven an exceptions list, determine if any entries have an \"IS\" operator with a wildcard value" + ], + "signature": [ + "(items: ", + { + "pluginId": "@kbn/securitysolution-list-utils", + "scope": "common", + "docId": "kibKbnSecuritysolutionListUtilsPluginApi", + "section": "def-common.ExceptionsBuilderReturnExceptionItem", + "text": "ExceptionsBuilderReturnExceptionItem" + }, + "[]) => boolean" + ], + "path": "packages/kbn-securitysolution-list-utils/src/helpers/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/securitysolution-list-utils", + "id": "def-common.hasWrongOperatorWithWildcard.$1", + "type": "Array", + "tags": [], + "label": "items", + "description": [], + "signature": [ + { + "pluginId": "@kbn/securitysolution-list-utils", + "scope": "common", + "docId": "kibKbnSecuritysolutionListUtilsPluginApi", + "section": "def-common.ExceptionsBuilderReturnExceptionItem", + "text": "ExceptionsBuilderReturnExceptionItem" + }, + "[]" + ], + "path": "packages/kbn-securitysolution-list-utils/src/helpers/index.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/securitysolution-list-utils", "id": "def-common.isEntryNested", diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index 100490f3691c4..11a08c128e4f5 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-detection-engine](https://github.com/orgs/elastic/tea | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 207 | 0 | 160 | 0 | +| 209 | 0 | 161 | 0 | ## Common diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index 063e4dce45c39..ef5bf78923fbd 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2024-06-11 +date: 2024-06-19 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 ee3dc7b73d588..21bee7c0e72a2 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.devdocs.json b/api_docs/kbn_securitysolution_utils.devdocs.json index 0850820f705ff..d36fe39b6481f 100644 --- a/api_docs/kbn_securitysolution_utils.devdocs.json +++ b/api_docs/kbn_securitysolution_utils.devdocs.json @@ -528,7 +528,7 @@ "label": "validateHasWildcardWithWrongOperator", "description": [], "signature": [ - "({ operator, value, }: { operator: \"wildcard\" | \"match\" | \"nested\" | \"exists\" | \"match_any\"; value: string; }) => boolean" + "({ operator, value, }: { operator: \"wildcard\" | \"match\" | \"nested\" | \"exists\" | \"match_any\"; value: string | string[]; }) => boolean" ], "path": "packages/kbn-securitysolution-utils/src/path_validations/index.ts", "deprecated": false, @@ -562,10 +562,13 @@ { "parentPluginId": "@kbn/securitysolution-utils", "id": "def-common.validateHasWildcardWithWrongOperator.$1.value", - "type": "string", + "type": "CompoundType", "tags": [], "label": "value", "description": [], + "signature": [ + "string | string[]" + ], "path": "packages/kbn-securitysolution-utils/src/path_validations/index.ts", "deprecated": false, "trackAdoption": false @@ -671,7 +674,7 @@ "label": "validateWildcardInput", "description": [], "signature": [ - "(value?: string | undefined) => string | undefined" + "(value: string | string[]) => string | undefined" ], "path": "packages/kbn-securitysolution-utils/src/path_validations/index.ts", "deprecated": false, @@ -680,17 +683,17 @@ { "parentPluginId": "@kbn/securitysolution-utils", "id": "def-common.validateWildcardInput.$1", - "type": "string", + "type": "CompoundType", "tags": [], "label": "value", "description": [], "signature": [ - "string | undefined" + "string | string[]" ], "path": "packages/kbn-securitysolution-utils/src/path_validations/index.ts", "deprecated": false, "trackAdoption": false, - "isRequired": false + "isRequired": true } ], "returnComment": [], diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index a19a44e6f6cab..dc2b54cb365bd 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2024-06-11 +date: 2024-06-19 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 11854e8850a85..1f2f176f841e3 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2024-06-11 +date: 2024-06-19 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 ff5fbf2426d1e..7bf9a997101b8 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_serverless_common_settings.mdx b/api_docs/kbn_serverless_common_settings.mdx index 730772b560aa3..ca8a403348209 100644 --- a/api_docs/kbn_serverless_common_settings.mdx +++ b/api_docs/kbn_serverless_common_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-common-settings title: "@kbn/serverless-common-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-common-settings plugin -date: 2024-06-11 +date: 2024-06-19 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 4d232098498a9..16ca494177e31 100644 --- a/api_docs/kbn_serverless_observability_settings.mdx +++ b/api_docs/kbn_serverless_observability_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-observability-settings title: "@kbn/serverless-observability-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-observability-settings plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/serverless-observability-settings'] --- import kbnServerlessObservabilitySettingsObj from './kbn_serverless_observability_settings.devdocs.json'; diff --git a/api_docs/kbn_serverless_project_switcher.mdx b/api_docs/kbn_serverless_project_switcher.mdx index d8fd4118b627b..e52d006de0233 100644 --- a/api_docs/kbn_serverless_project_switcher.mdx +++ b/api_docs/kbn_serverless_project_switcher.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-project-switcher title: "@kbn/serverless-project-switcher" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-project-switcher plugin -date: 2024-06-11 +date: 2024-06-19 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 5449ee4d0e166..d5b634131da1a 100644 --- a/api_docs/kbn_serverless_search_settings.mdx +++ b/api_docs/kbn_serverless_search_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-search-settings title: "@kbn/serverless-search-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-search-settings plugin -date: 2024-06-11 +date: 2024-06-19 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 d3aa91f147cc6..3caf5ac24cec2 100644 --- a/api_docs/kbn_serverless_security_settings.mdx +++ b/api_docs/kbn_serverless_security_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-security-settings title: "@kbn/serverless-security-settings" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-security-settings plugin -date: 2024-06-11 +date: 2024-06-19 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 819514b70b66a..08bc40afae32c 100644 --- a/api_docs/kbn_serverless_storybook_config.mdx +++ b/api_docs/kbn_serverless_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-serverless-storybook-config title: "@kbn/serverless-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/serverless-storybook-config plugin -date: 2024-06-11 +date: 2024-06-19 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 bf22ccb0b7858..9745ecdb8186c 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_solution.mdx b/api_docs/kbn_shared_ux_avatar_solution.mdx index b0545052ad3f9..404f874299660 100644 --- a/api_docs/kbn_shared_ux_avatar_solution.mdx +++ b/api_docs/kbn_shared_ux_avatar_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-solution title: "@kbn/shared-ux-avatar-solution" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-solution plugin -date: 2024-06-11 +date: 2024-06-19 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 5172332cf725b..9333d04496cb4 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen title: "@kbn/shared-ux-button-exit-full-screen" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen plugin -date: 2024-06-11 +date: 2024-06-19 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 014c09bcb7761..ef29350965f53 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2024-06-11 +date: 2024-06-19 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 5fcd5afc4d5ee..d3673b25f72e7 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2024-06-11 +date: 2024-06-19 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 e990e5d46fb4c..5cb78c021e1f9 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_chrome_navigation.mdx b/api_docs/kbn_shared_ux_chrome_navigation.mdx index e6a67845e79b1..0a2ead2d37889 100644 --- a/api_docs/kbn_shared_ux_chrome_navigation.mdx +++ b/api_docs/kbn_shared_ux_chrome_navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-chrome-navigation title: "@kbn/shared-ux-chrome-navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-chrome-navigation plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-chrome-navigation'] --- import kbnSharedUxChromeNavigationObj from './kbn_shared_ux_chrome_navigation.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_error_boundary.mdx b/api_docs/kbn_shared_ux_error_boundary.mdx index 801ef9a3fa9c8..239d1b323afe6 100644 --- a/api_docs/kbn_shared_ux_error_boundary.mdx +++ b/api_docs/kbn_shared_ux_error_boundary.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-error-boundary title: "@kbn/shared-ux-error-boundary" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-error-boundary plugin -date: 2024-06-11 +date: 2024-06-19 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 df7e2ec519e71..b8ece7498308d 100644 --- a/api_docs/kbn_shared_ux_file_context.mdx +++ b/api_docs/kbn_shared_ux_file_context.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-context title: "@kbn/shared-ux-file-context" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-context plugin -date: 2024-06-11 +date: 2024-06-19 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 0ffd173c74b37..d66a1c29d299b 100644 --- a/api_docs/kbn_shared_ux_file_image.mdx +++ b/api_docs/kbn_shared_ux_file_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image title: "@kbn/shared-ux-file-image" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image plugin -date: 2024-06-11 +date: 2024-06-19 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 599d5faf5efde..4af0208d8e674 100644 --- a/api_docs/kbn_shared_ux_file_image_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_image_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image-mocks title: "@kbn/shared-ux-file-image-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 0d04070d9d93f..da0e05f1c19c1 100644 --- a/api_docs/kbn_shared_ux_file_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-mocks title: "@kbn/shared-ux-file-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 fdbde8d7e637f..2f8bbde5a2807 100644 --- a/api_docs/kbn_shared_ux_file_picker.mdx +++ b/api_docs/kbn_shared_ux_file_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-picker title: "@kbn/shared-ux-file-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-picker plugin -date: 2024-06-11 +date: 2024-06-19 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 96e4f7f87bf2f..3bd1daa4136c0 100644 --- a/api_docs/kbn_shared_ux_file_types.mdx +++ b/api_docs/kbn_shared_ux_file_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-types title: "@kbn/shared-ux-file-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-types plugin -date: 2024-06-11 +date: 2024-06-19 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 dcdfb72314c55..259a2ef67160a 100644 --- a/api_docs/kbn_shared_ux_file_upload.mdx +++ b/api_docs/kbn_shared_ux_file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-upload title: "@kbn/shared-ux-file-upload" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-upload plugin -date: 2024-06-11 +date: 2024-06-19 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 38369361cd035..fbd2ad9877dfa 100644 --- a/api_docs/kbn_shared_ux_file_util.mdx +++ b/api_docs/kbn_shared_ux_file_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-util title: "@kbn/shared-ux-file-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-util plugin -date: 2024-06-11 +date: 2024-06-19 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 18b8df97b6813..b43f928c2921b 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app title: "@kbn/shared-ux-link-redirect-app" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app plugin -date: 2024-06-11 +date: 2024-06-19 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 9aaf0a39052de..29a5b2b890b55 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 2ee8857709100..70cd3f8c5a62e 100644 --- a/api_docs/kbn_shared_ux_markdown.mdx +++ b/api_docs/kbn_shared_ux_markdown.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown title: "@kbn/shared-ux-markdown" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown plugin -date: 2024-06-11 +date: 2024-06-19 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 b7b80dccb7459..e92d85b21dea6 100644 --- a/api_docs/kbn_shared_ux_markdown_mocks.mdx +++ b/api_docs/kbn_shared_ux_markdown_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown-mocks title: "@kbn/shared-ux-markdown-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 c7af36bb22b92..279b0490c5cba 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2024-06-11 +date: 2024-06-19 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 e412496b39690..17edc6261ff59 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 d877f662189c1..073fc1ac1af79 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2024-06-11 +date: 2024-06-19 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 7285a3664f0c5..17fcd1e4f0c90 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 a03eb820bf1be..fe6a0f30a222d 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2024-06-11 +date: 2024-06-19 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 f588a59f3aa58..1657db0e5712d 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 f6c285a6ecc01..faa0052cdefe5 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2024-06-11 +date: 2024-06-19 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 767b175f5c864..095b8d4616d75 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2024-06-11 +date: 2024-06-19 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 45339444578a5..bff0a5c9cfbcd 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 ee7512adcf485..bf07f8e2bbba6 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 1dea32b8130cd..adf63c4ec483b 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2024-06-11 +date: 2024-06-19 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 818862d1a52c4..5287ba423855e 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2024-06-11 +date: 2024-06-19 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 6f89f7f2ba874..33ab370ae2fac 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 8e88a8b9e7942..4029b95750832 100644 --- a/api_docs/kbn_shared_ux_prompt_not_found.mdx +++ b/api_docs/kbn_shared_ux_prompt_not_found.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-not-found title: "@kbn/shared-ux-prompt-not-found" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-not-found plugin -date: 2024-06-11 +date: 2024-06-19 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 091835b1b3fc4..92f3ebba2f5df 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2024-06-11 +date: 2024-06-19 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 43ee2a5dd4f34..12cbb0872b2e1 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2024-06-11 +date: 2024-06-19 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 ad56dc38953a8..d39b8ab858fc2 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2024-06-11 +date: 2024-06-19 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 efab1cd1d463c..b82e7bc78626a 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_tabbed_modal.mdx b/api_docs/kbn_shared_ux_tabbed_modal.mdx index 91498757e5542..261247fd4298c 100644 --- a/api_docs/kbn_shared_ux_tabbed_modal.mdx +++ b/api_docs/kbn_shared_ux_tabbed_modal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-tabbed-modal title: "@kbn/shared-ux-tabbed-modal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-tabbed-modal plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-tabbed-modal'] --- import kbnSharedUxTabbedModalObj from './kbn_shared_ux_tabbed_modal.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index 6aa26f9e0b094..1a435031fb47b 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2024-06-11 +date: 2024-06-19 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 8acc3fb8c8aac..5707fe5fd6e53 100644 --- a/api_docs/kbn_slo_schema.devdocs.json +++ b/api_docs/kbn_slo_schema.devdocs.json @@ -760,7 +760,7 @@ "label": "FetchHistoricalSummaryResponse", "description": [], "signature": [ - "{ sloId: string; instanceId: string; data: ({ date: string; } & { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; } & { summaryUpdatedAt?: string | null | undefined; })[]; }[]" + "{ sloId: string; instanceId: string; data: { date: string; 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/routes/fetch_historical_summary.ts", "deprecated": false, @@ -880,7 +880,7 @@ "label": "FindSLOParams", "description": [], "signature": [ - "{ filters?: string | undefined; kqlQuery?: string | undefined; page?: string | undefined; perPage?: string | undefined; sortBy?: \"status\" | \"error_budget_consumed\" | \"error_budget_remaining\" | \"sli_value\" | undefined; sortDirection?: \"asc\" | \"desc\" | undefined; hideStale?: boolean | undefined; }" + "{ filters?: string | undefined; kqlQuery?: string | undefined; page?: string | undefined; perPage?: string | undefined; sortBy?: \"status\" | \"error_budget_consumed\" | \"error_budget_remaining\" | \"sli_value\" | \"burn_rate_5m\" | \"burn_rate_1h\" | \"burn_rate_1d\" | undefined; sortDirection?: \"asc\" | \"desc\" | undefined; hideStale?: boolean | undefined; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/routes/find.ts", "deprecated": false, @@ -895,7 +895,7 @@ "label": "FindSLOResponse", "description": [], "signature": [ - "{ page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; } & { summaryUpdatedAt?: string | null | undefined; }; groupings: { [x: string]: string | number; }; } & { instanceId?: string | undefined; meta?: { synthetics?: { monitorId: string; locationId: string; configId: string; } | undefined; } | undefined; remote?: { remoteName: string; kibanaUrl: string; } | undefined; })[]; }" + "{ page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; fiveMinuteBurnRate: number; oneHourBurnRate: number; oneDayBurnRate: number; } & { summaryUpdatedAt?: string | null | undefined; }; groupings: { [x: string]: string | number; }; } & { instanceId?: string | undefined; meta?: { synthetics?: { monitorId: string; locationId: string; configId: string; } | undefined; } | undefined; remote?: { remoteName: string; kibanaUrl: string; } | undefined; })[]; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/routes/find.ts", "deprecated": false, @@ -993,7 +993,7 @@ "label": "GetSLOResponse", "description": [], "signature": [ - "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; } & { summaryUpdatedAt?: string | null | undefined; }; groupings: { [x: string]: string | number; }; } & { instanceId?: string | undefined; meta?: { synthetics?: { monitorId: string; locationId: string; configId: string; } | undefined; } | undefined; remote?: { remoteName: string; kibanaUrl: string; } | undefined; }" + "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; fiveMinuteBurnRate: number; oneHourBurnRate: number; oneDayBurnRate: number; } & { summaryUpdatedAt?: string | null | undefined; }; groupings: { [x: string]: string | number; }; } & { instanceId?: string | undefined; meta?: { synthetics?: { monitorId: string; locationId: string; configId: string; } | undefined; } | undefined; remote?: { remoteName: string; kibanaUrl: string; } | undefined; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/routes/get.ts", "deprecated": false, @@ -1083,7 +1083,7 @@ "label": "HistoricalSummaryResponse", "description": [], "signature": [ - "{ date: string; } & { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; } & { summaryUpdatedAt?: string | null | undefined; }" + "{ date: string; 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/routes/fetch_historical_summary.ts", "deprecated": false, @@ -1293,7 +1293,7 @@ "label": "SLOWithSummaryResponse", "description": [], "signature": [ - "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; } & { summaryUpdatedAt?: string | null | undefined; }; groupings: { [x: string]: string | number; }; } & { instanceId?: string | undefined; meta?: { synthetics?: { monitorId: string; locationId: string; configId: string; } | undefined; } | undefined; remote?: { remoteName: string; kibanaUrl: string; } | undefined; }" + "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; threshold: number; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; index: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.synthetics.availability\"; params: { monitorIds: { value: string; label: string; }[]; index: string; } & { tags?: { value: string; label: string; }[] | undefined; projects?: { value: string; label: string; }[] | undefined; filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; good: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; total: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.custom\"; params: { index: string; good: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; total: { metrics: (({ name: string; aggregation: \"sum\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.metric.timeslice\"; params: { index: string; metric: { metrics: (({ name: string; aggregation: \"min\" | \"max\" | \"sum\" | \"avg\" | \"last_value\" | \"cardinality\" | \"std_deviation\"; field: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"doc_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ name: string; aggregation: \"percentile\"; field: string; percentile: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }))[]; equation: string; threshold: number; comparator: \"GT\" | \"GTE\" | \"LT\" | \"LTE\"; }; timestampField: string; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; } | { type: \"sli.histogram.custom\"; params: { index: string; timestampField: string; good: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); total: ({ field: string; aggregation: \"value_count\"; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }) | ({ field: string; aggregation: \"range\"; from: number; to: number; } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; }); } & { filter?: string | { kqlQuery: string; filters: { meta: { alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; field?: string | undefined; params?: any; value?: string | undefined; }; query: { [x: string]: any; }; }[]; } | undefined; dataViewId?: string | undefined; }; }; timeWindow: { duration: string; type: \"rolling\"; } | { duration: string; type: \"calendarAligned\"; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; settings: { syncDelay: string; frequency: string; preventInitialBackfill: boolean; }; revision: number; enabled: boolean; tags: string[]; createdAt: string; updatedAt: string; groupBy: string | string[]; version: number; } & { summary: { status: \"HEALTHY\" | \"NO_DATA\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; fiveMinuteBurnRate: number; oneHourBurnRate: number; oneDayBurnRate: number; } & { summaryUpdatedAt?: string | null | undefined; }; groupings: { [x: string]: string | number; }; } & { instanceId?: string | undefined; meta?: { synthetics?: { monitorId: string; locationId: string; configId: string; } | undefined; } | undefined; remote?: { remoteName: string; kibanaUrl: string; } | undefined; }" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -3563,16 +3563,10 @@ "]>; data: ", "ArrayC", "<", - "IntersectionC", - "<[", "TypeC", "<{ date: ", "Type", - "; }>, ", - "IntersectionC", - "<[", - "TypeC", - "<{ status: ", + "; status: ", "UnionC", "<[", "LiteralC", @@ -3594,15 +3588,7 @@ "NumberC", "; isEstimated: ", "BooleanC", - "; }>; }>, ", - "PartialC", - "<{ summaryUpdatedAt: ", - "UnionC", - "<[", - "StringC", - ", ", - "NullC", - "]>; }>]>]>>; }>>" + "; }>; }>>; }>>" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/routes/fetch_historical_summary.ts", "deprecated": false, @@ -5384,7 +5370,13 @@ "LiteralC", "<\"sli_value\">, ", "LiteralC", - "<\"status\">]>; sortDirection: ", + "<\"status\">, ", + "LiteralC", + "<\"burn_rate_5m\">, ", + "LiteralC", + "<\"burn_rate_1h\">, ", + "LiteralC", + "<\"burn_rate_1d\">]>; sortDirection: ", "UnionC", "<[", "LiteralC", @@ -6888,7 +6880,13 @@ "NumberC", "; isEstimated: ", "BooleanC", - "; }>; }>, ", + "; }>; fiveMinuteBurnRate: ", + "NumberC", + "; oneHourBurnRate: ", + "NumberC", + "; oneDayBurnRate: ", + "NumberC", + "; }>, ", "PartialC", "<{ summaryUpdatedAt: ", "UnionC", @@ -9998,7 +9996,13 @@ "NumberC", "; isEstimated: ", "BooleanC", - "; }>; }>, ", + "; }>; fiveMinuteBurnRate: ", + "NumberC", + "; oneHourBurnRate: ", + "NumberC", + "; oneDayBurnRate: ", + "NumberC", + "; }>, ", "PartialC", "<{ summaryUpdatedAt: ", "UnionC", @@ -10531,16 +10535,10 @@ "label": "historicalSummarySchema", "description": [], "signature": [ - "IntersectionC", - "<[", "TypeC", "<{ date: ", "Type", - "; }>, ", - "IntersectionC", - "<[", - "TypeC", - "<{ status: ", + "; status: ", "UnionC", "<[", "LiteralC", @@ -10562,15 +10560,7 @@ "NumberC", "; isEstimated: ", "BooleanC", - "; }>; }>, ", - "PartialC", - "<{ summaryUpdatedAt: ", - "UnionC", - "<[", - "StringC", - ", ", - "NullC", - "]>; }>]>]>" + "; }>; }>" ], "path": "x-pack/packages/kbn-slo-schema/src/rest_specs/routes/fetch_historical_summary.ts", "deprecated": false, @@ -17608,7 +17598,13 @@ "NumberC", "; isEstimated: ", "BooleanC", - "; }>; }>, ", + "; }>; fiveMinuteBurnRate: ", + "NumberC", + "; oneHourBurnRate: ", + "NumberC", + "; oneDayBurnRate: ", + "NumberC", + "; }>, ", "PartialC", "<{ summaryUpdatedAt: ", "UnionC", @@ -17738,7 +17734,13 @@ "NumberC", "; isEstimated: ", "BooleanC", - "; }>; }>, ", + "; }>; fiveMinuteBurnRate: ", + "NumberC", + "; oneHourBurnRate: ", + "NumberC", + "; oneDayBurnRate: ", + "NumberC", + "; }>, ", "PartialC", "<{ summaryUpdatedAt: ", "UnionC", diff --git a/api_docs/kbn_slo_schema.mdx b/api_docs/kbn_slo_schema.mdx index 72a3c095dfa38..aa10690cafe76 100644 --- a/api_docs/kbn_slo_schema.mdx +++ b/api_docs/kbn_slo_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-slo-schema title: "@kbn/slo-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/slo-schema plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/slo-schema'] --- import kbnSloSchemaObj from './kbn_slo_schema.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index a2134633ef003..9748374475f97 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_predicates.mdx b/api_docs/kbn_sort_predicates.mdx index 420c3bbf32413..8f8c28195b779 100644 --- a/api_docs/kbn_sort_predicates.mdx +++ b/api_docs/kbn_sort_predicates.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-predicates title: "@kbn/sort-predicates" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-predicates plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-predicates'] --- import kbnSortPredicatesObj from './kbn_sort_predicates.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index da115b55bdc74..c6eab71a6e0c6 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2024-06-11 +date: 2024-06-19 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 e095fffb239ee..0112000b7a26c 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2024-06-11 +date: 2024-06-19 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 03dae24a09037..943833768aeac 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 43e7fc7dde3c9..0d9fa30a0949c 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.devdocs.json b/api_docs/kbn_test.devdocs.json index 7f24cc4e8ca6f..d3b0fa6729cbf 100644 --- a/api_docs/kbn_test.devdocs.json +++ b/api_docs/kbn_test.devdocs.json @@ -1782,6 +1782,38 @@ ], "returnComment": [] }, + { + "parentPluginId": "@kbn/test", + "id": "def-common.SamlSessionManager.getEmail", + "type": "Function", + "tags": [], + "label": "getEmail", + "description": [], + "signature": [ + "(role: string) => Promise" + ], + "path": "packages/kbn-test/src/auth/session_manager.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/test", + "id": "def-common.SamlSessionManager.getEmail.$1", + "type": "string", + "tags": [], + "label": "role", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-test/src/auth/session_manager.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, { "parentPluginId": "@kbn/test", "id": "def-common.SamlSessionManager.getUserData", @@ -1790,7 +1822,9 @@ "label": "getUserData", "description": [], "signature": [ - "(role: string) => Promise<{ email: string; fullname: string; }>" + "(role: string) => Promise<", + "UserProfile", + ">" ], "path": "packages/kbn-test/src/auth/session_manager.ts", "deprecated": false, diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index c97a8fe302427..64f82b945cbc0 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kiban | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 313 | 4 | 265 | 12 | +| 315 | 4 | 267 | 13 | ## Common diff --git a/api_docs/kbn_test_eui_helpers.mdx b/api_docs/kbn_test_eui_helpers.mdx index 1c20f825ae802..29b758c4be49c 100644 --- a/api_docs/kbn_test_eui_helpers.mdx +++ b/api_docs/kbn_test_eui_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-eui-helpers title: "@kbn/test-eui-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-eui-helpers plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-eui-helpers'] --- import kbnTestEuiHelpersObj from './kbn_test_eui_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index 51372b609070d..468d56ee570b1 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2024-06-11 +date: 2024-06-19 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 dcd0526c11825..0e0df8e1fee93 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2024-06-11 +date: 2024-06-19 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 2405f0531600f..a274863d31ad6 100644 --- a/api_docs/kbn_text_based_editor.mdx +++ b/api_docs/kbn_text_based_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-text-based-editor title: "@kbn/text-based-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/text-based-editor plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/text-based-editor'] --- import kbnTextBasedEditorObj from './kbn_text_based_editor.devdocs.json'; diff --git a/api_docs/kbn_timerange.mdx b/api_docs/kbn_timerange.mdx index 483c040bd817b..d3e5ad7feb050 100644 --- a/api_docs/kbn_timerange.mdx +++ b/api_docs/kbn_timerange.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-timerange title: "@kbn/timerange" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/timerange plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/timerange'] --- import kbnTimerangeObj from './kbn_timerange.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 33b8a314bf0c6..8312393b8890a 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_triggers_actions_ui_types.mdx b/api_docs/kbn_triggers_actions_ui_types.mdx index 6c116afe5e985..b8ef08f8eec8f 100644 --- a/api_docs/kbn_triggers_actions_ui_types.mdx +++ b/api_docs/kbn_triggers_actions_ui_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-triggers-actions-ui-types title: "@kbn/triggers-actions-ui-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/triggers-actions-ui-types plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/triggers-actions-ui-types'] --- import kbnTriggersActionsUiTypesObj from './kbn_triggers_actions_ui_types.devdocs.json'; diff --git a/api_docs/kbn_try_in_console.mdx b/api_docs/kbn_try_in_console.mdx index 96cd549838afb..94ddd39fdd645 100644 --- a/api_docs/kbn_try_in_console.mdx +++ b/api_docs/kbn_try_in_console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-try-in-console title: "@kbn/try-in-console" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/try-in-console plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/try-in-console'] --- import kbnTryInConsoleObj from './kbn_try_in_console.devdocs.json'; diff --git a/api_docs/kbn_ts_projects.mdx b/api_docs/kbn_ts_projects.mdx index 2cacfbad8d1fa..b13e774e8dd1d 100644 --- a/api_docs/kbn_ts_projects.mdx +++ b/api_docs/kbn_ts_projects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ts-projects title: "@kbn/ts-projects" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ts-projects plugin -date: 2024-06-11 +date: 2024-06-19 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 af8d91b5e00c8..83d4f24f65989 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_actions_browser.mdx b/api_docs/kbn_ui_actions_browser.mdx index 9ed6bb4798fc5..cf0d74cfcf5d6 100644 --- a/api_docs/kbn_ui_actions_browser.mdx +++ b/api_docs/kbn_ui_actions_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-actions-browser title: "@kbn/ui-actions-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-actions-browser plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-actions-browser'] --- import kbnUiActionsBrowserObj from './kbn_ui_actions_browser.devdocs.json'; diff --git a/api_docs/kbn_ui_shared_deps_src.devdocs.json b/api_docs/kbn_ui_shared_deps_src.devdocs.json index e74492ad28925..5a4f7663f18a0 100644 --- a/api_docs/kbn_ui_shared_deps_src.devdocs.json +++ b/api_docs/kbn_ui_shared_deps_src.devdocs.json @@ -443,17 +443,6 @@ "deprecated": false, "trackAdoption": false }, - { - "parentPluginId": "@kbn/ui-shared-deps-src", - "id": "def-common.externals.elasticeuidisteui_charts_theme", - "type": "string", - "tags": [], - "label": "'@elastic/eui/dist/eui_charts_theme'", - "description": [], - "path": "packages/kbn-ui-shared-deps-src/src/definitions.js", - "deprecated": false, - "trackAdoption": false - }, { "parentPluginId": "@kbn/ui-shared-deps-src", "id": "def-common.externals.hellopangeadnd", diff --git a/api_docs/kbn_ui_shared_deps_src.mdx b/api_docs/kbn_ui_shared_deps_src.mdx index 5576d2316b62c..3e366650b981a 100644 --- a/api_docs/kbn_ui_shared_deps_src.mdx +++ b/api_docs/kbn_ui_shared_deps_src.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-shared-deps-src title: "@kbn/ui-shared-deps-src" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-shared-deps-src plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-shared-deps-src'] --- import kbnUiSharedDepsSrcObj from './kbn_ui_shared_deps_src.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kiban | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 56 | 0 | 47 | 0 | +| 55 | 0 | 46 | 0 | ## Common diff --git a/api_docs/kbn_ui_theme.devdocs.json b/api_docs/kbn_ui_theme.devdocs.json index 94f3bf511028b..f7b49a9d755a3 100644 --- a/api_docs/kbn_ui_theme.devdocs.json +++ b/api_docs/kbn_ui_theme.devdocs.json @@ -69,18 +69,6 @@ "deprecated": true, "trackAdoption": false, "references": [ - { - "plugin": "@kbn/monaco", - "path": "packages/kbn-monaco/src/esql/lib/esql_theme.ts" - }, - { - "plugin": "@kbn/monaco", - "path": "packages/kbn-monaco/src/esql/lib/esql_theme.ts" - }, - { - "plugin": "@kbn/monaco", - "path": "packages/kbn-monaco/src/esql/lib/esql_theme.ts" - }, { "plugin": "@kbn/monaco", "path": "packages/kbn-monaco/src/esql/lib/esql_theme.ts" diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index a9cbfa1bc2b5c..a316100c89324 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2024-06-11 +date: 2024-06-19 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 09db586ba104e..ebaebbf0d1c19 100644 --- a/api_docs/kbn_unified_data_table.mdx +++ b/api_docs/kbn_unified_data_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-data-table title: "@kbn/unified-data-table" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-data-table plugin -date: 2024-06-11 +date: 2024-06-19 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 ebe3269c8c803..9016bea59786a 100644 --- a/api_docs/kbn_unified_doc_viewer.mdx +++ b/api_docs/kbn_unified_doc_viewer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-doc-viewer title: "@kbn/unified-doc-viewer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-doc-viewer plugin -date: 2024-06-11 +date: 2024-06-19 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 5568abd47ecc7..d7b8892935c74 100644 --- a/api_docs/kbn_unified_field_list.mdx +++ b/api_docs/kbn_unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unified-field-list title: "@kbn/unified-field-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unified-field-list plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unified-field-list'] --- import kbnUnifiedFieldListObj from './kbn_unified_field_list.devdocs.json'; diff --git a/api_docs/kbn_unsaved_changes_badge.mdx b/api_docs/kbn_unsaved_changes_badge.mdx index 84cbb2274d4e9..13d0692fda595 100644 --- a/api_docs/kbn_unsaved_changes_badge.mdx +++ b/api_docs/kbn_unsaved_changes_badge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unsaved-changes-badge title: "@kbn/unsaved-changes-badge" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unsaved-changes-badge plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unsaved-changes-badge'] --- import kbnUnsavedChangesBadgeObj from './kbn_unsaved_changes_badge.devdocs.json'; diff --git a/api_docs/kbn_unsaved_changes_prompt.mdx b/api_docs/kbn_unsaved_changes_prompt.mdx index d70c1c6a03494..28de503ac92cf 100644 --- a/api_docs/kbn_unsaved_changes_prompt.mdx +++ b/api_docs/kbn_unsaved_changes_prompt.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-unsaved-changes-prompt title: "@kbn/unsaved-changes-prompt" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/unsaved-changes-prompt plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/unsaved-changes-prompt'] --- import kbnUnsavedChangesPromptObj from './kbn_unsaved_changes_prompt.devdocs.json'; diff --git a/api_docs/kbn_use_tracked_promise.mdx b/api_docs/kbn_use_tracked_promise.mdx index 0a0f2a81cc78e..0e01d98f67532 100644 --- a/api_docs/kbn_use_tracked_promise.mdx +++ b/api_docs/kbn_use_tracked_promise.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-use-tracked-promise title: "@kbn/use-tracked-promise" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/use-tracked-promise plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/use-tracked-promise'] --- import kbnUseTrackedPromiseObj from './kbn_use_tracked_promise.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index 9605a46627000..0eca362028796 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2024-06-11 +date: 2024-06-19 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 f00e120265f65..51d6e23f8b802 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2024-06-11 +date: 2024-06-19 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 6cbd2fb1a9baa..857110d8f912f 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2024-06-11 +date: 2024-06-19 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 00548ef78e261..3de74eb88c3ab 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2024-06-11 +date: 2024-06-19 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 8966d8478772d..fff13aac1b112 100644 --- a/api_docs/kbn_visualization_ui_components.mdx +++ b/api_docs/kbn_visualization_ui_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-visualization-ui-components title: "@kbn/visualization-ui-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/visualization-ui-components plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-ui-components'] --- import kbnVisualizationUiComponentsObj from './kbn_visualization_ui_components.devdocs.json'; diff --git a/api_docs/kbn_visualization_utils.mdx b/api_docs/kbn_visualization_utils.mdx index 38b6a95bd68e9..94cabdfae49a6 100644 --- a/api_docs/kbn_visualization_utils.mdx +++ b/api_docs/kbn_visualization_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-visualization-utils title: "@kbn/visualization-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/visualization-utils plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/visualization-utils'] --- import kbnVisualizationUtilsObj from './kbn_visualization_utils.devdocs.json'; diff --git a/api_docs/kbn_xstate_utils.mdx b/api_docs/kbn_xstate_utils.mdx index 3fa03e8be17ef..a093809f851a7 100644 --- a/api_docs/kbn_xstate_utils.mdx +++ b/api_docs/kbn_xstate_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-xstate-utils title: "@kbn/xstate-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/xstate-utils plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/xstate-utils'] --- import kbnXstateUtilsObj from './kbn_xstate_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index 09fae7d002589..78c6e883b6e67 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2024-06-11 +date: 2024-06-19 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 2001282ced394..3ea762172aa25 100644 --- a/api_docs/kbn_zod_helpers.devdocs.json +++ b/api_docs/kbn_zod_helpers.devdocs.json @@ -58,6 +58,49 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/zod-helpers", + "id": "def-common.buildRouteValidationWithZod", + "type": "Function", + "tags": [], + "label": "buildRouteValidationWithZod", + "description": [ + "\nZod validation factory for Kibana route's request validation.\nIt allows to pass a Zod schema for parameters, query and/or body validation.\n\nExample:\n\n```ts\nrouter.versioned\n .post({\n access: 'public',\n path: MY_URL,\n })\n .addVersion(\n {\n version: 'my-version',\n validate: {\n request: {\n params: buildRouteValidationWithZod(MyRequestParamsZodSchema),\n query: buildRouteValidationWithZod(MyRequestQueryZodSchema),\n body: buildRouteValidationWithZod(MyRequestBodyZodSchema),\n },\n },\n },\n```" + ], + "signature": [ + "(schema: ZodSchema) => ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.RouteValidationFunction", + "text": "RouteValidationFunction" + }, + "" + ], + "path": "packages/kbn-zod-helpers/src/build_route_validation_with_zod.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/zod-helpers", + "id": "def-common.buildRouteValidationWithZod.$1", + "type": "Uncategorized", + "tags": [], + "label": "schema", + "description": [], + "signature": [ + "ZodSchema" + ], + "path": "packages/kbn-zod-helpers/src/build_route_validation_with_zod.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/zod-helpers", "id": "def-common.expectParseError", diff --git a/api_docs/kbn_zod_helpers.mdx b/api_docs/kbn_zod_helpers.mdx index adcfbc9ef19c1..8c5ae93d1de77 100644 --- a/api_docs/kbn_zod_helpers.mdx +++ b/api_docs/kbn_zod_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-zod-helpers title: "@kbn/zod-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/zod-helpers plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/zod-helpers'] --- import kbnZodHelpersObj from './kbn_zod_helpers.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/security-detection-rule-management](https://github.com/orgs/el | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 18 | 0 | 9 | 0 | +| 20 | 0 | 10 | 0 | ## Common diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 6d23035032bee..2578cad63ef05 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2024-06-11 +date: 2024-06-19 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 d94a552518333..7eaac80a171b1 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2024-06-11 +date: 2024-06-19 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 cc392a5f10a30..350d92ac57c19 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2024-06-11 +date: 2024-06-19 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 197f0012a6343..97af756fbcb2a 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2024-06-11 +date: 2024-06-19 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 513bbb8d27ab1..1aacddd53ae9c 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2024-06-11 +date: 2024-06-19 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 f40082e38334b..52990b67a8805 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2024-06-11 +date: 2024-06-19 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 6a5c51b025182..53e6a7de6aaf9 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2024-06-11 +date: 2024-06-19 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 bdf718a589092..7ff63218c70ce 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2024-06-11 +date: 2024-06-19 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 f41946a37eb71..bb6e28921dcea 100644 --- a/api_docs/links.mdx +++ b/api_docs/links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/links title: "links" image: https://source.unsplash.com/400x175/?github description: API docs for the links plugin -date: 2024-06-11 +date: 2024-06-19 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 fd7f13aade9e0..d1dbb67ba6ccb 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/logs_data_access.mdx b/api_docs/logs_data_access.mdx index 340652431b46d..8e77013849428 100644 --- a/api_docs/logs_data_access.mdx +++ b/api_docs/logs_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsDataAccess title: "logsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the logsDataAccess plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsDataAccess'] --- import logsDataAccessObj from './logs_data_access.devdocs.json'; diff --git a/api_docs/logs_explorer.mdx b/api_docs/logs_explorer.mdx index 7bf01280fdd18..f853c879c5cc2 100644 --- a/api_docs/logs_explorer.mdx +++ b/api_docs/logs_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsExplorer title: "logsExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the logsExplorer plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsExplorer'] --- import logsExplorerObj from './logs_explorer.devdocs.json'; diff --git a/api_docs/logs_shared.mdx b/api_docs/logs_shared.mdx index 42dea493dae96..b3b4149db03f5 100644 --- a/api_docs/logs_shared.mdx +++ b/api_docs/logs_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/logsShared title: "logsShared" image: https://source.unsplash.com/400x175/?github description: API docs for the logsShared plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'logsShared'] --- import logsSharedObj from './logs_shared.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 513d804ca09f0..128a57183b207 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2024-06-11 +date: 2024-06-19 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 7fa01d6407bf2..d143282a71fea 100644 --- a/api_docs/maps.devdocs.json +++ b/api_docs/maps.devdocs.json @@ -176,1503 +176,6 @@ } ], "initialIsOpen": false - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable", - "type": "Class", - "tags": [], - "label": "MapEmbeddable", - "description": [], - "signature": [ - { - "pluginId": "maps", - "scope": "public", - "docId": "kibMapsPluginApi", - "section": "def-public.MapEmbeddable", - "text": "MapEmbeddable" - }, - " extends ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.Embeddable", - "text": "Embeddable" - }, - "<", - { - "pluginId": "maps", - "scope": "public", - "docId": "kibMapsPluginApi", - "section": "def-public.MapEmbeddableInput", - "text": "MapEmbeddableInput" - }, - ", ", - { - "pluginId": "maps", - "scope": "public", - "docId": "kibMapsPluginApi", - "section": "def-public.MapEmbeddableOutput", - "text": "MapEmbeddableOutput" - }, - ", any> implements ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.ReferenceOrValueEmbeddable", - "text": "ReferenceOrValueEmbeddable" - }, - "<", - "MapByValueInput", - ", ", - "MapByReferenceInput", - ">,", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.FilterableEmbeddable", - "text": "FilterableEmbeddable" - } - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.type", - "type": "string", - "tags": [], - "label": "type", - "description": [], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.deferEmbeddableLoad", - "type": "boolean", - "tags": [], - "label": "deferEmbeddableLoad", - "description": [], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.Unnamed", - "type": "Function", - "tags": [], - "label": "Constructor", - "description": [], - "signature": [ - "any" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.Unnamed.$1", - "type": "Object", - "tags": [], - "label": "config", - "description": [], - "signature": [ - "MapEmbeddableConfig" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.Unnamed.$2", - "type": "CompoundType", - "tags": [], - "label": "initialInput", - "description": [], - "signature": [ - { - "pluginId": "maps", - "scope": "public", - "docId": "kibMapsPluginApi", - "section": "def-public.MapEmbeddableInput", - "text": "MapEmbeddableInput" - } - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.Unnamed.$3", - "type": "Object", - "tags": [], - "label": "parent", - "description": [], - "signature": [ - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.IContainer", - "text": "IContainer" - }, - "<{}, ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.ContainerInput", - "text": "ContainerInput" - }, - "<{}>, ", - { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.ContainerOutput", - "text": "ContainerOutput" - }, - "> | undefined" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } - ], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.getOnRenderComplete$", - "type": "Function", - "tags": [], - "label": "getOnRenderComplete$", - "description": [], - "signature": [ - "() => ", - "Observable", - "" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.reportsEmbeddableLoad", - "type": "Function", - "tags": [], - "label": "reportsEmbeddableLoad", - "description": [], - "signature": [ - "() => boolean" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.inputIsRefType", - "type": "Function", - "tags": [], - "label": "inputIsRefType", - "description": [], - "signature": [ - "(input: ", - "MapByValueInput", - " | ", - "MapByReferenceInput", - ") => input is ", - "MapByReferenceInput" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.inputIsRefType.$1", - "type": "CompoundType", - "tags": [], - "label": "input", - "description": [], - "signature": [ - "MapByValueInput", - " | ", - "MapByReferenceInput" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.getInputAsRefType", - "type": "Function", - "tags": [], - "label": "getInputAsRefType", - "description": [], - "signature": [ - "() => Promise<", - "MapByReferenceInput", - ">" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.getExplicitInputIsEqual", - "type": "Function", - "tags": [], - "label": "getExplicitInputIsEqual", - "description": [], - "signature": [ - "(lastExplicitInput: Partial<", - "MapByValueInput", - " | ", - "MapByReferenceInput", - ">) => Promise" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.getExplicitInputIsEqual.$1", - "type": "CompoundType", - "tags": [], - "label": "lastExplicitInput", - "description": [], - "signature": [ - "Partial<", - "MapByValueInput", - " | ", - "MapByReferenceInput", - ">" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.getInputAsValueType", - "type": "Function", - "tags": [], - "label": "getInputAsValueType", - "description": [], - "signature": [ - "() => Promise<", - "MapByValueInput", - ">" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.getLayerList", - "type": "Function", - "tags": [], - "label": "getLayerList", - "description": [], - "signature": [ - "() => ", - "ILayer", - "[]" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.getFilters", - "type": "Function", - "tags": [], - "label": "getFilters", - "description": [], - "signature": [ - "() => ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[]" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.getQuery", - "type": "Function", - "tags": [], - "label": "getQuery", - "description": [], - "signature": [ - "() => ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Query", - "text": "Query" - }, - " | undefined" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.supportedTriggers", - "type": "Function", - "tags": [], - "label": "supportedTriggers", - "description": [], - "signature": [ - "() => string[]" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.setRenderTooltipContent", - "type": "Function", - "tags": [], - "label": "setRenderTooltipContent", - "description": [], - "signature": [ - "(renderTooltipContent: ", - "RenderToolTipContent", - ") => void" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.setRenderTooltipContent.$1", - "type": "Function", - "tags": [], - "label": "renderTooltipContent", - "description": [], - "signature": [ - "RenderToolTipContent" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.setEventHandlers", - "type": "Function", - "tags": [], - "label": "setEventHandlers", - "description": [], - "signature": [ - "(eventHandlers: ", - "EventHandlers", - ") => void" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.setEventHandlers.$1", - "type": "Object", - "tags": [], - "label": "eventHandlers", - "description": [], - "signature": [ - "EventHandlers" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.setIsSharable", - "type": "Function", - "tags": [], - "label": "setIsSharable", - "description": [], - "signature": [ - "(isSharable: boolean) => void" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.setIsSharable.$1", - "type": "boolean", - "tags": [], - "label": "isSharable", - "description": [], - "signature": [ - "boolean" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.getInspectorAdapters", - "type": "Function", - "tags": [], - "label": "getInspectorAdapters", - "description": [], - "signature": [ - "() => ", - { - "pluginId": "inspector", - "scope": "common", - "docId": "kibInspectorPluginApi", - "section": "def-common.Adapters", - "text": "Adapters" - } - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.onUpdate", - "type": "Function", - "tags": [], - "label": "onUpdate", - "description": [], - "signature": [ - "() => void" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._getIsMovementSynchronized", - "type": "Function", - "tags": [], - "label": "_getIsMovementSynchronized", - "description": [], - "signature": [ - "() => boolean" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._getIsFilterByMapExtent", - "type": "Function", - "tags": [], - "label": "_getIsFilterByMapExtent", - "description": [], - "signature": [ - "() => boolean" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._gotoSynchronizedLocation", - "type": "Function", - "tags": [], - "label": "_gotoSynchronizedLocation", - "description": [], - "signature": [ - "() => void" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._propogateMapMovement", - "type": "Function", - "tags": [], - "label": "_propogateMapMovement", - "description": [], - "signature": [ - "(lat: number, lon: number, zoom: number) => void" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._propogateMapMovement.$1", - "type": "number", - "tags": [], - "label": "lat", - "description": [], - "signature": [ - "number" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._propogateMapMovement.$2", - "type": "number", - "tags": [], - "label": "lon", - "description": [], - "signature": [ - "number" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._propogateMapMovement.$3", - "type": "number", - "tags": [], - "label": "zoom", - "description": [], - "signature": [ - "number" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._getInputFilters", - "type": "Function", - "tags": [], - "label": "_getInputFilters", - "description": [], - "signature": [ - "() => ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[]" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._getSearchSessionId", - "type": "Function", - "tags": [], - "label": "_getSearchSessionId", - "description": [], - "signature": [ - "() => string | undefined" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._dispatchSetQuery", - "type": "Function", - "tags": [], - "label": "_dispatchSetQuery", - "description": [], - "signature": [ - "({ forceRefresh }: { forceRefresh: boolean; }) => void" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._dispatchSetQuery.$1", - "type": "Object", - "tags": [], - "label": "{ forceRefresh }", - "description": [], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._dispatchSetQuery.$1.forceRefresh", - "type": "boolean", - "tags": [], - "label": "forceRefresh", - "description": [], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false - } - ] - } - ], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._dispatchSetChartsPaletteServiceGetColor", - "type": "Function", - "tags": [], - "label": "_dispatchSetChartsPaletteServiceGetColor", - "description": [], - "signature": [ - "(syncColors?: boolean | undefined) => Promise" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._dispatchSetChartsPaletteServiceGetColor.$1", - "type": "CompoundType", - "tags": [], - "label": "syncColors", - "description": [], - "signature": [ - "boolean | undefined" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } - ], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.render", - "type": "Function", - "tags": [], - "label": "render", - "description": [ - "\n" - ], - "signature": [ - "(domNode: HTMLElement) => void" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.render.$1", - "type": "Object", - "tags": [], - "label": "domNode", - "description": [], - "signature": [ - "HTMLElement" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.setLayerList", - "type": "Function", - "tags": [], - "label": "setLayerList", - "description": [], - "signature": [ - "(layerList: ", - { - "pluginId": "maps", - "scope": "common", - "docId": "kibMapsPluginApi", - "section": "def-common.LayerDescriptor", - "text": "LayerDescriptor" - }, - "[]) => void" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.setLayerList.$1", - "type": "Array", - "tags": [], - "label": "layerList", - "description": [], - "signature": [ - { - "pluginId": "maps", - "scope": "common", - "docId": "kibMapsPluginApi", - "section": "def-common.LayerDescriptor", - "text": "LayerDescriptor" - }, - "[]" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.updateLayerById", - "type": "Function", - "tags": [], - "label": "updateLayerById", - "description": [], - "signature": [ - "(layerDescriptor: ", - { - "pluginId": "maps", - "scope": "common", - "docId": "kibMapsPluginApi", - "section": "def-common.LayerDescriptor", - "text": "LayerDescriptor" - }, - ") => void" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.updateLayerById.$1", - "type": "Object", - "tags": [], - "label": "layerDescriptor", - "description": [], - "signature": [ - { - "pluginId": "maps", - "scope": "common", - "docId": "kibMapsPluginApi", - "section": "def-common.LayerDescriptor", - "text": "LayerDescriptor" - } - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.onSingleValueTrigger", - "type": "Function", - "tags": [], - "label": "onSingleValueTrigger", - "description": [], - "signature": [ - "(actionId: string, key: string, value: ", - "RawValue", - ") => void" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.onSingleValueTrigger.$1", - "type": "string", - "tags": [], - "label": "actionId", - "description": [], - "signature": [ - "string" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.onSingleValueTrigger.$2", - "type": "string", - "tags": [], - "label": "key", - "description": [], - "signature": [ - "string" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.onSingleValueTrigger.$3", - "type": "CompoundType", - "tags": [], - "label": "value", - "description": [], - "signature": [ - "RawValue" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } - ], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.addFilters", - "type": "Function", - "tags": [], - "label": "addFilters", - "description": [], - "signature": [ - "(filters: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[], actionId?: string) => Promise" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.addFilters.$1", - "type": "Array", - "tags": [], - "label": "filters", - "description": [], - "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[]" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.addFilters.$2", - "type": "string", - "tags": [], - "label": "actionId", - "description": [], - "signature": [ - "string" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.getFilterActions", - "type": "Function", - "tags": [], - "label": "getFilterActions", - "description": [], - "signature": [ - "() => Promise<", - { - "pluginId": "uiActions", - "scope": "public", - "docId": "kibUiActionsPluginApi", - "section": "def-public.Action", - "text": "Action" - }, - "[]>" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.getActionContext", - "type": "Function", - "tags": [], - "label": "getActionContext", - "description": [], - "signature": [ - "() => ", - { - "pluginId": "uiActions", - "scope": "public", - "docId": "kibUiActionsPluginApi", - "section": "def-public.ActionExecutionContext", - "text": "ActionExecutionContext" - }, - "" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.linkToLibrary", - "type": "Uncategorized", - "tags": [], - "label": "linkToLibrary", - "description": [], - "signature": [ - "undefined" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.unlinkFromLibrary", - "type": "Uncategorized", - "tags": [], - "label": "unlinkFromLibrary", - "description": [], - "signature": [ - "undefined" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.checkForDuplicateTitle", - "type": "Function", - "tags": [], - "label": "checkForDuplicateTitle", - "description": [], - "signature": [ - "(newTitle: string, isTitleDuplicateConfirmed: boolean, onTitleDuplicate: () => void) => Promise" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.checkForDuplicateTitle.$1", - "type": "string", - "tags": [], - "label": "newTitle", - "description": [], - "signature": [ - "string" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.checkForDuplicateTitle.$2", - "type": "boolean", - "tags": [], - "label": "isTitleDuplicateConfirmed", - "description": [], - "signature": [ - "boolean" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.checkForDuplicateTitle.$3", - "type": "Function", - "tags": [], - "label": "onTitleDuplicate", - "description": [], - "signature": [ - "() => void" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.saveToLibrary", - "type": "Function", - "tags": [], - "label": "saveToLibrary", - "description": [], - "signature": [ - "(title: string) => Promise" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.saveToLibrary.$1", - "type": "string", - "tags": [], - "label": "title", - "description": [], - "signature": [ - "string" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.getByReferenceState", - "type": "Function", - "tags": [], - "label": "getByReferenceState", - "description": [], - "signature": [ - "(libraryId: string) => { savedObjectId: string; id: string; title?: string | undefined; query?: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Query", - "text": "Query" - }, - " | undefined; filters?: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[] | undefined; description?: string | undefined; version?: string | undefined; executionContext?: ", - { - "pluginId": "@kbn/core-execution-context-common", - "scope": "common", - "docId": "kibKbnCoreExecutionContextCommonPluginApi", - "section": "def-common.KibanaExecutionContext", - "text": "KibanaExecutionContext" - }, - " | undefined; viewMode?: ", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.ViewMode", - "text": "ViewMode" - }, - " | undefined; lastReloadRequestTime?: number | undefined; hidePanelTitles?: boolean | undefined; enhancements?: ", - { - "pluginId": "@kbn/utility-types", - "scope": "common", - "docId": "kibKbnUtilityTypesPluginApi", - "section": "def-common.SerializableRecord", - "text": "SerializableRecord" - }, - " | undefined; disabledActions?: string[] | undefined; disableTriggers?: boolean | undefined; searchSessionId?: string | undefined; syncColors?: boolean | undefined; syncCursor?: boolean | undefined; syncTooltips?: boolean | undefined; timeRange?: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.TimeRange", - "text": "TimeRange" - }, - " | undefined; timeslice?: [number, number] | undefined; isLayerTOCOpen?: boolean | undefined; openTOCDetails?: string[] | undefined; mapCenter?: ", - "MapCenterAndZoom", - " | undefined; mapBuffer?: ", - "MapExtent", - " | undefined; mapSettings?: Partial<", - "MapSettings", - "> | undefined; hiddenLayers?: string[] | undefined; hideFilterActions?: boolean | undefined; filterByMapExtent?: boolean | undefined; isMovementSynchronized?: boolean | undefined; }" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.getByReferenceState.$1", - "type": "string", - "tags": [], - "label": "libraryId", - "description": [], - "signature": [ - "string" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.getByValueState", - "type": "Function", - "tags": [], - "label": "getByValueState", - "description": [], - "signature": [ - "() => { attributes: ", - "MapAttributes", - "; id: string; title?: string | undefined; query?: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Query", - "text": "Query" - }, - " | undefined; filters?: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[] | undefined; description?: string | undefined; version?: string | undefined; executionContext?: ", - { - "pluginId": "@kbn/core-execution-context-common", - "scope": "common", - "docId": "kibKbnCoreExecutionContextCommonPluginApi", - "section": "def-common.KibanaExecutionContext", - "text": "KibanaExecutionContext" - }, - " | undefined; viewMode?: ", - { - "pluginId": "embeddable", - "scope": "common", - "docId": "kibEmbeddablePluginApi", - "section": "def-common.ViewMode", - "text": "ViewMode" - }, - " | undefined; lastReloadRequestTime?: number | undefined; hidePanelTitles?: boolean | undefined; enhancements?: ", - { - "pluginId": "@kbn/utility-types", - "scope": "common", - "docId": "kibKbnUtilityTypesPluginApi", - "section": "def-common.SerializableRecord", - "text": "SerializableRecord" - }, - " | undefined; disabledActions?: string[] | undefined; disableTriggers?: boolean | undefined; searchSessionId?: string | undefined; syncColors?: boolean | undefined; syncCursor?: boolean | undefined; syncTooltips?: boolean | undefined; timeRange?: ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.TimeRange", - "text": "TimeRange" - }, - " | undefined; timeslice?: [number, number] | undefined; isLayerTOCOpen?: boolean | undefined; openTOCDetails?: string[] | undefined; mapCenter?: ", - "MapCenterAndZoom", - " | undefined; mapBuffer?: ", - "MapExtent", - " | undefined; mapSettings?: Partial<", - "MapSettings", - "> | undefined; hiddenLayers?: string[] | undefined; hideFilterActions?: boolean | undefined; filterByMapExtent?: boolean | undefined; isMovementSynchronized?: boolean | undefined; }" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._setMapExtentFilter", - "type": "Function", - "tags": [], - "label": "_setMapExtentFilter", - "description": [], - "signature": [ - "(() => void) & ", - "Cancelable" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._clearMapExtentFilter", - "type": "Function", - "tags": [], - "label": "_clearMapExtentFilter", - "description": [], - "signature": [ - "() => void" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.destroy", - "type": "Function", - "tags": [], - "label": "destroy", - "description": [], - "signature": [ - "() => void" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable.reload", - "type": "Function", - "tags": [], - "label": "reload", - "description": [], - "signature": [ - "() => void" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._mapSyncHandler", - "type": "Function", - "tags": [], - "label": "_mapSyncHandler", - "description": [], - "signature": [ - "(lat: number, lon: number, zoom: number) => void" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._mapSyncHandler.$1", - "type": "number", - "tags": [], - "label": "lat", - "description": [], - "signature": [ - "number" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._mapSyncHandler.$2", - "type": "number", - "tags": [], - "label": "lon", - "description": [], - "signature": [ - "number" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._mapSyncHandler.$3", - "type": "number", - "tags": [], - "label": "zoom", - "description": [], - "signature": [ - "number" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "maps", - "id": "def-public.MapEmbeddable._handleStoreChanges", - "type": "Function", - "tags": [], - "label": "_handleStoreChanges", - "description": [], - "signature": [ - "() => void" - ], - "path": "x-pack/plugins/maps/public/embeddable/map_embeddable.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [], - "returnComment": [] - } - ], - "initialIsOpen": false } ], "functions": [ @@ -1693,7 +196,7 @@ "text": "MapApi" } ], - "path": "x-pack/plugins/maps/public/embeddable/map_api.ts", + "path": "x-pack/plugins/maps/public/react_embeddable/types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -1707,7 +210,7 @@ "signature": [ "unknown" ], - "path": "x-pack/plugins/maps/public/embeddable/map_api.ts", + "path": "x-pack/plugins/maps/public/react_embeddable/types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -4333,30 +2836,100 @@ "label": "MapApi", "description": [], "signature": [ + { + "pluginId": "embeddable", + "scope": "public", + "docId": "kibEmbeddablePluginApi", + "section": "def-public.DefaultEmbeddableApi", + "text": "DefaultEmbeddableApi" + }, + "<", + { + "pluginId": "maps", + "scope": "public", + "docId": "kibMapsPluginApi", + "section": "def-public.MapSerializedState", + "text": "MapSerializedState" + }, + ", ", + { + "pluginId": "maps", + "scope": "public", + "docId": "kibMapsPluginApi", + "section": "def-public.MapSerializedState", + "text": "MapSerializedState" + }, + "> & Partial<{ enhancements: { dynamicActions: ", + { + "pluginId": "uiActionsEnhanced", + "scope": "public", + "docId": "kibUiActionsEnhancedPluginApi", + "section": "def-public.DynamicActionManager", + "text": "DynamicActionManager" + }, + "; }; setDynamicActions: (newState: { dynamicActions: ", + { + "pluginId": "uiActionsEnhanced", + "scope": "common", + "docId": "kibUiActionsEnhancedPluginApi", + "section": "def-common.DynamicActionsState", + "text": "DynamicActionsState" + }, + "; } | undefined) => void; dynamicActionsState$: ", + { + "pluginId": "@kbn/presentation-publishing", + "scope": "common", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-common.PublishingSubject", + "text": "PublishingSubject" + }, + "<{ dynamicActions: ", + { + "pluginId": "uiActionsEnhanced", + "scope": "common", + "docId": "kibUiActionsEnhancedPluginApi", + "section": "def-common.DynamicActionsState", + "text": "DynamicActionsState" + }, + "; } | undefined>; }> & Partial<", { "pluginId": "@kbn/presentation-publishing", "scope": "common", "docId": "kibKbnPresentationPublishingPluginApi", - "section": "def-common.HasType", - "text": "HasType" + "section": "def-common.HasEditCapabilities", + "text": "HasEditCapabilities" }, - "<\"map\"> & { getLayerList: () => ", - "ILayer", - "[]; reload: () => void; } & ", + "> & ", + { + "pluginId": "inspector", + "scope": "public", + "docId": "kibInspectorPluginApi", + "section": "def-public.HasInspectorAdapters", + "text": "HasInspectorAdapters" + }, + " & ", { "pluginId": "@kbn/presentation-publishing", "scope": "common", "docId": "kibKbnPresentationPublishingPluginApi", - "section": "def-common.PublishesDataViews", - "text": "PublishesDataViews" + "section": "def-common.HasSupportedTriggers", + "text": "HasSupportedTriggers" + }, + " & ", + { + "pluginId": "@kbn/presentation-publishing", + "scope": "common", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-common.PublishesDataLoading", + "text": "PublishesDataLoading" }, " & ", { "pluginId": "@kbn/presentation-publishing", "scope": "common", "docId": "kibKbnPresentationPublishingPluginApi", - "section": "def-common.PublishesPanelTitle", - "text": "PublishesPanelTitle" + "section": "def-common.PublishesDataViews", + "text": "PublishesDataViews" }, " & ", { @@ -4398,79 +2971,104 @@ "section": "def-common.AggregateQuery", "text": "AggregateQuery" }, - " | undefined>; } & Partial<", + " | undefined>; } & ", { "pluginId": "@kbn/presentation-publishing", "scope": "common", "docId": "kibKbnPresentationPublishingPluginApi", - "section": "def-common.HasParentApi", - "text": "HasParentApi" + "section": "def-common.HasLibraryTransforms", + "text": "HasLibraryTransforms" + }, + "<", + { + "pluginId": "maps", + "scope": "public", + "docId": "kibMapsPluginApi", + "section": "def-public.MapSerializedState", + "text": "MapSerializedState" + }, + "> & { getLayerList: () => ", + "ILayer", + "[]; reload: () => void; setEventHandlers: (eventHandlers: ", + "EventHandlers", + ") => void; setLayerList: (layerList: ", + { + "pluginId": "maps", + "scope": "common", + "docId": "kibMapsPluginApi", + "section": "def-common.LayerDescriptor", + "text": "LayerDescriptor" + }, + "[]) => void; updateLayerById: (layerDescriptor: ", + { + "pluginId": "maps", + "scope": "common", + "docId": "kibMapsPluginApi", + "section": "def-common.LayerDescriptor", + "text": "LayerDescriptor" }, - ">" + ") => void; onRenderComplete$: ", + "Observable", + "; }" ], - "path": "x-pack/plugins/maps/public/embeddable/map_api.ts", + "path": "x-pack/plugins/maps/public/react_embeddable/types.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "maps", - "id": "def-public.MapEmbeddableInput", - "type": "Type", + "id": "def-public.MAPS_APP_LOCATOR", + "type": "string", "tags": [], - "label": "MapEmbeddableInput", + "label": "MAPS_APP_LOCATOR", "description": [], "signature": [ - "MapByValueInput", - " | ", - "MapByReferenceInput" + "\"MAPS_APP_LOCATOR\"" ], - "path": "x-pack/plugins/maps/public/embeddable/types.ts", + "path": "x-pack/plugins/maps/public/locators/map_locator/locator_definition.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false }, { "parentPluginId": "maps", - "id": "def-public.MapEmbeddableOutput", + "id": "def-public.MapSerializedState", "type": "Type", "tags": [], - "label": "MapEmbeddableOutput", + "label": "MapSerializedState", "description": [], "signature": [ { - "pluginId": "embeddable", - "scope": "public", - "docId": "kibEmbeddablePluginApi", - "section": "def-public.EmbeddableOutput", - "text": "EmbeddableOutput" - }, - " & { indexPatterns: ", + "pluginId": "@kbn/presentation-publishing", + "scope": "common", + "docId": "kibKbnPresentationPublishingPluginApi", + "section": "def-common.SerializedTitles", + "text": "SerializedTitles" + }, + " & Partial<", + "DynamicActionsSerializedState", + "> & { attributes?: ", + "MapAttributes", + " | undefined; savedObjectId?: string | undefined; isLayerTOCOpen?: boolean | undefined; openTOCDetails?: string[] | undefined; mapCenter?: ", + "MapCenterAndZoom", + " | undefined; mapBuffer?: ", + "MapExtent", + " | undefined; mapSettings?: Partial<", + "MapSettings", + "> | undefined; hiddenLayers?: string[] | undefined; hideFilterActions?: boolean | undefined; timeRange?: ", { - "pluginId": "dataViews", + "pluginId": "@kbn/es-query", "scope": "common", - "docId": "kibDataViewsPluginApi", - "section": "def-common.DataView", - "text": "DataView" + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.TimeRange", + "text": "TimeRange" }, - "[]; }" - ], - "path": "x-pack/plugins/maps/public/embeddable/types.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "maps", - "id": "def-public.MAPS_APP_LOCATOR", - "type": "string", - "tags": [], - "label": "MAPS_APP_LOCATOR", - "description": [], - "signature": [ - "\"MAPS_APP_LOCATOR\"" + " | undefined; filterByMapExtent?: boolean | undefined; isMovementSynchronized?: boolean | undefined; isSharable?: boolean | undefined; tooltipRenderer?: ", + "RenderToolTipContent", + " | undefined; }" ], - "path": "x-pack/plugins/maps/public/locators/map_locator/locator_definition.ts", + "path": "x-pack/plugins/maps/public/react_embeddable/types.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 8118edd1e2bba..ada73ac30ea8f 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2024-06-11 +date: 2024-06-19 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 | |-------------------|-----------|------------------------|-----------------| -| 286 | 0 | 281 | 31 | +| 209 | 0 | 205 | 28 | ## Client diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 2b2a320b12626..dcf3eed8d65e7 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2024-06-11 +date: 2024-06-19 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 16846804653a4..7621ac99fed0d 100644 --- a/api_docs/metrics_data_access.mdx +++ b/api_docs/metrics_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/metricsDataAccess title: "metricsDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the metricsDataAccess plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'metricsDataAccess'] --- import metricsDataAccessObj from './metrics_data_access.devdocs.json'; diff --git a/api_docs/ml.devdocs.json b/api_docs/ml.devdocs.json index 7bb947c63a348..4a7428c3c49bd 100644 --- a/api_docs/ml.devdocs.json +++ b/api_docs/ml.devdocs.json @@ -1688,7 +1688,9 @@ "MlTrainedModelConfig", ">; installElasticTrainedModelConfig(modelId: string): Promise<", "MlTrainedModelConfig", - ">; }; inferenceModels: { createInferenceEndpoint(inferenceId: string, taskType: ", + ">; getModelsDownloadStatus(): Promise>; }; inferenceModels: { createInferenceEndpoint(inferenceId: string, taskType: ", "InferenceTaskType", ", modelConfig: ", { diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index 049634eb87267..242a6df027850 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) for questi | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 154 | 3 | 67 | 101 | +| 154 | 3 | 67 | 102 | ## Client diff --git a/api_docs/mock_idp_plugin.mdx b/api_docs/mock_idp_plugin.mdx index 15a6b398a9c7d..a54ec10197db9 100644 --- a/api_docs/mock_idp_plugin.mdx +++ b/api_docs/mock_idp_plugin.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mockIdpPlugin title: "mockIdpPlugin" image: https://source.unsplash.com/400x175/?github description: API docs for the mockIdpPlugin plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mockIdpPlugin'] --- import mockIdpPluginObj from './mock_idp_plugin.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 803afa791dfce..f00d547d595b2 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index ffba0c06e58bd..37bdf4ef856ea 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index aa74fa1d36598..36019f225e1c4 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2024-06-11 +date: 2024-06-19 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 51a1b6d78593a..ed3a9bbce6fec 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2024-06-11 +date: 2024-06-19 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 0175e8715f138..2ac60ca6d01aa 100644 --- a/api_docs/no_data_page.mdx +++ b/api_docs/no_data_page.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/noDataPage title: "noDataPage" image: https://source.unsplash.com/400x175/?github description: API docs for the noDataPage plugin -date: 2024-06-11 +date: 2024-06-19 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 5919bf636a333..fefcc8e26a90f 100644 --- a/api_docs/notifications.mdx +++ b/api_docs/notifications.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/notifications title: "notifications" image: https://source.unsplash.com/400x175/?github description: API docs for the notifications plugin -date: 2024-06-11 +date: 2024-06-19 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 e6b73cbac5031..ed3e91babeb4f 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -155,7 +155,7 @@ "label": "AlertSummary", "description": [], "signature": [ - "({ alertSummaryFields }: AlertSummaryProps) => JSX.Element" + "({ alert, alertSummaryFields }: AlertSummaryProps) => JSX.Element" ], "path": "x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/alert_summary.tsx", "deprecated": false, @@ -166,7 +166,7 @@ "id": "def-public.AlertSummary.$1", "type": "Object", "tags": [], - "label": "{ alertSummaryFields }", + "label": "{ alert, alertSummaryFields }", "description": [], "signature": [ "AlertSummaryProps" @@ -2599,7 +2599,7 @@ "label": "observabilityShared", "description": [], "signature": [ - "{ locators: ObservabilitySharedLocators; navigation: { registerSections: (sections$: ", + "{ registerProfilingComponent: (key: string, component: React.FC) => void; locators: ObservabilitySharedLocators; navigation: { registerSections: (sections$: ", "Observable", "<", { @@ -2897,26 +2897,26 @@ "signature": [ "{ get: (id: string) => ", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionTypeModel", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", "text": "ActionTypeModel" }, "; list: () => ", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionTypeModel", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", "text": "ActionTypeModel" }, "[]; register: (objectType: ", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionTypeModel", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", "text": "ActionTypeModel" }, ") => void; has: (id: string) => boolean; }" @@ -10192,7 +10192,7 @@ "label": "value", "description": [], "signature": [ - "false" + "true" ], "path": "x-pack/plugins/observability_solution/observability/server/ui_settings.ts", "deprecated": false, diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index 938f1f1bd1ea9..8b0145aebed4c 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/observability_a_i_assistant.devdocs.json b/api_docs/observability_a_i_assistant.devdocs.json index 6dba439cb25b2..5df73feb9b25a 100644 --- a/api_docs/observability_a_i_assistant.devdocs.json +++ b/api_docs/observability_a_i_assistant.devdocs.json @@ -1,7 +1,102 @@ { "id": "observabilityAIAssistant", "client": { - "classes": [], + "classes": [ + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.ShortIdTable", + "type": "Class", + "tags": [], + "label": "ShortIdTable", + "description": [], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/short_id_table.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.ShortIdTable.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/short_id_table.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.ShortIdTable.take", + "type": "Function", + "tags": [], + "label": "take", + "description": [], + "signature": [ + "(originalId: string) => string" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/short_id_table.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.ShortIdTable.take.$1", + "type": "string", + "tags": [], + "label": "originalId", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/short_id_table.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.ShortIdTable.lookup", + "type": "Function", + "tags": [], + "label": "lookup", + "description": [], + "signature": [ + "(shortId: string) => string | undefined" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/short_id_table.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.ShortIdTable.lookup.$1", + "type": "string", + "tags": [], + "label": "shortId", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/short_id_table.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], "functions": [ { "parentPluginId": "observabilityAIAssistant", @@ -11,7 +106,7 @@ "label": "AssistantAvatar", "description": [], "signature": [ - "({ size = 's', css }: ", + "({ size = 's', css, className }: ", "AssistantAvatarProps", ") => JSX.Element" ], @@ -24,7 +119,7 @@ "id": "def-public.AssistantAvatar.$1", "type": "Object", "tags": [], - "label": "{ size = 's', css }", + "label": "{ size = 's', css, className }", "description": [], "signature": [ "AssistantAvatarProps" @@ -202,6 +297,37 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.concatenateChatCompletionChunks", + "type": "Function", + "tags": [], + "label": "concatenateChatCompletionChunks", + "description": [], + "signature": [ + "() => (source: ", + "Observable", + "<", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.ChatCompletionChunkEvent", + "text": "ChatCompletionChunkEvent" + }, + ">) => ", + "Observable", + "<", + "ConcatenatedMessage", + ">" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/concatenate_chat_completion_chunks.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "observabilityAIAssistant", "id": "def-public.ConnectorSelectorBase", @@ -385,11 +511,9 @@ "label": "createScreenContextAction", "description": [], "signature": [ - "(definition: TActionDefinition, respond: ", - "ScreenContextActionRespondFunction", - ") => ", + "(definition: TActionDefinition, respond: TRespondFunction) => ", "ScreenContextActionDefinition", - "" + ">" ], "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/utils/create_screen_context_action.ts", "deprecated": false, @@ -418,8 +542,7 @@ "label": "respond", "description": [], "signature": [ - "ScreenContextActionRespondFunction", - "" + "TRespondFunction" ], "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/utils/create_screen_context_action.ts", "deprecated": false, @@ -869,6 +992,165 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.DiscoveredDataset", + "type": "Interface", + "tags": [], + "label": "DiscoveredDataset", + "description": [], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.DiscoveredDataset.title", + "type": "string", + "tags": [], + "label": "title", + "description": [], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.DiscoveredDataset.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.DiscoveredDataset.indexPatterns", + "type": "Array", + "tags": [], + "label": "indexPatterns", + "description": [], + "signature": [ + "string[]" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.DiscoveredDataset.columns", + "type": "Array", + "tags": [], + "label": "columns", + "description": [], + "signature": [ + "unknown[]" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.FunctionDefinition", + "type": "Interface", + "tags": [], + "label": "FunctionDefinition", + "description": [], + "signature": [ + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.FunctionDefinition", + "text": "FunctionDefinition" + }, + "" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/functions/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.FunctionDefinition.name", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/functions/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.FunctionDefinition.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/functions/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.FunctionDefinition.visibility", + "type": "CompoundType", + "tags": [], + "label": "visibility", + "description": [], + "signature": [ + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.FunctionVisibility", + "text": "FunctionVisibility" + }, + " | undefined" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/functions/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.FunctionDefinition.descriptionForUser", + "type": "string", + "tags": [], + "label": "descriptionForUser", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/functions/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.FunctionDefinition.parameters", + "type": "Uncategorized", + "tags": [], + "label": "parameters", + "description": [], + "signature": [ + "TParameters | undefined" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/functions/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "observabilityAIAssistant", "id": "def-public.KnowledgeBaseEntry", @@ -1155,15 +1437,23 @@ "section": "def-common.Message", "text": "Message" }, - "[]; connectorId: string; function?: \"none\" | \"auto\" | undefined; signal: AbortSignal; }) => ", + "[]; connectorId: string; functions?: Pick<", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.FunctionDefinition", + "text": "FunctionDefinition" + }, + ", \"name\" | \"description\" | \"parameters\">[] | undefined; functionCall?: string | undefined; signal: AbortSignal; }) => ", "Observable", "<", { "pluginId": "observabilityAIAssistant", "scope": "common", "docId": "kibObservabilityAIAssistantPluginApi", - "section": "def-common.StreamingChatResponseEventWithoutError", - "text": "StreamingChatResponseEventWithoutError" + "section": "def-common.ChatCompletionChunkEvent", + "text": "ChatCompletionChunkEvent" }, ">" ], @@ -1231,13 +1521,35 @@ }, { "parentPluginId": "observabilityAIAssistant", - "id": "def-public.ObservabilityAIAssistantChatService.chat.$2.function", - "type": "CompoundType", + "id": "def-public.ObservabilityAIAssistantChatService.chat.$2.functions", + "type": "Array", + "tags": [], + "label": "functions", + "description": [], + "signature": [ + "Pick<", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.FunctionDefinition", + "text": "FunctionDefinition" + }, + ", \"name\" | \"description\" | \"parameters\">[] | undefined" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.ObservabilityAIAssistantChatService.chat.$2.functionCall", + "type": "string", "tags": [], - "label": "function", + "label": "functionCall", "description": [], "signature": [ - "\"none\" | \"auto\" | undefined" + "string | undefined" ], "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts", "deprecated": false, @@ -1280,7 +1592,9 @@ "section": "def-common.Message", "text": "Message" }, - "[]; persist: boolean; disableFunctions: boolean; signal: AbortSignal; responseLanguage: string; }) => ", + "[]; persist: boolean; disableFunctions: boolean | { except: string[]; }; signal: AbortSignal; responseLanguage?: string | undefined; instructions?: ", + "UserInstructionOrPlainText", + "[] | undefined; }) => ", "Observable", "<", { @@ -1385,10 +1699,13 @@ { "parentPluginId": "observabilityAIAssistant", "id": "def-public.ObservabilityAIAssistantChatService.complete.$1.disableFunctions", - "type": "boolean", + "type": "CompoundType", "tags": [], "label": "disableFunctions", "description": [], + "signature": [ + "boolean | { except: string[]; }" + ], "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts", "deprecated": false, "trackAdoption": false @@ -1414,6 +1731,24 @@ "tags": [], "label": "responseLanguage", "description": [], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.ObservabilityAIAssistantChatService.complete.$1.instructions", + "type": "Array", + "tags": [], + "label": "instructions", + "description": [], + "signature": [ + "UserInstructionOrPlainText", + "[] | undefined" + ], "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts", "deprecated": false, "trackAdoption": false @@ -1715,7 +2050,7 @@ "label": "callApi", "description": [], "signature": [ - "(endpoint: TEndpoint, ...args: MaybeOptionalArgs<", + "(endpoint: TEndpoint, ...args: MaybeOptionalArgs<", { "pluginId": "@kbn/server-route-repository", "scope": "common", @@ -2134,20 +2469,34 @@ "; responseLanguage: ", "StringC", "; disableFunctions: ", + "UnionC", + "<[", "Type", - "; instructions: ", + ", ", + "TypeC", + "<{ except: ", + "ArrayC", + "<", + "StringC", + ">; }>]>; instructions: ", "ArrayC", "<", "UnionC", "<[", "StringC", ", ", + "IntersectionC", + "<[", "TypeC", "<{ doc_id: ", "StringC", "; text: ", "StringC", - "; }>]>>; }>]>; }>, ", + "; }>, ", + "PartialC", + "<{ system: ", + "BooleanC", + "; }>]>]>>; }>]>; }>, ", "PartialC", "<{ body: ", "PartialC", @@ -2183,7 +2532,7 @@ "section": "def-common.Message", "text": "Message" }, - "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | undefined; instructions?: (string | { doc_id: string; text: string; })[] | undefined; }; } & { body?: { actions?: ({ name: string; description: string; } & { parameters?: any; })[] | undefined; } | undefined; query?: { format?: \"default\" | \"openai\" | undefined; } | undefined; }; }) => Promise<", + "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | { except: string[]; } | undefined; instructions?: (string | ({ doc_id: string; text: string; } & { system?: boolean | undefined; }))[] | undefined; }; } & { body?: { actions?: ({ name: string; description: string; } & { parameters?: any; })[] | undefined; } | undefined; query?: { format?: \"default\" | \"openai\" | undefined; } | undefined; }; }) => Promise<", "Readable", ">; } & ", "ObservabilityAIAssistantRouteCreateOptions", @@ -2228,20 +2577,34 @@ "; responseLanguage: ", "StringC", "; disableFunctions: ", + "UnionC", + "<[", "Type", - "; instructions: ", + ", ", + "TypeC", + "<{ except: ", + "ArrayC", + "<", + "StringC", + ">; }>]>; instructions: ", "ArrayC", "<", "UnionC", "<[", "StringC", ", ", + "IntersectionC", + "<[", "TypeC", "<{ doc_id: ", "StringC", "; text: ", "StringC", - "; }>]>>; }>]>; }>, ", + "; }>, ", + "PartialC", + "<{ system: ", + "BooleanC", + "; }>]>]>>; }>]>; }>, ", "TypeC", "<{ body: ", "TypeC", @@ -2263,12 +2626,28 @@ "section": "def-common.Message", "text": "Message" }, - "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | undefined; instructions?: (string | { doc_id: string; text: string; })[] | undefined; }; } & { body: { screenContexts: ", + "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | { except: string[]; } | undefined; instructions?: (string | ({ doc_id: string; text: string; } & { system?: boolean | undefined; }))[] | undefined; }; } & { body: { screenContexts: ", "ObservabilityAIAssistantScreenContextRequest", "[]; }; }; }) => Promise<", "Readable", ">; } & ", "ObservabilityAIAssistantRouteCreateOptions", + "; \"POST /internal/observability_ai_assistant/chat/recall\": { endpoint: \"POST /internal/observability_ai_assistant/chat/recall\"; params?: ", + "TypeC", + "<{ body: ", + "TypeC", + "<{ prompt: ", + "StringC", + "; context: ", + "StringC", + "; connectorId: ", + "StringC", + "; }>; }> | undefined; handler: ({}: ", + "ObservabilityAIAssistantRouteHandlerResources", + " & { params: { body: { prompt: string; context: string; connectorId: string; }; }; }) => Promise<", + "Readable", + ">; } & ", + "ObservabilityAIAssistantRouteCreateOptions", "; \"POST /internal/observability_ai_assistant/chat\": { endpoint: \"POST /internal/observability_ai_assistant/chat\"; params?: ", "TypeC", "<{ body: ", @@ -2750,20 +3129,34 @@ "; responseLanguage: ", "StringC", "; disableFunctions: ", + "UnionC", + "<[", "Type", - "; instructions: ", + ", ", + "TypeC", + "<{ except: ", + "ArrayC", + "<", + "StringC", + ">; }>]>; instructions: ", "ArrayC", "<", "UnionC", "<[", "StringC", ", ", + "IntersectionC", + "<[", "TypeC", "<{ doc_id: ", "StringC", "; text: ", "StringC", - "; }>]>>; }>]>; }>, ", + "; }>, ", + "PartialC", + "<{ system: ", + "BooleanC", + "; }>]>]>>; }>]>; }>, ", "PartialC", "<{ body: ", "PartialC", @@ -2799,7 +3192,7 @@ "section": "def-common.Message", "text": "Message" }, - "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | undefined; instructions?: (string | { doc_id: string; text: string; })[] | undefined; }; } & { body?: { actions?: ({ name: string; description: string; } & { parameters?: any; })[] | undefined; } | undefined; query?: { format?: \"default\" | \"openai\" | undefined; } | undefined; }; }) => Promise<", + "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | { except: string[]; } | undefined; instructions?: (string | ({ doc_id: string; text: string; } & { system?: boolean | undefined; }))[] | undefined; }; } & { body?: { actions?: ({ name: string; description: string; } & { parameters?: any; })[] | undefined; } | undefined; query?: { format?: \"default\" | \"openai\" | undefined; } | undefined; }; }) => Promise<", "Readable", ">; } & ", "ObservabilityAIAssistantRouteCreateOptions", @@ -2844,20 +3237,34 @@ "; responseLanguage: ", "StringC", "; disableFunctions: ", + "UnionC", + "<[", "Type", - "; instructions: ", + ", ", + "TypeC", + "<{ except: ", + "ArrayC", + "<", + "StringC", + ">; }>]>; instructions: ", "ArrayC", "<", "UnionC", "<[", "StringC", ", ", + "IntersectionC", + "<[", "TypeC", "<{ doc_id: ", "StringC", "; text: ", "StringC", - "; }>]>>; }>]>; }>, ", + "; }>, ", + "PartialC", + "<{ system: ", + "BooleanC", + "; }>]>]>>; }>]>; }>, ", "TypeC", "<{ body: ", "TypeC", @@ -2879,12 +3286,28 @@ "section": "def-common.Message", "text": "Message" }, - "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | undefined; instructions?: (string | { doc_id: string; text: string; })[] | undefined; }; } & { body: { screenContexts: ", + "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | { except: string[]; } | undefined; instructions?: (string | ({ doc_id: string; text: string; } & { system?: boolean | undefined; }))[] | undefined; }; } & { body: { screenContexts: ", "ObservabilityAIAssistantScreenContextRequest", "[]; }; }; }) => Promise<", "Readable", ">; } & ", "ObservabilityAIAssistantRouteCreateOptions", + "; \"POST /internal/observability_ai_assistant/chat/recall\": { endpoint: \"POST /internal/observability_ai_assistant/chat/recall\"; params?: ", + "TypeC", + "<{ body: ", + "TypeC", + "<{ prompt: ", + "StringC", + "; context: ", + "StringC", + "; connectorId: ", + "StringC", + "; }>; }> | undefined; handler: ({}: ", + "ObservabilityAIAssistantRouteHandlerResources", + " & { params: { body: { prompt: string; context: string; connectorId: string; }; }; }) => Promise<", + "Readable", + ">; } & ", + "ObservabilityAIAssistantRouteCreateOptions", "; \"POST /internal/observability_ai_assistant/chat\": { endpoint: \"POST /internal/observability_ai_assistant/chat\"; params?: ", "TypeC", "<{ body: ", @@ -3461,6 +3884,18 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.StreamingChatResponseEventType", + "type": "Enum", + "tags": [], + "label": "StreamingChatResponseEventType", + "description": [], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/conversation_complete.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "observabilityAIAssistant", "id": "def-public.VisualizeESQLUserIntention", @@ -3969,20 +4404,34 @@ "; responseLanguage: ", "StringC", "; disableFunctions: ", + "UnionC", + "<[", "Type", - "; instructions: ", + ", ", + "TypeC", + "<{ except: ", + "ArrayC", + "<", + "StringC", + ">; }>]>; instructions: ", "ArrayC", "<", "UnionC", "<[", "StringC", ", ", + "IntersectionC", + "<[", "TypeC", "<{ doc_id: ", "StringC", "; text: ", "StringC", - "; }>]>>; }>]>; }>, ", + "; }>, ", + "PartialC", + "<{ system: ", + "BooleanC", + "; }>]>]>>; }>]>; }>, ", "PartialC", "<{ body: ", "PartialC", @@ -4018,7 +4467,7 @@ "section": "def-common.Message", "text": "Message" }, - "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | undefined; instructions?: (string | { doc_id: string; text: string; })[] | undefined; }; } & { body?: { actions?: ({ name: string; description: string; } & { parameters?: any; })[] | undefined; } | undefined; query?: { format?: \"default\" | \"openai\" | undefined; } | undefined; }; }) => Promise<", + "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | { except: string[]; } | undefined; instructions?: (string | ({ doc_id: string; text: string; } & { system?: boolean | undefined; }))[] | undefined; }; } & { body?: { actions?: ({ name: string; description: string; } & { parameters?: any; })[] | undefined; } | undefined; query?: { format?: \"default\" | \"openai\" | undefined; } | undefined; }; }) => Promise<", "Readable", ">; } & ", "ObservabilityAIAssistantRouteCreateOptions", @@ -4063,20 +4512,34 @@ "; responseLanguage: ", "StringC", "; disableFunctions: ", + "UnionC", + "<[", "Type", - "; instructions: ", + ", ", + "TypeC", + "<{ except: ", + "ArrayC", + "<", + "StringC", + ">; }>]>; instructions: ", "ArrayC", "<", "UnionC", "<[", "StringC", ", ", + "IntersectionC", + "<[", "TypeC", "<{ doc_id: ", "StringC", "; text: ", "StringC", - "; }>]>>; }>]>; }>, ", + "; }>, ", + "PartialC", + "<{ system: ", + "BooleanC", + "; }>]>]>>; }>]>; }>, ", "TypeC", "<{ body: ", "TypeC", @@ -4098,12 +4561,28 @@ "section": "def-common.Message", "text": "Message" }, - "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | undefined; instructions?: (string | { doc_id: string; text: string; })[] | undefined; }; } & { body: { screenContexts: ", + "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | { except: string[]; } | undefined; instructions?: (string | ({ doc_id: string; text: string; } & { system?: boolean | undefined; }))[] | undefined; }; } & { body: { screenContexts: ", "ObservabilityAIAssistantScreenContextRequest", "[]; }; }; }) => Promise<", "Readable", ">; } & ", "ObservabilityAIAssistantRouteCreateOptions", + "; \"POST /internal/observability_ai_assistant/chat/recall\": { endpoint: \"POST /internal/observability_ai_assistant/chat/recall\"; params?: ", + "TypeC", + "<{ body: ", + "TypeC", + "<{ prompt: ", + "StringC", + "; context: ", + "StringC", + "; connectorId: ", + "StringC", + "; }>; }> | undefined; handler: ({}: ", + "ObservabilityAIAssistantRouteHandlerResources", + " & { params: { body: { prompt: string; context: string; connectorId: string; }; }; }) => Promise<", + "Readable", + ">; } & ", + "ObservabilityAIAssistantRouteCreateOptions", "; \"POST /internal/observability_ai_assistant/chat\": { endpoint: \"POST /internal/observability_ai_assistant/chat\"; params?: ", "TypeC", "<{ body: ", @@ -4256,6 +4735,29 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-public.ChatCompletionChunkEvent", + "type": "Type", + "tags": [], + "label": "ChatCompletionChunkEvent", + "description": [], + "signature": [ + "{ type: ", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.StreamingChatResponseEventType", + "text": "StreamingChatResponseEventType" + }, + ".ChatCompletionChunk; } & { id: string; message: { content?: string | undefined; function_call?: { name?: string | undefined; arguments?: string | undefined; } | undefined; }; }" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/conversation_complete.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "observabilityAIAssistant", "id": "def-public.CompatibleJSONSchema", @@ -4267,23 +4769,77 @@ "{ type?: ", "JSONSchema7TypeName", " | undefined; enum?: JSONSchemaOrPrimitive[] | readonly JSONSchemaOrPrimitive[] | undefined; const?: JSONSchemaOrPrimitive | undefined; minLength?: number | undefined; maxLength?: number | undefined; items?: ", - "CompatibleJSONSchema", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.CompatibleJSONSchema", + "text": "CompatibleJSONSchema" + }, " | ", - "CompatibleJSONSchema", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.CompatibleJSONSchema", + "text": "CompatibleJSONSchema" + }, "[] | undefined; required?: string[] | readonly string[] | undefined; properties?: Record | undefined; allOf?: ", - "CompatibleJSONSchema", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.CompatibleJSONSchema", + "text": "CompatibleJSONSchema" + }, "[] | readonly ", - "CompatibleJSONSchema", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.CompatibleJSONSchema", + "text": "CompatibleJSONSchema" + }, "[] | undefined; anyOf?: ", - "CompatibleJSONSchema", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.CompatibleJSONSchema", + "text": "CompatibleJSONSchema" + }, "[] | readonly ", - "CompatibleJSONSchema", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.CompatibleJSONSchema", + "text": "CompatibleJSONSchema" + }, "[] | undefined; oneOf?: ", - "CompatibleJSONSchema", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.CompatibleJSONSchema", + "text": "CompatibleJSONSchema" + }, "[] | readonly ", - "CompatibleJSONSchema", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.CompatibleJSONSchema", + "text": "CompatibleJSONSchema" + }, "[] | undefined; description?: string | undefined; }" ], "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/functions/types.ts", @@ -4740,20 +5296,34 @@ "; responseLanguage: ", "StringC", "; disableFunctions: ", + "UnionC", + "<[", "Type", - "; instructions: ", + ", ", + "TypeC", + "<{ except: ", + "ArrayC", + "<", + "StringC", + ">; }>]>; instructions: ", "ArrayC", "<", "UnionC", "<[", "StringC", ", ", + "IntersectionC", + "<[", "TypeC", "<{ doc_id: ", "StringC", "; text: ", "StringC", - "; }>]>>; }>]>; }>, ", + "; }>, ", + "PartialC", + "<{ system: ", + "BooleanC", + "; }>]>]>>; }>]>; }>, ", "PartialC", "<{ body: ", "PartialC", @@ -4789,7 +5359,7 @@ "section": "def-common.Message", "text": "Message" }, - "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | undefined; instructions?: (string | { doc_id: string; text: string; })[] | undefined; }; } & { body?: { actions?: ({ name: string; description: string; } & { parameters?: any; })[] | undefined; } | undefined; query?: { format?: \"default\" | \"openai\" | undefined; } | undefined; }; }) => Promise<", + "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | { except: string[]; } | undefined; instructions?: (string | ({ doc_id: string; text: string; } & { system?: boolean | undefined; }))[] | undefined; }; } & { body?: { actions?: ({ name: string; description: string; } & { parameters?: any; })[] | undefined; } | undefined; query?: { format?: \"default\" | \"openai\" | undefined; } | undefined; }; }) => Promise<", "Readable", ">; } & ", "ObservabilityAIAssistantRouteCreateOptions", @@ -4834,20 +5404,34 @@ "; responseLanguage: ", "StringC", "; disableFunctions: ", + "UnionC", + "<[", "Type", - "; instructions: ", + ", ", + "TypeC", + "<{ except: ", + "ArrayC", + "<", + "StringC", + ">; }>]>; instructions: ", "ArrayC", "<", "UnionC", "<[", "StringC", ", ", + "IntersectionC", + "<[", "TypeC", "<{ doc_id: ", "StringC", "; text: ", "StringC", - "; }>]>>; }>]>; }>, ", + "; }>, ", + "PartialC", + "<{ system: ", + "BooleanC", + "; }>]>]>>; }>]>; }>, ", "TypeC", "<{ body: ", "TypeC", @@ -4869,12 +5453,28 @@ "section": "def-common.Message", "text": "Message" }, - "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | undefined; instructions?: (string | { doc_id: string; text: string; })[] | undefined; }; } & { body: { screenContexts: ", + "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | { except: string[]; } | undefined; instructions?: (string | ({ doc_id: string; text: string; } & { system?: boolean | undefined; }))[] | undefined; }; } & { body: { screenContexts: ", "ObservabilityAIAssistantScreenContextRequest", "[]; }; }; }) => Promise<", "Readable", ">; } & ", "ObservabilityAIAssistantRouteCreateOptions", + "; \"POST /internal/observability_ai_assistant/chat/recall\": { endpoint: \"POST /internal/observability_ai_assistant/chat/recall\"; params?: ", + "TypeC", + "<{ body: ", + "TypeC", + "<{ prompt: ", + "StringC", + "; context: ", + "StringC", + "; connectorId: ", + "StringC", + "; }>; }> | undefined; handler: ({}: ", + "ObservabilityAIAssistantRouteHandlerResources", + " & { params: { body: { prompt: string; context: string; connectorId: string; }; }; }) => Promise<", + "Readable", + ">; } & ", + "ObservabilityAIAssistantRouteCreateOptions", "; \"POST /internal/observability_ai_assistant/chat\": { endpoint: \"POST /internal/observability_ai_assistant/chat\"; params?: ", "TypeC", "<{ body: ", @@ -4970,7 +5570,7 @@ "label": "ObservabilityAIAssistantAPIEndpoint", "description": [], "signature": [ - "\"POST /internal/observability_ai_assistant/chat\" | \"POST /internal/observability_ai_assistant/chat/complete\" | \"POST /api/observability_ai_assistant/chat/complete 2023-10-31\" | \"GET /internal/observability_ai_assistant/conversation/{conversationId}\" | \"POST /internal/observability_ai_assistant/conversations\" | \"POST /internal/observability_ai_assistant/conversation\" | \"PUT /internal/observability_ai_assistant/conversation/{conversationId}\" | \"PUT /internal/observability_ai_assistant/conversation/{conversationId}/title\" | \"DELETE /internal/observability_ai_assistant/conversation/{conversationId}\" | \"GET /internal/observability_ai_assistant/connectors\" | \"GET /internal/observability_ai_assistant/functions\" | \"POST /internal/observability_ai_assistant/functions/recall\" | \"POST /internal/observability_ai_assistant/functions/summarize\" | \"POST /internal/observability_ai_assistant/kb/setup\" | \"GET /internal/observability_ai_assistant/kb/status\" | \"GET /internal/observability_ai_assistant/kb/entries\" | \"POST /internal/observability_ai_assistant/kb/entries/import\" | \"POST /internal/observability_ai_assistant/kb/entries/save\" | \"DELETE /internal/observability_ai_assistant/kb/entries/{entryId}\"" + "\"POST /internal/observability_ai_assistant/chat\" | \"POST /internal/observability_ai_assistant/chat/recall\" | \"POST /internal/observability_ai_assistant/chat/complete\" | \"POST /api/observability_ai_assistant/chat/complete 2023-10-31\" | \"GET /internal/observability_ai_assistant/conversation/{conversationId}\" | \"POST /internal/observability_ai_assistant/conversations\" | \"POST /internal/observability_ai_assistant/conversation\" | \"PUT /internal/observability_ai_assistant/conversation/{conversationId}\" | \"PUT /internal/observability_ai_assistant/conversation/{conversationId}/title\" | \"DELETE /internal/observability_ai_assistant/conversation/{conversationId}\" | \"GET /internal/observability_ai_assistant/connectors\" | \"GET /internal/observability_ai_assistant/functions\" | \"POST /internal/observability_ai_assistant/functions/recall\" | \"POST /internal/observability_ai_assistant/functions/summarize\" | \"POST /internal/observability_ai_assistant/kb/setup\" | \"GET /internal/observability_ai_assistant/kb/status\" | \"GET /internal/observability_ai_assistant/kb/entries\" | \"POST /internal/observability_ai_assistant/kb/entries/import\" | \"POST /internal/observability_ai_assistant/kb/entries/save\" | \"DELETE /internal/observability_ai_assistant/kb/entries/{entryId}\"" ], "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/api/index.ts", "deprecated": false, @@ -5538,11 +6138,11 @@ "signature": [ ", \"respond\">, TResponse = ReturnOf>(definition: TActionDefinition, respond: ", + ", \"respond\">, TRespondFunction extends ", "ScreenContextActionRespondFunction", - ") => ", + ">>(definition: TActionDefinition, respond: TRespondFunction) => ", "ScreenContextActionDefinition", - "" + ">" ], "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts", "deprecated": false, @@ -5571,25 +6171,7 @@ "label": "respond", "description": [], "signature": [ - "({}: { args: TResponse; signal: AbortSignal; connectorId: string; client: Pick<", - { - "pluginId": "observabilityAIAssistant", - "scope": "public", - "docId": "kibObservabilityAIAssistantPluginApi", - "section": "def-public.ObservabilityAIAssistantChatService", - "text": "ObservabilityAIAssistantChatService" - }, - ", \"complete\" | \"chat\">; messages: ", - { - "pluginId": "observabilityAIAssistant", - "scope": "common", - "docId": "kibObservabilityAIAssistantPluginApi", - "section": "def-common.Message", - "text": "Message" - }, - "[]; }) => Promise<", - "FunctionResponse", - ">" + "TRespondFunction" ], "path": "x-pack/plugins/observability_solution/observability_ai_assistant/public/utils/create_screen_context_action.ts", "deprecated": false, @@ -6158,20 +6740,34 @@ "; responseLanguage: ", "StringC", "; disableFunctions: ", + "UnionC", + "<[", "Type", - "; instructions: ", + ", ", + "TypeC", + "<{ except: ", + "ArrayC", + "<", + "StringC", + ">; }>]>; instructions: ", "ArrayC", "<", "UnionC", "<[", "StringC", ", ", + "IntersectionC", + "<[", "TypeC", "<{ doc_id: ", "StringC", "; text: ", "StringC", - "; }>]>>; }>]>; }>, ", + "; }>, ", + "PartialC", + "<{ system: ", + "BooleanC", + "; }>]>]>>; }>]>; }>, ", "PartialC", "<{ body: ", "PartialC", @@ -6207,7 +6803,7 @@ "section": "def-common.Message", "text": "Message" }, - "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | undefined; instructions?: (string | { doc_id: string; text: string; })[] | undefined; }; } & { body?: { actions?: ({ name: string; description: string; } & { parameters?: any; })[] | undefined; } | undefined; query?: { format?: \"default\" | \"openai\" | undefined; } | undefined; }; }) => Promise<", + "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | { except: string[]; } | undefined; instructions?: (string | ({ doc_id: string; text: string; } & { system?: boolean | undefined; }))[] | undefined; }; } & { body?: { actions?: ({ name: string; description: string; } & { parameters?: any; })[] | undefined; } | undefined; query?: { format?: \"default\" | \"openai\" | undefined; } | undefined; }; }) => Promise<", "Readable", ">; } & ", "ObservabilityAIAssistantRouteCreateOptions", @@ -6252,20 +6848,34 @@ "; responseLanguage: ", "StringC", "; disableFunctions: ", + "UnionC", + "<[", "Type", - "; instructions: ", + ", ", + "TypeC", + "<{ except: ", + "ArrayC", + "<", + "StringC", + ">; }>]>; instructions: ", "ArrayC", "<", "UnionC", "<[", "StringC", ", ", + "IntersectionC", + "<[", "TypeC", "<{ doc_id: ", "StringC", "; text: ", "StringC", - "; }>]>>; }>]>; }>, ", + "; }>, ", + "PartialC", + "<{ system: ", + "BooleanC", + "; }>]>]>>; }>]>; }>, ", "TypeC", "<{ body: ", "TypeC", @@ -6287,12 +6897,28 @@ "section": "def-common.Message", "text": "Message" }, - "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | undefined; instructions?: (string | { doc_id: string; text: string; })[] | undefined; }; } & { body: { screenContexts: ", + "[]; connectorId: string; persist: boolean; } & { conversationId?: string | undefined; title?: string | undefined; responseLanguage?: string | undefined; disableFunctions?: boolean | { except: string[]; } | undefined; instructions?: (string | ({ doc_id: string; text: string; } & { system?: boolean | undefined; }))[] | undefined; }; } & { body: { screenContexts: ", "ObservabilityAIAssistantScreenContextRequest", "[]; }; }; }) => Promise<", "Readable", ">; } & ", "ObservabilityAIAssistantRouteCreateOptions", + "; \"POST /internal/observability_ai_assistant/chat/recall\": { endpoint: \"POST /internal/observability_ai_assistant/chat/recall\"; params?: ", + "TypeC", + "<{ body: ", + "TypeC", + "<{ prompt: ", + "StringC", + "; context: ", + "StringC", + "; connectorId: ", + "StringC", + "; }>; }> | undefined; handler: ({}: ", + "ObservabilityAIAssistantRouteHandlerResources", + " & { params: { body: { prompt: string; context: string; connectorId: string; }; }; }) => Promise<", + "Readable", + ">; } & ", + "ObservabilityAIAssistantRouteCreateOptions", "; \"POST /internal/observability_ai_assistant/chat\": { endpoint: \"POST /internal/observability_ai_assistant/chat\"; params?: ", "TypeC", "<{ body: ", @@ -6558,6 +7184,100 @@ } ], "initialIsOpen": false + }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-common.ShortIdTable", + "type": "Class", + "tags": [], + "label": "ShortIdTable", + "description": [], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/short_id_table.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-common.ShortIdTable.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/short_id_table.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-common.ShortIdTable.take", + "type": "Function", + "tags": [], + "label": "take", + "description": [], + "signature": [ + "(originalId: string) => string" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/short_id_table.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-common.ShortIdTable.take.$1", + "type": "string", + "tags": [], + "label": "originalId", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/short_id_table.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-common.ShortIdTable.lookup", + "type": "Function", + "tags": [], + "label": "lookup", + "description": [], + "signature": [ + "(shortId: string) => string | undefined" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/short_id_table.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-common.ShortIdTable.lookup.$1", + "type": "string", + "tags": [], + "label": "shortId", + "description": [], + "signature": [ + "string" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/short_id_table.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false } ], "functions": [ @@ -7535,6 +8255,95 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "observabilityAIAssistant", + "id": "def-common.CompatibleJSONSchema", + "type": "Type", + "tags": [], + "label": "CompatibleJSONSchema", + "description": [], + "signature": [ + "{ type?: ", + "JSONSchema7TypeName", + " | undefined; enum?: JSONSchemaOrPrimitive[] | readonly JSONSchemaOrPrimitive[] | undefined; const?: JSONSchemaOrPrimitive | undefined; minLength?: number | undefined; maxLength?: number | undefined; items?: ", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.CompatibleJSONSchema", + "text": "CompatibleJSONSchema" + }, + " | ", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.CompatibleJSONSchema", + "text": "CompatibleJSONSchema" + }, + "[] | undefined; required?: string[] | readonly string[] | undefined; properties?: Record | undefined; allOf?: ", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.CompatibleJSONSchema", + "text": "CompatibleJSONSchema" + }, + "[] | readonly ", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.CompatibleJSONSchema", + "text": "CompatibleJSONSchema" + }, + "[] | undefined; anyOf?: ", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.CompatibleJSONSchema", + "text": "CompatibleJSONSchema" + }, + "[] | readonly ", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.CompatibleJSONSchema", + "text": "CompatibleJSONSchema" + }, + "[] | undefined; oneOf?: ", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.CompatibleJSONSchema", + "text": "CompatibleJSONSchema" + }, + "[] | readonly ", + { + "pluginId": "observabilityAIAssistant", + "scope": "common", + "docId": "kibObservabilityAIAssistantPluginApi", + "section": "def-common.CompatibleJSONSchema", + "text": "CompatibleJSONSchema" + }, + "[] | undefined; description?: string | undefined; }" + ], + "path": "x-pack/plugins/observability_solution/observability_ai_assistant/common/functions/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "observabilityAIAssistant", "id": "def-common.ConversationCreateEvent", diff --git a/api_docs/observability_a_i_assistant.mdx b/api_docs/observability_a_i_assistant.mdx index f1e61f1c7c9f6..944c331bf49d7 100644 --- a/api_docs/observability_a_i_assistant.mdx +++ b/api_docs/observability_a_i_assistant.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAIAssistant title: "observabilityAIAssistant" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAIAssistant plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistant'] --- import observabilityAIAssistantObj from './observability_a_i_assistant.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 261 | 1 | 259 | 27 | +| 290 | 1 | 288 | 26 | ## Client @@ -37,6 +37,9 @@ Contact [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai ### Functions +### Classes + + ### Interfaces diff --git a/api_docs/observability_a_i_assistant_app.mdx b/api_docs/observability_a_i_assistant_app.mdx index 8952376ddd16f..3d3c4c26a72b5 100644 --- a/api_docs/observability_a_i_assistant_app.mdx +++ b/api_docs/observability_a_i_assistant_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAIAssistantApp title: "observabilityAIAssistantApp" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAIAssistantApp plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAIAssistantApp'] --- import observabilityAIAssistantAppObj from './observability_a_i_assistant_app.devdocs.json'; diff --git a/api_docs/observability_ai_assistant_management.mdx b/api_docs/observability_ai_assistant_management.mdx index fa722c1fcce96..0a6cd4f656e10 100644 --- a/api_docs/observability_ai_assistant_management.mdx +++ b/api_docs/observability_ai_assistant_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityAiAssistantManagement title: "observabilityAiAssistantManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityAiAssistantManagement plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityAiAssistantManagement'] --- import observabilityAiAssistantManagementObj from './observability_ai_assistant_management.devdocs.json'; diff --git a/api_docs/observability_logs_explorer.mdx b/api_docs/observability_logs_explorer.mdx index 9d5079f63e042..3106ad25e10ba 100644 --- a/api_docs/observability_logs_explorer.mdx +++ b/api_docs/observability_logs_explorer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityLogsExplorer title: "observabilityLogsExplorer" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityLogsExplorer plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityLogsExplorer'] --- import observabilityLogsExplorerObj from './observability_logs_explorer.devdocs.json'; diff --git a/api_docs/observability_onboarding.mdx b/api_docs/observability_onboarding.mdx index 6d31e46446e46..9c73c46efa49f 100644 --- a/api_docs/observability_onboarding.mdx +++ b/api_docs/observability_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityOnboarding title: "observabilityOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityOnboarding plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observabilityOnboarding'] --- import observabilityOnboardingObj from './observability_onboarding.devdocs.json'; diff --git a/api_docs/observability_shared.devdocs.json b/api_docs/observability_shared.devdocs.json index e44212f483bad..f7b814cdfd32f 100644 --- a/api_docs/observability_shared.devdocs.json +++ b/api_docs/observability_shared.devdocs.json @@ -65,7 +65,7 @@ }, ", pluginsSetup: ", "ObservabilitySharedSetup", - ") => { locators: ObservabilitySharedLocators; navigation: { registerSections: (sections$: ", + ") => { registerProfilingComponent: (key: string, component: React.FC) => void; locators: ObservabilitySharedLocators; navigation: { registerSections: (sections$: ", "Observable", "<", { @@ -376,7 +376,7 @@ "label": "EmbeddableFlamegraph", "description": [], "signature": [ - "(props: Props) => JSX.Element" + "({ height, ...props }: Props) => JSX.Element" ], "path": "x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_flamegraph.tsx", "deprecated": false, @@ -387,7 +387,7 @@ "id": "def-public.EmbeddableFlamegraph.$1", "type": "Object", "tags": [], - "label": "props", + "label": "{ height, ...props }", "description": [], "signature": [ "Props" @@ -3601,7 +3601,7 @@ "label": "ObservabilitySharedPluginSetup", "description": [], "signature": [ - "{ locators: ObservabilitySharedLocators; navigation: { registerSections: (sections$: ", + "{ registerProfilingComponent: (key: string, component: React.FC) => void; locators: ObservabilitySharedLocators; navigation: { registerSections: (sections$: ", "Observable", "<", { diff --git a/api_docs/observability_shared.mdx b/api_docs/observability_shared.mdx index f92b034279f37..f5e26915cb9b4 100644 --- a/api_docs/observability_shared.mdx +++ b/api_docs/observability_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observabilityShared title: "observabilityShared" image: https://source.unsplash.com/400x175/?github description: API docs for the observabilityShared plugin -date: 2024-06-11 +date: 2024-06-19 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 bc46dc495e466..ac6690ba3a9dd 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2024-06-11 +date: 2024-06-19 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 e8fa4df050124..4324d1d11c14a 100644 --- a/api_docs/painless_lab.mdx +++ b/api_docs/painless_lab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/painlessLab title: "painlessLab" image: https://source.unsplash.com/400x175/?github description: API docs for the painlessLab plugin -date: 2024-06-11 +date: 2024-06-19 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 0a212ae4196fc..80b6b3136a7d9 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -15,13 +15,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Count | Plugins or Packages with a
public API | Number of teams | |--------------|----------|------------------------| -| 803 | 689 | 42 | +| 805 | 689 | 42 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 48917 | 239 | 37385 | 1881 | +| 49310 | 238 | 37600 | 1879 | ## Plugin Directory @@ -30,7 +30,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 307 | 0 | 301 | 32 | | | [@elastic/appex-sharedux @elastic/kibana-management](https://github.com/orgs/elastic/teams/appex-sharedux ) | - | 2 | 0 | 2 | 0 | | | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 4 | 0 | 4 | 1 | -| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 74 | 0 | 9 | 2 | +| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | AIOps plugin maintained by ML team. | 72 | 0 | 9 | 2 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 868 | 1 | 836 | 54 | | | [@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 | 123 | | | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 9 | 0 | 9 | 0 | @@ -64,13 +64,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Data view management app | 2 | 0 | 2 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Data services are useful for searching and querying data from Elasticsearch. Helpful utilities include: a re-usable react query bar, KQL autocomplete, async search, Data Views (Index Patterns) and field formatters. | 1159 | 0 | 402 | 3 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | The Data Visualizer tools help you understand your data, by analyzing the metrics and fields in a log file or an existing Elasticsearch index. | 31 | 3 | 25 | 4 | -| | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | This plugin introduces the concept of dataset quality, where users can easily get an overview on the datasets they have. | 10 | 0 | 10 | 5 | +| | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | This plugin introduces the concept of data set quality, where users can easily get an overview on the data sets they have. | 10 | 0 | 10 | 5 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 15 | 0 | 9 | 2 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains the Discover application and the saved search embeddable. | 148 | 0 | 101 | 28 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 35 | 0 | 33 | 2 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | A stateful layer to register shared features and provide an access point to discover without a direct dependency | 16 | 0 | 15 | 2 | | | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | APIs used to assess the quality of data in Elasticsearch indexes | 2 | 0 | 0 | 0 | -| | [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/security-generative-ai) | Server APIs for the Elastic AI Assistant | 46 | 0 | 32 | 0 | +| | [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/security-generative-ai) | Server APIs for the Elastic AI Assistant | 48 | 0 | 34 | 1 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds embeddables service to Kibana | 557 | 1 | 447 | 9 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Extends embeddable plugin with more functionality | 19 | 0 | 19 | 2 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides encryption and decryption utilities for saved objects containing sensitive information. | 53 | 0 | 46 | 1 | @@ -101,7 +101,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 84 | 0 | 84 | 8 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 240 | 0 | 24 | 9 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Simple UI for managing files in Kibana | 2 | 0 | 2 | 0 | -| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1335 | 5 | 1214 | 71 | +| | [@elastic/fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1339 | 5 | 1217 | 72 | | ftrApis | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 72 | 0 | 14 | 5 | | globalSearchBar | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 0 | 0 | 0 | 0 | @@ -117,6 +117,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 4 | 0 | 4 | 0 | | inputControlVis | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds Input Control visualization to Kibana | 0 | 0 | 0 | 0 | | | [@elastic/kibana-presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | - | 127 | 2 | 100 | 4 | +| | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | A simple example of how to use core's routing services test | 38 | 0 | 33 | 1 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides UI and APIs for the interactive setup mode. | 28 | 0 | 18 | 0 | | | [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai-assistant) | - | 95 | 0 | 95 | 4 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 6 | 0 | 6 | 0 | @@ -135,10 +136,10 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@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. | 296 | 0 | 268 | 32 | | logstash | [@elastic/logstash](https://github.com/orgs/elastic/teams/logstash) | - | 0 | 0 | 0 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 44 | 0 | 44 | 7 | -| | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 286 | 0 | 281 | 31 | +| | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 209 | 0 | 205 | 28 | | | [@elastic/kibana-gis](https://github.com/orgs/elastic/teams/kibana-gis) | - | 60 | 0 | 60 | 0 | | | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | Exposes utilities for accessing metrics data | 104 | 8 | 104 | 6 | -| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the machine learning features provided by Elastic. | 154 | 3 | 67 | 101 | +| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the machine learning features provided by Elastic. | 154 | 3 | 67 | 102 | | | [@elastic/kibana-security](https://github.com/orgs/elastic/teams/kibana-security) | - | 2 | 0 | 2 | 0 | | | [@elastic/stack-monitoring](https://github.com/orgs/elastic/teams/stack-monitoring) | - | 15 | 3 | 13 | 1 | | | [@elastic/stack-monitoring](https://github.com/orgs/elastic/teams/stack-monitoring) | - | 9 | 0 | 9 | 0 | @@ -147,7 +148,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 3 | 0 | 3 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 1 | | | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 698 | 2 | 689 | 15 | -| | [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai-assistant) | - | 261 | 1 | 259 | 27 | +| | [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai-assistant) | - | 290 | 1 | 288 | 26 | | | [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai-assistant) | - | 4 | 0 | 4 | 0 | | | [@elastic/obs-ai-assistant](https://github.com/orgs/elastic/teams/obs-ai-assistant) | - | 2 | 0 | 2 | 0 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | This plugin exposes and registers observability log consumption features. | 19 | 0 | 19 | 1 | @@ -173,6 +174,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 32 | 0 | 13 | 0 | | | [@elastic/kibana-reporting-services](https://github.com/orgs/elastic/teams/kibana-reporting-services) | Kibana Screenshotting Plugin | 32 | 0 | 8 | 4 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | Plugin hosting shared features for connectors | 19 | 0 | 19 | 3 | +| | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 10 | 0 | 6 | 1 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | Plugin to provide access to and rendering of python notebooks for use in the persistent developer console. | 6 | 0 | 6 | 0 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 18 | 0 | 10 | 1 | | searchprofiler | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 0 | 0 | 0 | 0 | @@ -201,7 +203,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 242 | 1 | 198 | 17 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the transforms features provided by Elastic. Transforms enable you to convert existing Elasticsearch indices into summarized indices, which provide opportunities for new insights and analytics. | 4 | 0 | 4 | 1 | | translations | [@elastic/kibana-localization](https://github.com/orgs/elastic/teams/kibana-localization) | - | 0 | 0 | 0 | 0 | -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 588 | 1 | 562 | 58 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 588 | 1 | 562 | 52 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Adds UI Actions service to Kibana | 149 | 0 | 103 | 9 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Extends UI Actions plugin with more functionality | 212 | 0 | 145 | 11 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | This plugin contains services reliant on the plugin lifecycle for the unified doc viewer component (see @kbn/unified-doc-viewer). | 10 | 0 | 7 | 2 | @@ -235,22 +237,17 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] |--------------|----------------|-----------|--------------|----------|---------------|--------| | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 11 | 5 | 11 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 5 | 0 | 5 | 0 | -| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 51 | 0 | 0 | 0 | +| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 36 | 0 | 0 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 2 | 0 | 0 | 0 | -| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 41 | 0 | 0 | 0 | +| | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 54 | 0 | 0 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 27 | 3 | 27 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 5 | 0 | 5 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 23 | 0 | 22 | 0 | -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 179 | 0 | 176 | 0 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 190 | 0 | 187 | 0 | | | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 33 | 0 | 33 | 0 | -| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 116 | 0 | 104 | 3 | +| | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 237 | 0 | 223 | 2 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 73 | 0 | 73 | 2 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 98 | 0 | 0 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 1 | 0 | 0 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 19 | 0 | 0 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 23 | 0 | 0 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 19 | 0 | 0 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 22 | 0 | 1 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 18 | 0 | 18 | 0 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 4 | 0 | 4 | 0 | | | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | - | 49 | 0 | 49 | 8 | @@ -274,17 +271,18 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 217 | 0 | 180 | 9 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 79 | 0 | 50 | 9 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 24 | 0 | 24 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 144 | 3 | 141 | 20 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 146 | 2 | 142 | 20 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 10 | 0 | 8 | 4 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 8 | 0 | 8 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 3 | 0 | 3 | 0 | -| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 8 | 0 | 8 | 0 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 10 | 0 | 10 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 48 | 0 | 32 | 3 | +| | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 30 | 0 | 30 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 195 | 1 | 128 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 0 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 101 | 0 | 0 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 7 | 0 | 7 | 1 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 4 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 3 | 0 | 0 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 100 | 0 | 0 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 7 | 0 | 7 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 5 | 0 | 5 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 103 | 0 | 27 | 0 | @@ -324,8 +322,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 5 | 0 | 2 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 4 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 22 | 0 | 13 | 1 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 37 | 1 | 33 | 0 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 114 | 0 | 55 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 38 | 1 | 34 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 115 | 0 | 55 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 43 | 0 | 38 | 3 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 13 | 1 | 13 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 4 | 1 | @@ -350,7 +348,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 7 | 0 | 7 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 54 | 7 | 54 | 6 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 13 | 0 | 13 | 1 | -| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 487 | 2 | 193 | 0 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 489 | 2 | 193 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 92 | 0 | 79 | 10 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 44 | 0 | 43 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 4 | 0 | 2 | 0 | @@ -461,7 +459,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/obs-ux-management-team](https://github.com/orgs/elastic/teams/obs-ux-management-team) | - | 51 | 0 | 51 | 1 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 14 | 0 | 9 | 0 | | | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 80 | 0 | 80 | 1 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 1 | 0 | 0 | 0 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 5 | 0 | 4 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 44 | 0 | 43 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 7 | 0 | 7 | 0 | | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 5 | 0 | 5 | 0 | @@ -469,7 +467,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-management](https://github.com/orgs/elastic/teams/kibana-management) | - | 4 | 0 | 4 | 0 | | | [@elastic/ml-ui](https://github.com/orgs/elastic/teams/ml-ui) | - | 3 | 0 | 3 | 0 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 45 | 0 | 33 | 0 | -| | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 16 | 0 | 16 | 0 | +| | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 17 | 0 | 17 | 0 | | | [@elastic/security-solution](https://github.com/orgs/elastic/teams/security-solution) | - | 5 | 0 | 5 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 8 | 0 | 8 | 0 | @@ -480,15 +478,16 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 102 | 0 | 86 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 15 | 0 | 9 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 38 | 2 | 33 | 0 | -| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 112 | 0 | 84 | 0 | +| | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 120 | 0 | 94 | 1 | | | [@elastic/docs](https://github.com/orgs/elastic/teams/docs) | - | 77 | 0 | 77 | 2 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 5 | 0 | 5 | 1 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 41 | 0 | 27 | 6 | +| | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 152 | 0 | 0 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 33 | 0 | 24 | 1 | | | [@elastic/security-threat-hunting-explore](https://github.com/orgs/elastic/teams/security-threat-hunting-explore) | - | 13 | 0 | 5 | 0 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 35 | 0 | 34 | 0 | | | [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/security-generative-ai) | - | 165 | 0 | 138 | 9 | -| | [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/security-generative-ai) | - | 295 | 0 | 276 | 0 | +| | [@elastic/security-generative-ai](https://github.com/orgs/elastic/teams/security-generative-ai) | - | 305 | 0 | 286 | 0 | | | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 20 | 0 | 20 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 52 | 0 | 37 | 7 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 32 | 0 | 19 | 1 | @@ -496,9 +495,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 269 | 1 | 209 | 15 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 26 | 0 | 26 | 1 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 1 | 0 | -| | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 68 | 1 | 68 | 7 | +| | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 68 | 1 | 68 | 9 | | | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 53 | 0 | 51 | 0 | -| | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 194 | 0 | 184 | 10 | +| | [@elastic/kibana-esql](https://github.com/orgs/elastic/teams/kibana-esql) | - | 189 | 0 | 178 | 10 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 39 | 0 | 39 | 0 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 52 | 0 | 52 | 1 | | | [@elastic/security-threat-hunting-investigations](https://github.com/orgs/elastic/teams/security-threat-hunting-investigations) | - | 39 | 0 | 14 | 1 | @@ -622,6 +621,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 113 | 0 | 107 | 2 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 92 | 0 | 91 | 0 | | | [@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/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 3 | 0 | 3 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 13 | 2 | 8 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 5 | 0 | 5 | 1 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 2 | 0 | 1 | 1 | @@ -629,7 +629,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/security-detections-response](https://github.com/orgs/elastic/teams/security-detections-response) | - | 125 | 0 | 122 | 0 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 2 | 0 | 2 | 0 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 76 | 0 | 76 | 0 | -| | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 3687 | 0 | 3687 | 0 | +| | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 3717 | 0 | 3717 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 18 | 1 | 17 | 1 | | | [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) | - | 25 | 0 | 25 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 20 | 0 | 18 | 1 | @@ -655,7 +655,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 103 | 0 | 99 | 0 | | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 35 | 0 | 23 | 0 | | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 94 | 0 | 81 | 0 | -| | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 207 | 0 | 160 | 0 | +| | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 209 | 0 | 161 | 0 | | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 28 | 0 | 25 | 0 | | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 120 | 0 | 116 | 0 | | | [@elastic/security-detection-engine](https://github.com/orgs/elastic/teams/security-detection-engine) | - | 49 | 0 | 44 | 0 | @@ -714,7 +714,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 4 | 0 | 2 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 41 | 2 | 21 | 0 | | | [@elastic/kibana-core](https://github.com/orgs/elastic/teams/kibana-core) | - | 7 | 0 | 5 | 1 | -| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 313 | 4 | 265 | 12 | +| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 315 | 4 | 267 | 13 | | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 25 | 0 | 13 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 131 | 3 | 98 | 2 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 2 | 0 | 1 | 0 | @@ -726,7 +726,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 39 | 0 | 25 | 1 | | | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 86 | 0 | 86 | 1 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 42 | 0 | 28 | 0 | -| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 56 | 0 | 47 | 0 | +| | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 55 | 0 | 46 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 9 | 0 | 8 | 0 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the unified data table which can be integrated into apps | 153 | 0 | 81 | 1 | | | [@elastic/kibana-data-discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 18 | 0 | 17 | 5 | @@ -742,5 +742,5 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [@elastic/kibana-visualizations](https://github.com/orgs/elastic/teams/kibana-visualizations) | - | 10 | 0 | 9 | 1 | | | [@elastic/obs-ux-logs-team](https://github.com/orgs/elastic/teams/obs-ux-logs-team) | - | 13 | 0 | 13 | 0 | | | [@elastic/kibana-operations](https://github.com/orgs/elastic/teams/kibana-operations) | - | 6 | 0 | 2 | 0 | -| | [@elastic/security-detection-rule-management](https://github.com/orgs/elastic/teams/security-detection-rule-management) | - | 18 | 0 | 9 | 0 | +| | [@elastic/security-detection-rule-management](https://github.com/orgs/elastic/teams/security-detection-rule-management) | - | 20 | 0 | 10 | 0 | diff --git a/api_docs/presentation_panel.mdx b/api_docs/presentation_panel.mdx index 68e7ccba891c2..4df0ee0b57d97 100644 --- a/api_docs/presentation_panel.mdx +++ b/api_docs/presentation_panel.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationPanel title: "presentationPanel" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationPanel plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationPanel'] --- import presentationPanelObj from './presentation_panel.devdocs.json'; diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 882b644612c04..7e59e1b428305 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2024-06-11 +date: 2024-06-19 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 a304870f68ef0..b03bdffd3b0d2 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/profiling_data_access.mdx b/api_docs/profiling_data_access.mdx index 269dfdd0bfdb7..e3310f1e571a0 100644 --- a/api_docs/profiling_data_access.mdx +++ b/api_docs/profiling_data_access.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profilingDataAccess title: "profilingDataAccess" image: https://source.unsplash.com/400x175/?github description: API docs for the profilingDataAccess plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profilingDataAccess'] --- import profilingDataAccessObj from './profiling_data_access.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 9c27b1ce0f988..1b78a818df00f 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2024-06-11 +date: 2024-06-19 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 555872049b4f8..10d2a95a204f3 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2024-06-11 +date: 2024-06-19 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 5915c41db765e..3c7589a1dfaa4 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2024-06-11 +date: 2024-06-19 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 1010f0e9e7553..f39c370de06fc 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2024-06-11 +date: 2024-06-19 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 30a9bc8624f5e..a046e09ac1206 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2024-06-11 +date: 2024-06-19 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 cace4028adfcc..2b3aebccf8f44 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2024-06-11 +date: 2024-06-19 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 0e8cd621e301b..dfcabf012f816 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2024-06-11 +date: 2024-06-19 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 4fcb215e370e4..a4b75fa8bf8af 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2024-06-11 +date: 2024-06-19 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 ba8a16fee5cf3..997699a68579a 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2024-06-11 +date: 2024-06-19 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 bb2d09b6cf3e8..a353a4cff8bc9 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2024-06-11 +date: 2024-06-19 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 486f84a10a963..5fe795374d9fd 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2024-06-11 +date: 2024-06-19 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 c1e40ddf83a43..8b0e1bbb43838 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2024-06-11 +date: 2024-06-19 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 bc25ac5ae8126..f8503be8f380b 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/search_connectors.mdx b/api_docs/search_connectors.mdx index 4a5ba83608afe..45a03aa097976 100644 --- a/api_docs/search_connectors.mdx +++ b/api_docs/search_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchConnectors title: "searchConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the searchConnectors plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchConnectors'] --- import searchConnectorsObj from './search_connectors.devdocs.json'; diff --git a/api_docs/search_inference_endpoints.devdocs.json b/api_docs/search_inference_endpoints.devdocs.json new file mode 100644 index 0000000000000..41c4ee3197a75 --- /dev/null +++ b/api_docs/search_inference_endpoints.devdocs.json @@ -0,0 +1,176 @@ +{ + "id": "searchInferenceEndpoints", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [], + "setup": { + "parentPluginId": "searchInferenceEndpoints", + "id": "def-public.SearchInferenceEndpointsPluginSetup", + "type": "Interface", + "tags": [], + "label": "SearchInferenceEndpointsPluginSetup", + "description": [], + "path": "x-pack/plugins/search_inference_endpoints/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "searchInferenceEndpoints", + "id": "def-public.SearchInferenceEndpointsPluginStart", + "type": "Interface", + "tags": [], + "label": "SearchInferenceEndpointsPluginStart", + "description": [], + "path": "x-pack/plugins/search_inference_endpoints/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "searchInferenceEndpoints", + "id": "def-public.SearchInferenceEndpointsPluginStart.InferenceEdnpointsProvider", + "type": "Function", + "tags": [], + "label": "InferenceEdnpointsProvider", + "description": [], + "signature": [ + "React.FunctionComponent<", + "InferenceEndpointsProviderProps", + " & { children?: React.ReactNode; }>" + ], + "path": "x-pack/plugins/search_inference_endpoints/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "searchInferenceEndpoints", + "id": "def-public.SearchInferenceEndpointsPluginStart.InferenceEdnpointsProvider.$1", + "type": "CompoundType", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P & { children?: React.ReactNode; }" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "searchInferenceEndpoints", + "id": "def-public.SearchInferenceEndpointsPluginStart.InferenceEdnpointsProvider.$2", + "type": "Any", + "tags": [], + "label": "context", + "description": [], + "signature": [ + "any" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "searchInferenceEndpoints", + "id": "def-public.SearchInferenceEndpointsPluginStart.InferenceEndpoints", + "type": "Function", + "tags": [], + "label": "InferenceEndpoints", + "description": [], + "signature": [ + "React.FunctionComponent<{ children?: React.ReactNode; }>" + ], + "path": "x-pack/plugins/search_inference_endpoints/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "searchInferenceEndpoints", + "id": "def-public.SearchInferenceEndpointsPluginStart.InferenceEndpoints.$1", + "type": "CompoundType", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P & { children?: React.ReactNode; }" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "searchInferenceEndpoints", + "id": "def-public.SearchInferenceEndpointsPluginStart.InferenceEndpoints.$2", + "type": "Any", + "tags": [], + "label": "context", + "description": [], + "signature": [ + "any" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [], + "setup": { + "parentPluginId": "searchInferenceEndpoints", + "id": "def-server.SearchInferenceEndpointsPluginSetup", + "type": "Interface", + "tags": [], + "label": "SearchInferenceEndpointsPluginSetup", + "description": [], + "path": "x-pack/plugins/search_inference_endpoints/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "searchInferenceEndpoints", + "id": "def-server.SearchInferenceEndpointsPluginStart", + "type": "Interface", + "tags": [], + "label": "SearchInferenceEndpointsPluginStart", + "description": [], + "path": "x-pack/plugins/search_inference_endpoints/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/search_inference_endpoints.mdx b/api_docs/search_inference_endpoints.mdx new file mode 100644 index 0000000000000..960e4e7e48960 --- /dev/null +++ b/api_docs/search_inference_endpoints.mdx @@ -0,0 +1,41 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibSearchInferenceEndpointsPluginApi +slug: /kibana-dev-docs/api/searchInferenceEndpoints +title: "searchInferenceEndpoints" +image: https://source.unsplash.com/400x175/?github +description: API docs for the searchInferenceEndpoints plugin +date: 2024-06-19 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchInferenceEndpoints'] +--- +import searchInferenceEndpointsObj from './search_inference_endpoints.devdocs.json'; + + + +Contact [@elastic/search-kibana](https://github.com/orgs/elastic/teams/search-kibana) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 10 | 0 | 6 | 1 | + +## Client + +### Setup + + +### Start + + +## Server + +### Setup + + +### Start + + diff --git a/api_docs/search_notebooks.mdx b/api_docs/search_notebooks.mdx index 8c05d7da126d0..470c28d2868b4 100644 --- a/api_docs/search_notebooks.mdx +++ b/api_docs/search_notebooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchNotebooks title: "searchNotebooks" image: https://source.unsplash.com/400x175/?github description: API docs for the searchNotebooks plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchNotebooks'] --- import searchNotebooksObj from './search_notebooks.devdocs.json'; diff --git a/api_docs/search_playground.mdx b/api_docs/search_playground.mdx index 958d9d4590682..1f957baa893ee 100644 --- a/api_docs/search_playground.mdx +++ b/api_docs/search_playground.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/searchPlayground title: "searchPlayground" image: https://source.unsplash.com/400x175/?github description: API docs for the searchPlayground plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'searchPlayground'] --- import searchPlaygroundObj from './search_playground.devdocs.json'; diff --git a/api_docs/security.devdocs.json b/api_docs/security.devdocs.json index 99dea9ea6157f..f43fe4bd96a09 100644 --- a/api_docs/security.devdocs.json +++ b/api_docs/security.devdocs.json @@ -1952,7 +1952,7 @@ "section": "def-common.KibanaRequest", "text": "KibanaRequest" }, - ", createParams: Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; role_descriptors: Record>; }> | Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; kibana_role_descriptors: Record | undefined; } & { spaces: string[] | \"*\"[]; }>[]; elasticsearch: Readonly<{ cluster?: string[] | undefined; indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; run_as?: string[] | undefined; } & {}>; }>>; }>) => Promise<", + ", createParams: Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; role_descriptors: Record>; }> | Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; kibana_role_descriptors: Record | undefined; } & { spaces: string[] | \"*\"[]; }>[]; elasticsearch: Readonly<{ cluster?: string[] | undefined; indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; run_as?: string[] | undefined; } & {}>; }>>; }>) => Promise<", { "pluginId": "@kbn/security-plugin-types-server", "scope": "server", @@ -2000,7 +2000,7 @@ "Create operation parameters." ], "signature": [ - "Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; role_descriptors: Record>; }> | Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; kibana_role_descriptors: Record | undefined; } & { spaces: string[] | \"*\"[]; }>[]; elasticsearch: Readonly<{ cluster?: string[] | undefined; indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; run_as?: string[] | undefined; } & {}>; }>>; }>" + "Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; role_descriptors: Record>; }> | Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; kibana_role_descriptors: Record | undefined; } & { spaces: string[] | \"*\"[]; }>[]; elasticsearch: Readonly<{ cluster?: string[] | undefined; indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; run_as?: string[] | undefined; } & {}>; }>>; }>" ], "path": "x-pack/packages/security/plugin_types_server/src/authentication/api_keys/api_keys.ts", "deprecated": false, @@ -5072,7 +5072,7 @@ "label": "CreateAPIKeyParams", "description": [], "signature": [ - "Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; role_descriptors: Record>; }> | Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; kibana_role_descriptors: Record | undefined; } & { spaces: string[] | \"*\"[]; }>[]; elasticsearch: Readonly<{ cluster?: string[] | undefined; indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; run_as?: string[] | undefined; } & {}>; }>>; }> | Readonly<{ metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { type: \"cross_cluster\"; name: string; access: Readonly<{ search?: Readonly<{ query?: any; field_security?: any; allow_restricted_indices?: boolean | undefined; } & { names: string[]; }>[] | undefined; replication?: Readonly<{} & { names: string[]; }>[] | undefined; } & {}>; }>" + "Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; role_descriptors: Record>; }> | Readonly<{ type?: \"rest\" | undefined; metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { name: string; kibana_role_descriptors: Record | undefined; } & { spaces: string[] | \"*\"[]; }>[]; elasticsearch: Readonly<{ cluster?: string[] | undefined; indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; run_as?: string[] | undefined; } & {}>; }>>; }> | Readonly<{ metadata?: Readonly<{} & {}> | undefined; expiration?: string | undefined; } & { type: \"cross_cluster\"; name: string; access: Readonly<{ search?: Readonly<{ query?: any; field_security?: any; allow_restricted_indices?: boolean | undefined; } & { names: string[]; }>[] | undefined; replication?: Readonly<{} & { names: string[]; }>[] | undefined; } & {}>; }>" ], "path": "x-pack/packages/security/plugin_types_server/src/authentication/api_keys/api_keys.ts", "deprecated": false, @@ -5134,7 +5134,7 @@ "label": "CreateRestAPIKeyWithKibanaPrivilegesParams", "description": [], "signature": [ - "{ readonly type?: \"rest\" | undefined; readonly metadata?: Readonly<{} & {}> | undefined; readonly expiration?: string | undefined; readonly name: string; readonly kibana_role_descriptors: Record | undefined; } & { spaces: string[] | \"*\"[]; }>[]; elasticsearch: Readonly<{ cluster?: string[] | undefined; indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; run_as?: string[] | undefined; } & {}>; }>>; }" + "{ readonly type?: \"rest\" | undefined; readonly metadata?: Readonly<{} & {}> | undefined; readonly expiration?: string | undefined; readonly name: string; readonly kibana_role_descriptors: Record | undefined; } & { spaces: string[] | \"*\"[]; }>[]; elasticsearch: Readonly<{ cluster?: string[] | undefined; indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; run_as?: string[] | undefined; } & {}>; }>>; }" ], "path": "x-pack/packages/security/plugin_types_server/src/authentication/api_keys/api_keys.ts", "deprecated": false, @@ -5149,7 +5149,7 @@ "label": "ElasticsearchPrivilegesType", "description": [], "signature": [ - "{ readonly cluster?: string[] | undefined; readonly indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; readonly remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; readonly remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"grant\" | \"except\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; readonly run_as?: string[] | undefined; }" + "{ readonly cluster?: string[] | undefined; readonly indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; }>[] | undefined; readonly remote_cluster?: Readonly<{} & { privileges: string[]; clusters: string[]; }>[] | undefined; readonly remote_indices?: Readonly<{ query?: string | undefined; field_security?: Record<\"except\" | \"grant\", string[]> | undefined; allow_restricted_indices?: boolean | undefined; } & { names: string[]; privileges: string[]; clusters: string[]; }>[] | undefined; readonly run_as?: string[] | undefined; }" ], "path": "x-pack/packages/security/plugin_types_server/src/authorization/role_schema.ts", "deprecated": false, @@ -5497,83 +5497,83 @@ }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/security/security.ts" + "path": "x-pack/plugins/fleet/server/services/api_keys/security.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/api_keys/transform_api_keys.ts" + "path": "x-pack/plugins/fleet/server/services/security/security.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/api_keys/security.ts" + "path": "x-pack/plugins/fleet/server/services/api_keys/transform_api_keys.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/epm/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/services/setup/fleet_server_policies_enrollment_keys.ts" + "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/setup/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/settings/index.ts" + "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/package_policy/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts" + "path": "x-pack/plugins/fleet/server/services/setup/fleet_server_policies_enrollment_keys.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/setup/handlers.ts" }, { "plugin": "fleet", - "path": "x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts" + "path": "x-pack/plugins/fleet/server/routes/settings/index.ts" }, { "plugin": "cloudDefend", @@ -5595,10 +5595,6 @@ "plugin": "enterpriseSearch", "path": "x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts" }, - { - "plugin": "enterpriseSearch", - "path": "x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts" - }, { "plugin": "lists", "path": "x-pack/plugins/lists/server/get_user.ts" @@ -5659,14 +5655,6 @@ "plugin": "serverlessSearch", "path": "x-pack/plugins/serverless_search/server/routes/api_key_routes.ts" }, - { - "plugin": "serverlessSearch", - "path": "x-pack/plugins/serverless_search/server/routes/api_key_routes.ts" - }, - { - "plugin": "serverlessSearch", - "path": "x-pack/plugins/serverless_search/server/routes/indices_routes.ts" - }, { "plugin": "transform", "path": "x-pack/plugins/transform/server/routes/api/reauthorize_transforms/route_handler_factory.ts" diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 1d3c9e5ce7cc3..ad7fdca6c19f6 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2024-06-11 +date: 2024-06-19 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 362a1a61e51eb..659f8ee587527 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -485,7 +485,7 @@ "\nExperimental flag needed to enable the link" ], "signature": [ - "\"assistantKnowledgeBaseByDefault\" | \"assistantModelEvaluation\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"responseActionsSentinelOneV2Enabled\" | \"responseActionsSentinelOneGetFileEnabled\" | \"agentStatusClientEnabled\" | \"responseActionsCrowdstrikeManualHostIsolationEnabled\" | \"responseActionScanEnabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutDisabled\" | \"newUserDetailsFlyoutManagedUser\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | \"alertSuppressionForEsqlRuleEnabled\" | \"riskEnginePrivilegesRouteEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"crowdstrikeDataInAnalyzerEnabled\" | \"jamfDataInAnalyzerEnabled\" | \"jsonPrebuiltRulesDiffingEnabled\" | \"timelineEsqlTabDisabled\" | \"unifiedComponentsInTimelineEnabled\" | \"analyzerDatePickersAndSourcererDisabled\" | \"perFieldPrebuiltRulesDiffingEnabled\" | \"malwareOnWriteScanOptionAvailable\" | \"unifiedManifestEnabled\" | \"aiAssistantFlyoutMode\" | \"valueListItemsModalEnabled\" | \"bulkCustomHighlightedFieldsEnabled\" | \"manualRuleRunEnabled\" | undefined" + "\"assistantKnowledgeBaseByDefault\" | \"assistantModelEvaluation\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"responseActionsSentinelOneV2Enabled\" | \"responseActionsSentinelOneGetFileEnabled\" | \"agentStatusClientEnabled\" | \"responseActionsCrowdstrikeManualHostIsolationEnabled\" | \"responseActionScanEnabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutDisabled\" | \"notesEnabled\" | \"newUserDetailsFlyoutManagedUser\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | \"alertSuppressionForEsqlRuleEnabled\" | \"riskEnginePrivilegesRouteEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"crowdstrikeDataInAnalyzerEnabled\" | \"jamfDataInAnalyzerEnabled\" | \"jsonPrebuiltRulesDiffingEnabled\" | \"timelineEsqlTabDisabled\" | \"unifiedComponentsInTimelineEnabled\" | \"analyzerDatePickersAndSourcererDisabled\" | \"perFieldPrebuiltRulesDiffingEnabled\" | \"malwareOnWriteScanOptionAvailable\" | \"unifiedManifestEnabled\" | \"aiAssistantFlyoutMode\" | \"valueListItemsModalEnabled\" | \"bulkCustomHighlightedFieldsEnabled\" | \"manualRuleRunEnabled\" | \"filterProcessDescendantsForEventFiltersEnabled\" | undefined" ], "path": "x-pack/plugins/security_solution/public/common/links/types.ts", "deprecated": false, @@ -565,7 +565,7 @@ "\nExperimental flag needed to disable the link. Opposite of experimentalKey" ], "signature": [ - "\"assistantKnowledgeBaseByDefault\" | \"assistantModelEvaluation\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"responseActionsSentinelOneV2Enabled\" | \"responseActionsSentinelOneGetFileEnabled\" | \"agentStatusClientEnabled\" | \"responseActionsCrowdstrikeManualHostIsolationEnabled\" | \"responseActionScanEnabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutDisabled\" | \"newUserDetailsFlyoutManagedUser\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | \"alertSuppressionForEsqlRuleEnabled\" | \"riskEnginePrivilegesRouteEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"crowdstrikeDataInAnalyzerEnabled\" | \"jamfDataInAnalyzerEnabled\" | \"jsonPrebuiltRulesDiffingEnabled\" | \"timelineEsqlTabDisabled\" | \"unifiedComponentsInTimelineEnabled\" | \"analyzerDatePickersAndSourcererDisabled\" | \"perFieldPrebuiltRulesDiffingEnabled\" | \"malwareOnWriteScanOptionAvailable\" | \"unifiedManifestEnabled\" | \"aiAssistantFlyoutMode\" | \"valueListItemsModalEnabled\" | \"bulkCustomHighlightedFieldsEnabled\" | \"manualRuleRunEnabled\" | undefined" + "\"assistantKnowledgeBaseByDefault\" | \"assistantModelEvaluation\" | \"excludePoliciesInFilterEnabled\" | \"kubernetesEnabled\" | \"donutChartEmbeddablesEnabled\" | \"previewTelemetryUrlEnabled\" | \"extendedRuleExecutionLoggingEnabled\" | \"socTrendsEnabled\" | \"responseActionsEnabled\" | \"endpointResponseActionsEnabled\" | \"responseActionUploadEnabled\" | \"automatedProcessActionsEnabled\" | \"responseActionsSentinelOneV1Enabled\" | \"responseActionsSentinelOneV2Enabled\" | \"responseActionsSentinelOneGetFileEnabled\" | \"agentStatusClientEnabled\" | \"responseActionsCrowdstrikeManualHostIsolationEnabled\" | \"responseActionScanEnabled\" | \"alertsPageChartsEnabled\" | \"alertTypeEnabled\" | \"expandableFlyoutDisabled\" | \"notesEnabled\" | \"newUserDetailsFlyoutManagedUser\" | \"riskScoringPersistence\" | \"riskScoringRoutesEnabled\" | \"esqlRulesDisabled\" | \"protectionUpdatesEnabled\" | \"disableTimelineSaveTour\" | \"alertSuppressionForEsqlRuleEnabled\" | \"riskEnginePrivilegesRouteEnabled\" | \"sentinelOneDataInAnalyzerEnabled\" | \"sentinelOneManualHostActionsEnabled\" | \"crowdstrikeDataInAnalyzerEnabled\" | \"jamfDataInAnalyzerEnabled\" | \"jsonPrebuiltRulesDiffingEnabled\" | \"timelineEsqlTabDisabled\" | \"unifiedComponentsInTimelineEnabled\" | \"analyzerDatePickersAndSourcererDisabled\" | \"perFieldPrebuiltRulesDiffingEnabled\" | \"malwareOnWriteScanOptionAvailable\" | \"unifiedManifestEnabled\" | \"aiAssistantFlyoutMode\" | \"valueListItemsModalEnabled\" | \"bulkCustomHighlightedFieldsEnabled\" | \"manualRuleRunEnabled\" | \"filterProcessDescendantsForEventFiltersEnabled\" | undefined" ], "path": "x-pack/plugins/security_solution/public/common/links/types.ts", "deprecated": false, @@ -1964,7 +1964,7 @@ "label": "experimentalFeatures", "description": [], "signature": [ - "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly agentStatusClientEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutDisabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly alertSuppressionForEsqlRuleEnabled: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineEnabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly perFieldPrebuiltRulesDiffingEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly aiAssistantFlyoutMode: boolean; readonly valueListItemsModalEnabled: boolean; readonly bulkCustomHighlightedFieldsEnabled: boolean; readonly manualRuleRunEnabled: boolean; }" + "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly agentStatusClientEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutDisabled: boolean; readonly notesEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly alertSuppressionForEsqlRuleEnabled: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineEnabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly perFieldPrebuiltRulesDiffingEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly aiAssistantFlyoutMode: boolean; readonly valueListItemsModalEnabled: boolean; readonly bulkCustomHighlightedFieldsEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/public/types.ts", "deprecated": false, @@ -3071,7 +3071,7 @@ "\nThe security solution generic experimental features" ], "signature": [ - "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly agentStatusClientEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutDisabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly alertSuppressionForEsqlRuleEnabled: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineEnabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly perFieldPrebuiltRulesDiffingEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly aiAssistantFlyoutMode: boolean; readonly valueListItemsModalEnabled: boolean; readonly bulkCustomHighlightedFieldsEnabled: boolean; readonly manualRuleRunEnabled: boolean; }" + "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly agentStatusClientEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutDisabled: boolean; readonly notesEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly alertSuppressionForEsqlRuleEnabled: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineEnabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly perFieldPrebuiltRulesDiffingEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly aiAssistantFlyoutMode: boolean; readonly valueListItemsModalEnabled: boolean; readonly bulkCustomHighlightedFieldsEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/server/plugin_contract.ts", "deprecated": false, @@ -3247,7 +3247,7 @@ "label": "ExperimentalFeatures", "description": [], "signature": [ - "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly agentStatusClientEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutDisabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly alertSuppressionForEsqlRuleEnabled: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineEnabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly perFieldPrebuiltRulesDiffingEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly aiAssistantFlyoutMode: boolean; readonly valueListItemsModalEnabled: boolean; readonly bulkCustomHighlightedFieldsEnabled: boolean; readonly manualRuleRunEnabled: boolean; }" + "{ readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly donutChartEmbeddablesEnabled: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly extendedRuleExecutionLoggingEnabled: boolean; readonly socTrendsEnabled: boolean; readonly responseActionsEnabled: boolean; readonly endpointResponseActionsEnabled: boolean; readonly responseActionUploadEnabled: boolean; readonly automatedProcessActionsEnabled: boolean; readonly responseActionsSentinelOneV1Enabled: boolean; readonly responseActionsSentinelOneV2Enabled: boolean; readonly responseActionsSentinelOneGetFileEnabled: boolean; readonly agentStatusClientEnabled: boolean; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: boolean; readonly responseActionScanEnabled: boolean; readonly alertsPageChartsEnabled: boolean; readonly alertTypeEnabled: boolean; readonly expandableFlyoutDisabled: boolean; readonly notesEnabled: boolean; readonly assistantModelEvaluation: boolean; readonly assistantKnowledgeBaseByDefault: boolean; readonly newUserDetailsFlyoutManagedUser: boolean; readonly riskScoringPersistence: boolean; readonly riskScoringRoutesEnabled: boolean; readonly esqlRulesDisabled: boolean; readonly protectionUpdatesEnabled: boolean; readonly disableTimelineSaveTour: boolean; readonly alertSuppressionForEsqlRuleEnabled: boolean; readonly riskEnginePrivilegesRouteEnabled: boolean; readonly sentinelOneDataInAnalyzerEnabled: boolean; readonly sentinelOneManualHostActionsEnabled: boolean; readonly crowdstrikeDataInAnalyzerEnabled: boolean; readonly jamfDataInAnalyzerEnabled: boolean; readonly jsonPrebuiltRulesDiffingEnabled: boolean; readonly timelineEsqlTabDisabled: boolean; readonly unifiedComponentsInTimelineEnabled: boolean; readonly analyzerDatePickersAndSourcererDisabled: boolean; readonly perFieldPrebuiltRulesDiffingEnabled: boolean; readonly malwareOnWriteScanOptionAvailable: boolean; readonly unifiedManifestEnabled: boolean; readonly aiAssistantFlyoutMode: boolean; readonly valueListItemsModalEnabled: boolean; readonly bulkCustomHighlightedFieldsEnabled: boolean; readonly manualRuleRunEnabled: boolean; readonly filterProcessDescendantsForEventFiltersEnabled: boolean; }" ], "path": "x-pack/plugins/security_solution/common/experimental_features.ts", "deprecated": false, @@ -3313,7 +3313,7 @@ "\nA list of allowed values that can be used in `xpack.securitySolution.enableExperimental`.\nThis object is then used to validate and parse the value entered." ], "signature": [ - "{ readonly excludePoliciesInFilterEnabled: false; readonly kubernetesEnabled: true; readonly donutChartEmbeddablesEnabled: false; readonly previewTelemetryUrlEnabled: false; readonly extendedRuleExecutionLoggingEnabled: false; readonly socTrendsEnabled: false; readonly responseActionsEnabled: true; readonly endpointResponseActionsEnabled: true; readonly responseActionUploadEnabled: true; readonly automatedProcessActionsEnabled: true; readonly responseActionsSentinelOneV1Enabled: true; readonly responseActionsSentinelOneV2Enabled: true; readonly responseActionsSentinelOneGetFileEnabled: false; readonly agentStatusClientEnabled: false; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: false; readonly responseActionScanEnabled: false; readonly alertsPageChartsEnabled: true; readonly alertTypeEnabled: false; readonly expandableFlyoutDisabled: false; readonly assistantModelEvaluation: false; readonly assistantKnowledgeBaseByDefault: false; readonly newUserDetailsFlyoutManagedUser: false; readonly riskScoringPersistence: true; readonly riskScoringRoutesEnabled: true; readonly esqlRulesDisabled: false; readonly protectionUpdatesEnabled: true; readonly disableTimelineSaveTour: false; readonly alertSuppressionForEsqlRuleEnabled: false; readonly riskEnginePrivilegesRouteEnabled: true; readonly sentinelOneDataInAnalyzerEnabled: true; readonly sentinelOneManualHostActionsEnabled: true; readonly crowdstrikeDataInAnalyzerEnabled: false; readonly jamfDataInAnalyzerEnabled: false; readonly jsonPrebuiltRulesDiffingEnabled: true; readonly timelineEsqlTabDisabled: false; readonly unifiedComponentsInTimelineEnabled: false; readonly analyzerDatePickersAndSourcererDisabled: false; readonly perFieldPrebuiltRulesDiffingEnabled: true; readonly malwareOnWriteScanOptionAvailable: true; readonly unifiedManifestEnabled: false; readonly aiAssistantFlyoutMode: true; readonly valueListItemsModalEnabled: true; readonly bulkCustomHighlightedFieldsEnabled: false; readonly manualRuleRunEnabled: false; }" + "{ readonly excludePoliciesInFilterEnabled: false; readonly kubernetesEnabled: true; readonly donutChartEmbeddablesEnabled: false; readonly previewTelemetryUrlEnabled: false; readonly extendedRuleExecutionLoggingEnabled: false; readonly socTrendsEnabled: false; readonly responseActionsEnabled: true; readonly endpointResponseActionsEnabled: true; readonly responseActionUploadEnabled: true; readonly automatedProcessActionsEnabled: true; readonly responseActionsSentinelOneV1Enabled: true; readonly responseActionsSentinelOneV2Enabled: true; readonly responseActionsSentinelOneGetFileEnabled: false; readonly agentStatusClientEnabled: false; readonly responseActionsCrowdstrikeManualHostIsolationEnabled: false; readonly responseActionScanEnabled: false; readonly alertsPageChartsEnabled: true; readonly alertTypeEnabled: false; readonly expandableFlyoutDisabled: false; readonly notesEnabled: false; readonly assistantModelEvaluation: false; readonly assistantKnowledgeBaseByDefault: false; readonly newUserDetailsFlyoutManagedUser: false; readonly riskScoringPersistence: true; readonly riskScoringRoutesEnabled: true; readonly esqlRulesDisabled: false; readonly protectionUpdatesEnabled: true; readonly disableTimelineSaveTour: false; readonly alertSuppressionForEsqlRuleEnabled: false; readonly riskEnginePrivilegesRouteEnabled: true; readonly sentinelOneDataInAnalyzerEnabled: true; readonly sentinelOneManualHostActionsEnabled: true; readonly crowdstrikeDataInAnalyzerEnabled: false; readonly jamfDataInAnalyzerEnabled: false; readonly jsonPrebuiltRulesDiffingEnabled: true; readonly timelineEsqlTabDisabled: false; readonly unifiedComponentsInTimelineEnabled: false; readonly analyzerDatePickersAndSourcererDisabled: false; readonly perFieldPrebuiltRulesDiffingEnabled: true; readonly malwareOnWriteScanOptionAvailable: true; readonly unifiedManifestEnabled: false; readonly aiAssistantFlyoutMode: true; readonly valueListItemsModalEnabled: true; readonly bulkCustomHighlightedFieldsEnabled: false; readonly manualRuleRunEnabled: false; readonly filterProcessDescendantsForEventFiltersEnabled: false; }" ], "path": "x-pack/plugins/security_solution/common/experimental_features.ts", "deprecated": false, diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 03ea3b2480c4c..10fb34dfcd75a 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/security_solution_ess.mdx b/api_docs/security_solution_ess.mdx index 9c8a64954240b..c96d7a20b5e9f 100644 --- a/api_docs/security_solution_ess.mdx +++ b/api_docs/security_solution_ess.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionEss title: "securitySolutionEss" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionEss plugin -date: 2024-06-11 +date: 2024-06-19 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 59f6a019ad5cd..7acea530f5fc5 100644 --- a/api_docs/security_solution_serverless.mdx +++ b/api_docs/security_solution_serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolutionServerless title: "securitySolutionServerless" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolutionServerless plugin -date: 2024-06-11 +date: 2024-06-19 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 1b94c7c90a500..1eb61bcf6de72 100644 --- a/api_docs/serverless.mdx +++ b/api_docs/serverless.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverless title: "serverless" image: https://source.unsplash.com/400x175/?github description: API docs for the serverless plugin -date: 2024-06-11 +date: 2024-06-19 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 b42efb91ca4d9..c403796a2f280 100644 --- a/api_docs/serverless_observability.mdx +++ b/api_docs/serverless_observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessObservability title: "serverlessObservability" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessObservability plugin -date: 2024-06-11 +date: 2024-06-19 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 2115b3489f857..c882d39ee8c64 100644 --- a/api_docs/serverless_search.mdx +++ b/api_docs/serverless_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/serverlessSearch title: "serverlessSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the serverlessSearch plugin -date: 2024-06-11 +date: 2024-06-19 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 0603147e72fc2..39e2308ac3b35 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2024-06-11 +date: 2024-06-19 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 e68ad14edf50a..0955b3e6997b2 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/slo.devdocs.json b/api_docs/slo.devdocs.json index af855a896ee0d..033f65c0cbd60 100644 --- a/api_docs/slo.devdocs.json +++ b/api_docs/slo.devdocs.json @@ -133,7 +133,7 @@ "label": "observabilityShared", "description": [], "signature": [ - "{ locators: ObservabilitySharedLocators; navigation: { registerSections: (sections$: ", + "{ registerProfilingComponent: (key: string, component: React.FC) => void; locators: ObservabilitySharedLocators; navigation: { registerSections: (sections$: ", "Observable", "<", { @@ -410,26 +410,26 @@ "signature": [ "{ get: (id: string) => ", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionTypeModel", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", "text": "ActionTypeModel" }, "; list: () => ", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionTypeModel", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", "text": "ActionTypeModel" }, "[]; register: (objectType: ", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionTypeModel", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", "text": "ActionTypeModel" }, ") => void; has: (id: string) => boolean; }" diff --git a/api_docs/slo.mdx b/api_docs/slo.mdx index 8fdb4eaa04148..8bb7ffb38c2ea 100644 --- a/api_docs/slo.mdx +++ b/api_docs/slo.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/slo title: "slo" image: https://source.unsplash.com/400x175/?github description: API docs for the slo plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'slo'] --- import sloObj from './slo.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index 2ca322654e94b..359079def7dbc 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.devdocs.json b/api_docs/spaces.devdocs.json index 0d6cb86b49fa4..b19ac58371016 100644 --- a/api_docs/spaces.devdocs.json +++ b/api_docs/spaces.devdocs.json @@ -1793,7 +1793,14 @@ "\nSolution selected for this space." ], "signature": [ - "\"search\" | \"security\" | \"observability\" | \"classic\" | undefined" + { + "pluginId": "cloud", + "scope": "common", + "docId": "kibCloudPluginApi", + "section": "def-common.OnBoardingDefaultSolution", + "text": "OnBoardingDefaultSolution" + }, + " | \"classic\" | undefined" ], "path": "x-pack/plugins/spaces/common/types/space/v1.ts", "deprecated": false, @@ -1825,7 +1832,15 @@ "The space to represent with an avatar." ], "signature": [ - "{ id?: string | undefined; name?: string | undefined; description?: string | undefined; color?: string | undefined; initials?: string | undefined; imageUrl?: string | undefined; disabledFeatures?: string[] | undefined; _reserved?: boolean | undefined; solution?: \"search\" | \"security\" | \"observability\" | \"classic\" | undefined; }" + "{ id?: string | undefined; name?: string | undefined; description?: string | undefined; color?: string | undefined; initials?: string | undefined; imageUrl?: string | undefined; disabledFeatures?: string[] | undefined; _reserved?: boolean | undefined; solution?: ", + { + "pluginId": "cloud", + "scope": "common", + "docId": "kibCloudPluginApi", + "section": "def-common.OnBoardingDefaultSolution", + "text": "OnBoardingDefaultSolution" + }, + " | \"classic\" | undefined; }" ], "path": "x-pack/plugins/spaces/public/space_avatar/types.ts", "deprecated": false, @@ -3701,7 +3716,14 @@ "\nSolution selected for this space." ], "signature": [ - "\"search\" | \"security\" | \"observability\" | \"classic\" | undefined" + { + "pluginId": "cloud", + "scope": "common", + "docId": "kibCloudPluginApi", + "section": "def-common.OnBoardingDefaultSolution", + "text": "OnBoardingDefaultSolution" + }, + " | \"classic\" | undefined" ], "path": "x-pack/plugins/spaces/common/types/space/v1.ts", "deprecated": false, @@ -4951,7 +4973,14 @@ "\nSolution selected for this space." ], "signature": [ - "\"search\" | \"security\" | \"observability\" | \"classic\" | undefined" + { + "pluginId": "cloud", + "scope": "common", + "docId": "kibCloudPluginApi", + "section": "def-common.OnBoardingDefaultSolution", + "text": "OnBoardingDefaultSolution" + }, + " | \"classic\" | undefined" ], "path": "x-pack/plugins/spaces/common/types/space/v1.ts", "deprecated": false, diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index 5193a15fcbc01..385368b87f654 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2024-06-11 +date: 2024-06-19 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 d6a8b16ae38b1..3314b5bb8aa63 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.devdocs.json b/api_docs/stack_connectors.devdocs.json index 435af06ccdd6b..447872c4249ed 100644 --- a/api_docs/stack_connectors.devdocs.json +++ b/api_docs/stack_connectors.devdocs.json @@ -186,7 +186,7 @@ }, "; }>" ], - "path": "x-pack/plugins/stack_connectors/server/connector_types/webhook/index.ts", + "path": "x-pack/plugins/stack_connectors/server/connector_types/webhook/schema.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index 347a77004506a..086640353d71c 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2024-06-11 +date: 2024-06-19 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 ba906ca62bce6..03645e34e324d 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2024-06-11 +date: 2024-06-19 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 eed6fe65f0569..2c751b121ed49 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2024-06-11 +date: 2024-06-19 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 2582ea69c97d5..51d70a87ff4b4 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2024-06-11 +date: 2024-06-19 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 7d18f8c737b58..176820c472d6f 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2024-06-11 +date: 2024-06-19 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 2440b39faa33c..ce2e6eb5c50d8 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2024-06-11 +date: 2024-06-19 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 ed6b2e11b79cb..7426e2bcc3ed3 100644 --- a/api_docs/text_based_languages.mdx +++ b/api_docs/text_based_languages.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/textBasedLanguages title: "textBasedLanguages" image: https://source.unsplash.com/400x175/?github description: API docs for the textBasedLanguages plugin -date: 2024-06-11 +date: 2024-06-19 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 ca40b31c5cbc4..5e6c067200b75 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; diff --git a/api_docs/timelines.devdocs.json b/api_docs/timelines.devdocs.json index 13cc1e46270eb..781b3110679a8 100644 --- a/api_docs/timelines.devdocs.json +++ b/api_docs/timelines.devdocs.json @@ -4200,11 +4200,11 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx" + "path": "x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx" + "path": "x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx" }, { "plugin": "securitySolution", @@ -4224,11 +4224,11 @@ }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx" + "path": "x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx" }, { "plugin": "securitySolution", - "path": "x-pack/plugins/security_solution/public/timelines/components/timeline/helpers.tsx" + "path": "x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx" }, { "plugin": "securitySolution", diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 5a5eaeef52fba..0fbca32935a9a 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2024-06-11 +date: 2024-06-19 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 5aae87cf75110..c283e9cbb1940 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.devdocs.json b/api_docs/triggers_actions_ui.devdocs.json index d546fabae08c8..f2d0b86703d56 100644 --- a/api_docs/triggers_actions_ui.devdocs.json +++ b/api_docs/triggers_actions_ui.devdocs.json @@ -1841,10 +1841,10 @@ "ActionConnectorWithoutId", ", Record>, \"config\" | \"secrets\" | \"name\">; id: string; }) => Promise<", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionConnector", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionConnector", "text": "ActionConnector" }, ", Record>>" @@ -2190,7 +2190,7 @@ "tags": [], "label": "ActionConnectorFieldsProps", "description": [], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2201,7 +2201,7 @@ "tags": [], "label": "readOnly", "description": [], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2212,7 +2212,7 @@ "tags": [], "label": "isEdit", "description": [], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2225,10 +2225,16 @@ "description": [], "signature": [ "(validator: ", - "ConnectorValidationFunc", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ConnectorValidationFunc", + "text": "ConnectorValidationFunc" + }, ") => void" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2240,9 +2246,15 @@ "label": "validator", "description": [], "signature": [ - "ConnectorValidationFunc" + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ConnectorValidationFunc", + "text": "ConnectorValidationFunc" + } ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2262,15 +2274,15 @@ "description": [], "signature": [ { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionParamsProps", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionParamsProps", "text": "ActionParamsProps" }, "" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2284,7 +2296,7 @@ "signature": [ "{ [P in keyof TParams]?: TParams[P] | undefined; }" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2295,7 +2307,7 @@ "tags": [], "label": "index", "description": [], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2317,7 +2329,7 @@ }, ", index: number) => void" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2331,7 +2343,7 @@ "signature": [ "string" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2352,7 +2364,7 @@ "text": "SavedObjectAttribute" } ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "isRequired": false @@ -2367,7 +2379,7 @@ "signature": [ "number" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2384,14 +2396,14 @@ "description": [], "signature": [ { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.IErrorObject", - "text": "IErrorObject" + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.RuleFormErrors", + "text": "RuleFormErrors" } ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2405,7 +2417,7 @@ "signature": [ "string | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2426,7 +2438,7 @@ }, "[] | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2440,7 +2452,7 @@ "signature": [ "string | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2454,7 +2466,7 @@ "signature": [ "boolean | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2467,15 +2479,15 @@ "description": [], "signature": [ { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionConnector", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionConnector", "text": "ActionConnector" }, ", Record> | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2489,7 +2501,7 @@ "signature": [ "boolean | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2503,7 +2515,7 @@ "signature": [ "boolean | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2517,7 +2529,7 @@ "signature": [ "string | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2531,7 +2543,7 @@ "signature": [ "boolean | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2544,15 +2556,15 @@ "description": [], "signature": [ { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionConnectorMode", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionConnectorMode", "text": "ActionConnectorMode" }, " | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2566,7 +2578,7 @@ "signature": [ "((field?: string | undefined) => void) | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2580,7 +2592,7 @@ "signature": [ "string | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "isRequired": false @@ -2598,7 +2610,7 @@ "signature": [ "string | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false } @@ -2722,15 +2734,15 @@ "description": [], "signature": [ { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionTypeModel", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", "text": "ActionTypeModel" }, "" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2741,7 +2753,7 @@ "tags": [], "label": "id", "description": [], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2755,7 +2767,7 @@ "signature": [ "string | React.ComponentType<{}>" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2766,7 +2778,7 @@ "tags": [], "label": "selectMessage", "description": [], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2780,7 +2792,7 @@ "signature": [ "string | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2794,15 +2806,15 @@ "signature": [ "(actionParams: ActionParams) => Promise<", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.GenericValidationResult", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.GenericValidationResult", "text": "GenericValidationResult" }, ">" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -2816,7 +2828,7 @@ "signature": [ "ActionParams" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -2834,15 +2846,15 @@ "signature": [ "React.LazyExoticComponent> | null" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2856,39 +2868,39 @@ "signature": [ "React.ExoticComponent<(", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionParamsProps", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionParamsProps", "text": "ActionParamsProps" }, " & React.RefAttributes, any, any>>) | (", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionParamsProps", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionParamsProps", "text": "ActionParamsProps" }, " & { children?: React.ReactNode; })> & { readonly _result: React.ComponentType<", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionParamsProps", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionParamsProps", "text": "ActionParamsProps" }, ">; }" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "returnComment": [], @@ -2918,10 +2930,16 @@ "description": [], "signature": [ "React.LazyExoticComponent> | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2936,7 +2954,7 @@ "RecursivePartial", " | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2951,7 +2969,7 @@ "RecursivePartial", " | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2963,9 +2981,16 @@ "label": "customConnectorSelectItem", "description": [], "signature": [ - "CustomConnectorSelectionItem | undefined" + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.CustomConnectorSelectionItem", + "text": "CustomConnectorSelectionItem" + }, + " | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2979,7 +3004,7 @@ "signature": [ "boolean | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -2993,7 +3018,7 @@ "signature": [ "{ id: string; name: string; }[] | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -3007,7 +3032,7 @@ "signature": [ "((params: ActionParams) => {} | ActionParams) | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -3021,7 +3046,7 @@ "signature": [ "ActionParams" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -3039,7 +3064,7 @@ "signature": [ "boolean | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -3053,7 +3078,7 @@ "signature": [ "number | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false }, @@ -3067,7 +3092,7 @@ "signature": [ "boolean | undefined" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false } @@ -3964,15 +3989,15 @@ "description": [], "signature": [ { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.GenericValidationResult", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.GenericValidationResult", "text": "GenericValidationResult" }, "" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -3986,7 +4011,7 @@ "signature": [ "{ [P in Extract]: unknown; }" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false } @@ -4202,26 +4227,26 @@ "signature": [ "{ get: (id: string) => ", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionTypeModel", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", "text": "ActionTypeModel" }, "; list: () => ", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionTypeModel", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", "text": "ActionTypeModel" }, "[]; register: (objectType: ", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionTypeModel", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", "text": "ActionTypeModel" }, ") => void; has: (id: string) => boolean; }" @@ -4741,10 +4766,10 @@ "signature": [ "(ruleParams: Params, isServerless?: boolean | undefined) => ", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ValidationResult", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ValidationResult", "text": "ValidationResult" } ], @@ -5578,26 +5603,26 @@ "signature": [ "{ get: (id: string) => ", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionTypeModel", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", "text": "ActionTypeModel" }, "; list: () => ", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionTypeModel", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", "text": "ActionTypeModel" }, "[]; register: (objectType: ", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionTypeModel", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", "text": "ActionTypeModel" }, ") => void; has: (id: string) => boolean; }" @@ -5919,7 +5944,7 @@ "tags": [], "label": "ValidationResult", "description": [], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false, "children": [ @@ -5933,7 +5958,7 @@ "signature": [ "{ [x: string]: any; }" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts", "deprecated": false, "trackAdoption": false } @@ -5949,7 +5974,7 @@ "tags": [], "label": "ActionConnectorMode", "description": [], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -6000,14 +6025,32 @@ "label": "ActionConnector", "description": [], "signature": [ - "PreConfiguredActionConnector", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.PreConfiguredActionConnector", + "text": "PreConfiguredActionConnector" + }, " | ", - "SystemAction", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.SystemAction", + "text": "SystemAction" + }, " | ", - "UserConfiguredActionConnector", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.UserConfiguredActionConnector", + "text": "UserConfiguredActionConnector" + }, "" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -6044,31 +6087,31 @@ "signature": [ "{ get: (id: string) => ", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionTypeModel", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", "text": "ActionTypeModel" }, - "; list: () => ", + "; list: () => ", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionTypeModel", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", "text": "ActionTypeModel" }, - "[]; register: (objectType: ", + "[]; register: (objectType: ", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionTypeModel", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", "text": "ActionTypeModel" }, - ") => void; has: (id: string) => boolean; }" + ") => void; has: (id: string) => boolean; }" ], - "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "path": "packages/kbn-alerts-ui-shared/src/common/types/action_types.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false @@ -6360,9 +6403,21 @@ ], "signature": [ "Pick<", - "UserConfiguredActionConnector", + { + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.UserConfiguredActionConnector", + "text": "UserConfiguredActionConnector" + }, ", \"config\" | \"secrets\" | \"actionTypeId\" | \"isDeprecated\"> & Partial, \"id\" | \"name\">>" ], "path": "x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/types.ts", @@ -7526,10 +7581,10 @@ "TypeRegistry", "<", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionTypeModel", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", "text": "ActionTypeModel" }, ">" @@ -7601,10 +7656,10 @@ "TypeRegistry", "<", { - "pluginId": "triggersActionsUi", - "scope": "public", - "docId": "kibTriggersActionsUiPluginApi", - "section": "def-public.ActionTypeModel", + "pluginId": "@kbn/alerts-ui-shared", + "scope": "common", + "docId": "kibKbnAlertsUiSharedPluginApi", + "section": "def-common.ActionTypeModel", "text": "ActionTypeModel" }, ">" diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 7a9bf080cebc6..947d503130b72 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; @@ -21,7 +21,7 @@ Contact [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-o | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 588 | 1 | 562 | 58 | +| 588 | 1 | 562 | 52 | ## Client diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 8aff6afe8096f..9927f905c3232 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2024-06-11 +date: 2024-06-19 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 ce8a93db008b2..f0c965a6d86fd 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2024-06-11 +date: 2024-06-19 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 1c0c6900cf790..797100653e885 100644 --- a/api_docs/unified_doc_viewer.mdx +++ b/api_docs/unified_doc_viewer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedDocViewer title: "unifiedDocViewer" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedDocViewer plugin -date: 2024-06-11 +date: 2024-06-19 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 6745db22ace34..673a04e07caa2 100644 --- a/api_docs/unified_histogram.mdx +++ b/api_docs/unified_histogram.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedHistogram title: "unifiedHistogram" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedHistogram plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedHistogram'] --- import unifiedHistogramObj from './unified_histogram.devdocs.json'; diff --git a/api_docs/unified_search.devdocs.json b/api_docs/unified_search.devdocs.json index 3a6a417a835bd..9a084801174c4 100644 --- a/api_docs/unified_search.devdocs.json +++ b/api_docs/unified_search.devdocs.json @@ -1593,9 +1593,9 @@ "signature": [ "{ optIn: (optInConfig: ", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.OptInConfig", "text": "OptInConfig" }, @@ -1603,9 +1603,9 @@ "Observable", "<", { - "pluginId": "@kbn/analytics-client", + "pluginId": "@kbn/ebt", "scope": "common", - "docId": "kibKbnAnalyticsClientPluginApi", + "docId": "kibKbnEbtPluginApi", "section": "def-common.TelemetryCounter", "text": "TelemetryCounter" }, diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index bc9e1b5cceb02..da150ddd187d5 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2024-06-11 +date: 2024-06-19 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 1fa75e9d91114..3828629b7de40 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2024-06-11 +date: 2024-06-19 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 7427ed014ab4e..8b9feb48b2554 100644 --- a/api_docs/uptime.mdx +++ b/api_docs/uptime.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uptime title: "uptime" image: https://source.unsplash.com/400x175/?github description: API docs for the uptime plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uptime'] --- import uptimeObj from './uptime.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index 786ffccb5fc8b..64a832b6cff6c 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.devdocs.json b/api_docs/usage_collection.devdocs.json index b573f8c8d3d7e..609cbc0a5a633 100644 --- a/api_docs/usage_collection.devdocs.json +++ b/api_docs/usage_collection.devdocs.json @@ -1869,7 +1869,7 @@ "signature": [ "\"boolean\" | \"keyword\" | \"text\" | \"date\" | \"integer\" | \"long\" | \"short\" | \"byte\" | \"float\" | \"double\"" ], - "path": "packages/analytics/client/src/schema/types.ts", + "path": "packages/analytics/ebt/client/src/schema/types.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index a3632a0e1399b..436db094913ba 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2024-06-11 +date: 2024-06-19 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 246f215e4a667..6101ac7d027d2 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index 8aa955e90525e..890eb0c990a2e 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2024-06-11 +date: 2024-06-19 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 6d6a8b98184c6..6aff52a01cd88 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2024-06-11 +date: 2024-06-19 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 29762006e05dd..dca9fa99c07b0 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2024-06-11 +date: 2024-06-19 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 64a1e021592e8..6f52c4d6726a1 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2024-06-11 +date: 2024-06-19 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 b27ec510444ac..c984f1e20a831 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2024-06-11 +date: 2024-06-19 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 89a8d5022048e..3dce4637737ae 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2024-06-11 +date: 2024-06-19 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 a97c930b2d592..1106d22596c13 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2024-06-11 +date: 2024-06-19 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 05cb3ca4c0339..d899070ea869f 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2024-06-11 +date: 2024-06-19 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 c116169b0d83e..89682e5f50528 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2024-06-11 +date: 2024-06-19 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 849e56df99bc5..4ae390b8e9a7c 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2024-06-11 +date: 2024-06-19 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 fa8ac8adbac8a..862c90efea645 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2024-06-11 +date: 2024-06-19 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; diff --git a/config/serverless.es.yml b/config/serverless.es.yml index 0a609b0d40b2c..a170cf569b54d 100644 --- a/config/serverless.es.yml +++ b/config/serverless.es.yml @@ -64,5 +64,11 @@ data_visualizer.resultLinks.fileBeat.enabled: false # Search Playground xpack.searchPlayground.ui.enabled: true +# Search InferenceEndpoints +xpack.searchInferenceEndpoints.ui.enabled: false + # Search Notebooks xpack.search.notebooks.catalog.url: https://elastic-enterprise-search.s3.us-east-2.amazonaws.com/serverless/catalog.json + +# Search Homepage +xpack.search.homepage.ui.enabled: true diff --git a/config/serverless.yml b/config/serverless.yml index cb4bbac7c92fa..bea8e422e2925 100644 --- a/config/serverless.yml +++ b/config/serverless.yml @@ -70,6 +70,9 @@ core.lifecycle.disablePreboot: true # Enable ZDT migration algorithm migrations.algorithm: zdt +# Enable elasticsearch response size circuit breaker +elasticsearch.maxResponseSize: "100mb" + # Limit batch size to reduce possibility of failures. # A longer migration time is acceptable due to the ZDT algorithm. migrations.batchSize: 250 @@ -105,6 +108,8 @@ xpack.index_management.enableDataStreamsStorageColumn: false xpack.index_management.enableMappingsSourceFieldSection: false # Disable toggle for enabling data retention in DSL form from Index Management UI xpack.index_management.enableTogglingDataRetention: false +# Disable the Monaco migration in Console +console.dev.enableMonaco: false # Keep deeplinks visible so that they are shown in the sidenav dev_tools.deeplinks.navLinkStatus: visible diff --git a/dev_docs/tutorials/performance/adding_custom_performance_metrics.mdx b/dev_docs/tutorials/performance/adding_custom_performance_metrics.mdx index 4db207ad4aecb..1c1224c1c858a 100644 --- a/dev_docs/tutorials/performance/adding_custom_performance_metrics.mdx +++ b/dev_docs/tutorials/performance/adding_custom_performance_metrics.mdx @@ -3,7 +3,6 @@ id: kibDevTutorialAddingCustomPerformanceMetrics slug: /kibana-dev-docs/tutorial/performance/adding_custom_performance_metrics title: Adding Performance Metrics summary: Learn how to instrument your code and analyze performance -date: 2023-01-13 tags: ['kibana', 'onboarding', 'setup', 'performance', 'development', 'telemetry'] --- @@ -241,7 +240,7 @@ const MyApp = () => { if (fetchedData.status === 'success') { setData(fetchedData); - // Call onPageReady once the meaningful data has rendered and visible to the user + // Call onPageReady once the meaningful data has rendered and visible to the user onPageReady(); } diff --git a/docs/api/spaces-management/get.asciidoc b/docs/api/spaces-management/get.asciidoc index d2dbeb0a3f4b4..f3e3462b2e0da 100644 --- a/docs/api/spaces-management/get.asciidoc +++ b/docs/api/spaces-management/get.asciidoc @@ -32,6 +32,6 @@ The API returns the following: "initials": "MK", "disabledFeatures": [], "imageUrl": "", - "solution": "search" + "solution": "es" } -------------------------------------------------- diff --git a/docs/api/spaces-management/get_all.asciidoc b/docs/api/spaces-management/get_all.asciidoc index 0fd332c12b739..92a65d670ad87 100644 --- a/docs/api/spaces-management/get_all.asciidoc +++ b/docs/api/spaces-management/get_all.asciidoc @@ -72,7 +72,7 @@ The API returns the following: "initials": "MK", "disabledFeatures": ["discover"], "imageUrl": "", - "solution": "observability" + "solution": "oblt" } ] -------------------------------------------------- diff --git a/docs/api/spaces-management/post.asciidoc b/docs/api/spaces-management/post.asciidoc index b72d4df79c3d6..4c5976249f80e 100644 --- a/docs/api/spaces-management/post.asciidoc +++ b/docs/api/spaces-management/post.asciidoc @@ -37,7 +37,7 @@ experimental[] Create a {kib} space. For best results, your image should be 64x64. Images will not be optimized by this API call, so care should be taken when using custom images. `solution`:: - (Optional, string) The solution defined for the space. Can be one of `security`, `observability`, `search`, `classic` + (Optional, string) The solution defined for the space. Can be one of `security`, `oblt`, `es`, `classic` [[spaces-api-post-response-codes]] ==== Response codes diff --git a/docs/api/spaces-management/put.asciidoc b/docs/api/spaces-management/put.asciidoc index 0d1c8d5f2e779..88d0d41114eb5 100644 --- a/docs/api/spaces-management/put.asciidoc +++ b/docs/api/spaces-management/put.asciidoc @@ -37,7 +37,7 @@ experimental[] Update an existing {kib} space. For best results, your image should be 64x64. Images will not be optimized by this API call, so care should be taken when using custom images. `solution`:: - (Optional, string) The solution defined for the space. Can be one of `security`, `observability`, `search`, `classic`. + (Optional, string) The solution defined for the space. Can be one of `security`, `oblt`, `es`, `classic`. [[spaces-api-put-response-codes]] ==== Response codes diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 8bd8e4b00040e..fc15206bd284d 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -638,6 +638,10 @@ the infrastructure monitoring use-case within Kibana. |The ingest_pipelines plugin provides Kibana support for Elasticsearch's ingest pipelines. +|{kib-repo}blob/{branch}/x-pack/plugins/integration_assistant/README.md[integrationAssistant] +|Team owner: Security Integrations Scalability + + |{kib-repo}blob/{branch}/x-pack/plugins/observability_solution/investigate/README.md[investigate] |undefined @@ -785,6 +789,14 @@ It uses Chromium and Puppeteer underneath to run the browser in headless mode. |This plugin contains common assets and endpoints for the use of connectors in Kibana. Primarily used by the enterprise_search and serverless_search plugins. +|{kib-repo}blob/{branch}/x-pack/plugins/search_homepage/README.mdx[searchHomepage] +|The Search Homepage is a shared homepage for elasticsearch users. + + +|{kib-repo}blob/{branch}/x-pack/plugins/search_inference_endpoints/README.md[searchInferenceEndpoints] +|The Inference Endpoints is a tool used to manage inference endpoints + + |{kib-repo}blob/{branch}/x-pack/plugins/search_notebooks/README.mdx[searchNotebooks] |This plugin contains endpoints and components for rendering search python notebooks in the persistent dev console. diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 5de4ea45c3204..98886aedd5535 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -268,7 +268,8 @@ the `xpack.banners.backgroundColor` configuration property. [horizontal] [[xpackdashboardmode-roles]]`xpackDashboardMode:roles`:: -**Deprecated. Use <> instead.** +deprecated:[7.7.0] +Deprecated; use <> instead. The roles that belong to <>. [float] @@ -309,6 +310,7 @@ Limits the number of rows per page in the document table. Sets the maximum number of rows for the entire document table. This is the maximum number of documents fetched from {es}. [[discover-searchFieldsFromSource]]`discover:searchFieldsFromSource`:: +deprecated:[8.15.0] Load fields from the original JSON {ref}/mapping-source-field.html[`_source`]. When disabled, *Discover* loads fields using the {es} search API's {ref}/search-fields.html#search-fields-param[`fields`] parameter. @@ -338,7 +340,7 @@ Highlights results in *Discover* and saved searches on dashboards. Highlighting slows requests when working on big documents. [[doctable-legacy]]`doc_table:legacy`:: -Controls the way the document table looks and works. +deprecated:[8.15.0] Controls the way the document table looks and works. To use the new *Document Explorer* instead of the classic view, turn off this option. The *Document Explorer* offers better data sorting, resizable columns, and a full screen view. @@ -511,7 +513,7 @@ deprecated::[8.11.0,'Rollups are deprecated and will be removed in a future vers [horizontal] [[rollups-enableindexpatterns]]`rollups:enableIndexPatterns`:: -Enables the creation of data views that capture rollup indices, which in +deprecated:[8.15.0] Enables the creation of data views that capture rollup indices, which in turn enables visualizations based on rollup data. Refresh the page to apply the changes. @@ -545,7 +547,7 @@ because requests can be spread across all shard copies. However, results might be inconsistent because different shards might be in different refresh states. [[search-includefrozen]]`search:includeFrozen`:: -**This setting is deprecated and will not be supported as of 9.0.** +deprecated:[7.16.0] Includes {ref}/frozen-indices.html[frozen indices] in results. Searching through frozen indices might increase the search time. This setting is off by default. Users must opt-in to include frozen indices. @@ -622,11 +624,11 @@ of buckets to try to represent. [horizontal] [[visualization-colormapping]]`visualization:colorMapping`:: -**This setting is deprecated and will not be supported in a future version.** +deprecated:[7.11.0] Maps values to specific colors in charts using the *Compatibility* palette. [[visualization-uselegacytimeaxis]]`visualization:useLegacyTimeAxis`:: -**This setting is deprecated and will not be supported in a future version.** +deprecated:[8.10.0] Enables the legacy time axis for charts in Lens, Discover, Visualize and TSVB [[visualization-heatmap-maxbuckets]]`visualization:heatmap:maxBuckets`:: diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 4405b746ca34f..cb096ec127154 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -89,6 +89,10 @@ configuration is effectively ignored when <> is enable The maximum number of sockets that can be used for communications with {es}. *Default: `Infinity`* +[[elasticsearch-maxResponseSize]] `elasticsearch.maxResponseSize`:: +Either `false` or a `byteSize` value. When set, responses from {es} with a size higher than the defined limit will be rejected. +This is intended to be used as a circuit-breaker mechanism to avoid memory errors in case of unexpectedly high responses coming from {es}. +*Default: `false`* [[elasticsearch-maxIdleSockets]] `elasticsearch.maxIdleSockets`:: The maximum number of idle sockets to keep open between {kib} and {es}. If more sockets become idle, they will be closed. diff --git a/docs/user/ml/index.asciidoc b/docs/user/ml/index.asciidoc index 870022e05512e..6785561acf355 100644 --- a/docs/user/ml/index.asciidoc +++ b/docs/user/ml/index.asciidoc @@ -148,35 +148,27 @@ advanced statistical methods to help you interpret your data and its behavior. [[log-rate-analysis]] === Log rate analysis -Log rate analysis is a feature that uses advanced statistical methods to -identify reasons for increases or decreases in log rates. It makes it easy to -find and investigate causes of unusual spikes or drops by using the analysis -workflow view. Examine the histogram chart of the log rates for a given -{data-source}, and find the reason behind a particular change possibly in -millions of log events across multiple fields and values. - -You can find log rate analysis under **{ml-app}** > **AIOps Labs** where -you can select the {data-source} or saved search that you want to analyze. +Log rate analysis uses advanced statistical methods to identify reasons for increases or decreases in log rates and displays the statistically significant data in a tabular format. +It makes it easy to find and investigate causes of unusual spikes or drops by using the analysis workflow view. +Examine the histogram chart of the log rates for a given {data-source}, and find the reason behind a particular change possibly in millions of log events across multiple fields and values. + +You can find log rate analysis embedded in multiple applications. +In {kib}, you can find it under **{ml-app}** > **AIOps Labs** where you can select the {data-source} or saved search that you want to analyze. [role="screenshot"] image::user/ml/images/ml-log-rate-analysis-before.png[Log event histogram chart] -Select a spike or drop in the log event histogram chart to start the analysis. It -identifies statistically significant field-value combinations that contribute to -the spike or drop and displays them in a table. You can optionally choose to summarize -the results into groups. The table also shows an indicator of the level of -impact and a sparkline showing the shape of the impact in the chart. Hovering -over a row displays the impact on the histogram chart in more detail. You can -inspect a field in **Discover**, further investigate in **Log pattern analysis**, -or copy the table row information as a query filter to the clipboard by -selecting the corresponding option under the **Actions** column. You can also -pin a table row by clicking on it then move the cursor to the histogram chart. -It displays a tooltip with exact count values for the pinned field which enables -closer investigation. - -Brushes in the chart show the baseline time range and the deviation in the -analyzed data. You can move the brushes to redefine both the baseline and the -deviation and rerun the analysis with the modified values. +Select a spike or drop in the log event histogram chart to start the analysis. +It identifies statistically significant field-value combinations that contribute to the spike or drop and displays them in a table. +You can optionally choose to summarize the results into groups. +The table also shows an indicator of the level of impact and a sparkline showing the shape of the impact in the chart. +Hovering over a row displays the impact on the histogram chart in more detail. +You can inspect a field in **Discover**, further investigate in **Log pattern analysis**, or copy the table row information as a query filter to the clipboard by selecting the corresponding option under the **Actions** column. +You can also pin a table row by clicking on it then move the cursor to the histogram chart. +It displays a tooltip with exact count values for the pinned field which enables closer investigation. + +Brushes in the chart show the baseline time range and the deviation in the analyzed data. +You can move the brushes to redefine both the baseline and the deviation and rerun the analysis with the modified values. [role="screenshot"] image::user/ml/images/ml-log-rate-analysis.png[Log rate spike explained] diff --git a/examples/controls_example/public/react_controls/control_group/get_control_group_factory.tsx b/examples/controls_example/public/react_controls/control_group/get_control_group_factory.tsx index 703d31f18db1d..d78feb6743795 100644 --- a/examples/controls_example/public/react_controls/control_group/get_control_group_factory.tsx +++ b/examples/controls_example/public/react_controls/control_group/get_control_group_factory.tsx @@ -52,8 +52,8 @@ export const getControlGroupEmbeddableFactory = (services: { }) => { const controlGroupEmbeddableFactory: ReactEmbeddableFactory< ControlGroupSerializedState, - ControlGroupApi, - ControlGroupRuntimeState + ControlGroupRuntimeState, + ControlGroupApi > = { type: CONTROL_GROUP_TYPE, deserializeState: (state) => deserializeControlGroup(state), @@ -117,7 +117,7 @@ export const getControlGroupEmbeddableFactory = (services: { }, snapshotRuntimeState: () => { // TODO: Remove this if it ends up being unnecessary - return {} as unknown as ControlGroupSerializedState; + return {} as unknown as ControlGroupRuntimeState; }, dataLoading: dataLoading$, children$: children$ as PublishingSubject<{ diff --git a/examples/controls_example/public/react_controls/control_group/types.ts b/examples/controls_example/public/react_controls/control_group/types.ts index b1807d31d801b..9fb6af3cef803 100644 --- a/examples/controls_example/public/react_controls/control_group/types.ts +++ b/examples/controls_example/public/react_controls/control_group/types.ts @@ -42,7 +42,7 @@ export type ControlGroupUnsavedChanges = Omit< export type ControlPanelState = DefaultControlState & { type: string; order: number }; export type ControlGroupApi = PresentationContainer & - DefaultEmbeddableApi & + DefaultEmbeddableApi & PublishesFilters & PublishesDataViews & HasSerializedChildState & diff --git a/examples/controls_example/public/react_controls/control_renderer.tsx b/examples/controls_example/public/react_controls/control_renderer.tsx index 471b12894bae7..1629673e64f7b 100644 --- a/examples/controls_example/public/react_controls/control_renderer.tsx +++ b/examples/controls_example/public/react_controls/control_renderer.tsx @@ -59,7 +59,7 @@ export const ControlRenderer = < return fullApi; }; - const { rawState: initialState } = parentApi.getSerializedStateForChild(uuid); + const { rawState: initialState } = parentApi.getSerializedStateForChild(uuid) ?? {}; const { api, Component } = factory.buildControl( initialState as unknown as StateType, diff --git a/examples/embeddable_examples/public/app/render_examples.tsx b/examples/embeddable_examples/public/app/render_examples.tsx index 4998b3bc5a59c..7e268e29f620e 100644 --- a/examples/embeddable_examples/public/app/render_examples.tsx +++ b/examples/embeddable_examples/public/app/render_examples.tsx @@ -98,7 +98,7 @@ export const RenderExamples = () => { - + key={hidePanelChrome ? 'hideChrome' : 'showChrome'} type={SEARCH_EMBEDDABLE_ID} getParentApi={() => parentApi} diff --git a/examples/embeddable_examples/public/react_embeddables/data_table/data_table_react_embeddable.tsx b/examples/embeddable_examples/public/react_embeddables/data_table/data_table_react_embeddable.tsx index 3bf161bd51490..d373a00896346 100644 --- a/examples/embeddable_examples/public/react_embeddables/data_table/data_table_react_embeddable.tsx +++ b/examples/embeddable_examples/public/react_embeddables/data_table/data_table_react_embeddable.tsx @@ -26,12 +26,12 @@ import { BehaviorSubject } from 'rxjs'; import { StartDeps } from '../../plugin'; import { DATA_TABLE_ID } from './constants'; import { initializeDataTableQueries } from './data_table_queries'; -import { DataTableApi, DataTableSerializedState } from './types'; +import { DataTableApi, DataTableRuntimeState, DataTableSerializedState } from './types'; export const getDataTableFactory = ( core: CoreStart, services: StartDeps -): ReactEmbeddableFactory => ({ +): ReactEmbeddableFactory => ({ type: DATA_TABLE_ID, deserializeState: (state) => { return state.rawState as DataTableSerializedState; diff --git a/examples/embeddable_examples/public/react_embeddables/data_table/types.ts b/examples/embeddable_examples/public/react_embeddables/data_table/types.ts index d693577bc89fb..787bda380bcfa 100644 --- a/examples/embeddable_examples/public/react_embeddables/data_table/types.ts +++ b/examples/embeddable_examples/public/react_embeddables/data_table/types.ts @@ -15,4 +15,6 @@ import { export type DataTableSerializedState = SerializedTitles & SerializedTimeRange; +export type DataTableRuntimeState = DataTableSerializedState; + export type DataTableApi = DefaultEmbeddableApi & PublishesDataLoading; diff --git a/examples/embeddable_examples/public/react_embeddables/eui_markdown/eui_markdown_react_embeddable.tsx b/examples/embeddable_examples/public/react_embeddables/eui_markdown/eui_markdown_react_embeddable.tsx index 64cdf1cb06e08..cd36c6dc5f1c9 100644 --- a/examples/embeddable_examples/public/react_embeddables/eui_markdown/eui_markdown_react_embeddable.tsx +++ b/examples/embeddable_examples/public/react_embeddables/eui_markdown/eui_markdown_react_embeddable.tsx @@ -19,10 +19,15 @@ import { euiThemeVars } from '@kbn/ui-theme'; import React from 'react'; import { BehaviorSubject } from 'rxjs'; import { EUI_MARKDOWN_ID } from './constants'; -import { MarkdownEditorSerializedState, MarkdownEditorApi } from './types'; +import { + MarkdownEditorApi, + MarkdownEditorRuntimeState, + MarkdownEditorSerializedState, +} from './types'; export const markdownEmbeddableFactory: ReactEmbeddableFactory< MarkdownEditorSerializedState, + MarkdownEditorRuntimeState, MarkdownEditorApi > = { type: EUI_MARKDOWN_ID, diff --git a/examples/embeddable_examples/public/react_embeddables/eui_markdown/types.ts b/examples/embeddable_examples/public/react_embeddables/eui_markdown/types.ts index 31678aa4a2a33..b341ffab1640c 100644 --- a/examples/embeddable_examples/public/react_embeddables/eui_markdown/types.ts +++ b/examples/embeddable_examples/public/react_embeddables/eui_markdown/types.ts @@ -13,4 +13,6 @@ export type MarkdownEditorSerializedState = SerializedTitles & { content: string; }; +export type MarkdownEditorRuntimeState = MarkdownEditorSerializedState; + export type MarkdownEditorApi = DefaultEmbeddableApi; diff --git a/examples/embeddable_examples/public/react_embeddables/field_list/field_list_react_embeddable.tsx b/examples/embeddable_examples/public/react_embeddables/field_list/field_list_react_embeddable.tsx index 2931760310c2b..6c296b00786e9 100644 --- a/examples/embeddable_examples/public/react_embeddables/field_list/field_list_react_embeddable.tsx +++ b/examples/embeddable_examples/public/react_embeddables/field_list/field_list_react_embeddable.tsx @@ -25,7 +25,12 @@ import { cloneDeep } from 'lodash'; import React, { useEffect } from 'react'; import { BehaviorSubject, skip, Subscription, switchMap } from 'rxjs'; import { FIELD_LIST_DATA_VIEW_REF_NAME, FIELD_LIST_ID } from './constants'; -import { FieldListApi, Services, FieldListSerializedStateState } from './types'; +import { + FieldListApi, + Services, + FieldListSerializedStateState, + FieldListRuntimeState, +} from './types'; const DataViewPicker = withSuspense(LazyDataViewPicker, null); @@ -46,6 +51,7 @@ export const getFieldListFactory = ( ) => { const fieldListEmbeddableFactory: ReactEmbeddableFactory< FieldListSerializedStateState, + FieldListRuntimeState, FieldListApi > = { type: FIELD_LIST_ID, diff --git a/examples/embeddable_examples/public/react_embeddables/field_list/types.ts b/examples/embeddable_examples/public/react_embeddables/field_list/types.ts index 0da67bcd0f70b..781f43754228c 100644 --- a/examples/embeddable_examples/public/react_embeddables/field_list/types.ts +++ b/examples/embeddable_examples/public/react_embeddables/field_list/types.ts @@ -19,7 +19,14 @@ export type FieldListSerializedStateState = SerializedTitles & { selectedFieldNames?: string[]; }; -export type FieldListApi = DefaultEmbeddableApi & PublishesSelectedFields & PublishesDataViews; +export type FieldListRuntimeState = FieldListSerializedStateState; + +export type FieldListApi = DefaultEmbeddableApi< + FieldListSerializedStateState, + FieldListSerializedStateState +> & + PublishesSelectedFields & + PublishesDataViews; export interface Services { dataViews: DataViewsPublicPluginStart; diff --git a/examples/embeddable_examples/public/react_embeddables/saved_book/saved_book_react_embeddable.tsx b/examples/embeddable_examples/public/react_embeddables/saved_book/saved_book_react_embeddable.tsx index 94e54b6ee350c..af96793e6522e 100644 --- a/examples/embeddable_examples/public/react_embeddables/saved_book/saved_book_react_embeddable.tsx +++ b/examples/embeddable_examples/public/react_embeddables/saved_book/saved_book_react_embeddable.tsx @@ -35,14 +35,14 @@ import { const bookSerializedStateIsByReference = ( state?: BookSerializedState ): state is BookByReferenceSerializedState => { - return Boolean(state && (state as BookByReferenceSerializedState).savedBookId !== undefined); + return Boolean(state && (state as BookByReferenceSerializedState).savedBookId); }; export const getSavedBookEmbeddableFactory = (core: CoreStart) => { const savedBookEmbeddableFactory: ReactEmbeddableFactory< BookSerializedState, - BookApi, - BookRuntimeState + BookRuntimeState, + BookApi > = { type: SAVED_BOOK_ID, deserializeState: async (serializedState) => { @@ -86,7 +86,7 @@ export const getSavedBookEmbeddableFactory = (core: CoreStart) => { defaultMessage: 'book', }), serializeState: async () => { - if (savedBookId$.value === undefined) { + if (!Boolean(savedBookId$.value)) { // if this book is currently by value, we serialize the entire state. const bookByValueState: BookByValueSerializedState = { attributes: serializeBookAttributes(bookAttributesManager), @@ -97,7 +97,7 @@ export const getSavedBookEmbeddableFactory = (core: CoreStart) => { // if this book is currently by reference, we serialize the reference and write to the external store. const bookByReferenceState: BookByReferenceSerializedState = { - savedBookId: savedBookId$.value, + savedBookId: savedBookId$.value!, ...serializeTitles(), }; @@ -123,6 +123,11 @@ export const getSavedBookEmbeddableFactory = (core: CoreStart) => { unlinkFromLibrary: () => { savedBookId$.next(undefined); }, + getByValueRuntimeSnapshot: () => { + const snapshot = api.snapshotRuntimeState(); + delete snapshot.savedBookId; + return snapshot; + }, }, { savedBookId: [savedBookId$, (val) => savedBookId$.next(val)], diff --git a/examples/embeddable_examples/public/react_embeddables/saved_book/types.ts b/examples/embeddable_examples/public/react_embeddables/saved_book/types.ts index ec855bbd38f96..00290698a9b1d 100644 --- a/examples/embeddable_examples/public/react_embeddables/saved_book/types.ts +++ b/examples/embeddable_examples/public/react_embeddables/saved_book/types.ts @@ -45,6 +45,6 @@ export interface BookRuntimeState Partial, SerializedTitles {} -export type BookApi = DefaultEmbeddableApi & +export type BookApi = DefaultEmbeddableApi & HasEditCapabilities & - HasInPlaceLibraryTransforms; + HasInPlaceLibraryTransforms; diff --git a/examples/embeddable_examples/public/react_embeddables/search/search_react_embeddable.tsx b/examples/embeddable_examples/public/react_embeddables/search/search_react_embeddable.tsx index 2b789a17da4ae..ef73c179b8f8e 100644 --- a/examples/embeddable_examples/public/react_embeddables/search/search_react_embeddable.tsx +++ b/examples/embeddable_examples/public/react_embeddables/search/search_react_embeddable.tsx @@ -21,10 +21,10 @@ import React, { useEffect } from 'react'; import { BehaviorSubject, switchMap, tap } from 'rxjs'; import { SEARCH_EMBEDDABLE_ID } from './constants'; import { getCount } from './get_count'; -import { SearchApi, Services, SearchSerializedState } from './types'; +import { SearchApi, Services, SearchSerializedState, SearchRuntimeState } from './types'; export const getSearchEmbeddableFactory = (services: Services) => { - const factory: ReactEmbeddableFactory = { + const factory: ReactEmbeddableFactory = { type: SEARCH_EMBEDDABLE_ID, deserializeState: (state) => state.rawState, buildEmbeddable: async (state, buildApi, uuid, parentApi) => { diff --git a/examples/embeddable_examples/public/react_embeddables/search/types.ts b/examples/embeddable_examples/public/react_embeddables/search/types.ts index 835b2380d46a7..c82213931a002 100644 --- a/examples/embeddable_examples/public/react_embeddables/search/types.ts +++ b/examples/embeddable_examples/public/react_embeddables/search/types.ts @@ -20,6 +20,8 @@ import { export type SearchSerializedState = SerializedTimeRange; +export type SearchRuntimeState = SearchSerializedState; + export type SearchApi = DefaultEmbeddableApi & PublishesDataViews & PublishesDataLoading & diff --git a/examples/portable_dashboards_example/public/filter_debugger_embeddable.tsx b/examples/portable_dashboards_example/public/filter_debugger_embeddable.tsx index fd2c23731011b..8c5d136640b1d 100644 --- a/examples/portable_dashboards_example/public/filter_debugger_embeddable.tsx +++ b/examples/portable_dashboards_example/public/filter_debugger_embeddable.tsx @@ -18,7 +18,7 @@ import { FILTER_DEBUGGER_EMBEDDABLE_ID } from './constants'; export type Api = DefaultEmbeddableApi<{}>; -export const factory: ReactEmbeddableFactory<{}, Api> = { +export const factory: ReactEmbeddableFactory<{}, {}, Api> = { type: FILTER_DEBUGGER_EMBEDDABLE_ID, deserializeState: () => { return {}; diff --git a/examples/response_stream/public/containers/app/pages/page_reducer_stream/index.tsx b/examples/response_stream/public/containers/app/pages/page_reducer_stream/index.tsx index a471d6f589034..4bded9ac1e27f 100644 --- a/examples/response_stream/public/containers/app/pages/page_reducer_stream/index.tsx +++ b/examples/response_stream/public/containers/app/pages/page_reducer_stream/index.tsx @@ -132,21 +132,18 @@ export const PageReducerStream: FC = () => { label="Simulate errors (gets applied to new streams only, not currently running ones)." checked={simulateErrors} onChange={(e) => setSimulateErrors(!simulateErrors)} - compressed /> setCompressResponse(!compressResponse)} - compressed /> setFlushFix(!flushFix)} - compressed /> diff --git a/examples/response_stream/public/containers/app/pages/page_redux_stream/index.tsx b/examples/response_stream/public/containers/app/pages/page_redux_stream/index.tsx index aaf0a7b5dbbb7..15512ac92d12d 100644 --- a/examples/response_stream/public/containers/app/pages/page_redux_stream/index.tsx +++ b/examples/response_stream/public/containers/app/pages/page_redux_stream/index.tsx @@ -136,21 +136,18 @@ export const PageReduxStream: FC = () => { label="Simulate errors (gets applied to new streams only, not currently running ones)." checked={simulateErrors} onChange={(e) => dispatch(setSimulateErrors(!simulateErrors))} - compressed /> dispatch(setCompressResponse(!compressResponse))} - compressed /> dispatch(setFlushFix(!flushFix))} - compressed /> diff --git a/examples/response_stream/public/containers/app/pages/page_simple_string_stream/index.tsx b/examples/response_stream/public/containers/app/pages/page_simple_string_stream/index.tsx index 3e33ade381489..981329dfcec3a 100644 --- a/examples/response_stream/public/containers/app/pages/page_simple_string_stream/index.tsx +++ b/examples/response_stream/public/containers/app/pages/page_simple_string_stream/index.tsx @@ -83,7 +83,6 @@ export const PageSimpleStringStream: FC = () => { label="Toggle compression setting for response stream." checked={compressResponse} onChange={(e) => setCompressResponse(!compressResponse)} - compressed /> diff --git a/fleet_packages.json b/fleet_packages.json index 1ffa4bae4d6a6..76dd27e8c27f0 100644 --- a/fleet_packages.json +++ b/fleet_packages.json @@ -30,7 +30,7 @@ }, { "name": "elastic_agent", - "version": "1.19.2" + "version": "1.20.0" }, { "name": "endpoint", @@ -56,6 +56,6 @@ }, { "name": "security_detection_engine", - "version": "8.14.2" + "version": "8.14.3" } ] \ No newline at end of file diff --git a/nav-kibana-dev.docnav.json b/nav-kibana-dev.docnav.json index cfe16536856d0..60dcb5d054393 100644 --- a/nav-kibana-dev.docnav.json +++ b/nav-kibana-dev.docnav.json @@ -115,7 +115,8 @@ "label": "Tutorials", "items": [ { - "id": "kibDevTutorialAddingCustomPerformanceMetrics" + "id": "kibDevTutorialAddingCustomPerformanceMetrics", + "label": "Adding Performance Metrics" }, { "id": "kibDevTutorialSetupWindowsDevWSL" diff --git a/oas_docs/.spectral.yaml b/oas_docs/.spectral.yaml new file mode 100644 index 0000000000000..b328e320e23eb --- /dev/null +++ b/oas_docs/.spectral.yaml @@ -0,0 +1,103 @@ +extends: ["spectral:oas"] +rules: +# Built-in rules + # Descriptions + oas3-parameter-description: warn + oas2-parameter-description: warn + tag-description: info + # Document info + info-contact: info + info-description: warn + info-license: warn + # Examples + oas3-valid-media-example: false + oas3-valid-schema-example: false + oas2-valid-media-example: false + # Operations + operation-operationId: false + operation-operationId-unique: false + operation-operationId-valid-in-url: false + operation-tag-defined: warn + operation-tags: warn + # Responses + operation-success-response: warn + # Schema + oas3-schema: error + oas2-schema: error + # Tags + openapi-tags: warn + openapi-tags-alphabetical: info + # Turn off some built-in rules + operation-description: false + operation-singular-tag: false +# Custom rules + # Descriptions + avoid-problematic-words: + description: Ban certain words from descriptions + message: "Use appropriate replacements for problematic terms" + severity: warn + given: "$..*.description" + then: + function: pattern + functionOptions: + notMatch: /(blacklist|whitelist|execute|kill)/i + # Examples + operation-success-examples: + formats: ["oas3_1"] + description: Response code 200 should have at least one example. + message: "Each response body should have a realistic example. It must not contain any sensitive or confidential data." + severity: info + given: $.paths[*].[*].responses.[200].content.[application/json] + then: + field: examples + function: defined + # Extensions + internal-extension: + description: Operations should not have x-internal extension. + message: "Do not publish x-internal operations" + severity: error + given: $.paths[*].[get,put,post,delete,options,head,patch,trace] + then: + field: x-internal + function: undefined + # Operations + operation-summary: + description: Operations should have summaries. + message: "Each operation should have a summary" + severity: error + recommended: true + given: $.paths[*].[get,put,post,delete,options,head,patch,trace] + then: + field: summary + function: defined + operation-summary-length: + description: Operation summary should be between 5 and 45 characters + given: "$.paths[*].[get,put,post,delete,options,head,patch,trace]" + then: + field: summary + function: length + functionOptions: + max: 45 + min: 5 + severity: warn + simple-verbs-in-summary: + given: + - "$.paths[*][*].summary" + then: + function: pattern + functionOptions: + notMatch: "Retrieve|Return|List *" + severity: warn + description: Summaries should use common verbs. + message: "Summaries should use common verbs like Get, Update, Delete whenever possible" + # NOTE: This one hiccups on acronyms so perhaps too noisy + # docs-operation-summary-sentence-case: + # description: Operation summary should be sentence cased + # given: "$.paths[*].[get,put,post,delete,options,head,patch,trace]" + # then: + # field: summary + # function: pattern + # functionOptions: + # match: /^[A-Z]+[^A-Z]+$/ + # severity: warn + diff --git a/oas_docs/README.md b/oas_docs/README.md new file mode 100644 index 0000000000000..70e39571c0af6 --- /dev/null +++ b/oas_docs/README.md @@ -0,0 +1,4 @@ +The `bundle.json` and `bundle.serverless.json` files are generated automatically. + +The `kibana.openapi.serverless.yaml` file is a temporary OpenAPI document created by joining some manually-maintained files. +To create it and lint it, run `make api-docs` and `make api-docs-lint`. \ No newline at end of file diff --git a/oas_docs/kibana.info.serverless.yaml b/oas_docs/kibana.info.serverless.yaml new file mode 100644 index 0000000000000..c96325cf7c75d --- /dev/null +++ b/oas_docs/kibana.info.serverless.yaml @@ -0,0 +1,47 @@ +openapi: 3.0.3 +info: + title: Kibana Serverless APIs + description: | + The Kibana REST APIs for Elastic serverless enable you to manage resources + such as connectors, data views, and saved objects. The API calls are + stateless. Each request that you make happens in isolation from other calls + and must include all of the necessary information for Kibana to fulfill the + request. API requests return JSON output, which is a format that is + machine-readable and works well for automation. + + To interact with Kibana APIs, use the following operations: + + - GET: Fetches the information. + - POST: Adds new information. + - PUT: Updates the existing information. + - DELETE: Removes the information. + + You can prepend any Kibana API endpoint with `kbn:` and run the request in + **Dev Tools → Console**. For example: + + ``` + GET kbn:/api/data_views + ``` + version: "1.0.2" + license: + name: Elastic License 2.0 + url: https://www.elastic.co/licensing/elastic-license + contact: + name: Kibana Team +# servers: +# - url: https://{kibana_url} +# variables: +# kibana_url: +# default: localhost:5601 +# security: +# - apiKeyAuth: [] +# components: +# securitySchemes: +# apiKeyAuth: +# type: apiKey +# in: header +# name: Authorization +# description: > +# These APIs use key-based authentication. +# You must create an API key and use the encoded value in the request header. +# For example: `Authorization: ApiKey base64AccessApiKey` \ No newline at end of file diff --git a/oas_docs/kibana.serverless.yaml b/oas_docs/kibana.serverless.yaml new file mode 100644 index 0000000000000..d7b5512e96099 --- /dev/null +++ b/oas_docs/kibana.serverless.yaml @@ -0,0 +1,7394 @@ +openapi: 3.0.3 +info: + title: Kibana Serverless APIs + description: | + The Kibana REST APIs for Elastic serverless enable you to manage resources + such as connectors, data views, and saved objects. The API calls are + stateless. Each request that you make happens in isolation from other calls + and must include all of the necessary information for Kibana to fulfill the + request. API requests return JSON output, which is a format that is + machine-readable and works well for automation. + + To interact with Kibana APIs, use the following operations: + + - GET: Fetches the information. + - POST: Adds new information. + - PUT: Updates the existing information. + - DELETE: Removes the information. + + You can prepend any Kibana API endpoint with `kbn:` and run the request in + **Dev Tools → Console**. For example: + + ``` + GET kbn:/api/data_views + ``` + version: 1.0.2 + license: + name: Elastic License 2.0 + url: https://www.elastic.co/licensing/elastic-license + contact: + name: Kibana Team +servers: + - url: https://{kibanaUrl} + variables: + kibanaUrl: + default: localhost:5601 + - url: https://{kibana_url} + variables: + kibana_url: + default: localhost:5601 + - url: / +tags: + - name: APM agent keys + description: > + Configure APM agent keys to authorize requests from APM agents to the APM + Server. + x-displayName: APM agent keys + - name: APM annotations + description: > + Annotate visualizations in the APM app with significant events. + Annotations enable you to easily see how events are impacting the + performance of your applications. + x-displayName: APM annotations + - name: connectors + description: Connector APIs enable you to create and manage connectors. + x-displayName: connectors + - name: data views + description: >- + Data view APIs enable you to manage data views, formerly known as Kibana + index patterns. + x-displayName: data views + - name: ml + description: Machine learning + x-displayName: ml + - name: saved objects + description: >- + Manage Kibana saved objects, including dashboards, visualizations, and + more. + x-displayName: saved objects +paths: + /api/apm/agent_keys: + post: + summary: Create an APM agent key + description: Create a new agent key for APM. + tags: + - APM agent keys + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + privileges: + type: array + items: + type: string + enum: + - event:write + - config_agent:read + responses: + '200': + description: Agent key created successfully + content: + application/json: + schema: + type: object + properties: + api_key: + type: string + expiration: + type: integer + format: int64 + id: + type: string + name: + type: string + encoded: + type: string + /api/apm/services/{serviceName}/annotation/search: + get: + summary: Search for annotations + description: Search for annotations related to a specific service. + tags: + - APM annotations + parameters: + - name: serviceName + in: path + required: true + description: The name of the service + schema: + type: string + - name: environment + in: query + required: false + description: The environment to filter annotations by + schema: + type: string + - name: start + in: query + required: false + description: The start date for the search + schema: + type: string + - name: end + in: query + required: false + description: The end date for the search + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + annotations: + type: array + items: + type: object + properties: + type: + type: string + enum: + - version + id: + type: string + '@timestamp': + type: number + text: + type: string + /api/apm/services/{serviceName}/annotation: + post: + summary: Create a service annotation + description: Create a new annotation for a specific service. + tags: + - APM annotations + parameters: + - name: serviceName + in: path + required: true + description: The name of the service + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + '@timestamp': + type: string + service: + type: object + properties: + version: + type: string + environment: + type: string + message: + type: string + tags: + type: array + items: + type: string + responses: + '200': + description: Annotation created successfully + content: + application/json: + schema: + type: object + properties: + _id: + type: string + _index: + type: string + _source: + type: object + properties: + annotation: + type: string + tags: + type: array + items: + type: string + message: + type: string + service: + type: object + properties: + name: + type: string + environment: + type: string + version: + type: string + event: + type: object + properties: + created: + type: string + '@timestamp': + type: string + /api/actions/connector: + post: + summary: Create a connector + operationId: createConnector + tags: + - connectors + parameters: + - $ref: '#/components/parameters/Connectors_kbn_xsrf' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Connectors_create_connector_request' + examples: + createEmailConnectorRequest: + $ref: >- + #/components/examples/Connectors_create_email_connector_request + createIndexConnectorRequest: + $ref: >- + #/components/examples/Connectors_create_index_connector_request + createWebhookConnectorRequest: + $ref: >- + #/components/examples/Connectors_create_webhook_connector_request + createXmattersConnectorRequest: + $ref: >- + #/components/examples/Connectors_create_xmatters_connector_request + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '#/components/schemas/Connectors_connector_response_properties' + examples: + createEmailConnectorResponse: + $ref: >- + #/components/examples/Connectors_create_email_connector_response + createIndexConnectorResponse: + $ref: >- + #/components/examples/Connectors_create_index_connector_response + createWebhookConnectorResponse: + $ref: >- + #/components/examples/Connectors_create_webhook_connector_response + createXmattersConnectorResponse: + $ref: >- + #/components/examples/Connectors_create_xmatters_connector_response + '401': + $ref: '#/components/responses/Connectors_401' + security: + - Connectors_apiKeyAuth: [] + /api/actions/connector/{connectorId}: + get: + summary: Get a connector information + operationId: getConnector + tags: + - connectors + parameters: + - $ref: '#/components/parameters/Connectors_connector_id' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '#/components/schemas/Connectors_connector_response_properties' + examples: + getConnectorResponse: + $ref: '#/components/examples/Connectors_get_connector_response' + '401': + $ref: '#/components/responses/Connectors_401' + '404': + $ref: '#/components/responses/Connectors_404' + security: + - Connectors_apiKeyAuth: [] + delete: + summary: Delete a connector + operationId: deleteConnector + tags: + - connectors + parameters: + - $ref: '#/components/parameters/Connectors_kbn_xsrf' + - $ref: '#/components/parameters/Connectors_connector_id' + responses: + '204': + description: Indicates a successful call. + '401': + $ref: '#/components/responses/Connectors_401' + '404': + $ref: '#/components/responses/Connectors_404' + security: + - Connectors_apiKeyAuth: [] + post: + summary: Create a connector + operationId: createConnectorId + tags: + - connectors + parameters: + - $ref: '#/components/parameters/Connectors_kbn_xsrf' + - in: path + name: connectorId + description: > + A UUID v1 or v4 identifier for the connector. If you omit this + parameter, an identifier is randomly generated. + required: true + schema: + type: string + example: ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Connectors_create_connector_request' + examples: + createIndexConnectorRequest: + $ref: >- + #/components/examples/Connectors_create_index_connector_request + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '#/components/schemas/Connectors_connector_response_properties' + examples: + createIndexConnectorResponse: + $ref: >- + #/components/examples/Connectors_create_index_connector_response + '401': + $ref: '#/components/responses/Connectors_401' + security: + - Connectors_apiKeyAuth: [] + put: + summary: Update a connector + operationId: updateConnector + tags: + - connectors + parameters: + - $ref: '#/components/parameters/Connectors_kbn_xsrf' + - $ref: '#/components/parameters/Connectors_connector_id' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Connectors_update_connector_request' + examples: + updateIndexConnectorRequest: + $ref: >- + #/components/examples/Connectors_update_index_connector_request + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '#/components/schemas/Connectors_connector_response_properties' + '400': + $ref: '#/components/responses/Connectors_401' + '401': + $ref: '#/components/responses/Connectors_401' + '404': + $ref: '#/components/responses/Connectors_404' + security: + - Connectors_apiKeyAuth: [] + /api/actions/connectors: + get: + summary: Get all connectors + operationId: getConnectors + tags: + - connectors + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: array + items: + $ref: >- + #/components/schemas/Connectors_connector_response_properties + examples: + getConnectorsResponse: + $ref: '#/components/examples/Connectors_get_connectors_response' + '401': + $ref: '#/components/responses/Connectors_401' + security: + - Connectors_apiKeyAuth: [] + /api/actions/connector_types: + get: + summary: Get all connector types + operationId: getConnectorTypes + tags: + - connectors + parameters: + - in: query + name: feature_id + description: >- + A filter to limit the retrieved connector types to those that + support a specific feature (such as alerting or cases). + schema: + $ref: '#/components/schemas/Connectors_features' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + title: Get connector types response body properties + description: The properties vary for each connector type. + type: array + items: + type: object + properties: + enabled: + type: boolean + description: >- + Indicates whether the connector type is enabled in + Kibana. + example: true + enabled_in_config: + type: boolean + description: >- + Indicates whether the connector type is enabled in the + Kibana configuration file. + example: true + enabled_in_license: + type: boolean + description: >- + Indicates whether the connector is enabled in the + license. + example: true + id: + $ref: '#/components/schemas/Connectors_connector_types' + is_system_action_type: + type: boolean + example: false + minimum_license_required: + type: string + description: The license that is required to use the connector type. + example: basic + name: + type: string + description: The name of the connector type. + example: Index + supported_feature_ids: + type: array + description: The features that are supported by the connector type. + items: + $ref: '#/components/schemas/Connectors_features' + example: + - alerting + - cases + - siem + examples: + getConnectorTypesServerlessResponse: + $ref: >- + #/components/examples/Connectors_get_connector_types_generativeai_response + '401': + $ref: '#/components/responses/Connectors_401' + security: + - Connectors_apiKeyAuth: [] + /s/{spaceId}/api/data_views: + get: + summary: Get all data views + operationId: getAllDataViews + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_space_id' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + data_view: + type: array + items: + type: object + properties: + id: + type: string + name: + type: string + namespaces: + type: array + items: + type: string + title: + type: string + typeMeta: + type: object + examples: + getAllDataViewsResponse: + $ref: '#/components/examples/Data_views_get_data_views_response' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_400_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + /s/{spaceId}/api/data_views/data_view: + post: + summary: Create a data view + operationId: createDataView + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_kbn_xsrf' + - $ref: '#/components/parameters/Data_views_space_id' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_create_data_view_request_object' + examples: + createDataViewRequest: + $ref: '#/components/examples/Data_views_create_data_view_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_data_view_response_object' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_400_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + /s/{spaceId}/api/data_views/data_view/{viewId}: + get: + summary: Get a data view + operationId: getDataView + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_view_id' + - $ref: '#/components/parameters/Data_views_space_id' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_data_view_response_object' + examples: + getDataViewResponse: + $ref: '#/components/examples/Data_views_get_data_view_response' + '404': + description: Object is not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_404_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + delete: + summary: Delete a data view + operationId: deleteDataView + description: > + WARNING: When you delete a data view, it cannot be recovered. This + functionality is in technical preview and may be changed or removed in a + future release. Elastic will work to fix any issues, but features in + technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_kbn_xsrf' + - $ref: '#/components/parameters/Data_views_view_id' + - $ref: '#/components/parameters/Data_views_space_id' + responses: + '204': + description: Indicates a successful call. + '404': + description: Object is not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_404_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + post: + summary: Update a data view + operationId: updateDataView + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_kbn_xsrf' + - $ref: '#/components/parameters/Data_views_view_id' + - $ref: '#/components/parameters/Data_views_space_id' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_update_data_view_request_object' + examples: + updateDataViewRequest: + $ref: '#/components/examples/Data_views_update_data_view_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_data_view_response_object' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_400_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + /s/{spaceId}/api/data_views/default: + get: + summary: Get the default data view identifier + operationId: getDefaultDataView + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_space_id' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + data_view_id: + type: string + examples: + getDefaultDataViewResponse: + $ref: >- + #/components/examples/Data_views_get_default_data_view_response + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_400_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + post: + summary: Sets the default data view identifier + operationId: setDefaultDatailView + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_kbn_xsrf' + - $ref: '#/components/parameters/Data_views_space_id' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - data_view_id + properties: + data_view_id: + type: string + nullable: true + description: > + The data view identifier. NOTE: The API does not validate + whether it is a valid identifier. Use `null` to unset the + default data view. + force: + type: boolean + description: Update an existing default data view identifier. + default: false + examples: + setDefaultDataViewRequest: + $ref: '#/components/examples/Data_views_set_default_data_view_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + acknowledged: + type: boolean + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_400_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + /s/{spaceId}/api/data_views/data_view/{viewId}/fields: + post: + summary: Update data view fields metadata + operationId: updateFieldsMetadata + description: > + Update fields presentation metadata such as count, customLabel and + format. This functionality is in technical preview and may be changed or + removed in a future release. Elastic will work to fix any issues, but + features in technical preview are not subject to the support SLA of + official GA features. You can update multiple fields in one request. + Updates are merged with persisted metadata. To remove existing metadata, + specify null as the value. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_kbn_xsrf' + - $ref: '#/components/parameters/Data_views_view_id' + - $ref: '#/components/parameters/Data_views_space_id' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - fields + properties: + fields: + description: The field object. + type: object + examples: + updateFieldsMetadataRequest: + $ref: '#/components/examples/Data_views_update_field_metadata_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + acknowledged: + type: boolean + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_400_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + /s/{spaceId}/api/data_views/data_view/{viewId}/runtime_field: + post: + summary: Create a runtime field + operationId: createRuntimeField + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_kbn_xsrf' + - $ref: '#/components/parameters/Data_views_view_id' + - $ref: '#/components/parameters/Data_views_space_id' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - name + - runtimeField + properties: + name: + type: string + description: | + The name for a runtime field. + runtimeField: + type: object + description: | + The runtime field definition object. + examples: + createRuntimeFieldRequest: + $ref: '#/components/examples/Data_views_create_runtime_field_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + put: + summary: Create or update a runtime field + operationId: createUpdateRuntimeField + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_kbn_xsrf' + - $ref: '#/components/parameters/Data_views_space_id' + - name: viewId + in: path + description: | + The ID of the data view fields you want to update. + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - name + - runtimeField + properties: + name: + type: string + description: | + The name for a runtime field. + runtimeField: + type: object + description: | + The runtime field definition object. + examples: + updateRuntimeFieldRequest: + $ref: '#/components/examples/Data_views_create_runtime_field_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + data_view: + type: object + fields: + type: array + items: + type: object + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_400_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + /s/{spaceId}/api/data_views/data_view/{viewId}/runtime_field/{fieldName}: + get: + summary: Get a runtime field + operationId: getRuntimeField + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_field_name' + - $ref: '#/components/parameters/Data_views_view_id' + - $ref: '#/components/parameters/Data_views_space_id' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + data_view: + type: object + fields: + type: array + items: + type: object + examples: + getRuntimeFieldResponse: + $ref: '#/components/examples/Data_views_get_runtime_field_response' + '404': + description: Object is not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_404_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + delete: + summary: Delete a runtime field from a data view + operationId: deleteRuntimeField + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_field_name' + - $ref: '#/components/parameters/Data_views_view_id' + - $ref: '#/components/parameters/Data_views_space_id' + responses: + '200': + description: Indicates a successful call. + '404': + description: Object is not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_404_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + post: + summary: Update a runtime field + operationId: updateRuntimeField + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_field_name' + - $ref: '#/components/parameters/Data_views_view_id' + - $ref: '#/components/parameters/Data_views_space_id' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - runtimeField + properties: + runtimeField: + type: object + description: | + The runtime field definition object. + + You can update following fields: + + - `type` + - `script` + examples: + updateRuntimeFieldRequest: + $ref: '#/components/examples/Data_views_update_runtime_field_request' + responses: + '200': + description: Indicates a successful call. + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_400_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + /api/data_views: + get: + summary: Get all data views in the default space + operationId: getAllDataViewsDefault + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + data_view: + type: array + items: + type: object + properties: + id: + type: string + name: + type: string + namespaces: + type: array + items: + type: string + title: + type: string + typeMeta: + type: object + examples: + getAllDataViewsResponse: + $ref: '#/components/examples/Data_views_get_data_views_response' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_400_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + /api/data_views/data_view: + post: + summary: Create a data view in the default space + operationId: createDataViewDefault + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_kbn_xsrf' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_create_data_view_request_object' + examples: + createDataViewRequest: + $ref: '#/components/examples/Data_views_create_data_view_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_data_view_response_object' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_400_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + /api/data_views/data_view/{viewId}: + get: + summary: Get a data view in the default space + operationId: getDataViewDefault + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_view_id' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_data_view_response_object' + examples: + getDataViewResponse: + $ref: '#/components/examples/Data_views_get_data_view_response' + '404': + description: Object is not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_404_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + delete: + summary: Delete a data view from the default space + operationId: deleteDataViewDefault + description: > + WARNING: When you delete a data view, it cannot be recovered. This + functionality is in technical preview and may be changed or removed in a + future release. Elastic will work to fix any issues, but features in + technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_kbn_xsrf' + - $ref: '#/components/parameters/Data_views_view_id' + responses: + '204': + description: Indicates a successful call. + '404': + description: Object is not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_404_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + post: + summary: Update a data view in the default space + operationId: updateDataViewDefault + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_kbn_xsrf' + - $ref: '#/components/parameters/Data_views_view_id' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_update_data_view_request_object' + examples: + updateDataViewRequest: + $ref: '#/components/examples/Data_views_update_data_view_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_data_view_response_object' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_400_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + /api/data_views/data_view/{viewId}/fields: + post: + summary: Update data view fields metadata in the default space + operationId: updateFieldsMetadataDefault + description: > + Update fields presentation metadata such as count, customLabel and + format. This functionality is in technical preview and may be changed or + removed in a future release. Elastic will work to fix any issues, but + features in technical preview are not subject to the support SLA of + official GA features. You can update multiple fields in one request. + Updates are merged with persisted metadata. To remove existing metadata, + specify null as the value. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_kbn_xsrf' + - $ref: '#/components/parameters/Data_views_view_id' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - fields + properties: + fields: + description: The field object. + type: object + examples: + updateFieldsMetadataRequest: + $ref: '#/components/examples/Data_views_update_field_metadata_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + acknowledged: + type: boolean + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_400_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + /api/data_views/data_view/{viewId}/runtime_field: + post: + summary: Create a runtime field in the default space + operationId: createRuntimeFieldDefault + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_kbn_xsrf' + - $ref: '#/components/parameters/Data_views_view_id' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - name + - runtimeField + properties: + name: + type: string + description: | + The name for a runtime field. + runtimeField: + type: object + description: | + The runtime field definition object. + examples: + createRuntimeFieldRequest: + $ref: '#/components/examples/Data_views_create_runtime_field_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + put: + summary: Create or update a runtime field in the default space + operationId: createUpdateRuntimeFieldDefault + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_kbn_xsrf' + - name: viewId + in: path + description: | + The ID of the data view fields you want to update. + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - name + - runtimeField + properties: + name: + type: string + description: | + The name for a runtime field. + runtimeField: + type: object + description: | + The runtime field definition object. + examples: + updateRuntimeFieldRequest: + $ref: '#/components/examples/Data_views_create_runtime_field_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + data_view: + type: object + fields: + type: array + items: + type: object + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_400_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + /api/data_views/data_view/{viewId}/runtime_field/{fieldName}: + get: + summary: Get a runtime field in the default space + operationId: getRuntimeFieldDefault + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_field_name' + - $ref: '#/components/parameters/Data_views_view_id' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + data_view: + type: object + fields: + type: array + items: + type: object + examples: + getRuntimeFieldResponse: + $ref: '#/components/examples/Data_views_get_runtime_field_response' + '404': + description: Object is not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_404_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + delete: + summary: Delete a runtime field from a data view in the default space + operationId: deleteRuntimeFieldDefault + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_field_name' + - $ref: '#/components/parameters/Data_views_view_id' + responses: + '200': + description: Indicates a successful call. + '404': + description: Object is not found. + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_404_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + post: + summary: Update a runtime field in the default space + operationId: updateRuntimeFieldDefault + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_field_name' + - $ref: '#/components/parameters/Data_views_view_id' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - runtimeField + properties: + runtimeField: + type: object + description: | + The runtime field definition object. + + You can update following fields: + + - `type` + - `script` + examples: + updateRuntimeFieldRequest: + $ref: '#/components/examples/Data_views_update_runtime_field_request' + responses: + '200': + description: Indicates a successful call. + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_400_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + /api/data_views/default: + get: + summary: Get the default data view in the default space + operationId: getDefaultDataViewDefault + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + data_view_id: + type: string + examples: + getDefaultDataViewResponse: + $ref: >- + #/components/examples/Data_views_get_default_data_view_response + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_400_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + post: + summary: Set the default data view in the default space + operationId: setDefaultDatailViewDefault + description: > + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/Data_views_kbn_xsrf' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - data_view_id + properties: + data_view_id: + type: string + nullable: true + description: > + The data view identifier. NOTE: The API does not validate + whether it is a valid identifier. Use `null` to unset the + default data view. + force: + type: boolean + description: Update an existing default data view identifier. + default: false + examples: + setDefaultDataViewRequest: + $ref: '#/components/examples/Data_views_set_default_data_view_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + acknowledged: + type: boolean + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/Data_views_400_response' + security: + - Data_views_basicAuth: [] + - Data_views_apiKeyAuth: [] + /api/ml/saved_objects/sync: + get: + summary: Sync machine learning saved objects + description: > + Synchronizes Kibana saved objects for machine learning jobs and trained + models. This API runs automatically when you start Kibana and + periodically thereafter. + operationId: mlSync + tags: + - ml + parameters: + - $ref: '#/components/parameters/Machine_learning_APIs_simulateParam' + responses: + '200': + description: Indicates a successful call + content: + application/json: + schema: + $ref: '#/components/schemas/Machine_learning_APIs_mlSync200Response' + examples: + syncExample: + $ref: '#/components/examples/Machine_learning_APIs_mlSyncExample' + '401': + description: Authorization information is missing or invalid. + content: + application/json: + schema: + $ref: '#/components/schemas/Machine_learning_APIs_mlSync4xxResponse' + security: + - Machine_learning_APIs_apiKeyAuth: [] + /api/saved_objects/_export: + post: + summary: Export saved objects in the default space + operationId: exportSavedObjectsDefault + description: > + Retrieve sets of saved objects that you want to import into Kibana. + + You must include `type` or `objects` in the request body. + + + NOTE: The `savedObjects.maxImportExportSize` configuration setting + limits the number of saved objects which may be exported. + + + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - saved objects + parameters: + - $ref: '#/components/parameters/Serverless_saved_objects_kbn_xsrf' + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + excludeExportDetails: + description: Do not add export details entry at the end of the stream. + type: boolean + default: false + includeReferencesDeep: + description: >- + Includes all of the referenced objects in the exported + objects. + type: boolean + objects: + description: A list of objects to export. + type: array + items: + type: object + type: + description: >- + The saved object types to include in the export. Use `*` to + export all the types. + oneOf: + - type: string + - type: array + items: + type: string + examples: + exportSavedObjectsRequest: + $ref: >- + #/components/examples/Serverless_saved_objects_export_objects_request + responses: + '200': + description: Indicates a successful call. + content: + application/x-ndjson: + schema: + type: object + additionalProperties: true + examples: + exportSavedObjectsResponse: + $ref: >- + #/components/examples/Serverless_saved_objects_export_objects_response + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Serverless_saved_objects_400_response' + security: + - Serverless_saved_objects_apiKeyAuth: [] + /api/saved_objects/_import: + post: + summary: Import saved objects in the default space + operationId: importSavedObjectsDefault + description: > + Create sets of Kibana saved objects from a file created by the export + API. + + Saved objects can be imported only into the same version, a newer minor + on the same major, or the next major. Exported saved objects are not + backwards compatible and cannot be imported into an older version of + Kibana. + + + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - saved objects + parameters: + - $ref: '#/components/parameters/Serverless_saved_objects_kbn_xsrf' + - in: query + name: createNewCopies + schema: + type: boolean + required: false + description: > + Creates copies of saved objects, regenerates each object ID, and + resets the origin. When used, potential conflict errors are avoided. + NOTE: This option cannot be used with the `overwrite` and + `compatibilityMode` options. + - in: query + name: overwrite + schema: + type: boolean + required: false + description: > + Overwrites saved objects when they already exist. When used, + potential conflict errors are automatically resolved by overwriting + the destination object. NOTE: This option cannot be used with the + `createNewCopies` option. + - in: query + name: compatibilityMode + schema: + type: boolean + required: false + description: > + Applies various adjustments to the saved objects that are being + imported to maintain compatibility between different Kibana + versions. Use this option only if you encounter issues with imported + saved objects. NOTE: This option cannot be used with the + `createNewCopies` option. + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: + description: > + A file exported using the export API. NOTE: The + `savedObjects.maxImportExportSize` configuration setting + limits the number of saved objects which may be included in + this file. Similarly, the + `savedObjects.maxImportPayloadBytes` setting limits the + overall size of the file that can be imported. + examples: + importObjectsRequest: + $ref: >- + #/components/examples/Serverless_saved_objects_import_objects_request + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + description: > + Indicates when the import was successfully completed. When + set to false, some objects may not have been created. For + additional information, refer to the `errors` and + `successResults` properties. + successCount: + type: integer + description: Indicates the number of successfully imported records. + errors: + type: array + items: + type: object + description: > + Indicates the import was unsuccessful and specifies the + objects that failed to import. + + + NOTE: One object may result in multiple errors, which + requires separate steps to resolve. For instance, a + `missing_references` error and conflict error. + successResults: + type: array + items: + type: object + description: > + Indicates the objects that are successfully imported, with + any metadata if applicable. + + + NOTE: Objects are created only when all resolvable errors + are addressed, including conflicts and missing references. + If objects are created as new copies, each entry in the + `successResults` array includes a `destinationId` + attribute. + examples: + importObjectsResponse: + $ref: >- + #/components/examples/Serverless_saved_objects_import_objects_response + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Serverless_saved_objects_400_response' + security: + - Serverless_saved_objects_apiKeyAuth: [] + /s/{spaceId}/api/saved_objects/_export: + post: + summary: Export saved objects + operationId: exportSavedObjects + description: > + Retrieves sets of saved objects that you want to import into Kibana. + + You must include `type` or `objects` in the request body. + + + NOTE: The `savedObjects.maxImportExportSize` configuration setting + limits the number of saved objects which may be exported. + + + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - saved objects + parameters: + - $ref: '#/components/parameters/Serverless_saved_objects_kbn_xsrf' + - $ref: '#/components/parameters/Serverless_saved_objects_space_id' + requestBody: + required: true + content: + application/json: + schema: + $ref: >- + #/components/schemas/Serverless_saved_objects_export_objects_request + examples: + exportSavedObjectsRequest: + $ref: >- + #/components/examples/Serverless_saved_objects_export_objects_request + responses: + '200': + description: Indicates a successful call. + content: + application/x-ndjson: + schema: + type: object + additionalProperties: true + examples: + exportSavedObjectsResponse: + $ref: >- + #/components/examples/Serverless_saved_objects_export_objects_response + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Serverless_saved_objects_400_response' + security: + - Serverless_saved_objects_apiKeyAuth: [] + /s/{spaceId}/api/saved_objects/_import: + post: + summary: Import saved objects + operationId: importSavedObjects + description: > + Creates sets of Kibana saved objects from a file created by the export + API. + + Saved objects can be imported only into the same version, a newer minor + on the same major, or the next major. Exported saved objects are not + backwards compatible and cannot be imported into an older version of + Kibana. + + + This functionality is in technical preview and may be changed or removed + in a future release. Elastic will work to fix any issues, but features + in technical preview are not subject to the support SLA of official GA + features. + tags: + - saved objects + parameters: + - $ref: '#/components/parameters/Serverless_saved_objects_kbn_xsrf' + - $ref: '#/components/parameters/Serverless_saved_objects_space_id' + - $ref: '#/components/parameters/Serverless_saved_objects_compatibility_mode' + - $ref: '#/components/parameters/Serverless_saved_objects_create_new_copies' + - $ref: '#/components/parameters/Serverless_saved_objects_overwrite' + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: >- + #/components/schemas/Serverless_saved_objects_import_objects_request + examples: + importObjectsRequest: + $ref: >- + #/components/examples/Serverless_saved_objects_import_objects_request + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: >- + #/components/schemas/Serverless_saved_objects_200_import_objects_response + examples: + importObjectsResponse: + $ref: >- + #/components/examples/Serverless_saved_objects_import_objects_response + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/Serverless_saved_objects_400_response' + security: + - Serverless_saved_objects_apiKeyAuth: [] +components: + securitySchemes: + Connectors_apiKeyAuth: + type: apiKey + in: header + name: Authorization + description: > + Serverless APIs support only key-based authentication. You must create + an API key and use the encoded value in the request header. For example: + 'Authorization: ApiKey base64AccessApiKey'. + Data_views_basicAuth: + type: http + scheme: basic + Data_views_apiKeyAuth: + type: apiKey + in: header + name: Authorization + description: > + Serverless APIs support only key-based authentication. You must create + an API key and use the encoded value in the request header. For example: + 'Authorization: ApiKey base64AccessApiKey'. + Machine_learning_APIs_apiKeyAuth: + type: apiKey + in: header + name: ApiKey + Serverless_saved_objects_apiKeyAuth: + type: apiKey + in: header + name: Authorization + description: > + Serverless APIs support only key-based authentication. You must create + an API key and use the encoded value in the request header. For example: + 'Authorization: ApiKey base64AccessApiKey'. + parameters: + Connectors_kbn_xsrf: + schema: + type: string + in: header + name: kbn-xsrf + description: Cross-site request forgery protection + required: true + Connectors_connector_id: + in: path + name: connectorId + description: An identifier for the connector. + required: true + schema: + type: string + example: df770e30-8b8b-11ed-a780-3b746c987a81 + Data_views_space_id: + in: path + name: spaceId + description: >- + An identifier for the space. If `/s/` and the identifier are omitted + from the path, the default space is used. + required: true + schema: + type: string + example: default + Data_views_kbn_xsrf: + schema: + type: string + in: header + name: kbn-xsrf + description: Cross-site request forgery protection + required: true + Data_views_view_id: + in: path + name: viewId + description: An identifier for the data view. + required: true + schema: + type: string + example: ff959d40-b880-11e8-a6d9-e546fe2bba5f + Data_views_field_name: + in: path + name: fieldName + description: The name of the runtime field. + required: true + schema: + type: string + example: hour_of_day + Machine_learning_APIs_simulateParam: + in: query + name: simulate + description: >- + When true, simulates the synchronization by returning only the list of + actions that would be performed. + required: false + schema: + type: boolean + example: 'true' + Serverless_saved_objects_kbn_xsrf: + schema: + type: string + in: header + name: kbn-xsrf + description: Cross-site request forgery protection + required: true + Serverless_saved_objects_space_id: + in: path + name: spaceId + description: >- + An identifier for the space. If `/s/` and the identifier are omitted + from the path, the default space is used. + required: true + schema: + type: string + example: default + Serverless_saved_objects_compatibility_mode: + in: query + name: compatibilityMode + schema: + type: boolean + required: false + description: > + Applies various adjustments to the saved objects that are being imported + to maintain compatibility between different Kibana versions. Use this + option only if you encounter issues with imported saved objects. NOTE: + This option cannot be used with the `createNewCopies` option. + Serverless_saved_objects_create_new_copies: + in: query + name: createNewCopies + schema: + type: boolean + required: false + description: > + Creates copies of saved objects, regenerates each object ID, and resets + the origin. When used, potential conflict errors are avoided. NOTE: This + option cannot be used with the `overwrite` and `compatibilityMode` + options. + Serverless_saved_objects_overwrite: + in: query + name: overwrite + schema: + type: boolean + required: false + description: > + Overwrites saved objects when they already exist. When used, potential + conflict errors are automatically resolved by overwriting the + destination object. NOTE: This option cannot be used with the + `createNewCopies` option. + schemas: + Connectors_create_connector_request_bedrock: + title: Create Amazon Bedrock connector request + description: >- + The Amazon Bedrock connector uses axios to send a POST request to Amazon + Bedrock. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_bedrock' + connector_type_id: + type: string + description: The type of connector. + enum: + - .bedrock + example: .bedrock + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_bedrock' + Connectors_create_connector_request_gemini: + title: Create Google Gemini connector request + description: >- + The Google Gemini connector uses axios to send a POST request to Google + Gemini. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_gemini' + connector_type_id: + type: string + description: The type of connector. + enum: + - .gemini + example: .gemini + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_gemini' + Connectors_create_connector_request_cases_webhook: + title: Create Webhook - Case Managment connector request + description: > + The Webhook - Case Management connector uses axios to send POST, PUT, + and GET requests to a case management RESTful API web service. + type: object + required: + - config + - connector_type_id + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_cases_webhook' + connector_type_id: + type: string + description: The type of connector. + enum: + - .cases-webhook + example: .cases-webhook + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_cases_webhook' + Connectors_create_connector_request_d3security: + title: Create D3 Security connector request + description: > + The connector uses axios to send a POST request to a D3 Security + endpoint. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_d3security' + connector_type_id: + type: string + description: The type of connector. + enum: + - .d3security + example: .d3security + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_d3security' + Connectors_create_connector_request_email: + title: Create email connector request + description: > + The email connector uses the SMTP protocol to send mail messages, using + an integration of Nodemailer. An exception is Microsoft Exchange, which + uses HTTP protocol for sending emails, Send mail. Email message text is + sent as both plain text and html text. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_email' + connector_type_id: + type: string + description: The type of connector. + enum: + - .email + example: .email + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_email' + Connectors_create_connector_request_genai: + title: Create OpenAI connector request + description: > + The OpenAI connector uses axios to send a POST request to either OpenAI + or Azure OpenAPI. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_genai' + connector_type_id: + type: string + description: The type of connector. + enum: + - .gen-ai + example: .gen-ai + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_genai' + Connectors_create_connector_request_index: + title: Create index connector request + description: The index connector indexes a document into Elasticsearch. + type: object + required: + - config + - connector_type_id + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_index' + connector_type_id: + type: string + description: The type of connector. + enum: + - .index + example: .index + name: + type: string + description: The display name for the connector. + example: my-connector + Connectors_create_connector_request_jira: + title: Create Jira connector request + description: The Jira connector uses the REST API v2 to create Jira issues. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_jira' + connector_type_id: + type: string + description: The type of connector. + enum: + - .jira + example: .jira + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_jira' + Connectors_create_connector_request_opsgenie: + title: Create Opsgenie connector request + description: The Opsgenie connector uses the Opsgenie alert API. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_opsgenie' + connector_type_id: + type: string + description: The type of connector. + enum: + - .opsgenie + example: .opsgenie + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_opsgenie' + Connectors_create_connector_request_pagerduty: + title: Create PagerDuty connector request + description: > + The PagerDuty connector uses the v2 Events API to trigger, acknowledge, + and resolve PagerDuty alerts. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_pagerduty' + connector_type_id: + type: string + description: The type of connector. + enum: + - .pagerduty + example: .pagerduty + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_pagerduty' + Connectors_create_connector_request_resilient: + title: Create IBM Resilient connector request + description: >- + The IBM Resilient connector uses the RESILIENT REST v2 to create IBM + Resilient incidents. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_resilient' + connector_type_id: + description: The type of connector. + type: string + example: .resilient + enum: + - .resilient + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_resilient' + Connectors_create_connector_request_sentinelone: + title: Create SentinelOne connector request + description: > + The SentinelOne connector communicates with SentinelOne Management + Console via REST API. This functionality is in technical preview and may + be changed or removed in a future release. Elastic will work to fix any + issues, but features in technical preview are not subject to the support + SLA of official GA features. + x-technical-preview: true + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_sentinelone' + connector_type_id: + type: string + description: The type of connector. + enum: + - .sentinelone + example: .sentinelone + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_sentinelone' + Connectors_create_connector_request_serverlog: + title: Create server log connector request + description: This connector writes an entry to the Kibana server log. + type: object + required: + - connector_type_id + - name + properties: + connector_type_id: + type: string + description: The type of connector. + enum: + - .server-log + example: .server-log + name: + type: string + description: The display name for the connector. + example: my-connector + Connectors_create_connector_request_servicenow: + title: Create ServiceNow ITSM connector request + description: > + The ServiceNow ITSM connector uses the import set API to create + ServiceNow incidents. You can use the connector for rule actions and + cases. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_servicenow' + connector_type_id: + type: string + description: The type of connector. + enum: + - .servicenow + example: .servicenow + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_servicenow' + Connectors_create_connector_request_servicenow_itom: + title: Create ServiceNow ITOM connector request + description: > + The ServiceNow ITOM connector uses the event API to create ServiceNow + events. You can use the connector for rule actions. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_servicenow_itom' + connector_type_id: + type: string + description: The type of connector. + enum: + - .servicenow-itom + example: .servicenow-itom + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_servicenow' + Connectors_create_connector_request_servicenow_sir: + title: Create ServiceNow SecOps connector request + description: > + The ServiceNow SecOps connector uses the import set API to create + ServiceNow security incidents. You can use the connector for rule + actions and cases. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_servicenow' + connector_type_id: + type: string + description: The type of connector. + enum: + - .servicenow-sir + example: .servicenow-sir + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_servicenow' + Connectors_create_connector_request_slack_api: + title: Create Slack connector request + description: The Slack connector uses an API method to send Slack messages. + type: object + required: + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_slack_api' + connector_type_id: + type: string + description: The type of connector. + enum: + - .slack_api + example: .slack_api + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_slack_api' + Connectors_create_connector_request_slack_webhook: + title: Create Slack connector request + description: The Slack connector uses Slack Incoming Webhooks. + type: object + required: + - connector_type_id + - name + - secrets + properties: + connector_type_id: + type: string + description: The type of connector. + enum: + - .slack + example: .slack + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_slack_webhook' + Connectors_create_connector_request_swimlane: + title: Create Swimlane connector request + description: >- + The Swimlane connector uses the Swimlane REST API to create Swimlane + records. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_swimlane' + connector_type_id: + type: string + description: The type of connector. + enum: + - .swimlane + example: .swimlane + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_swimlane' + Connectors_create_connector_request_teams: + title: Create Microsoft Teams connector request + description: The Microsoft Teams connector uses Incoming Webhooks. + type: object + required: + - connector_type_id + - name + - secrets + properties: + connector_type_id: + type: string + description: The type of connector. + enum: + - .teams + example: .teams + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_teams' + Connectors_create_connector_request_tines: + title: Create Tines connector request + description: > + The Tines connector uses Tines Webhook actions to send events via POST + request. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_tines' + connector_type_id: + type: string + description: The type of connector. + enum: + - .tines + example: .tines + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_tines' + Connectors_create_connector_request_torq: + title: Create Torq connector request + description: > + The Torq connector uses a Torq webhook to trigger workflows with Kibana + actions. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_torq' + connector_type_id: + type: string + description: The type of connector. + enum: + - .torq + example: .torq + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_torq' + Connectors_create_connector_request_webhook: + title: Create Webhook connector request + description: > + The Webhook connector uses axios to send a POST or PUT request to a web + service. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_webhook' + connector_type_id: + type: string + description: The type of connector. + enum: + - .webhook + example: .webhook + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_webhook' + Connectors_create_connector_request_xmatters: + title: Create xMatters connector request + description: > + The xMatters connector uses the xMatters Workflow for Elastic to send + actionable alerts to on-call xMatters resources. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_xmatters' + connector_type_id: + type: string + description: The type of connector. + enum: + - .xmatters + example: .xmatters + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_xmatters' + Connectors_config_properties_bedrock: + title: Connector request properties for an Amazon Bedrock connector + description: Defines properties for connectors when type is `.bedrock`. + type: object + required: + - apiUrl + properties: + apiUrl: + type: string + description: The Amazon Bedrock request URL. + defaultModel: + type: string + description: > + The generative artificial intelligence model for Amazon Bedrock to + use. Current support is for the Anthropic Claude models. + default: anthropic.claude-3-sonnet-20240229-v1:0 + Connectors_secrets_properties_bedrock: + title: Connector secrets properties for an Amazon Bedrock connector + description: Defines secrets for connectors when type is `.bedrock`. + type: object + required: + - accessKey + - secret + properties: + accessKey: + type: string + description: The AWS access key for authentication. + secret: + type: string + description: The AWS secret for authentication. + Connectors_config_properties_gemini: + title: Connector request properties for an Google Gemini connector + description: Defines properties for connectors when type is `.gemini`. + type: object + required: + - apiUrl + - gcpRegion + - gcpProjectID + properties: + apiUrl: + type: string + description: The Google Gemini request URL. + defaultModel: + type: string + description: >- + The generative artificial intelligence model for Google Gemini to + use. + default: gemini-1.5-pro-preview-0409 + gcpRegion: + type: string + description: The GCP region where the Vertex AI endpoint enabled. + gcpProjectID: + type: string + description: The Google ProjectID that has Vertex AI endpoint enabled. + Connectors_secrets_properties_gemini: + title: Connector secrets properties for a Google Gemini connector + description: Defines secrets for connectors when type is `.gemini`. + type: object + required: + - credentialsJSON + properties: + credentialsJSON: + type: string + description: >- + The service account credentials JSON file. The service account + should have Vertex AI user IAM role assigned to it. + Connectors_config_properties_cases_webhook: + title: Connector request properties for Webhook - Case Management connector + required: + - createIncidentJson + - createIncidentResponseKey + - createIncidentUrl + - getIncidentResponseExternalTitleKey + - getIncidentUrl + - updateIncidentJson + - updateIncidentUrl + - viewIncidentUrl + description: Defines properties for connectors when type is `.cases-webhook`. + type: object + properties: + createCommentJson: + type: string + description: > + A JSON payload sent to the create comment URL to create a case + comment. You can use variables to add Kibana Cases data to the + payload. The required variable is `case.comment`. Due to Mustache + template variables (the text enclosed in triple braces, for example, + `{{{case.title}}}`), the JSON is not validated when you create the + connector. The JSON is validated once the Mustache variables have + been placed when the REST method runs. Manually ensure that the JSON + is valid, disregarding the Mustache variables, so the later + validation will pass. + example: '{"body": {{{case.comment}}}}' + createCommentMethod: + type: string + description: > + The REST API HTTP request method to create a case comment in the + third-party system. Valid values are `patch`, `post`, and `put`. + default: put + enum: + - patch + - post + - put + createCommentUrl: + type: string + description: > + The REST API URL to create a case comment by ID in the third-party + system. You can use a variable to add the external system ID to the + URL. If you are using the `xpack.actions.allowedHosts setting`, add + the hostname to the allowed hosts. + example: https://example.com/issue/{{{external.system.id}}}/comment + createIncidentJson: + type: string + description: > + A JSON payload sent to the create case URL to create a case. You can + use variables to add case data to the payload. Required variables + are `case.title` and `case.description`. Due to Mustache template + variables (which is the text enclosed in triple braces, for example, + `{{{case.title}}}`), the JSON is not validated when you create the + connector. The JSON is validated after the Mustache variables have + been placed when REST method runs. Manually ensure that the JSON is + valid to avoid future validation errors; disregard Mustache + variables during your review. + example: >- + {"fields": {"summary": {{{case.title}}},"description": + {{{case.description}}},"labels": {{{case.tags}}}}} + createIncidentMethod: + type: string + description: > + The REST API HTTP request method to create a case in the third-party + system. Valid values are `patch`, `post`, and `put`. + enum: + - patch + - post + - put + default: post + createIncidentResponseKey: + type: string + description: >- + The JSON key in the create external case response that contains the + case ID. + createIncidentUrl: + type: string + description: > + The REST API URL to create a case in the third-party system. If you + are using the `xpack.actions.allowedHosts` setting, add the hostname + to the allowed hosts. + getIncidentResponseExternalTitleKey: + type: string + description: >- + The JSON key in get external case response that contains the case + title. + getIncidentUrl: + type: string + description: > + The REST API URL to get the case by ID from the third-party system. + If you are using the `xpack.actions.allowedHosts` setting, add the + hostname to the allowed hosts. You can use a variable to add the + external system ID to the URL. Due to Mustache template variables + (the text enclosed in triple braces, for example, + `{{{case.title}}}`), the JSON is not validated when you create the + connector. The JSON is validated after the Mustache variables have + been placed when REST method runs. Manually ensure that the JSON is + valid, disregarding the Mustache variables, so the later validation + will pass. + example: https://example.com/issue/{{{external.system.id}}} + hasAuth: + type: boolean + description: >- + If true, a username and password for login type authentication must + be provided. + default: true + headers: + type: string + description: > + A set of key-value pairs sent as headers with the request URLs for + the create case, update case, get case, and create comment methods. + updateIncidentJson: + type: string + description: > + The JSON payload sent to the update case URL to update the case. You + can use variables to add Kibana Cases data to the payload. Required + variables are `case.title` and `case.description`. Due to Mustache + template variables (which is the text enclosed in triple braces, for + example, `{{{case.title}}}`), the JSON is not validated when you + create the connector. The JSON is validated after the Mustache + variables have been placed when REST method runs. Manually ensure + that the JSON is valid to avoid future validation errors; disregard + Mustache variables during your review. + example: >- + {"fields": {"summary": {{{case.title}}},"description": + {{{case.description}}},"labels": {{{case.tags}}}}} + updateIncidentMethod: + type: string + description: > + The REST API HTTP request method to update the case in the + third-party system. Valid values are `patch`, `post`, and `put`. + default: put + enum: + - patch + - post + - put + updateIncidentUrl: + type: string + description: > + The REST API URL to update the case by ID in the third-party system. + You can use a variable to add the external system ID to the URL. If + you are using the `xpack.actions.allowedHosts` setting, add the + hostname to the allowed hosts. + example: https://example.com/issue/{{{external.system.ID}}} + viewIncidentUrl: + type: string + description: > + The URL to view the case in the external system. You can use + variables to add the external system ID or external system title to + the URL. + example: >- + https://testing-jira.atlassian.net/browse/{{{external.system.title}}} + Connectors_secrets_properties_cases_webhook: + title: Connector secrets properties for Webhook - Case Management connector + type: object + properties: + password: + type: string + description: >- + The password for HTTP basic authentication. If `hasAuth` is set to + `true`, this property is required. + user: + type: string + description: >- + The username for HTTP basic authentication. If `hasAuth` is set to + `true`, this property is required. + Connectors_config_properties_d3security: + title: Connector request properties for a D3 Security connector + description: Defines properties for connectors when type is `.d3security`. + type: object + required: + - url + properties: + url: + type: string + description: > + The D3 Security API request URL. If you are using the + `xpack.actions.allowedHosts` setting, add the hostname to the + allowed hosts. + Connectors_secrets_properties_d3security: + title: Connector secrets properties for a D3 Security connector + description: Defines secrets for connectors when type is `.d3security`. + required: + - token + type: object + properties: + token: + type: string + description: The D3 Security token. + Connectors_config_properties_email: + title: Connector request properties for an email connector + description: Defines properties for connectors when type is `.email`. + required: + - from + type: object + properties: + clientId: + description: > + The client identifier, which is a part of OAuth 2.0 client + credentials authentication, in GUID format. If `service` is + `exchange_server`, this property is required. + type: string + nullable: true + from: + description: > + The from address for all emails sent by the connector. It must be + specified in `user@host-name` format. + type: string + hasAuth: + description: > + Specifies whether a user and password are required inside the + secrets configuration. + default: true + type: boolean + host: + description: > + The host name of the service provider. If the `service` is + `elastic_cloud` (for Elastic Cloud notifications) or one of + Nodemailer's well-known email service providers, this property is + ignored. If `service` is `other`, this property must be defined. + type: string + oauthTokenUrl: + type: string + nullable: true + port: + description: > + The port to connect to on the service provider. If the `service` is + `elastic_cloud` (for Elastic Cloud notifications) or one of + Nodemailer's well-known email service providers, this property is + ignored. If `service` is `other`, this property must be defined. + type: integer + secure: + description: > + Specifies whether the connection to the service provider will use + TLS. If the `service` is `elastic_cloud` (for Elastic Cloud + notifications) or one of Nodemailer's well-known email service + providers, this property is ignored. + type: boolean + service: + description: | + The name of the email service. + type: string + enum: + - elastic_cloud + - exchange_server + - gmail + - other + - outlook365 + - ses + tenantId: + description: > + The tenant identifier, which is part of OAuth 2.0 client credentials + authentication, in GUID format. If `service` is `exchange_server`, + this property is required. + type: string + nullable: true + Connectors_secrets_properties_email: + title: Connector secrets properties for an email connector + description: Defines secrets for connectors when type is `.email`. + type: object + properties: + clientSecret: + type: string + description: > + The Microsoft Exchange Client secret for OAuth 2.0 client + credentials authentication. It must be URL-encoded. If `service` is + `exchange_server`, this property is required. + password: + type: string + description: > + The password for HTTP basic authentication. If `hasAuth` is set to + `true`, this property is required. + user: + type: string + description: > + The username for HTTP basic authentication. If `hasAuth` is set to + `true`, this property is required. + Connectors_config_properties_genai_azure: + title: >- + Connector request properties for an OpenAI connector that uses Azure + OpenAI + description: > + Defines properties for connectors when type is `.gen-ai` and the API + provider is `Azure OpenAI'. + type: object + required: + - apiProvider + - apiUrl + properties: + apiProvider: + type: string + description: The OpenAI API provider. + enum: + - Azure OpenAI + apiUrl: + type: string + description: The OpenAI API endpoint. + Connectors_config_properties_genai_openai: + title: Connector request properties for an OpenAI connector + description: > + Defines properties for connectors when type is `.gen-ai` and the API + provider is `OpenAI'. + type: object + required: + - apiProvider + - apiUrl + properties: + apiProvider: + type: string + description: The OpenAI API provider. + enum: + - OpenAI + apiUrl: + type: string + description: The OpenAI API endpoint. + defaultModel: + type: string + description: The default model to use for requests. + Connectors_config_properties_genai: + title: Connector request properties for an OpenAI connector + description: Defines properties for connectors when type is `.gen-ai`. + oneOf: + - $ref: '#/components/schemas/Connectors_config_properties_genai_azure' + - $ref: '#/components/schemas/Connectors_config_properties_genai_openai' + discriminator: + propertyName: apiProvider + mapping: + Azure OpenAI: '#/components/schemas/Connectors_config_properties_genai_azure' + OpenAI: '#/components/schemas/Connectors_config_properties_genai_openai' + Connectors_secrets_properties_genai: + title: Connector secrets properties for an OpenAI connector + description: Defines secrets for connectors when type is `.gen-ai`. + type: object + properties: + apiKey: + type: string + description: The OpenAI API key. + Connectors_config_properties_index: + title: Connector request properties for an index connector + required: + - index + description: Defines properties for connectors when type is `.index`. + type: object + properties: + executionTimeField: + description: A field that indicates when the document was indexed. + default: null + type: string + nullable: true + index: + description: The Elasticsearch index to be written to. + type: string + refresh: + description: > + The refresh policy for the write request, which affects when changes + are made visible to search. Refer to the refresh setting for + Elasticsearch document APIs. + default: false + type: boolean + Connectors_config_properties_jira: + title: Connector request properties for a Jira connector + required: + - apiUrl + - projectKey + description: Defines properties for connectors when type is `.jira`. + type: object + properties: + apiUrl: + description: The Jira instance URL. + type: string + projectKey: + description: The Jira project key. + type: string + Connectors_secrets_properties_jira: + title: Connector secrets properties for a Jira connector + required: + - apiToken + - email + description: Defines secrets for connectors when type is `.jira`. + type: object + properties: + apiToken: + description: The Jira API authentication token for HTTP basic authentication. + type: string + email: + description: The account email for HTTP Basic authentication. + type: string + Connectors_config_properties_opsgenie: + title: Connector request properties for an Opsgenie connector + required: + - apiUrl + description: Defines properties for connectors when type is `.opsgenie`. + type: object + properties: + apiUrl: + description: > + The Opsgenie URL. For example, `https://api.opsgenie.com` or + `https://api.eu.opsgenie.com`. If you are using the + `xpack.actions.allowedHosts` setting, add the hostname to the + allowed hosts. + type: string + Connectors_secrets_properties_opsgenie: + title: Connector secrets properties for an Opsgenie connector + required: + - apiKey + description: Defines secrets for connectors when type is `.opsgenie`. + type: object + properties: + apiKey: + description: The Opsgenie API authentication key for HTTP Basic authentication. + type: string + Connectors_config_properties_pagerduty: + title: Connector request properties for a PagerDuty connector + description: Defines properties for connectors when type is `.pagerduty`. + type: object + properties: + apiUrl: + description: The PagerDuty event URL. + type: string + nullable: true + example: https://events.pagerduty.com/v2/enqueue + Connectors_secrets_properties_pagerduty: + title: Connector secrets properties for a PagerDuty connector + description: Defines secrets for connectors when type is `.pagerduty`. + type: object + required: + - routingKey + properties: + routingKey: + description: > + A 32 character PagerDuty Integration Key for an integration on a + service. + type: string + Connectors_config_properties_resilient: + title: Connector request properties for a IBM Resilient connector + required: + - apiUrl + - orgId + description: Defines properties for connectors when type is `.resilient`. + type: object + properties: + apiUrl: + description: The IBM Resilient instance URL. + type: string + orgId: + description: The IBM Resilient organization ID. + type: string + Connectors_secrets_properties_resilient: + title: Connector secrets properties for IBM Resilient connector + required: + - apiKeyId + - apiKeySecret + description: Defines secrets for connectors when type is `.resilient`. + type: object + properties: + apiKeyId: + type: string + description: The authentication key ID for HTTP Basic authentication. + apiKeySecret: + type: string + description: The authentication key secret for HTTP Basic authentication. + Connectors_config_properties_sentinelone: + title: Connector request properties for a SentinelOne connector + required: + - url + description: Defines properties for connectors when type is `.sentinelone`. + type: object + properties: + url: + description: > + The SentinelOne tenant URL. If you are using the + `xpack.actions.allowedHosts` setting, add the hostname to the + allowed hosts. + type: string + Connectors_secrets_properties_sentinelone: + title: Connector secrets properties for a SentinelOne connector + description: Defines secrets for connectors when type is `.sentinelone`. + type: object + required: + - token + properties: + token: + description: The A SentinelOne API token. + type: string + Connectors_config_properties_servicenow: + title: Connector request properties for a ServiceNow ITSM connector + required: + - apiUrl + description: Defines properties for connectors when type is `.servicenow`. + type: object + properties: + apiUrl: + type: string + description: The ServiceNow instance URL. + clientId: + description: > + The client ID assigned to your OAuth application. This property is + required when `isOAuth` is `true`. + type: string + isOAuth: + description: > + The type of authentication to use. The default value is false, which + means basic authentication is used instead of open authorization + (OAuth). + default: false + type: boolean + jwtKeyId: + description: > + The key identifier assigned to the JWT verifier map of your OAuth + application. This property is required when `isOAuth` is `true`. + type: string + userIdentifierValue: + description: > + The identifier to use for OAuth authentication. This identifier + should be the user field you selected when you created an OAuth JWT + API endpoint for external clients in your ServiceNow instance. For + example, if the selected user field is `Email`, the user identifier + should be the user's email address. This property is required when + `isOAuth` is `true`. + type: string + usesTableApi: + description: > + Determines whether the connector uses the Table API or the Import + Set API. This property is supported only for ServiceNow ITSM and + ServiceNow SecOps connectors. NOTE: If this property is set to + `false`, the Elastic application should be installed in ServiceNow. + default: true + type: boolean + Connectors_secrets_properties_servicenow: + title: >- + Connector secrets properties for ServiceNow ITOM, ServiceNow ITSM, and + ServiceNow SecOps connectors + description: >- + Defines secrets for connectors when type is `.servicenow`, + `.servicenow-sir`, or `.servicenow-itom`. + type: object + properties: + clientSecret: + type: string + description: >- + The client secret assigned to your OAuth application. This property + is required when `isOAuth` is `true`. + password: + type: string + description: >- + The password for HTTP basic authentication. This property is + required when `isOAuth` is `false`. + privateKey: + type: string + description: >- + The RSA private key that you created for use in ServiceNow. This + property is required when `isOAuth` is `true`. + privateKeyPassword: + type: string + description: >- + The password for the RSA private key. This property is required when + `isOAuth` is `true` and you set a password on your private key. + username: + type: string + description: >- + The username for HTTP basic authentication. This property is + required when `isOAuth` is `false`. + Connectors_config_properties_servicenow_itom: + title: Connector request properties for a ServiceNow ITSM connector + required: + - apiUrl + description: Defines properties for connectors when type is `.servicenow`. + type: object + properties: + apiUrl: + type: string + description: The ServiceNow instance URL. + clientId: + description: > + The client ID assigned to your OAuth application. This property is + required when `isOAuth` is `true`. + type: string + isOAuth: + description: > + The type of authentication to use. The default value is false, which + means basic authentication is used instead of open authorization + (OAuth). + default: false + type: boolean + jwtKeyId: + description: > + The key identifier assigned to the JWT verifier map of your OAuth + application. This property is required when `isOAuth` is `true`. + type: string + userIdentifierValue: + description: > + The identifier to use for OAuth authentication. This identifier + should be the user field you selected when you created an OAuth JWT + API endpoint for external clients in your ServiceNow instance. For + example, if the selected user field is `Email`, the user identifier + should be the user's email address. This property is required when + `isOAuth` is `true`. + type: string + Connectors_config_properties_slack_api: + title: Connector request properties for a Slack connector + description: Defines properties for connectors when type is `.slack_api`. + type: object + properties: + allowedChannels: + type: array + description: A list of valid Slack channels. + items: + type: object + required: + - id + - name + maxItems: 25 + properties: + id: + type: string + description: The Slack channel ID. + example: C123ABC456 + minLength: 1 + name: + type: string + description: The Slack channel name. + minLength: 1 + Connectors_secrets_properties_slack_api: + title: Connector secrets properties for a Web API Slack connector + description: Defines secrets for connectors when type is `.slack`. + required: + - token + type: object + properties: + token: + type: string + description: Slack bot user OAuth token. + Connectors_secrets_properties_slack_webhook: + title: Connector secrets properties for a Webhook Slack connector + description: Defines secrets for connectors when type is `.slack`. + required: + - webhookUrl + type: object + properties: + webhookUrl: + type: string + description: Slack webhook url. + Connectors_config_properties_swimlane: + title: Connector request properties for a Swimlane connector + required: + - apiUrl + - appId + - connectorType + description: Defines properties for connectors when type is `.swimlane`. + type: object + properties: + apiUrl: + description: The Swimlane instance URL. + type: string + appId: + description: The Swimlane application ID. + type: string + connectorType: + description: >- + The type of connector. Valid values are `all`, `alerts`, and + `cases`. + type: string + enum: + - all + - alerts + - cases + mappings: + title: Connector mappings properties for a Swimlane connector + description: The field mapping. + type: object + properties: + alertIdConfig: + title: Alert identifier mapping + description: Mapping for the alert ID. + type: object + required: + - fieldType + - id + - key + - name + properties: + fieldType: + type: string + description: The type of field in Swimlane. + id: + type: string + description: The identifier for the field in Swimlane. + key: + type: string + description: The key for the field in Swimlane. + name: + type: string + description: The name of the field in Swimlane. + caseIdConfig: + title: Case identifier mapping + description: Mapping for the case ID. + type: object + required: + - fieldType + - id + - key + - name + properties: + fieldType: + type: string + description: The type of field in Swimlane. + id: + type: string + description: The identifier for the field in Swimlane. + key: + type: string + description: The key for the field in Swimlane. + name: + type: string + description: The name of the field in Swimlane. + caseNameConfig: + title: Case name mapping + description: Mapping for the case name. + type: object + required: + - fieldType + - id + - key + - name + properties: + fieldType: + type: string + description: The type of field in Swimlane. + id: + type: string + description: The identifier for the field in Swimlane. + key: + type: string + description: The key for the field in Swimlane. + name: + type: string + description: The name of the field in Swimlane. + commentsConfig: + title: Case comment mapping + description: Mapping for the case comments. + type: object + required: + - fieldType + - id + - key + - name + properties: + fieldType: + type: string + description: The type of field in Swimlane. + id: + type: string + description: The identifier for the field in Swimlane. + key: + type: string + description: The key for the field in Swimlane. + name: + type: string + description: The name of the field in Swimlane. + descriptionConfig: + title: Case description mapping + description: Mapping for the case description. + type: object + required: + - fieldType + - id + - key + - name + properties: + fieldType: + type: string + description: The type of field in Swimlane. + id: + type: string + description: The identifier for the field in Swimlane. + key: + type: string + description: The key for the field in Swimlane. + name: + type: string + description: The name of the field in Swimlane. + ruleNameConfig: + title: Rule name mapping + description: Mapping for the name of the alert's rule. + type: object + required: + - fieldType + - id + - key + - name + properties: + fieldType: + type: string + description: The type of field in Swimlane. + id: + type: string + description: The identifier for the field in Swimlane. + key: + type: string + description: The key for the field in Swimlane. + name: + type: string + description: The name of the field in Swimlane. + severityConfig: + title: Severity mapping + description: Mapping for the severity. + type: object + required: + - fieldType + - id + - key + - name + properties: + fieldType: + type: string + description: The type of field in Swimlane. + id: + type: string + description: The identifier for the field in Swimlane. + key: + type: string + description: The key for the field in Swimlane. + name: + type: string + description: The name of the field in Swimlane. + Connectors_secrets_properties_swimlane: + title: Connector secrets properties for a Swimlane connector + description: Defines secrets for connectors when type is `.swimlane`. + type: object + properties: + apiToken: + description: Swimlane API authentication token. + type: string + Connectors_secrets_properties_teams: + title: Connector secrets properties for a Microsoft Teams connector + description: Defines secrets for connectors when type is `.teams`. + type: object + required: + - webhookUrl + properties: + webhookUrl: + type: string + description: > + The URL of the incoming webhook. If you are using the + `xpack.actions.allowedHosts` setting, add the hostname to the + allowed hosts. + Connectors_config_properties_tines: + title: Connector request properties for a Tines connector + description: Defines properties for connectors when type is `.tines`. + type: object + required: + - url + properties: + url: + description: > + The Tines tenant URL. If you are using the + `xpack.actions.allowedHosts` setting, make sure this hostname is + added to the allowed hosts. + type: string + Connectors_secrets_properties_tines: + title: Connector secrets properties for a Tines connector + description: Defines secrets for connectors when type is `.tines`. + type: object + required: + - email + - token + properties: + email: + description: The email used to sign in to Tines. + type: string + token: + description: The Tines API token. + type: string + Connectors_config_properties_torq: + title: Connector request properties for a Torq connector + description: Defines properties for connectors when type is `.torq`. + type: object + required: + - webhookIntegrationUrl + properties: + webhookIntegrationUrl: + description: The endpoint URL of the Elastic Security integration in Torq. + type: string + Connectors_secrets_properties_torq: + title: Connector secrets properties for a Torq connector + description: Defines secrets for connectors when type is `.torq`. + type: object + required: + - token + properties: + token: + description: The secret of the webhook authentication header. + type: string + Connectors_config_properties_webhook: + title: Connector request properties for a Webhook connector + description: Defines properties for connectors when type is `.webhook`. + type: object + properties: + authType: + type: string + nullable: true + enum: + - webhook-authentication-basic + - webhook-authentication-ssl + description: | + The type of authentication to use: basic, SSL, or none. + ca: + type: string + description: > + A base64 encoded version of the certificate authority file that the + connector can trust to sign and validate certificates. This option + is available for all authentication types. + certType: + type: string + description: > + If the `authType` is `webhook-authentication-ssl`, specifies whether + the certificate authentication data is in a CRT and key file format + or a PFX file format. + enum: + - ssl-crt-key + - ssl-pfx + hasAuth: + type: boolean + description: > + If `true`, a user name and password must be provided for login type + authentication. + headers: + type: object + nullable: true + description: A set of key-value pairs sent as headers with the request. + method: + type: string + default: post + enum: + - post + - put + description: | + The HTTP request method, either `post` or `put`. + url: + type: string + description: > + The request URL. If you are using the `xpack.actions.allowedHosts` + setting, add the hostname to the allowed hosts. + verificationMode: + type: string + enum: + - certificate + - full + - none + default: full + description: > + Controls the verification of certificates. Use `full` to validate + that the certificate has an issue date within the `not_before` and + `not_after` dates, chains to a trusted certificate authority (CA), + and has a hostname or IP address that matches the names within the + certificate. Use `certificate` to validate the certificate and + verify that it is signed by a trusted authority; this option does + not check the certificate hostname. Use `none` to skip certificate + validation. + Connectors_secrets_properties_webhook: + title: Connector secrets properties for a Webhook connector + description: Defines secrets for connectors when type is `.webhook`. + type: object + properties: + crt: + type: string + description: >- + If `authType` is `webhook-authentication-ssl` and `certType` is + `ssl-crt-key`, it is a base64 encoded version of the CRT or CERT + file. + key: + type: string + description: >- + If `authType` is `webhook-authentication-ssl` and `certType` is + `ssl-crt-key`, it is a base64 encoded version of the KEY file. + pfx: + type: string + description: >- + If `authType` is `webhook-authentication-ssl` and `certType` is + `ssl-pfx`, it is a base64 encoded version of the PFX or P12 file. + password: + type: string + description: > + The password for HTTP basic authentication or the passphrase for the + SSL certificate files. If `hasAuth` is set to `true` and `authType` + is `webhook-authentication-basic`, this property is required. + user: + type: string + description: > + The username for HTTP basic authentication. If `hasAuth` is set to + `true` and `authType` is `webhook-authentication-basic`, this + property is required. + Connectors_config_properties_xmatters: + title: Connector request properties for an xMatters connector + description: Defines properties for connectors when type is `.xmatters`. + type: object + properties: + configUrl: + description: > + The request URL for the Elastic Alerts trigger in xMatters. It is + applicable only when `usesBasic` is `true`. + type: string + nullable: true + usesBasic: + description: >- + Specifies whether the connector uses HTTP basic authentication + (`true`) or URL authentication (`false`). + type: boolean + default: true + Connectors_secrets_properties_xmatters: + title: Connector secrets properties for an xMatters connector + description: Defines secrets for connectors when type is `.xmatters`. + type: object + properties: + password: + description: > + A user name for HTTP basic authentication. It is applicable only + when `usesBasic` is `true`. + type: string + secretsUrl: + description: > + The request URL for the Elastic Alerts trigger in xMatters with the + API key included in the URL. It is applicable only when `usesBasic` + is `false`. + type: string + user: + description: > + A password for HTTP basic authentication. It is applicable only when + `usesBasic` is `true`. + type: string + Connectors_create_connector_request: + title: Create connector request body properties + description: The properties vary depending on the connector type. + oneOf: + - $ref: '#/components/schemas/Connectors_create_connector_request_bedrock' + - $ref: '#/components/schemas/Connectors_create_connector_request_gemini' + - $ref: >- + #/components/schemas/Connectors_create_connector_request_cases_webhook + - $ref: '#/components/schemas/Connectors_create_connector_request_d3security' + - $ref: '#/components/schemas/Connectors_create_connector_request_email' + - $ref: '#/components/schemas/Connectors_create_connector_request_genai' + - $ref: '#/components/schemas/Connectors_create_connector_request_index' + - $ref: '#/components/schemas/Connectors_create_connector_request_jira' + - $ref: '#/components/schemas/Connectors_create_connector_request_opsgenie' + - $ref: '#/components/schemas/Connectors_create_connector_request_pagerduty' + - $ref: '#/components/schemas/Connectors_create_connector_request_resilient' + - $ref: '#/components/schemas/Connectors_create_connector_request_sentinelone' + - $ref: '#/components/schemas/Connectors_create_connector_request_serverlog' + - $ref: '#/components/schemas/Connectors_create_connector_request_servicenow' + - $ref: >- + #/components/schemas/Connectors_create_connector_request_servicenow_itom + - $ref: >- + #/components/schemas/Connectors_create_connector_request_servicenow_sir + - $ref: '#/components/schemas/Connectors_create_connector_request_slack_api' + - $ref: >- + #/components/schemas/Connectors_create_connector_request_slack_webhook + - $ref: '#/components/schemas/Connectors_create_connector_request_swimlane' + - $ref: '#/components/schemas/Connectors_create_connector_request_teams' + - $ref: '#/components/schemas/Connectors_create_connector_request_tines' + - $ref: '#/components/schemas/Connectors_create_connector_request_torq' + - $ref: '#/components/schemas/Connectors_create_connector_request_webhook' + - $ref: '#/components/schemas/Connectors_create_connector_request_xmatters' + discriminator: + propertyName: connector_type_id + mapping: + .bedrock: '#/components/schemas/Connectors_create_connector_request_bedrock' + .gemini: '#/components/schemas/Connectors_create_connector_request_gemini' + .cases-webhook: >- + #/components/schemas/Connectors_create_connector_request_cases_webhook + .d3security: '#/components/schemas/Connectors_create_connector_request_d3security' + .email: '#/components/schemas/Connectors_create_connector_request_email' + .gen-ai: '#/components/schemas/Connectors_create_connector_request_genai' + .index: '#/components/schemas/Connectors_create_connector_request_index' + .jira: '#/components/schemas/Connectors_create_connector_request_jira' + .opsgenie: '#/components/schemas/Connectors_create_connector_request_opsgenie' + .pagerduty: '#/components/schemas/Connectors_create_connector_request_pagerduty' + .resilient: '#/components/schemas/Connectors_create_connector_request_resilient' + .sentinelone: '#/components/schemas/Connectors_create_connector_request_sentinelone' + .server-log: '#/components/schemas/Connectors_create_connector_request_serverlog' + .servicenow: '#/components/schemas/Connectors_create_connector_request_servicenow' + .servicenow-itom: >- + #/components/schemas/Connectors_create_connector_request_servicenow_itom + .servicenow-sir: >- + #/components/schemas/Connectors_create_connector_request_servicenow_sir + .slack_api: '#/components/schemas/Connectors_create_connector_request_slack_api' + .slack: >- + #/components/schemas/Connectors_create_connector_request_slack_webhook + .swimlane: '#/components/schemas/Connectors_create_connector_request_swimlane' + .teams: '#/components/schemas/Connectors_create_connector_request_teams' + .tines: '#/components/schemas/Connectors_create_connector_request_tines' + .torq: '#/components/schemas/Connectors_create_connector_request_torq' + .webhook: '#/components/schemas/Connectors_create_connector_request_webhook' + .xmatters: '#/components/schemas/Connectors_create_connector_request_xmatters' + Connectors_connector_response_properties_bedrock: + title: Connector response properties for an Amazon Bedrock connector + type: object + required: + - config + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_bedrock' + connector_type_id: + type: string + description: The type of connector. + enum: + - .bedrock + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + Connectors_connector_response_properties_gemini: + title: Connector response properties for a Google Gemini connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_gemini' + connector_type_id: + type: string + description: The type of connector. + enum: + - .gemini + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_cases_webhook: + title: Connector request properties for a Webhook - Case Management connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_cases_webhook' + connector_type_id: + description: The type of connector. + type: string + enum: + - .cases-webhook + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_d3security: + title: Connector response properties for a D3 Security connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_d3security' + connector_type_id: + type: string + description: The type of connector. + enum: + - .d3security + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_email: + title: Connector response properties for an email connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_email' + connector_type_id: + type: string + description: The type of connector. + enum: + - .email + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_genai: + title: Connector response properties for an OpenAI connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_genai' + connector_type_id: + type: string + description: The type of connector. + enum: + - .gen-ai + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_index: + title: Connector response properties for an index connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_index' + connector_type_id: + type: string + description: The type of connector. + enum: + - .index + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_jira: + title: Connector response properties for a Jira connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_jira' + connector_type_id: + type: string + description: The type of connector. + enum: + - .jira + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_opsgenie: + title: Connector response properties for an Opsgenie connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_opsgenie' + connector_type_id: + type: string + description: The type of connector. + enum: + - .opsgenie + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_pagerduty: + title: Connector response properties for a PagerDuty connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_pagerduty' + connector_type_id: + type: string + description: The type of connector. + enum: + - .pagerduty + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_resilient: + title: Connector response properties for a IBM Resilient connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_resilient' + connector_type_id: + type: string + description: The type of connector. + enum: + - .resilient + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_sentinelone: + title: Connector response properties for a SentinelOne connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_sentinelone' + connector_type_id: + type: string + description: The type of connector. + enum: + - .sentinelone + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_serverlog: + title: Connector response properties for a server log connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + type: object + nullable: true + connector_type_id: + type: string + description: The type of connector. + enum: + - .server-log + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_servicenow: + title: Connector response properties for a ServiceNow ITSM connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_servicenow' + connector_type_id: + type: string + description: The type of connector. + enum: + - .servicenow + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_servicenow_itom: + title: Connector response properties for a ServiceNow ITOM connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_servicenow_itom' + connector_type_id: + type: string + description: The type of connector. + enum: + - .servicenow-itom + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_servicenow_sir: + title: Connector response properties for a ServiceNow SecOps connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_servicenow' + connector_type_id: + type: string + description: The type of connector. + enum: + - .servicenow-sir + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_slack_api: + title: Connector response properties for a Slack connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_slack_api' + connector_type_id: + type: string + description: The type of connector. + enum: + - .slack_api + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_slack_webhook: + title: Connector response properties for a Slack connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + connector_type_id: + type: string + description: The type of connector. + enum: + - .slack + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_swimlane: + title: Connector response properties for a Swimlane connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_swimlane' + connector_type_id: + type: string + description: The type of connector. + enum: + - .swimlane + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_teams: + title: Connector response properties for a Microsoft Teams connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + type: object + connector_type_id: + type: string + description: The type of connector. + enum: + - .teams + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_tines: + title: Connector response properties for a Tines connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_tines' + connector_type_id: + type: string + description: The type of connector. + enum: + - .tines + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_torq: + title: Connector response properties for a Torq connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_torq' + connector_type_id: + type: string + description: The type of connector. + enum: + - .torq + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_webhook: + title: Connector response properties for a Webhook connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_webhook' + connector_type_id: + type: string + description: The type of connector. + enum: + - .webhook + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_connector_response_properties_xmatters: + title: Connector response properties for an xMatters connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_xmatters' + connector_type_id: + type: string + description: The type of connector. + enum: + - .xmatters + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/Connectors_is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/Connectors_is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/Connectors_is_preconfigured' + is_system_action: + $ref: '#/components/schemas/Connectors_is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/Connectors_referenced_by_count' + Connectors_is_deprecated: + type: boolean + description: Indicates whether the connector type is deprecated. + example: false + Connectors_is_missing_secrets: + type: boolean + description: >- + Indicates whether secrets are missing for the connector. Secrets + configuration properties vary depending on the connector type. + example: false + Connectors_is_preconfigured: + type: boolean + description: > + Indicates whether it is a preconfigured connector. If true, the `config` + and `is_missing_secrets` properties are omitted from the response. + example: false + Connectors_is_system_action: + type: boolean + description: Indicates whether the connector is used for system actions. + example: false + Connectors_referenced_by_count: + type: integer + description: > + Indicates the number of saved objects that reference the connector. If + `is_preconfigured` is true, this value is not calculated. This property + is returned only by the get all connectors API. + example: 2 + Connectors_connector_response_properties: + title: Connector response properties + description: The properties vary depending on the connector type. + oneOf: + - $ref: >- + #/components/schemas/Connectors_connector_response_properties_bedrock + - $ref: '#/components/schemas/Connectors_connector_response_properties_gemini' + - $ref: >- + #/components/schemas/Connectors_connector_response_properties_cases_webhook + - $ref: >- + #/components/schemas/Connectors_connector_response_properties_d3security + - $ref: '#/components/schemas/Connectors_connector_response_properties_email' + - $ref: '#/components/schemas/Connectors_connector_response_properties_genai' + - $ref: '#/components/schemas/Connectors_connector_response_properties_index' + - $ref: '#/components/schemas/Connectors_connector_response_properties_jira' + - $ref: >- + #/components/schemas/Connectors_connector_response_properties_opsgenie + - $ref: >- + #/components/schemas/Connectors_connector_response_properties_pagerduty + - $ref: >- + #/components/schemas/Connectors_connector_response_properties_resilient + - $ref: >- + #/components/schemas/Connectors_connector_response_properties_sentinelone + - $ref: >- + #/components/schemas/Connectors_connector_response_properties_serverlog + - $ref: >- + #/components/schemas/Connectors_connector_response_properties_servicenow + - $ref: >- + #/components/schemas/Connectors_connector_response_properties_servicenow_itom + - $ref: >- + #/components/schemas/Connectors_connector_response_properties_servicenow_sir + - $ref: >- + #/components/schemas/Connectors_connector_response_properties_slack_api + - $ref: >- + #/components/schemas/Connectors_connector_response_properties_slack_webhook + - $ref: >- + #/components/schemas/Connectors_connector_response_properties_swimlane + - $ref: '#/components/schemas/Connectors_connector_response_properties_teams' + - $ref: '#/components/schemas/Connectors_connector_response_properties_tines' + - $ref: '#/components/schemas/Connectors_connector_response_properties_torq' + - $ref: >- + #/components/schemas/Connectors_connector_response_properties_webhook + - $ref: >- + #/components/schemas/Connectors_connector_response_properties_xmatters + discriminator: + propertyName: connector_type_id + mapping: + .bedrock: >- + #/components/schemas/Connectors_connector_response_properties_bedrock + .gemini: '#/components/schemas/Connectors_connector_response_properties_gemini' + .cases-webhook: >- + #/components/schemas/Connectors_connector_response_properties_cases_webhook + .d3security: >- + #/components/schemas/Connectors_connector_response_properties_d3security + .email: '#/components/schemas/Connectors_connector_response_properties_email' + .gen-ai: '#/components/schemas/Connectors_connector_response_properties_genai' + .index: '#/components/schemas/Connectors_connector_response_properties_index' + .jira: '#/components/schemas/Connectors_connector_response_properties_jira' + .opsgenie: >- + #/components/schemas/Connectors_connector_response_properties_opsgenie + .pagerduty: >- + #/components/schemas/Connectors_connector_response_properties_pagerduty + .resilient: >- + #/components/schemas/Connectors_connector_response_properties_resilient + .sentinelone: >- + #/components/schemas/Connectors_connector_response_properties_sentinelone + .server-log: >- + #/components/schemas/Connectors_connector_response_properties_serverlog + .servicenow: >- + #/components/schemas/Connectors_connector_response_properties_servicenow + .servicenow-itom: >- + #/components/schemas/Connectors_connector_response_properties_servicenow_itom + .servicenow-sir: >- + #/components/schemas/Connectors_connector_response_properties_servicenow_sir + .slack_api: >- + #/components/schemas/Connectors_connector_response_properties_slack_api + .slack: >- + #/components/schemas/Connectors_connector_response_properties_slack_webhook + .swimlane: >- + #/components/schemas/Connectors_connector_response_properties_swimlane + .teams: '#/components/schemas/Connectors_connector_response_properties_teams' + .tines: '#/components/schemas/Connectors_connector_response_properties_tines' + .torq: '#/components/schemas/Connectors_connector_response_properties_torq' + .webhook: >- + #/components/schemas/Connectors_connector_response_properties_webhook + .xmatters: >- + #/components/schemas/Connectors_connector_response_properties_xmatters + Connectors_update_connector_request_bedrock: + title: Update Amazon Bedrock connector request + type: object + required: + - config + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_bedrock' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_bedrock' + Connectors_update_connector_request_gemini: + title: Update Google Gemini connector request + type: object + required: + - config + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_gemini' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_gemini' + Connectors_update_connector_request_cases_webhook: + title: Update Webhook - Case Managment connector request + type: object + required: + - config + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_cases_webhook' + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_cases_webhook' + Connectors_update_connector_request_d3security: + title: Update D3 Security connector request + type: object + required: + - config + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_d3security' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_d3security' + Connectors_update_connector_request_email: + title: Update email connector request + type: object + required: + - config + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_email' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_email' + Connectors_update_connector_request_index: + title: Update index connector request + type: object + required: + - config + - name + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_index' + name: + type: string + description: The display name for the connector. + Connectors_update_connector_request_jira: + title: Update Jira connector request + type: object + required: + - config + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_jira' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_jira' + Connectors_update_connector_request_opsgenie: + title: Update Opsgenie connector request + type: object + required: + - config + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_opsgenie' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_opsgenie' + Connectors_update_connector_request_pagerduty: + title: Update PagerDuty connector request + type: object + required: + - config + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_pagerduty' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_pagerduty' + Connectors_update_connector_request_resilient: + title: Update IBM Resilient connector request + type: object + required: + - config + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_resilient' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_resilient' + Connectors_update_connector_request_sentinelone: + title: Update SentinelOne connector request + type: object + required: + - config + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_sentinelone' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_sentinelone' + Connectors_update_connector_request_serverlog: + title: Update server log connector request + type: object + required: + - name + properties: + name: + type: string + description: The display name for the connector. + Connectors_update_connector_request_servicenow: + title: Update ServiceNow ITSM connector or ServiceNow SecOps request + type: object + required: + - config + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_servicenow' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_servicenow' + Connectors_update_connector_request_servicenow_itom: + title: Create ServiceNow ITOM connector request + type: object + required: + - config + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_servicenow_itom' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_servicenow' + Connectors_update_connector_request_slack_api: + title: Update Slack connector request + type: object + required: + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_slack_api' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_slack_api' + Connectors_update_connector_request_slack_webhook: + title: Update Slack connector request + type: object + required: + - name + - secrets + properties: + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_slack_webhook' + Connectors_update_connector_request_swimlane: + title: Update Swimlane connector request + type: object + required: + - config + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_swimlane' + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_swimlane' + Connectors_update_connector_request_teams: + title: Update Microsoft Teams connector request + type: object + required: + - name + - secrets + properties: + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_teams' + Connectors_update_connector_request_tines: + title: Update Tines connector request + type: object + required: + - config + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_tines' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_tines' + Connectors_update_connector_request_torq: + title: Update Torq connector request + type: object + required: + - config + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_torq' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_torq' + Connectors_update_connector_request_webhook: + title: Update Webhook connector request + type: object + required: + - config + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_webhook' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_webhook' + Connectors_update_connector_request_xmatters: + title: Update xMatters connector request + type: object + required: + - config + - name + - secrets + properties: + config: + $ref: '#/components/schemas/Connectors_config_properties_xmatters' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/Connectors_secrets_properties_xmatters' + Connectors_update_connector_request: + title: Update connector request body properties + description: The properties vary depending on the connector type. + oneOf: + - $ref: '#/components/schemas/Connectors_update_connector_request_bedrock' + - $ref: '#/components/schemas/Connectors_update_connector_request_gemini' + - $ref: >- + #/components/schemas/Connectors_update_connector_request_cases_webhook + - $ref: '#/components/schemas/Connectors_update_connector_request_d3security' + - $ref: '#/components/schemas/Connectors_update_connector_request_email' + - $ref: '#/components/schemas/Connectors_create_connector_request_genai' + - $ref: '#/components/schemas/Connectors_update_connector_request_index' + - $ref: '#/components/schemas/Connectors_update_connector_request_jira' + - $ref: '#/components/schemas/Connectors_update_connector_request_opsgenie' + - $ref: '#/components/schemas/Connectors_update_connector_request_pagerduty' + - $ref: '#/components/schemas/Connectors_update_connector_request_resilient' + - $ref: '#/components/schemas/Connectors_update_connector_request_sentinelone' + - $ref: '#/components/schemas/Connectors_update_connector_request_serverlog' + - $ref: '#/components/schemas/Connectors_update_connector_request_servicenow' + - $ref: >- + #/components/schemas/Connectors_update_connector_request_servicenow_itom + - $ref: '#/components/schemas/Connectors_update_connector_request_slack_api' + - $ref: >- + #/components/schemas/Connectors_update_connector_request_slack_webhook + - $ref: '#/components/schemas/Connectors_update_connector_request_swimlane' + - $ref: '#/components/schemas/Connectors_update_connector_request_teams' + - $ref: '#/components/schemas/Connectors_update_connector_request_tines' + - $ref: '#/components/schemas/Connectors_update_connector_request_torq' + - $ref: '#/components/schemas/Connectors_update_connector_request_webhook' + - $ref: '#/components/schemas/Connectors_update_connector_request_xmatters' + Connectors_features: + type: string + description: | + The feature that uses the connector. + enum: + - alerting + - cases + - generativeAIForSecurity + - generativeAIForObservability + - generativeAIForSearchPlayground + - siem + - uptime + Connectors_connector_types: + title: Connector types + type: string + description: >- + The type of connector. For example, `.email`, `.index`, `.jira`, + `.opsgenie`, or `.server-log`. + enum: + - .bedrock + - .gemini + - .cases-webhook + - .d3security + - .email + - .gen-ai + - .index + - .jira + - .opsgenie + - .pagerduty + - .resilient + - .sentinelone + - .servicenow + - .servicenow-itom + - .servicenow-sir + - .server-log + - .slack + - .slack_api + - .swimlane + - .teams + - .tines + - .torq + - .webhook + - .xmatters + example: .server-log + Data_views_400_response: + title: Bad request + type: object + required: + - statusCode + - error + - message + properties: + statusCode: + type: number + example: 400 + error: + type: string + example: Bad Request + message: + type: string + Data_views_allownoindex: + type: boolean + description: Allows the data view saved object to exist before the data is available. + Data_views_fieldattrs: + type: object + description: A map of field attributes by field name. + Data_views_fieldformats: + type: object + description: A map of field formats by field name. + Data_views_namespaces: + type: array + description: >- + An array of space identifiers for sharing the data view between multiple + spaces. + items: + type: string + default: default + Data_views_runtimefieldmap: + type: object + description: A map of runtime field definitions by field name. + Data_views_sourcefilters: + type: array + description: The array of field names you want to filter out in Discover. + items: + type: object + required: + - value + properties: + value: + type: string + Data_views_timefieldname: + type: string + description: The timestamp field name, which you use for time-based data views. + Data_views_title: + type: string + description: >- + Comma-separated list of data streams, indices, and aliases that you want + to search. Supports wildcards (`*`). + Data_views_type: + type: string + description: When set to `rollup`, identifies the rollup data views. + Data_views_typemeta: + type: object + description: >- + When you use rollup indices, contains the field list for the rollup data + view API endpoints. + Data_views_create_data_view_request_object: + title: Create data view request + type: object + required: + - data_view + properties: + data_view: + type: object + required: + - title + description: The data view object. + properties: + allowNoIndex: + $ref: '#/components/schemas/Data_views_allownoindex' + fieldAttrs: + $ref: '#/components/schemas/Data_views_fieldattrs' + fieldFormats: + $ref: '#/components/schemas/Data_views_fieldformats' + fields: + type: object + id: + type: string + name: + type: string + description: The data view name. + namespaces: + $ref: '#/components/schemas/Data_views_namespaces' + runtimeFieldMap: + $ref: '#/components/schemas/Data_views_runtimefieldmap' + sourceFilters: + $ref: '#/components/schemas/Data_views_sourcefilters' + timeFieldName: + $ref: '#/components/schemas/Data_views_timefieldname' + title: + $ref: '#/components/schemas/Data_views_title' + type: + $ref: '#/components/schemas/Data_views_type' + typeMeta: + $ref: '#/components/schemas/Data_views_typemeta' + version: + type: string + override: + type: boolean + description: >- + Override an existing data view if a data view with the provided + title already exists. + default: false + Data_views_data_view_response_object: + title: Data view response properties + type: object + properties: + data_view: + type: object + properties: + allowNoIndex: + $ref: '#/components/schemas/Data_views_allownoindex' + fieldAttrs: + $ref: '#/components/schemas/Data_views_fieldattrs' + fieldFormats: + $ref: '#/components/schemas/Data_views_fieldformats' + fields: + type: object + id: + type: string + example: ff959d40-b880-11e8-a6d9-e546fe2bba5f + name: + type: string + description: The data view name. + namespaces: + $ref: '#/components/schemas/Data_views_namespaces' + runtimeFieldMap: + $ref: '#/components/schemas/Data_views_runtimefieldmap' + sourceFilters: + $ref: '#/components/schemas/Data_views_sourcefilters' + timeFieldName: + $ref: '#/components/schemas/Data_views_timefieldname' + title: + $ref: '#/components/schemas/Data_views_title' + typeMeta: + $ref: '#/components/schemas/Data_views_typemeta' + version: + type: string + example: WzQ2LDJd + Data_views_404_response: + type: object + properties: + error: + type: string + example: Not Found + enum: + - Not Found + message: + type: string + example: >- + Saved object [index-pattern/caaad6d0-920c-11ed-b36a-874bd1548a00] + not found + statusCode: + type: integer + example: 404 + enum: + - 404 + Data_views_update_data_view_request_object: + title: Update data view request + type: object + required: + - data_view + properties: + data_view: + type: object + description: > + The data view properties you want to update. Only the specified + properties are updated in the data view. Unspecified fields stay as + they are persisted. + properties: + allowNoIndex: + $ref: '#/components/schemas/Data_views_allownoindex' + fieldFormats: + $ref: '#/components/schemas/Data_views_fieldformats' + fields: + type: object + name: + type: string + runtimeFieldMap: + $ref: '#/components/schemas/Data_views_runtimefieldmap' + sourceFilters: + $ref: '#/components/schemas/Data_views_sourcefilters' + timeFieldName: + $ref: '#/components/schemas/Data_views_timefieldname' + title: + $ref: '#/components/schemas/Data_views_title' + type: + $ref: '#/components/schemas/Data_views_type' + typeMeta: + $ref: '#/components/schemas/Data_views_typemeta' + refresh_fields: + type: boolean + description: Reloads the data view fields after the data view is updated. + default: false + Machine_learning_APIs_mlSyncResponseSuccess: + type: boolean + description: The success or failure of the synchronization. + Machine_learning_APIs_mlSyncResponseAnomalyDetectors: + type: object + title: Sync API response for anomaly detection jobs + description: >- + The sync machine learning saved objects API response contains this + object when there are anomaly detection jobs affected by the + synchronization. There is an object for each relevant job, which + contains the synchronization status. + properties: + success: + $ref: '#/components/schemas/Machine_learning_APIs_mlSyncResponseSuccess' + Machine_learning_APIs_mlSyncResponseDatafeeds: + type: object + title: Sync API response for datafeeds + description: >- + The sync machine learning saved objects API response contains this + object when there are datafeeds affected by the synchronization. There + is an object for each relevant datafeed, which contains the + synchronization status. + properties: + success: + $ref: '#/components/schemas/Machine_learning_APIs_mlSyncResponseSuccess' + Machine_learning_APIs_mlSyncResponseDataFrameAnalytics: + type: object + title: Sync API response for data frame analytics jobs + description: >- + The sync machine learning saved objects API response contains this + object when there are data frame analytics jobs affected by the + synchronization. There is an object for each relevant job, which + contains the synchronization status. + properties: + success: + $ref: '#/components/schemas/Machine_learning_APIs_mlSyncResponseSuccess' + Machine_learning_APIs_mlSyncResponseSavedObjectsCreated: + type: object + title: Sync API response for created saved objects + description: >- + If saved objects are missing for machine learning jobs or trained + models, they are created when you run the sync machine learning saved + objects API. + properties: + anomaly-detector: + type: object + description: >- + If saved objects are missing for anomaly detection jobs, they are + created. + additionalProperties: + $ref: >- + #/components/schemas/Machine_learning_APIs_mlSyncResponseAnomalyDetectors + data-frame-analytics: + type: object + description: >- + If saved objects are missing for data frame analytics jobs, they are + created. + additionalProperties: + $ref: >- + #/components/schemas/Machine_learning_APIs_mlSyncResponseDataFrameAnalytics + trained-model: + type: object + description: If saved objects are missing for trained models, they are created. + additionalProperties: + $ref: >- + #/components/schemas/Machine_learning_APIs_mlSyncResponseTrainedModels + Machine_learning_APIs_mlSyncResponseSavedObjectsDeleted: + type: object + title: Sync API response for deleted saved objects + description: >- + If saved objects exist for machine learning jobs or trained models that + no longer exist, they are deleted when you run the sync machine learning + saved objects API. + properties: + anomaly-detector: + type: object + description: >- + If there are saved objects exist for nonexistent anomaly detection + jobs, they are deleted. + additionalProperties: + $ref: >- + #/components/schemas/Machine_learning_APIs_mlSyncResponseAnomalyDetectors + data-frame-analytics: + type: object + description: >- + If there are saved objects exist for nonexistent data frame + analytics jobs, they are deleted. + additionalProperties: + $ref: >- + #/components/schemas/Machine_learning_APIs_mlSyncResponseDataFrameAnalytics + trained-model: + type: object + description: >- + If there are saved objects exist for nonexistent trained models, + they are deleted. + additionalProperties: + $ref: >- + #/components/schemas/Machine_learning_APIs_mlSyncResponseTrainedModels + Machine_learning_APIs_mlSyncResponseTrainedModels: + type: object + title: Sync API response for trained models + description: >- + The sync machine learning saved objects API response contains this + object when there are trained models affected by the synchronization. + There is an object for each relevant trained model, which contains the + synchronization status. + properties: + success: + $ref: '#/components/schemas/Machine_learning_APIs_mlSyncResponseSuccess' + Machine_learning_APIs_mlSync200Response: + type: object + title: Successful sync API response + properties: + datafeedsAdded: + type: object + description: >- + If a saved object for an anomaly detection job is missing a datafeed + identifier, it is added when you run the sync machine learning saved + objects API. + additionalProperties: + $ref: '#/components/schemas/Machine_learning_APIs_mlSyncResponseDatafeeds' + datafeedsRemoved: + type: object + description: >- + If a saved object for an anomaly detection job references a datafeed + that no longer exists, it is deleted when you run the sync machine + learning saved objects API. + additionalProperties: + $ref: '#/components/schemas/Machine_learning_APIs_mlSyncResponseDatafeeds' + savedObjectsCreated: + $ref: >- + #/components/schemas/Machine_learning_APIs_mlSyncResponseSavedObjectsCreated + savedObjectsDeleted: + $ref: >- + #/components/schemas/Machine_learning_APIs_mlSyncResponseSavedObjectsDeleted + Machine_learning_APIs_mlSync4xxResponse: + type: object + title: Unsuccessful sync API response + properties: + error: + type: string + example: Unauthorized + message: + type: string + statusCode: + type: integer + example: 401 + Serverless_saved_objects_400_response: + title: Bad request + type: object + required: + - error + - message + - statusCode + properties: + error: + type: string + enum: + - Bad Request + message: + type: string + statusCode: + type: integer + enum: + - 400 + Serverless_saved_objects_export_objects_request: + type: object + properties: + excludeExportDetails: + description: Do not add export details entry at the end of the stream. + type: boolean + default: false + includeReferencesDeep: + description: Includes all of the referenced objects in the exported objects. + type: boolean + objects: + description: A list of objects to export. + type: array + items: + type: object + type: + description: >- + The saved object types to include in the export. Use `*` to export + all the types. + oneOf: + - type: string + - type: array + items: + type: string + Serverless_saved_objects_import_objects_request: + type: object + properties: + file: + description: > + A file exported using the export API. NOTE: The + `savedObjects.maxImportExportSize` configuration setting limits the + number of saved objects which may be included in this file. + Similarly, the `savedObjects.maxImportPayloadBytes` setting limits + the overall size of the file that can be imported. + Serverless_saved_objects_200_import_objects_response: + type: object + properties: + success: + type: boolean + description: > + Indicates when the import was successfully completed. When set to + false, some objects may not have been created. For additional + information, refer to the `errors` and `successResults` properties. + successCount: + type: integer + description: Indicates the number of successfully imported records. + errors: + type: array + items: + type: object + description: > + Indicates the import was unsuccessful and specifies the objects that + failed to import. + + + NOTE: One object may result in multiple errors, which requires + separate steps to resolve. For instance, a `missing_references` + error and conflict error. + successResults: + type: array + items: + type: object + description: > + Indicates the objects that are successfully imported, with any + metadata if applicable. + + + NOTE: Objects are created only when all resolvable errors are + addressed, including conflicts and missing references. If objects + are created as new copies, each entry in the `successResults` array + includes a `destinationId` attribute. + examples: + Connectors_create_email_connector_request: + summary: Create an email connector. + value: + name: email-connector-1 + connector_type_id: .email + config: + from: tester@example.com + hasAuth: true + host: https://example.com + port: 1025 + secure: false + service: other + secrets: + user: username + password: password + Connectors_create_index_connector_request: + summary: Create an index connector. + value: + name: my-connector + connector_type_id: .index + config: + index: test-index + Connectors_create_webhook_connector_request: + summary: Create a webhook connector with SSL authentication. + value: + name: my-webhook-connector + connector_type_id: .webhook + config: + method: post + url: https://example.com + authType: webhook-authentication-ssl + certType: ssl-crt-key + secrets: + crt: QmFnIEF0dH... + key: LS0tLS1CRUdJ... + password: my-passphrase + Connectors_create_xmatters_connector_request: + summary: Create an xMatters connector with URL authentication. + value: + name: my-xmatters-connector + connector_type_id: .xmatters + config: + usesBasic: false + secrets: + secretsUrl: https://example.com?apiKey=xxxxx + Connectors_create_email_connector_response: + summary: A new email connector. + value: + id: 90a82c60-478f-11ee-a343-f98a117c727f + connector_type_id: .email + name: email-connector-1 + config: + from: tester@example.com + service: other + host: https://example.com + port: 1025 + secure: false + hasAuth: true + tenantId: null + clientId: null + oauthTokenUrl: null + is_preconfigured: false + is_deprecated: false + is_missing_secrets: false + is_system_action: false + Connectors_create_index_connector_response: + summary: A new index connector. + value: + id: c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad + connector_type_id: .index + name: my-connector + config: + index: test-index + refresh: false + executionTimeField: null + is_preconfigured: false + is_deprecated: false + is_missing_secrets: false + is_system_action: false + Connectors_create_webhook_connector_response: + summary: A new webhook connector. + value: + id: 900eb010-3b9d-11ee-a642-8ffbb94e38bd + name: my-webhook-connector + config: + method: post + url: https://example.com + authType: webhook-authentication-ssl + certType: ssl-crt-key + verificationMode: full + headers: null + hasAuth: true + connector_type_id: .webhook + is_preconfigured: false + is_deprecated: false + is_missing_secrets: false + is_system_action: false + Connectors_create_xmatters_connector_response: + summary: A new xMatters connector. + value: + id: 4d2d8da0-4d1f-11ee-9367-577408be4681 + name: my-xmatters-connector + config: + usesBasic: false + configUrl: null + connector_type_id: .xmatters + is_preconfigured: false + is_deprecated: false + is_missing_secrets: false + is_system_action: false + Connectors_get_connector_response: + summary: Get connector details. + value: + id: df770e30-8b8b-11ed-a780-3b746c987a81 + name: my_server_log_connector + config: {} + connector_type_id: .server-log + is_preconfigured: false + is_deprecated: false + is_missing_secrets: false + is_system_action: false + Connectors_update_index_connector_request: + summary: Update an index connector. + value: + name: updated-connector + config: + index: updated-index + Connectors_get_connectors_response: + summary: A list of connectors + value: + - id: preconfigured-email-connector + name: my-preconfigured-email-notification + connector_type_id: .email + is_preconfigured: true + is_deprecated: false + referenced_by_count: 0 + is_system_action: false + - id: e07d0c80-8b8b-11ed-a780-3b746c987a81 + name: my-index-connector + config: + index: test-index + refresh: false + executionTimeField: null + connector_type_id: .index + is_preconfigured: false + is_deprecated: false + referenced_by_count: 2 + is_missing_secrets: false + is_system_action: false + Connectors_get_connector_types_generativeai_response: + summary: A list of connector types for the `generativeAI` feature. + value: + - id: .gen-ai + name: OpenAI + enabled: true + enabled_in_config: true + enabled_in_license: true + minimum_license_required: enterprise + supported_feature_ids: + - generativeAIForSecurity + - generativeAIForObservability + - generativeAIForSearchPlayground + is_system_action_type: false + - id: .bedrock + name: AWS Bedrock + enabled: true + enabled_in_config: true + enabled_in_license: true + minimum_license_required: enterprise + supported_feature_ids: + - generativeAIForSecurity + - generativeAIForObservability + - generativeAIForSearchPlayground + is_system_action_type: false + - id: .gemini + name: Google Gemini + enabled: true + enabled_in_config: true + enabled_in_license: true + minimum_license_required: enterprise + supported_feature_ids: + - generativeAIForSecurity + is_system_action_type: false + Data_views_get_data_views_response: + summary: The get all data views API returns a list of data views. + value: + data_view: + - id: ff959d40-b880-11e8-a6d9-e546fe2bba5f + namespaces: + - default + title: kibana_sample_data_ecommerce + typeMeta: {} + name: Kibana Sample Data eCommerce + - id: d3d7af60-4c81-11e8-b3d7-01146121b73d + namespaces: + - default + title: kibana_sample_data_flights + name: Kibana Sample Data Flights + - id: 90943e30-9a47-11e8-b64d-95841ca0b247 + namespaces: + - default + title: kibana_sample_data_logs + name: Kibana Sample Data Logs + Data_views_create_data_view_request: + summary: Create a data view with runtime fields. + value: + data_view: + title: logstash-* + name: My Logstash data view + runtimeFieldMap: + runtime_shape_name: + type: keyword + script: + source: emit(doc['shape_name'].value) + Data_views_get_data_view_response: + summary: >- + The get data view API returns a JSON object that contains information + about the data view. + value: + data_view: + id: ff959d40-b880-11e8-a6d9-e546fe2bba5f + version: WzUsMV0= + title: kibana_sample_data_ecommerce + timeFieldName: order_date + sourceFilters: [] + fields: + _id: + count: 0 + name: _id + type: string + esTypes: + - _id + scripted: false + searchable: true + aggregatable: false + readFromDocValues: false + format: + id: string + shortDotsEnable: false + isMapped: true + _index: + count: 0 + name: _index + type: string + esTypes: + - _index + scripted: false + searchable: true + aggregatable: true + readFromDocValues: false + format: + id: string + shortDotsEnable: false + isMapped: true + _score: + count: 0 + name: _score + type: number + scripted: false + searchable: false + aggregatable: false + readFromDocValues: false + format: + id: number + shortDotsEnable: false + isMapped: true + _source: + count: 0 + name: _source + type: _source + esTypes: + - _source + scripted: false + searchable: false + aggregatable: false + readFromDocValues: false + format: + id: _source + shortDotsEnable: false + isMapped: true + category: + count: 0 + name: category + type: string + esTypes: + - text + scripted: false + searchable: true + aggregatable: false + readFromDocValues: false + format: + id: string + shortDotsEnable: false + isMapped: true + category.keyword: + count: 0 + name: category.keyword + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + subType: + multi: + parent: category + format: + id: string + shortDotsEnable: false + isMapped: true + currency: + count: 0 + name: currency + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + customer_birth_date: + count: 0 + name: customer_birth_date + type: date + esTypes: + - date + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: date + shortDotsEnable: false + isMapped: true + customer_first_name: + count: 0 + name: customer_first_name + type: string + esTypes: + - text + scripted: false + searchable: true + aggregatable: false + readFromDocValues: false + format: + id: string + shortDotsEnable: false + isMapped: true + customer_first_name.keyword: + count: 0 + name: customer_first_name.keyword + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + subType: + multi: + parent: customer_first_name + format: + id: string + shortDotsEnable: false + isMapped: true + customer_full_name: + count: 0 + name: customer_full_name + type: string + esTypes: + - text + scripted: false + searchable: true + aggregatable: false + readFromDocValues: false + format: + id: string + shortDotsEnable: false + isMapped: true + customer_full_name.keyword: + count: 0 + name: customer_full_name.keyword + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + subType: + multi: + parent: customer_full_name + format: + id: string + shortDotsEnable: false + isMapped: true + customer_gender: + count: 0 + name: customer_gender + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + customer_id: + count: 0 + name: customer_id + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + customer_last_name: + count: 0 + name: customer_last_name + type: string + esTypes: + - text + scripted: false + searchable: true + aggregatable: false + readFromDocValues: false + format: + id: string + shortDotsEnable: false + isMapped: true + customer_last_name.keyword: + count: 0 + name: customer_last_name.keyword + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + subType: + multi: + parent: customer_last_name + format: + id: string + shortDotsEnable: false + isMapped: true + customer_phone: + count: 0 + name: customer_phone + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + day_of_week: + count: 0 + name: day_of_week + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + day_of_week_i: + count: 0 + name: day_of_week_i + type: number + esTypes: + - integer + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + shortDotsEnable: false + isMapped: true + email: + count: 0 + name: email + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + event.dataset: + count: 0 + name: event.dataset + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + geoip.city_name: + count: 0 + name: geoip.city_name + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + geoip.continent_name: + count: 0 + name: geoip.continent_name + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + geoip.country_iso_code: + count: 0 + name: geoip.country_iso_code + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + geoip.location: + count: 0 + name: geoip.location + type: geo_point + esTypes: + - geo_point + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: geo_point + params: + transform: wkt + shortDotsEnable: false + isMapped: true + geoip.region_name: + count: 0 + name: geoip.region_name + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + manufacturer: + count: 0 + name: manufacturer + type: string + esTypes: + - text + scripted: false + searchable: true + aggregatable: false + readFromDocValues: false + format: + id: string + shortDotsEnable: false + isMapped: true + manufacturer.keyword: + count: 0 + name: manufacturer.keyword + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + subType: + multi: + parent: manufacturer + format: + id: string + shortDotsEnable: false + isMapped: true + order_date: + count: 0 + name: order_date + type: date + esTypes: + - date + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: date + shortDotsEnable: false + isMapped: true + order_id: + count: 0 + name: order_id + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + products._id: + count: 0 + name: products._id + type: string + esTypes: + - text + scripted: false + searchable: true + aggregatable: false + readFromDocValues: false + format: + id: string + shortDotsEnable: false + isMapped: true + products._id.keyword: + count: 0 + name: products._id.keyword + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + subType: + multi: + parent: products._id + format: + id: string + shortDotsEnable: false + isMapped: true + products.base_price: + count: 0 + name: products.base_price + type: number + esTypes: + - half_float + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + params: + pattern: $0,0.00 + shortDotsEnable: false + isMapped: true + products.base_unit_price: + count: 0 + name: products.base_unit_price + type: number + esTypes: + - half_float + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + params: + pattern: $0,0.00 + shortDotsEnable: false + isMapped: true + products.category: + count: 0 + name: products.category + type: string + esTypes: + - text + scripted: false + searchable: true + aggregatable: false + readFromDocValues: false + format: + id: string + shortDotsEnable: false + isMapped: true + products.category.keyword: + count: 0 + name: products.category.keyword + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + subType: + multi: + parent: products.category + format: + id: string + shortDotsEnable: false + isMapped: true + products.created_on: + count: 0 + name: products.created_on + type: date + esTypes: + - date + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: date + shortDotsEnable: false + isMapped: true + products.discount_amount: + count: 0 + name: products.discount_amount + type: number + esTypes: + - half_float + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + shortDotsEnable: false + isMapped: true + products.discount_percentage: + count: 0 + name: products.discount_percentage + type: number + esTypes: + - half_float + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + shortDotsEnable: false + isMapped: true + products.manufacturer: + count: 1 + name: products.manufacturer + type: string + esTypes: + - text + scripted: false + searchable: true + aggregatable: false + readFromDocValues: false + format: + id: string + shortDotsEnable: false + isMapped: true + products.manufacturer.keyword: + count: 0 + name: products.manufacturer.keyword + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + subType: + multi: + parent: products.manufacturer + format: + id: string + shortDotsEnable: false + isMapped: true + products.min_price: + count: 0 + name: products.min_price + type: number + esTypes: + - half_float + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + params: + pattern: $0,0.00 + shortDotsEnable: false + isMapped: true + products.price: + count: 1 + name: products.price + type: number + esTypes: + - half_float + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + params: + pattern: $0,0.00 + shortDotsEnable: false + isMapped: true + products.product_id: + count: 0 + name: products.product_id + type: number + esTypes: + - long + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + shortDotsEnable: false + isMapped: true + products.product_name: + count: 1 + name: products.product_name + type: string + esTypes: + - text + scripted: false + searchable: true + aggregatable: false + readFromDocValues: false + format: + id: string + shortDotsEnable: false + isMapped: true + products.product_name.keyword: + count: 0 + name: products.product_name.keyword + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + subType: + multi: + parent: products.product_name + format: + id: string + shortDotsEnable: false + isMapped: true + products.quantity: + count: 0 + name: products.quantity + type: number + esTypes: + - integer + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + shortDotsEnable: false + isMapped: true + products.sku: + count: 0 + name: products.sku + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + products.tax_amount: + count: 0 + name: products.tax_amount + type: number + esTypes: + - half_float + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + shortDotsEnable: false + isMapped: true + products.taxful_price: + count: 0 + name: products.taxful_price + type: number + esTypes: + - half_float + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + params: + pattern: $0,0.00 + shortDotsEnable: false + isMapped: true + products.taxless_price: + count: 0 + name: products.taxless_price + type: number + esTypes: + - half_float + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + params: + pattern: $0,0.00 + shortDotsEnable: false + isMapped: true + products.unit_discount_amount: + count: 0 + name: products.unit_discount_amount + type: number + esTypes: + - half_float + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + shortDotsEnable: false + isMapped: true + sku: + count: 0 + name: sku + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + taxful_total_price: + count: 0 + name: taxful_total_price + type: number + esTypes: + - half_float + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + params: + pattern: $0,0.[00] + shortDotsEnable: false + isMapped: true + taxless_total_price: + count: 0 + name: taxless_total_price + type: number + esTypes: + - half_float + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + params: + pattern: $0,0.00 + shortDotsEnable: false + isMapped: true + total_quantity: + count: 1 + name: total_quantity + type: number + esTypes: + - integer + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + shortDotsEnable: false + isMapped: true + total_unique_products: + count: 0 + name: total_unique_products + type: number + esTypes: + - integer + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + shortDotsEnable: false + isMapped: true + type: + count: 0 + name: type + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + user: + count: 0 + name: user + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + typeMeta: {} + fieldFormats: + taxful_total_price: + id: number + params: + pattern: $0,0.[00] + products.price: + id: number + params: + pattern: $0,0.00 + taxless_total_price: + id: number + params: + pattern: $0,0.00 + products.taxless_price: + id: number + params: + pattern: $0,0.00 + products.taxful_price: + id: number + params: + pattern: $0,0.00 + products.min_price: + id: number + params: + pattern: $0,0.00 + products.base_unit_price: + id: number + params: + pattern: $0,0.00 + products.base_price: + id: number + params: + pattern: $0,0.00 + runtimeFieldMap: {} + fieldAttrs: + products.manufacturer: + count: 1 + products.price: + count: 1 + products.product_name: + count: 1 + total_quantity: + count: 1 + allowNoIndex: false + name: Kibana Sample Data eCommerce + namespaces: + - default + Data_views_update_data_view_request: + summary: Update some properties for a data view. + value: + data_view: + title: kibana_sample_data_ecommerce + timeFieldName: order_date + allowNoIndex: false + name: Kibana Sample Data eCommerce + refresh_fields: true + Data_views_get_default_data_view_response: + summary: The get default data view API returns the default data view identifier. + value: + data_view_id: ff959d40-b880-11e8-a6d9-e546fe2bba5f + Data_views_set_default_data_view_request: + summary: Set the default data view identifier. + value: + data_view_id: ff959d40-b880-11e8-a6d9-e546fe2bba5f + force: true + Data_views_update_field_metadata_request: + summary: Set popularity count for field foo. + value: + fields: + foo: + count: 123 + Data_views_create_runtime_field_request: + summary: Create a runtime field. + value: + name: runtimeFoo + runtimeField: + type: long + script: + source: emit(doc["foo"].value) + Data_views_get_runtime_field_response: + summary: >- + The get runtime field API returns a JSON object that contains + information about the runtime field (`hour_of_day`) and the data view + (`d3d7af60-4c81-11e8-b3d7-01146121b73d`). + value: + fields: + - count: 0 + name: hour_of_day + type: number + esTypes: + - long + scripted: false + searchable: true + aggregatable: true + readFromDocValues: false + shortDotsEnable: false + runtimeField: + type: long + script: + source: emit(doc['timestamp'].value.getHour()); + data_view: + id: d3d7af60-4c81-11e8-b3d7-01146121b73d + version: WzM2LDJd + title: kibana_sample_data_flights + timeFieldName: timestamp + sourceFilters: [] + fields: + hour_of_day: + count: 0 + name: hour_of_day + type: number + esTypes: + - long + scripted: false + searchable: true + aggregatable: true + readFromDocValues: false + format: + id: number + params: + pattern: '00' + shortDotsEnable: false + runtimeField: + type: long + script: + source: emit(doc['timestamp'].value.getHour()); + AvgTicketPrice: + count: 0 + name: AvgTicketPrice + type: number + esTypes: + - float + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + params: + pattern: $0,0.[00] + shortDotsEnable: false + isMapped: true + Cancelled: + count: 0 + name: Cancelled + type: boolean + esTypes: + - boolean + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: boolean + shortDotsEnable: false + isMapped: true + Carrier: + count: 0 + name: Carrier + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + Dest: + count: 0 + name: Dest + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + DestAirportID: + count: 0 + name: DestAirportID + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + DestCityName: + count: 0 + name: DestCityName + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + DestCountry: + count: 0 + name: DestCountry + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + DestLocation: + count: 0 + name: DestLocation + type: geo_point + esTypes: + - geo_point + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: geo_point + params: + transform: wkt + shortDotsEnable: false + isMapped: true + DestRegion: + count: 0 + name: DestRegion + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + DestWeather: + count: 0 + name: DestWeather + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + DistanceKilometers: + count: 0 + name: DistanceKilometers + type: number + esTypes: + - float + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + shortDotsEnable: false + isMapped: true + DistanceMiles: + count: 0 + name: DistanceMiles + type: number + esTypes: + - float + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + shortDotsEnable: false + isMapped: true + FlightDelay: + count: 0 + name: FlightDelay + type: boolean + esTypes: + - boolean + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: boolean + shortDotsEnable: false + isMapped: true + FlightDelayMin: + count: 0 + name: FlightDelayMin + type: number + esTypes: + - integer + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + shortDotsEnable: false + isMapped: true + FlightDelayType: + count: 0 + name: FlightDelayType + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + FlightNum: + count: 0 + name: FlightNum + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + FlightTimeHour: + count: 0 + name: FlightTimeHour + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + FlightTimeMin: + count: 0 + name: FlightTimeMin + type: number + esTypes: + - float + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + shortDotsEnable: false + isMapped: true + Origin: + count: 0 + name: Origin + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + OriginAirportID: + count: 0 + name: OriginAirportID + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + OriginCityName: + count: 0 + name: OriginCityName + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + OriginCountry: + count: 0 + name: OriginCountry + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + OriginLocation: + count: 0 + name: OriginLocation + type: geo_point + esTypes: + - geo_point + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: geo_point + params: + transform: wkt + shortDotsEnable: false + isMapped: true + OriginRegion: + count: 0 + name: OriginRegion + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + OriginWeather: + count: 0 + name: OriginWeather + type: string + esTypes: + - keyword + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: string + shortDotsEnable: false + isMapped: true + _id: + count: 0 + name: _id + type: string + esTypes: + - _id + scripted: false + searchable: true + aggregatable: false + readFromDocValues: false + format: + id: string + shortDotsEnable: false + isMapped: true + _index: + count: 0 + name: _index + type: string + esTypes: + - _index + scripted: false + searchable: true + aggregatable: true + readFromDocValues: false + format: + id: string + shortDotsEnable: false + isMapped: true + _score: + count: 0 + name: _score + type: number + scripted: false + searchable: false + aggregatable: false + readFromDocValues: false + format: + id: number + shortDotsEnable: false + isMapped: true + _source: + count: 0 + name: _source + type: _source + esTypes: + - _source + scripted: false + searchable: false + aggregatable: false + readFromDocValues: false + format: + id: _source + shortDotsEnable: false + isMapped: true + dayOfWeek: + count: 0 + name: dayOfWeek + type: number + esTypes: + - integer + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: number + shortDotsEnable: false + isMapped: true + timestamp: + count: 0 + name: timestamp + type: date + esTypes: + - date + scripted: false + searchable: true + aggregatable: true + readFromDocValues: true + format: + id: date + shortDotsEnable: false + isMapped: true + fieldFormats: + hour_of_day: + id: number + params: + pattern: '00' + AvgTicketPrice: + id: number + params: + pattern: $0,0.[00] + runtimeFieldMap: + hour_of_day: + type: long + script: + source: emit(doc['timestamp'].value.getHour()); + fieldAttrs: {} + allowNoIndex: false + name: Kibana Sample Data Flights + Data_views_update_runtime_field_request: + summary: Update an existing runtime field on a data view. + value: + runtimeField: + script: + source: emit(doc["bar"].value) + Machine_learning_APIs_mlSyncExample: + summary: Two anomaly detection jobs required synchronization in this example. + value: + savedObjectsCreated: + anomaly-detector: + myjob1: + success: true + myjob2: + success: true + savedObjectsDeleted: {} + datafeedsAdded: {} + datafeedsRemoved: {} + Serverless_saved_objects_export_objects_request: + summary: Export a specific saved object. + value: + objects: + - type: map + id: de71f4f0-1902-11e9-919b-ffe5949a18d2 + includeReferencesDeep: false + excludeExportDetails: true + Serverless_saved_objects_export_objects_response: + summary: >- + The export objects API response contains a JSON record for each exported + object. + value: + attributes: + description: '' + layerListJSON: >- + [{"id":"0hmz5","alpha":1,"sourceDescriptor":{"type":"EMS_TMS","isAutoSelect":true,"lightModeDefault":"road_map_desaturated"},"visible":true,"style":{},"type":"EMS_VECTOR_TILE","minZoom":0,"maxZoom":24},{"id":"edh66","label":"Total + Requests by + Destination","minZoom":0,"maxZoom":24,"alpha":0.5,"sourceDescriptor":{"type":"EMS_FILE","id":"world_countries","tooltipProperties":["name","iso2"]},"visible":true,"style":{"type":"VECTOR","properties":{"fillColor":{"type":"DYNAMIC","options":{"field":{"name":"__kbnjoin__count__673ff994-fc75-4c67-909b-69fcb0e1060e","origin":"join"},"color":"Greys","fieldMetaOptions":{"isEnabled":false,"sigma":3}}},"lineColor":{"type":"STATIC","options":{"color":"#FFFFFF"}},"lineWidth":{"type":"STATIC","options":{"size":1}},"iconSize":{"type":"STATIC","options":{"size":10}},"symbolizeAs":{"options":{"value":"circle"}},"icon":{"type":"STATIC","options":{"value":"marker"}}}},"type":"GEOJSON_VECTOR","joins":[{"leftField":"iso2","right":{"type":"ES_TERM_SOURCE","id":"673ff994-fc75-4c67-909b-69fcb0e1060e","indexPatternTitle":"kibana_sample_data_logs","term":"geo.dest","indexPatternRefName":"layer_1_join_0_index_pattern","metrics":[{"type":"count","label":"web + logs + count"}],"applyGlobalQuery":true}}]},{"id":"gaxya","label":"Actual + Requests","minZoom":9,"maxZoom":24,"alpha":1,"sourceDescriptor":{"id":"b7486535-171b-4d3b-bb2e-33c1a0a2854c","type":"ES_SEARCH","geoField":"geo.coordinates","limit":2048,"filterByMapBounds":true,"tooltipProperties":["clientip","timestamp","host","request","response","machine.os","agent","bytes"],"indexPatternRefName":"layer_2_source_index_pattern","applyGlobalQuery":true,"scalingType":"LIMIT"},"visible":true,"style":{"type":"VECTOR","properties":{"fillColor":{"type":"STATIC","options":{"color":"#2200ff"}},"lineColor":{"type":"STATIC","options":{"color":"#FFFFFF"}},"lineWidth":{"type":"STATIC","options":{"size":2}},"iconSize":{"type":"DYNAMIC","options":{"field":{"name":"bytes","origin":"source"},"minSize":1,"maxSize":23,"fieldMetaOptions":{"isEnabled":false,"sigma":3}}},"symbolizeAs":{"options":{"value":"circle"}},"icon":{"type":"STATIC","options":{"value":"marker"}}}},"type":"GEOJSON_VECTOR"},{"id":"tfi3f","label":"Total + Requests and + Bytes","minZoom":0,"maxZoom":9,"alpha":1,"sourceDescriptor":{"type":"ES_GEO_GRID","resolution":"COARSE","id":"8aaa65b5-a4e9-448b-9560-c98cb1c5ac5b","geoField":"geo.coordinates","requestType":"point","metrics":[{"type":"count","label":"web + logs + count"},{"type":"sum","field":"bytes"}],"indexPatternRefName":"layer_3_source_index_pattern","applyGlobalQuery":true},"visible":true,"style":{"type":"VECTOR","properties":{"fillColor":{"type":"DYNAMIC","options":{"field":{"name":"doc_count","origin":"source"},"color":"Blues","fieldMetaOptions":{"isEnabled":false,"sigma":3}}},"lineColor":{"type":"STATIC","options":{"color":"#cccccc"}},"lineWidth":{"type":"STATIC","options":{"size":1}},"iconSize":{"type":"DYNAMIC","options":{"field":{"name":"sum_of_bytes","origin":"source"},"minSize":7,"maxSize":25,"fieldMetaOptions":{"isEnabled":false,"sigma":3}}},"labelText":{"type":"DYNAMIC","options":{"field":{"name":"doc_count","origin":"source"},"fieldMetaOptions":{"isEnabled":false,"sigma":3}}},"labelSize":{"type":"DYNAMIC","options":{"field":{"name":"doc_count","origin":"source"},"minSize":12,"maxSize":24,"fieldMetaOptions":{"isEnabled":false,"sigma":3}}},"symbolizeAs":{"options":{"value":"circle"}},"icon":{"type":"STATIC","options":{"value":"marker"}}}},"type":"GEOJSON_VECTOR"}] + mapStateJSON: >- + {"zoom":3.64,"center":{"lon":-88.92107,"lat":42.16337},"timeFilters":{"from":"now-7d","to":"now"},"refreshConfig":{"isPaused":true,"interval":0},"query":{"language":"kuery","query":""},"settings":{"autoFitToDataBounds":false}} + title: '[Logs] Total Requests and Bytes' + uiStateJSON: '{"isDarkMode":false}' + coreMigrationVersion: 8.8.0 + created_at: '2023-08-23T20:03:32.204Z' + id: de71f4f0-1902-11e9-919b-ffe5949a18d2 + managed: false + references: + - id: 90943e30-9a47-11e8-b64d-95841ca0b247 + name: layer_1_join_0_index_pattern + type: index-pattern + - id: 90943e30-9a47-11e8-b64d-95841ca0b247 + name: layer_2_source_index_pattern + type: index-pattern + - id: 90943e30-9a47-11e8-b64d-95841ca0b247 + name: layer_3_source_index_pattern + type: index-pattern + type: map + typeMigrationVersion: 8.4.0 + updated_at: '2023-08-23T20:03:32.204Z' + version: WzEzLDFd + Serverless_saved_objects_import_objects_request: + value: + file: file.ndjson + Serverless_saved_objects_import_objects_response: + summary: >- + The import objects API response indicates a successful import and the + objects are created. Since these objects are created as new copies, each + entry in the successResults array includes a destinationId attribute. + value: + successCount: 1 + success: true + successResults: + - type: index-pattern + id: 90943e30-9a47-11e8-b64d-95841ca0b247 + meta: + title: Kibana Sample Data Logs + icon: indexPatternApp + managed: false + destinationId: 82d2760c-468f-49cf-83aa-b9a35b6a8943 + responses: + Connectors_401: + description: Authorization information is missing or invalid. + content: + application/json: + schema: + type: object + title: Unauthorized response + properties: + error: + type: string + example: Unauthorized + enum: + - Unauthorized + message: + type: string + statusCode: + type: integer + example: 401 + enum: + - 401 + Connectors_404: + description: Object is not found. + content: + application/json: + schema: + type: object + title: Not found response + properties: + error: + type: string + example: Not Found + enum: + - Not Found + message: + type: string + example: >- + Saved object [action/baf33fc0-920c-11ed-b36a-874bd1548a00] not + found + statusCode: + type: integer + example: 404 + enum: + - 404 +x-tagGroups: + - name: APM UI + tags: + - APM agent keys + - APM annotations + - name: Connectors + tags: + - connectors + - name: Data views + tags: + - data views + - name: Machine learning APIs + tags: + - ml + - name: Serverless saved objects + tags: + - saved objects diff --git a/oas_docs/makefile b/oas_docs/makefile new file mode 100644 index 0000000000000..805da0b6218a7 --- /dev/null +++ b/oas_docs/makefile @@ -0,0 +1,29 @@ +# ELASTICSEARCH CONFIDENTIAL +# __________________ +# +# Copyright Elasticsearch B.V. All rights reserved. +# +# NOTICE: All information contained herein is, and remains +# the property of Elasticsearch B.V. and its suppliers, if any. +# The intellectual and technical concepts contained herein +# are proprietary to Elasticsearch B.V. and its suppliers and +# may be covered by U.S. and Foreign Patents, patents in +# process, and are protected by trade secret or copyright +# law. Dissemination of this information or reproduction of +# this material is strictly forbidden unless prior written +# permission is obtained from Elasticsearch B.V. + +.PHONY: api-docs +api-docs: ## Generate kibana.serverless.yaml + @npx @redocly/cli join "kibana.info.serverless.yaml" "../x-pack/plugins/observability_solution/apm/docs/openapi/apm.yaml" "../x-pack/plugins/actions/docs/openapi/bundled_serverless.yaml" "../src/plugins/data_views/docs/openapi/bundled.yaml" "../x-pack/plugins/ml/common/openapi/ml_apis_serverless.yaml" "../packages/core/saved-objects/docs/openapi/bundled_serverless.yaml" -o "kibana.serverless.yaml" --prefix-components-with-info-prop title + +.PHONY: api-docs-lint +api-docs-lint: ## Run spectral API docs linter + @npx @stoplight/spectral-cli lint "kibana.serverless.yaml" --ruleset ".spectral.yaml" + +help: ## Display help + @awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) +#------------- -------------- + +.DEFAULT_GOAL := help + diff --git a/package.json b/package.json index 29f52a071c4a2..efd020e91df66 100644 --- a/package.json +++ b/package.json @@ -80,11 +80,11 @@ "resolutions": { "**/@bazel/typescript/protobufjs": "6.11.4", "**/@hello-pangea/dnd": "16.6.0", - "**/@langchain/core": "0.1.53", + "**/@langchain/core": "0.2.3", "**/@types/node": "20.10.5", "**/@typescript-eslint/utils": "5.62.0", "**/chokidar": "^3.5.3", - "**/d3-scale/**/d3-color": "npm:@elastic/d3-color@2.0.1", + "**/d3-scale/**/d3-color": "npm:@elastic/kibana-d3-color@2.0.1", "**/globule/minimatch": "^3.1.2", "**/hoist-non-react-statics": "^3.3.2", "**/isomorphic-fetch/node-fetch": "^2.6.7", @@ -109,7 +109,7 @@ "@elastic/ecs": "^8.11.1", "@elastic/elasticsearch": "^8.13.1", "@elastic/ems-client": "8.5.1", - "@elastic/eui": "94.6.0", + "@elastic/eui": "95.0.0-backport.0", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "1.2.1", "@elastic/numeral": "^2.5.1", @@ -131,7 +131,7 @@ "@formatjs/intl-pluralrules": "^5.2.12", "@formatjs/intl-relativetimeformat": "^11.2.12", "@formatjs/intl-utils": "^3.8.4", - "@grpc/grpc-js": "^1.6.8", + "@grpc/grpc-js": "^1.8.22", "@hapi/accept": "^5.0.2", "@hapi/boom": "^9.1.4", "@hapi/cookie": "^11.0.2", @@ -166,14 +166,9 @@ "@kbn/alerts-restricted-fixtures-plugin": "link:x-pack/test/alerting_api_integration/common/plugins/alerts_restricted", "@kbn/alerts-ui-shared": "link:packages/kbn-alerts-ui-shared", "@kbn/analytics": "link:packages/kbn-analytics", - "@kbn/analytics-client": "link:packages/analytics/client", "@kbn/analytics-collection-utils": "link:packages/analytics/utils/analytics_collection_utils", "@kbn/analytics-ftr-helpers-plugin": "link:test/analytics/plugins/analytics_ftr_helpers", "@kbn/analytics-plugin-a-plugin": "link:test/analytics/plugins/analytics_plugin_a", - "@kbn/analytics-shippers-elastic-v3-browser": "link:packages/analytics/shippers/elastic_v3/browser", - "@kbn/analytics-shippers-elastic-v3-common": "link:packages/analytics/shippers/elastic_v3/common", - "@kbn/analytics-shippers-elastic-v3-server": "link:packages/analytics/shippers/elastic_v3/server", - "@kbn/analytics-shippers-fullstory": "link:packages/analytics/shippers/fullstory", "@kbn/apm-config-loader": "link:packages/kbn-apm-config-loader", "@kbn/apm-data-access-plugin": "link:x-pack/plugins/observability_solution/apm_data_access", "@kbn/apm-data-view": "link:packages/kbn-apm-data-view", @@ -222,6 +217,7 @@ "@kbn/content-management-table-list-view": "link:packages/content-management/table_list_view", "@kbn/content-management-table-list-view-common": "link:packages/content-management/table_list_view_common", "@kbn/content-management-table-list-view-table": "link:packages/content-management/table_list_view_table", + "@kbn/content-management-user-profiles": "link:packages/content-management/user_profiles", "@kbn/content-management-utils": "link:packages/kbn-content-management-utils", "@kbn/controls-example-plugin": "link:examples/controls_example", "@kbn/controls-plugin": "link:src/plugins/controls", @@ -433,6 +429,7 @@ "@kbn/discover-utils": "link:packages/kbn-discover-utils", "@kbn/doc-links": "link:packages/kbn-doc-links", "@kbn/dom-drag-drop": "link:packages/kbn-dom-drag-drop", + "@kbn/ebt": "link:packages/analytics/ebt", "@kbn/ebt-tools": "link:packages/kbn-ebt-tools", "@kbn/ecs-data-quality-dashboard": "link:x-pack/packages/security-solution/ecs_data_quality_dashboard", "@kbn/ecs-data-quality-dashboard-plugin": "link:x-pack/plugins/ecs_data_quality_dashboard", @@ -540,6 +537,7 @@ "@kbn/ingest-pipelines-plugin": "link:x-pack/plugins/ingest_pipelines", "@kbn/input-control-vis-plugin": "link:src/plugins/input_control_vis", "@kbn/inspector-plugin": "link:src/plugins/inspector", + "@kbn/integration-assistant-plugin": "link:x-pack/plugins/integration_assistant", "@kbn/interactive-setup-plugin": "link:src/plugins/interactive_setup", "@kbn/interactive-setup-test-endpoints-plugin": "link:test/interactive_setup_api_integration/plugins/test_endpoints", "@kbn/interpreter": "link:packages/kbn-interpreter", @@ -649,6 +647,7 @@ "@kbn/observability-shared-plugin": "link:x-pack/plugins/observability_solution/observability_shared", "@kbn/oidc-provider-plugin": "link:x-pack/test/security_api_integration/plugins/oidc_provider", "@kbn/open-telemetry-instrumented-plugin": "link:test/common/plugins/otel_metrics", + "@kbn/openapi-common": "link:packages/kbn-openapi-common", "@kbn/osquery-io-ts-types": "link:packages/kbn-osquery-io-ts-types", "@kbn/osquery-plugin": "link:x-pack/plugins/osquery", "@kbn/paertial-results-example-plugin": "link:examples/partial_results_example", @@ -692,6 +691,7 @@ "@kbn/resizable-layout": "link:packages/kbn-resizable-layout", "@kbn/resizable-layout-examples-plugin": "link:examples/resizable_layout_examples", "@kbn/resolver-test-plugin": "link:x-pack/test/plugin_functional/plugins/resolver_test", + "@kbn/response-ops-feature-flag-service": "link:packages/response-ops/feature_flag_service", "@kbn/response-stream-plugin": "link:examples/response_stream", "@kbn/rison": "link:packages/kbn-rison", "@kbn/rollup-plugin": "link:x-pack/plugins/rollup", @@ -705,6 +705,7 @@ "@kbn/safer-lodash-set": "link:packages/kbn-safer-lodash-set", "@kbn/saml-provider-plugin": "link:x-pack/test/security_api_integration/plugins/saml_provider", "@kbn/sample-task-plugin": "link:x-pack/test/plugin_api_integration/plugins/sample_task_plugin", + "@kbn/sample-task-plugin-mget": "link:x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget", "@kbn/saved-object-export-transforms-plugin": "link:test/plugin_functional/plugins/saved_object_export_transforms", "@kbn/saved-object-import-warnings-plugin": "link:test/plugin_functional/plugins/saved_object_import_warnings", "@kbn/saved-object-test-plugin": "link:x-pack/test/saved_object_api_integration/common/plugins/saved_object_test_plugin", @@ -726,7 +727,9 @@ "@kbn/search-connectors-plugin": "link:x-pack/plugins/search_connectors", "@kbn/search-errors": "link:packages/kbn-search-errors", "@kbn/search-examples-plugin": "link:examples/search_examples", + "@kbn/search-homepage": "link:x-pack/plugins/search_homepage", "@kbn/search-index-documents": "link:packages/kbn-search-index-documents", + "@kbn/search-inference-endpoints": "link:x-pack/plugins/search_inference_endpoints", "@kbn/search-notebooks": "link:x-pack/plugins/search_notebooks", "@kbn/search-playground": "link:x-pack/plugins/search_playground", "@kbn/search-response-warnings": "link:packages/kbn-search-response-warnings", @@ -761,6 +764,7 @@ "@kbn/securitysolution-list-constants": "link:packages/kbn-securitysolution-list-constants", "@kbn/securitysolution-list-hooks": "link:packages/kbn-securitysolution-list-hooks", "@kbn/securitysolution-list-utils": "link:packages/kbn-securitysolution-list-utils", + "@kbn/securitysolution-lists-common": "link:packages/kbn-securitysolution-lists-common", "@kbn/securitysolution-rules": "link:packages/kbn-securitysolution-rules", "@kbn/securitysolution-t-grid": "link:packages/kbn-securitysolution-t-grid", "@kbn/securitysolution-utils": "link:packages/kbn-securitysolution-utils", @@ -924,11 +928,11 @@ "@kbn/watcher-plugin": "link:x-pack/plugins/watcher", "@kbn/xstate-utils": "link:packages/kbn-xstate-utils", "@kbn/zod-helpers": "link:packages/kbn-zod-helpers", - "@langchain/community": "^0.0.44", - "@langchain/core": "^0.1.53", - "@langchain/openai": "^0.0.25", + "@langchain/community": "^0.2.4", + "@langchain/core": "0.2.3", + "@langchain/langgraph": "^0.0.23", + "@langchain/openai": "^0.0.34", "@langtrase/trace-attributes": "^3.0.8", - "@langtrase/typescript-sdk": "^2.2.1", "@launchdarkly/node-server-sdk": "^9.4.5", "@loaders.gl/core": "^3.4.7", "@loaders.gl/json": "^3.4.7", @@ -949,10 +953,10 @@ "@paralleldrive/cuid2": "^2.2.2", "@reduxjs/toolkit": "1.9.7", "@slack/webhook": "^7.0.1", - "@smithy/eventstream-codec": "^2.0.12", - "@smithy/eventstream-serde-node": "^2.1.1", - "@smithy/types": "^2.9.1", - "@smithy/util-utf8": "^2.0.0", + "@smithy/eventstream-codec": "^3.0.0", + "@smithy/eventstream-serde-node": "^3.0.0", + "@smithy/types": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", "@tanstack/react-query": "^4.29.12", "@tanstack/react-query-devtools": "^4.29.12", "@turf/along": "6.0.1", @@ -1015,7 +1019,7 @@ "deepmerge": "^4.2.2", "del": "^6.1.0", "diff": "^5.1.0", - "elastic-apm-node": "^4.6.0", + "elastic-apm-node": "^4.7.0", "email-addresses": "^5.0.0", "eventsource-parser": "^1.1.1", "execa": "^5.1.1", @@ -1064,9 +1068,10 @@ "jsonwebtoken": "^9.0.2", "jsts": "^1.6.2", "kea": "^2.6.0", - "langchain": "^0.1.30", - "langsmith": "^0.1.14", + "langchain": "0.2.3", + "langsmith": "^0.1.30", "launchdarkly-js-client-sdk": "^3.3.0", + "launchdarkly-node-server-sdk": "^7.0.3", "load-json-file": "^6.2.0", "lodash": "^4.17.21", "lru-cache": "^4.1.5", @@ -1089,6 +1094,7 @@ "node-forge": "^1.3.1", "nodemailer": "^6.9.9", "normalize-path": "^3.0.0", + "nunjucks": "^3.2.4", "object-hash": "^1.3.1", "object-path-immutable": "^3.1.1", "openai": "^4.24.1", @@ -1501,6 +1507,7 @@ "@types/node-forge": "^1.3.10", "@types/nodemailer": "^6.4.0", "@types/normalize-path": "^3.0.0", + "@types/nunjucks": "^3.2.6", "@types/object-hash": "^1.3.0", "@types/opn": "^5.1.0", "@types/ora": "^1.3.5", @@ -1661,7 +1668,7 @@ "lmdb": "^2.9.2", "loader-utils": "^2.0.4", "marge": "^1.0.1", - "micromatch": "^4.0.5", + "micromatch": "^4.0.7", "mini-css-extract-plugin": "1.1.0", "minimist": "^1.2.6", "mocha": "^10.1.0", @@ -1671,6 +1678,7 @@ "mochawesome-merge": "^4.3.0", "mock-fs": "^5.1.2", "ms-chromium-edge-driver": "^0.5.1", + "msw": "^2.3.1", "multistream": "^4.1.0", "mutation-observer": "^1.0.3", "native-hdr-histogram": "^1.0.0", diff --git a/packages/analytics/README.md b/packages/analytics/README.md deleted file mode 100644 index a9198dae88975..0000000000000 --- a/packages/analytics/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# @kbn/analytics-* - -This module implements the Analytics client used for Event-Based Telemetry. The intention of the client is to be usable on both: the UI and the Server sides. - -## Client - -Holds the public APIs to report events, enrich the events' context and set up the transport mechanisms. Refer to the [client's docs](./client/README.md) for more information. - -## Prebuilt shippers - -Elastic-approved shippers are available as `@kbn/analytics-shippers-*` packages. Refer to the [shippers' docs](./shippers/README.md) for more information. diff --git a/packages/analytics/ebt/README.md b/packages/analytics/ebt/README.md new file mode 100644 index 0000000000000..8758a90304cba --- /dev/null +++ b/packages/analytics/ebt/README.md @@ -0,0 +1,11 @@ +# @kbn/ebt/* + +This module implements the Analytics client used for Event-Based Telemetry. The intention of the client is to be usable on both: the UI and the Server sides. + +## Client + +`@kbn/ebt/client` holds the public APIs to report events, enrich the events' context and set up the transport mechanisms. Refer to the [client's docs](./client/README.md) for more information. + +## Prebuilt shippers + +Elastic-approved shippers are available as `@kbn/ebt/shippers/*` packages. Refer to the [shippers' docs](./shippers/README.md) for more information. diff --git a/packages/analytics/client/README.md b/packages/analytics/ebt/client/README.md similarity index 99% rename from packages/analytics/client/README.md rename to packages/analytics/ebt/client/README.md index e51795faa6a03..508fc984c1a58 100644 --- a/packages/analytics/client/README.md +++ b/packages/analytics/ebt/client/README.md @@ -1,4 +1,4 @@ -# @kbn/analytics-client +# @kbn/ebt/client This module implements the Analytics client used for Event-Based Telemetry. The intention of the client is to be usable on both: the UI and the Server sides. @@ -7,7 +7,7 @@ This module implements the Analytics client used for Event-Based Telemetry. The It all starts by creating the client with the `createAnalytics` API: ```typescript -import { createAnalytics } from '@kbn/analytics-client'; +import { createAnalytics } from '@kbn/ebt/client'; const analytics = createAnalytics({ // Set to `true` when running in developer mode. @@ -167,7 +167,7 @@ import type { EventContext, IShipper, TelemetryCounter -} from '@kbn/analytics-client'; +} from '@kbn/ebt/client'; class MyVeryOwnShipper implements IShipper { constructor(myOptions: MyOptions, initContext: AnalyticsClientInitContext) { diff --git a/packages/analytics/client/index.ts b/packages/analytics/ebt/client/index.ts similarity index 98% rename from packages/analytics/client/index.ts rename to packages/analytics/ebt/client/index.ts index 4354d1f2d0cfd..7127a21a5517e 100644 --- a/packages/analytics/client/index.ts +++ b/packages/analytics/ebt/client/index.ts @@ -33,6 +33,7 @@ export type { ShipperName, // Types for the registerContextProvider API ContextProviderOpts, + ContextProviderName, // Types for the registerEventType API EventTypeOpts, } from './src/analytics_client'; diff --git a/packages/analytics/client/src/analytics_client/analytics_client.test.ts b/packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts similarity index 65% rename from packages/analytics/client/src/analytics_client/analytics_client.test.ts rename to packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts index 650b1e1b4e750..0d72f003e77a6 100644 --- a/packages/analytics/client/src/analytics_client/analytics_client.test.ts +++ b/packages/analytics/ebt/client/src/analytics_client/analytics_client.test.ts @@ -8,7 +8,6 @@ // eslint-disable-next-line max-classes-per-file import { Subject, lastValueFrom, take, toArray } from 'rxjs'; -import { fakeSchedulers } from 'rxjs-marbles/jest'; import type { MockedLogger } from '@kbn/logging-mocks'; import { loggerMock } from '@kbn/logging-mocks'; import { AnalyticsClient } from './analytics_client'; @@ -21,7 +20,7 @@ describe('AnalyticsClient', () => { let logger: MockedLogger; beforeEach(() => { - jest.useFakeTimers({ legacyFakeTimers: true }); + jest.useFakeTimers(); logger = loggerMock.create(); analyticsClient = new AnalyticsClient({ logger, @@ -338,76 +337,67 @@ describe('AnalyticsClient', () => { expect(optIn).toHaveBeenCalledWith(true); }); - test( - 'Spreads the context updates to the shipper (only after opt-in)', - fakeSchedulers((advance) => { - const extendContextMock = jest.fn(); - analyticsClient.registerShipper(MockedShipper, { extendContextMock }); - expect(extendContextMock).toHaveBeenCalledTimes(0); // Not until we have opt-in - analyticsClient.optIn({ global: { enabled: true } }); - advance(10); - expect(extendContextMock).toHaveBeenCalledWith({}); // The initial context - - const context$ = new Subject<{ a_field: boolean }>(); - analyticsClient.registerContextProvider({ - name: 'contextProviderA', - schema: { - a_field: { - type: 'boolean', - _meta: { - description: 'a_field description', - }, + test('Spreads the context updates to the shipper (only after opt-in)', async () => { + const extendContextMock = jest.fn(); + analyticsClient.registerShipper(MockedShipper, { extendContextMock }); + expect(extendContextMock).toHaveBeenCalledTimes(0); // Not until we have opt-in + analyticsClient.optIn({ global: { enabled: true } }); + await jest.advanceTimersByTimeAsync(10); + expect(extendContextMock).toHaveBeenCalledWith({}); // The initial context + + const context$ = new Subject<{ a_field: boolean }>(); + analyticsClient.registerContextProvider({ + name: 'contextProviderA', + schema: { + a_field: { + type: 'boolean', + _meta: { + description: 'a_field description', }, }, - context$, - }); + }, + context$, + }); - context$.next({ a_field: true }); - expect(extendContextMock).toHaveBeenCalledWith({ a_field: true }); // After update - }) - ); - - test( - 'Does not spread the context if opt-in === false', - fakeSchedulers((advance) => { - const extendContextMock = jest.fn(); - analyticsClient.registerShipper(MockedShipper, { extendContextMock }); - expect(extendContextMock).toHaveBeenCalledTimes(0); // Not until we have opt-in - analyticsClient.optIn({ global: { enabled: false } }); - advance(10); - expect(extendContextMock).toHaveBeenCalledTimes(0); // Not until we have opt-in - }) - ); - - test( - 'Handles errors in the shipper', - fakeSchedulers(async (advance) => { - const optInMock = jest.fn().mockImplementation(() => { - throw new Error('Something went terribly wrong'); - }); - const extendContextMock = jest.fn().mockImplementation(() => { - throw new Error('Something went terribly wrong'); - }); - const shutdownMock = jest.fn().mockImplementation(() => { - throw new Error('Something went terribly wrong'); - }); - analyticsClient.registerShipper(MockedShipper, { - optInMock, - extendContextMock, - shutdownMock, - }); - expect(() => analyticsClient.optIn({ global: { enabled: true } })).not.toThrow(); - advance(10); - expect(optInMock).toHaveBeenCalledWith(true); - expect(extendContextMock).toHaveBeenCalledWith({}); // The initial context - expect(logger.warn).toHaveBeenCalledWith( - `Shipper "${MockedShipper.shipperName}" failed to extend the context`, - expect.any(Error) - ); - await expect(analyticsClient.shutdown()).resolves.toBeUndefined(); - expect(shutdownMock).toHaveBeenCalled(); - }) - ); + context$.next({ a_field: true }); + expect(extendContextMock).toHaveBeenCalledWith({ a_field: true }); // After update + }); + + test('Does not spread the context if opt-in === false', async () => { + const extendContextMock = jest.fn(); + analyticsClient.registerShipper(MockedShipper, { extendContextMock }); + expect(extendContextMock).toHaveBeenCalledTimes(0); // Not until we have opt-in + analyticsClient.optIn({ global: { enabled: false } }); + await jest.advanceTimersByTimeAsync(10); + expect(extendContextMock).toHaveBeenCalledTimes(0); // Not until we have opt-in + }); + + test('Handles errors in the shipper', async () => { + const optInMock = jest.fn().mockImplementation(() => { + throw new Error('Something went terribly wrong'); + }); + const extendContextMock = jest.fn().mockImplementation(() => { + throw new Error('Something went terribly wrong'); + }); + const shutdownMock = jest.fn().mockImplementation(() => { + throw new Error('Something went terribly wrong'); + }); + analyticsClient.registerShipper(MockedShipper, { + optInMock, + extendContextMock, + shutdownMock, + }); + expect(() => analyticsClient.optIn({ global: { enabled: true } })).not.toThrow(); + await jest.advanceTimersByTimeAsync(10); + expect(optInMock).toHaveBeenCalledWith(true); + expect(extendContextMock).toHaveBeenCalledWith({}); // The initial context + expect(logger.warn).toHaveBeenCalledWith( + `Shipper "${MockedShipper.shipperName}" failed to extend the context`, + expect.any(Error) + ); + await expect(analyticsClient.shutdown()).resolves.toBeUndefined(); + expect(shutdownMock).toHaveBeenCalled(); + }); }); describe('ContextProvider APIs', () => { @@ -633,89 +623,86 @@ describe('AnalyticsClient', () => { ]); }); - test( - 'Sends events from the internal queue when there are shippers and an opt-in response is true', - fakeSchedulers(async (advance) => { - const telemetryCounterPromise = lastValueFrom( - analyticsClient.telemetryCounter$.pipe(take(3 + 2), toArray()) // Waiting for 3 enqueued + 2 batch-shipped events - ); - - // Send multiple events of 1 type to test the grouping logic as well - analyticsClient.reportEvent('event-type-a', { a_field: 'a' }); - analyticsClient.reportEvent('event-type-b', { b_field: 100 }); - analyticsClient.reportEvent('event-type-a', { a_field: 'b' }); - - // As proven in the previous test, the events are still enqueued. - // Let's register a shipper and opt-in to test the dequeue logic. - const reportEventsMock = jest.fn(); - analyticsClient.registerShipper(MockedShipper1, { reportEventsMock }); - analyticsClient.optIn({ global: { enabled: true } }); - advance(10); - - expect(reportEventsMock).toHaveBeenCalledTimes(2); - expect(reportEventsMock).toHaveBeenNthCalledWith(1, [ - { - context: {}, - event_type: 'event-type-a', - properties: { a_field: 'a' }, - timestamp: expect.any(String), - }, - { - context: {}, - event_type: 'event-type-a', - properties: { a_field: 'b' }, - timestamp: expect.any(String), - }, - ]); - expect(reportEventsMock).toHaveBeenNthCalledWith(2, [ - { - context: {}, - event_type: 'event-type-b', - properties: { b_field: 100 }, - timestamp: expect.any(String), - }, - ]); - - // Expect 3 enqueued events, and 2 sent_to_shipper batched requests - await expect(telemetryCounterPromise).resolves.toEqual([ - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-b', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'sent_to_shipper', - source: 'client', - event_type: 'event-type-a', - code: 'OK', - count: 2, - }, - { - type: 'sent_to_shipper', - source: 'client', - event_type: 'event-type-b', - code: 'OK', - count: 1, - }, - ]); - }) - ); + test('Sends events from the internal queue when there are shippers and an opt-in response is true', async () => { + const telemetryCounterPromise = lastValueFrom( + analyticsClient.telemetryCounter$.pipe(take(3 + 2), toArray()) // Waiting for 3 enqueued + 2 batch-shipped events + ); + + // Send multiple events of 1 type to test the grouping logic as well + analyticsClient.reportEvent('event-type-a', { a_field: 'a' }); + analyticsClient.reportEvent('event-type-b', { b_field: 100 }); + analyticsClient.reportEvent('event-type-a', { a_field: 'b' }); + + // As proven in the previous test, the events are still enqueued. + // Let's register a shipper and opt-in to test the dequeue logic. + const reportEventsMock = jest.fn(); + analyticsClient.registerShipper(MockedShipper1, { reportEventsMock }); + analyticsClient.optIn({ global: { enabled: true } }); + await jest.advanceTimersByTimeAsync(10); + + expect(reportEventsMock).toHaveBeenCalledTimes(2); + expect(reportEventsMock).toHaveBeenNthCalledWith(1, [ + { + context: {}, + event_type: 'event-type-a', + properties: { a_field: 'a' }, + timestamp: expect.any(String), + }, + { + context: {}, + event_type: 'event-type-a', + properties: { a_field: 'b' }, + timestamp: expect.any(String), + }, + ]); + expect(reportEventsMock).toHaveBeenNthCalledWith(2, [ + { + context: {}, + event_type: 'event-type-b', + properties: { b_field: 100 }, + timestamp: expect.any(String), + }, + ]); + + // Expect 3 enqueued events, and 2 sent_to_shipper batched requests + await expect(telemetryCounterPromise).resolves.toEqual([ + { + type: 'enqueued', + source: 'client', + event_type: 'event-type-a', + code: 'enqueued', + count: 1, + }, + { + type: 'enqueued', + source: 'client', + event_type: 'event-type-b', + code: 'enqueued', + count: 1, + }, + { + type: 'enqueued', + source: 'client', + event_type: 'event-type-a', + code: 'enqueued', + count: 1, + }, + { + type: 'sent_to_shipper', + source: 'client', + event_type: 'event-type-a', + code: 'OK', + count: 2, + }, + { + type: 'sent_to_shipper', + source: 'client', + event_type: 'event-type-b', + code: 'OK', + count: 1, + }, + ]); + }); test('Discards events from the internal queue when there are shippers and an opt-in response is false', async () => { const telemetryCounterPromise = lastValueFrom( @@ -823,259 +810,250 @@ describe('AnalyticsClient', () => { expect(analyticsClient['internalEventQueue$'].observed).toBe(false); }); - test( - 'Discards only one type of the enqueued events based on event_type config', - fakeSchedulers(async (advance) => { - const telemetryCounterPromise = lastValueFrom( - analyticsClient.telemetryCounter$.pipe(take(3 + 1), toArray()) // Waiting for 3 enqueued + 1 batch-shipped events - ); - - // Send multiple events of 1 type to test the grouping logic as well - analyticsClient.reportEvent('event-type-a', { a_field: 'a' }); - analyticsClient.reportEvent('event-type-b', { b_field: 100 }); - analyticsClient.reportEvent('event-type-a', { a_field: 'b' }); - - const reportEventsMock = jest.fn(); - analyticsClient.registerShipper(MockedShipper1, { reportEventsMock }); - analyticsClient.optIn({ - global: { enabled: true }, - event_types: { ['event-type-a']: { enabled: false } }, - }); - advance(10); - - expect(reportEventsMock).toHaveBeenCalledTimes(1); - expect(reportEventsMock).toHaveBeenNthCalledWith(1, [ - { - context: {}, - event_type: 'event-type-b', - properties: { b_field: 100 }, - timestamp: expect.any(String), - }, - ]); - - // Expect 3 enqueued events, and 1 sent_to_shipper batched request - await expect(telemetryCounterPromise).resolves.toEqual([ - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-b', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'sent_to_shipper', - source: 'client', - event_type: 'event-type-b', - code: 'OK', - count: 1, - }, - ]); - }) - ); - - test( - 'Discards the event at the shipper level (for a specific event)', - fakeSchedulers(async (advance) => { - const telemetryCounterPromise = lastValueFrom( - analyticsClient.telemetryCounter$.pipe(take(3 + 2), toArray()) // Waiting for 3 enqueued + 2 batch-shipped events - ); - - // Send multiple events of 1 type to test the grouping logic as well - analyticsClient.reportEvent('event-type-a', { a_field: 'a' }); - analyticsClient.reportEvent('event-type-b', { b_field: 100 }); - analyticsClient.reportEvent('event-type-a', { a_field: 'b' }); - - // Register 2 shippers and set 1 of them as disabled for event-type-a - const reportEventsMock1 = jest.fn(); - const reportEventsMock2 = jest.fn(); - analyticsClient.registerShipper(MockedShipper1, { reportEventsMock: reportEventsMock1 }); - analyticsClient.registerShipper(MockedShipper2, { reportEventsMock: reportEventsMock2 }); - analyticsClient.optIn({ - global: { enabled: true }, - event_types: { - ['event-type-a']: { enabled: true, shippers: { [MockedShipper2.shipperName]: false } }, - }, - }); - advance(10); - - expect(reportEventsMock1).toHaveBeenCalledTimes(2); - expect(reportEventsMock1).toHaveBeenNthCalledWith(1, [ - { - context: {}, - event_type: 'event-type-a', - properties: { a_field: 'a' }, - timestamp: expect.any(String), - }, - { - context: {}, - event_type: 'event-type-a', - properties: { a_field: 'b' }, - timestamp: expect.any(String), - }, - ]); - expect(reportEventsMock1).toHaveBeenNthCalledWith(2, [ - { - context: {}, - event_type: 'event-type-b', - properties: { b_field: 100 }, - timestamp: expect.any(String), - }, - ]); - expect(reportEventsMock2).toHaveBeenCalledTimes(1); - expect(reportEventsMock2).toHaveBeenNthCalledWith(1, [ - { - context: {}, - event_type: 'event-type-b', - properties: { b_field: 100 }, - timestamp: expect.any(String), - }, - ]); - - // Expect 3 enqueued events, and 2 sent_to_shipper batched requests - await expect(telemetryCounterPromise).resolves.toEqual([ - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-b', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'sent_to_shipper', - source: 'client', - event_type: 'event-type-a', - code: 'OK', - count: 2, - }, - { - type: 'sent_to_shipper', - source: 'client', - event_type: 'event-type-b', - code: 'OK', - count: 1, - }, - ]); - }) - ); - - test( - 'Discards all the events at the shipper level (globally disabled)', - fakeSchedulers(async (advance) => { - const telemetryCounterPromise = lastValueFrom( - analyticsClient.telemetryCounter$.pipe(take(3 + 2), toArray()) // Waiting for 3 enqueued + 2 batch-shipped events - ); - - // Send multiple events of 1 type to test the grouping logic as well - analyticsClient.reportEvent('event-type-a', { a_field: 'a' }); - analyticsClient.reportEvent('event-type-b', { b_field: 100 }); - analyticsClient.reportEvent('event-type-a', { a_field: 'b' }); - - // Register 2 shippers and set 1 of them as globally disabled - const reportEventsMock1 = jest.fn(); - const reportEventsMock2 = jest.fn(); - analyticsClient.registerShipper(MockedShipper1, { reportEventsMock: reportEventsMock1 }); - analyticsClient.registerShipper(MockedShipper2, { reportEventsMock: reportEventsMock2 }); - analyticsClient.optIn({ - global: { enabled: true, shippers: { [MockedShipper2.shipperName]: false } }, - event_types: { - ['event-type-a']: { enabled: true }, - }, - }); - advance(10); - - expect(reportEventsMock1).toHaveBeenCalledTimes(2); - expect(reportEventsMock1).toHaveBeenNthCalledWith(1, [ - { - context: {}, - event_type: 'event-type-a', - properties: { a_field: 'a' }, - timestamp: expect.any(String), - }, - { - context: {}, - event_type: 'event-type-a', - properties: { a_field: 'b' }, - timestamp: expect.any(String), - }, - ]); - expect(reportEventsMock1).toHaveBeenNthCalledWith(2, [ - { - context: {}, - event_type: 'event-type-b', - properties: { b_field: 100 }, - timestamp: expect.any(String), - }, - ]); - expect(reportEventsMock2).toHaveBeenCalledTimes(0); - - // Expect 3 enqueued events, and 2 sent_to_shipper batched requests - await expect(telemetryCounterPromise).resolves.toEqual([ - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-b', - code: 'enqueued', - count: 1, - }, - { - type: 'enqueued', - source: 'client', - event_type: 'event-type-a', - code: 'enqueued', - count: 1, - }, - { - type: 'sent_to_shipper', - source: 'client', - event_type: 'event-type-a', - code: 'OK', - count: 2, - }, - { - type: 'sent_to_shipper', - source: 'client', - event_type: 'event-type-b', - code: 'OK', - count: 1, - }, - ]); - }) - ); + test('Discards only one type of the enqueued events based on event_type config', async () => { + const telemetryCounterPromise = lastValueFrom( + analyticsClient.telemetryCounter$.pipe(take(3 + 1), toArray()) // Waiting for 3 enqueued + 1 batch-shipped events + ); + + // Send multiple events of 1 type to test the grouping logic as well + analyticsClient.reportEvent('event-type-a', { a_field: 'a' }); + analyticsClient.reportEvent('event-type-b', { b_field: 100 }); + analyticsClient.reportEvent('event-type-a', { a_field: 'b' }); + + const reportEventsMock = jest.fn(); + analyticsClient.registerShipper(MockedShipper1, { reportEventsMock }); + analyticsClient.optIn({ + global: { enabled: true }, + event_types: { ['event-type-a']: { enabled: false } }, + }); + await jest.advanceTimersByTimeAsync(10); + + expect(reportEventsMock).toHaveBeenCalledTimes(1); + expect(reportEventsMock).toHaveBeenNthCalledWith(1, [ + { + context: {}, + event_type: 'event-type-b', + properties: { b_field: 100 }, + timestamp: expect.any(String), + }, + ]); + + // Expect 3 enqueued events, and 1 sent_to_shipper batched request + await expect(telemetryCounterPromise).resolves.toEqual([ + { + type: 'enqueued', + source: 'client', + event_type: 'event-type-a', + code: 'enqueued', + count: 1, + }, + { + type: 'enqueued', + source: 'client', + event_type: 'event-type-b', + code: 'enqueued', + count: 1, + }, + { + type: 'enqueued', + source: 'client', + event_type: 'event-type-a', + code: 'enqueued', + count: 1, + }, + { + type: 'sent_to_shipper', + source: 'client', + event_type: 'event-type-b', + code: 'OK', + count: 1, + }, + ]); + }); + + test('Discards the event at the shipper level (for a specific event)', async () => { + const telemetryCounterPromise = lastValueFrom( + analyticsClient.telemetryCounter$.pipe(take(3 + 2), toArray()) // Waiting for 3 enqueued + 2 batch-shipped events + ); + + // Send multiple events of 1 type to test the grouping logic as well + analyticsClient.reportEvent('event-type-a', { a_field: 'a' }); + analyticsClient.reportEvent('event-type-b', { b_field: 100 }); + analyticsClient.reportEvent('event-type-a', { a_field: 'b' }); + + // Register 2 shippers and set 1 of them as disabled for event-type-a + const reportEventsMock1 = jest.fn(); + const reportEventsMock2 = jest.fn(); + analyticsClient.registerShipper(MockedShipper1, { reportEventsMock: reportEventsMock1 }); + analyticsClient.registerShipper(MockedShipper2, { reportEventsMock: reportEventsMock2 }); + analyticsClient.optIn({ + global: { enabled: true }, + event_types: { + ['event-type-a']: { enabled: true, shippers: { [MockedShipper2.shipperName]: false } }, + }, + }); + await jest.advanceTimersByTimeAsync(10); + + expect(reportEventsMock1).toHaveBeenCalledTimes(2); + expect(reportEventsMock1).toHaveBeenNthCalledWith(1, [ + { + context: {}, + event_type: 'event-type-a', + properties: { a_field: 'a' }, + timestamp: expect.any(String), + }, + { + context: {}, + event_type: 'event-type-a', + properties: { a_field: 'b' }, + timestamp: expect.any(String), + }, + ]); + expect(reportEventsMock1).toHaveBeenNthCalledWith(2, [ + { + context: {}, + event_type: 'event-type-b', + properties: { b_field: 100 }, + timestamp: expect.any(String), + }, + ]); + expect(reportEventsMock2).toHaveBeenCalledTimes(1); + expect(reportEventsMock2).toHaveBeenNthCalledWith(1, [ + { + context: {}, + event_type: 'event-type-b', + properties: { b_field: 100 }, + timestamp: expect.any(String), + }, + ]); + + // Expect 3 enqueued events, and 2 sent_to_shipper batched requests + await expect(telemetryCounterPromise).resolves.toEqual([ + { + type: 'enqueued', + source: 'client', + event_type: 'event-type-a', + code: 'enqueued', + count: 1, + }, + { + type: 'enqueued', + source: 'client', + event_type: 'event-type-b', + code: 'enqueued', + count: 1, + }, + { + type: 'enqueued', + source: 'client', + event_type: 'event-type-a', + code: 'enqueued', + count: 1, + }, + { + type: 'sent_to_shipper', + source: 'client', + event_type: 'event-type-a', + code: 'OK', + count: 2, + }, + { + type: 'sent_to_shipper', + source: 'client', + event_type: 'event-type-b', + code: 'OK', + count: 1, + }, + ]); + }); + + test('Discards all the events at the shipper level (globally disabled)', async () => { + const telemetryCounterPromise = lastValueFrom( + analyticsClient.telemetryCounter$.pipe(take(3 + 2), toArray()) // Waiting for 3 enqueued + 2 batch-shipped events + ); + + // Send multiple events of 1 type to test the grouping logic as well + analyticsClient.reportEvent('event-type-a', { a_field: 'a' }); + analyticsClient.reportEvent('event-type-b', { b_field: 100 }); + analyticsClient.reportEvent('event-type-a', { a_field: 'b' }); + + // Register 2 shippers and set 1 of them as globally disabled + const reportEventsMock1 = jest.fn(); + const reportEventsMock2 = jest.fn(); + analyticsClient.registerShipper(MockedShipper1, { reportEventsMock: reportEventsMock1 }); + analyticsClient.registerShipper(MockedShipper2, { reportEventsMock: reportEventsMock2 }); + analyticsClient.optIn({ + global: { enabled: true, shippers: { [MockedShipper2.shipperName]: false } }, + event_types: { + ['event-type-a']: { enabled: true }, + }, + }); + await jest.advanceTimersByTimeAsync(10); + + expect(reportEventsMock1).toHaveBeenCalledTimes(2); + expect(reportEventsMock1).toHaveBeenNthCalledWith(1, [ + { + context: {}, + event_type: 'event-type-a', + properties: { a_field: 'a' }, + timestamp: expect.any(String), + }, + { + context: {}, + event_type: 'event-type-a', + properties: { a_field: 'b' }, + timestamp: expect.any(String), + }, + ]); + expect(reportEventsMock1).toHaveBeenNthCalledWith(2, [ + { + context: {}, + event_type: 'event-type-b', + properties: { b_field: 100 }, + timestamp: expect.any(String), + }, + ]); + expect(reportEventsMock2).toHaveBeenCalledTimes(0); + + // Expect 3 enqueued events, and 2 sent_to_shipper batched requests + await expect(telemetryCounterPromise).resolves.toEqual([ + { + type: 'enqueued', + source: 'client', + event_type: 'event-type-a', + code: 'enqueued', + count: 1, + }, + { + type: 'enqueued', + source: 'client', + event_type: 'event-type-b', + code: 'enqueued', + count: 1, + }, + { + type: 'enqueued', + source: 'client', + event_type: 'event-type-a', + code: 'enqueued', + count: 1, + }, + { + type: 'sent_to_shipper', + source: 'client', + event_type: 'event-type-a', + code: 'OK', + count: 2, + }, + { + type: 'sent_to_shipper', + source: 'client', + event_type: 'event-type-b', + code: 'OK', + count: 1, + }, + ]); + }); test('Discards incoming events when opt-in response is false', async () => { // Set OptIn and shipper first to test the "once-set up" scenario diff --git a/packages/analytics/client/src/analytics_client/analytics_client.ts b/packages/analytics/ebt/client/src/analytics_client/analytics_client.ts similarity index 100% rename from packages/analytics/client/src/analytics_client/analytics_client.ts rename to packages/analytics/ebt/client/src/analytics_client/analytics_client.ts diff --git a/packages/analytics/client/src/analytics_client/context_service.test.ts b/packages/analytics/ebt/client/src/analytics_client/context_service.test.ts similarity index 100% rename from packages/analytics/client/src/analytics_client/context_service.test.ts rename to packages/analytics/ebt/client/src/analytics_client/context_service.test.ts diff --git a/packages/analytics/client/src/analytics_client/context_service.ts b/packages/analytics/ebt/client/src/analytics_client/context_service.ts similarity index 100% rename from packages/analytics/client/src/analytics_client/context_service.ts rename to packages/analytics/ebt/client/src/analytics_client/context_service.ts diff --git a/packages/analytics/client/src/analytics_client/index.ts b/packages/analytics/ebt/client/src/analytics_client/index.ts similarity index 100% rename from packages/analytics/client/src/analytics_client/index.ts rename to packages/analytics/ebt/client/src/analytics_client/index.ts diff --git a/packages/analytics/client/src/analytics_client/mocks.ts b/packages/analytics/ebt/client/src/analytics_client/mocks.ts similarity index 100% rename from packages/analytics/client/src/analytics_client/mocks.ts rename to packages/analytics/ebt/client/src/analytics_client/mocks.ts diff --git a/packages/analytics/client/src/analytics_client/opt_in_config.test.ts b/packages/analytics/ebt/client/src/analytics_client/opt_in_config.test.ts similarity index 100% rename from packages/analytics/client/src/analytics_client/opt_in_config.test.ts rename to packages/analytics/ebt/client/src/analytics_client/opt_in_config.test.ts diff --git a/packages/analytics/client/src/analytics_client/opt_in_config.ts b/packages/analytics/ebt/client/src/analytics_client/opt_in_config.ts similarity index 100% rename from packages/analytics/client/src/analytics_client/opt_in_config.ts rename to packages/analytics/ebt/client/src/analytics_client/opt_in_config.ts diff --git a/packages/analytics/client/src/analytics_client/shippers_registry.test.ts b/packages/analytics/ebt/client/src/analytics_client/shippers_registry.test.ts similarity index 100% rename from packages/analytics/client/src/analytics_client/shippers_registry.test.ts rename to packages/analytics/ebt/client/src/analytics_client/shippers_registry.test.ts diff --git a/packages/analytics/client/src/analytics_client/shippers_registry.ts b/packages/analytics/ebt/client/src/analytics_client/shippers_registry.ts similarity index 100% rename from packages/analytics/client/src/analytics_client/shippers_registry.ts rename to packages/analytics/ebt/client/src/analytics_client/shippers_registry.ts diff --git a/packages/analytics/client/src/analytics_client/types.ts b/packages/analytics/ebt/client/src/analytics_client/types.ts similarity index 100% rename from packages/analytics/client/src/analytics_client/types.ts rename to packages/analytics/ebt/client/src/analytics_client/types.ts diff --git a/packages/analytics/client/src/events/index.ts b/packages/analytics/ebt/client/src/events/index.ts similarity index 100% rename from packages/analytics/client/src/events/index.ts rename to packages/analytics/ebt/client/src/events/index.ts diff --git a/packages/analytics/client/src/events/types.ts b/packages/analytics/ebt/client/src/events/types.ts similarity index 100% rename from packages/analytics/client/src/events/types.ts rename to packages/analytics/ebt/client/src/events/types.ts diff --git a/packages/analytics/client/src/mocks.ts b/packages/analytics/ebt/client/src/mocks.ts similarity index 100% rename from packages/analytics/client/src/mocks.ts rename to packages/analytics/ebt/client/src/mocks.ts diff --git a/packages/analytics/client/src/schema/index.ts b/packages/analytics/ebt/client/src/schema/index.ts similarity index 100% rename from packages/analytics/client/src/schema/index.ts rename to packages/analytics/ebt/client/src/schema/index.ts diff --git a/packages/analytics/client/src/schema/types.test.ts b/packages/analytics/ebt/client/src/schema/types.test.ts similarity index 100% rename from packages/analytics/client/src/schema/types.test.ts rename to packages/analytics/ebt/client/src/schema/types.test.ts diff --git a/packages/analytics/client/src/schema/types.ts b/packages/analytics/ebt/client/src/schema/types.ts similarity index 100% rename from packages/analytics/client/src/schema/types.ts rename to packages/analytics/ebt/client/src/schema/types.ts diff --git a/packages/analytics/client/src/schema/validation/excess.test.ts b/packages/analytics/ebt/client/src/schema/validation/excess.test.ts similarity index 100% rename from packages/analytics/client/src/schema/validation/excess.test.ts rename to packages/analytics/ebt/client/src/schema/validation/excess.test.ts diff --git a/packages/analytics/client/src/schema/validation/excess.ts b/packages/analytics/ebt/client/src/schema/validation/excess.ts similarity index 100% rename from packages/analytics/client/src/schema/validation/excess.ts rename to packages/analytics/ebt/client/src/schema/validation/excess.ts diff --git a/packages/analytics/client/src/schema/validation/index.ts b/packages/analytics/ebt/client/src/schema/validation/index.ts similarity index 100% rename from packages/analytics/client/src/schema/validation/index.ts rename to packages/analytics/ebt/client/src/schema/validation/index.ts diff --git a/packages/analytics/client/src/schema/validation/schema_to_io_ts.test.ts b/packages/analytics/ebt/client/src/schema/validation/schema_to_io_ts.test.ts similarity index 100% rename from packages/analytics/client/src/schema/validation/schema_to_io_ts.test.ts rename to packages/analytics/ebt/client/src/schema/validation/schema_to_io_ts.test.ts diff --git a/packages/analytics/client/src/schema/validation/schema_to_io_ts.ts b/packages/analytics/ebt/client/src/schema/validation/schema_to_io_ts.ts similarity index 100% rename from packages/analytics/client/src/schema/validation/schema_to_io_ts.ts rename to packages/analytics/ebt/client/src/schema/validation/schema_to_io_ts.ts diff --git a/packages/analytics/client/src/schema/validation/validate_schema.test.ts b/packages/analytics/ebt/client/src/schema/validation/validate_schema.test.ts similarity index 100% rename from packages/analytics/client/src/schema/validation/validate_schema.test.ts rename to packages/analytics/ebt/client/src/schema/validation/validate_schema.test.ts diff --git a/packages/analytics/client/src/schema/validation/validate_schema.ts b/packages/analytics/ebt/client/src/schema/validation/validate_schema.ts similarity index 100% rename from packages/analytics/client/src/schema/validation/validate_schema.ts rename to packages/analytics/ebt/client/src/schema/validation/validate_schema.ts diff --git a/packages/analytics/client/src/shippers/index.ts b/packages/analytics/ebt/client/src/shippers/index.ts similarity index 100% rename from packages/analytics/client/src/shippers/index.ts rename to packages/analytics/ebt/client/src/shippers/index.ts diff --git a/packages/analytics/client/src/shippers/mocks.ts b/packages/analytics/ebt/client/src/shippers/mocks.ts similarity index 100% rename from packages/analytics/client/src/shippers/mocks.ts rename to packages/analytics/ebt/client/src/shippers/mocks.ts diff --git a/packages/analytics/client/src/shippers/types.ts b/packages/analytics/ebt/client/src/shippers/types.ts similarity index 100% rename from packages/analytics/client/src/shippers/types.ts rename to packages/analytics/ebt/client/src/shippers/types.ts diff --git a/packages/analytics/ebt/index.ts b/packages/analytics/ebt/index.ts new file mode 100644 index 0000000000000..ec407b406d406 --- /dev/null +++ b/packages/analytics/ebt/index.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// Exporting the types here as a utility only +// The recommended way of using this library is to import from the subdirectories /client, /shippers/* +// The reason is to avoid leaking server-side code to the browser, and vice-versa +export type { + AnalyticsClient, + // Types for the constructor + AnalyticsClientInitContext, + // Types for the registerShipper API + ShipperClassConstructor, + RegisterShipperOpts, + // Types for the optIn API + OptInConfig, + OptInConfigPerType, + ShipperName, + // Types for the registerContextProvider API + ContextProviderOpts, + ContextProviderName, + // Types for the registerEventType API + EventTypeOpts, + // Events + Event, + EventContext, + EventType, + TelemetryCounter, + TelemetryCounterType, + // Schema + RootSchema, + SchemaObject, + SchemaArray, + SchemaChildValue, + SchemaMeta, + SchemaValue, + SchemaMetaOptional, + PossibleSchemaTypes, + AllowedSchemaBooleanTypes, + AllowedSchemaNumberTypes, + AllowedSchemaStringTypes, + AllowedSchemaTypes, + // Shippers + IShipper, +} from './client'; +export type { ElasticV3ShipperOptions } from './shippers/elastic_v3/common'; +export type { ElasticV3BrowserShipper } from './shippers/elastic_v3/browser'; +export type { ElasticV3ServerShipper } from './shippers/elastic_v3/server'; +export type { + FullStoryShipperConfig, + FullStoryShipper, + FullStorySnippetConfig, +} from './shippers/fullstory'; diff --git a/packages/analytics/shippers/fullstory/jest.config.js b/packages/analytics/ebt/jest.config.js similarity index 75% rename from packages/analytics/shippers/fullstory/jest.config.js rename to packages/analytics/ebt/jest.config.js index 9d1637823c150..7296624c287b5 100644 --- a/packages/analytics/shippers/fullstory/jest.config.js +++ b/packages/analytics/ebt/jest.config.js @@ -7,7 +7,7 @@ */ module.exports = { - preset: '@kbn/test/jest_node', - rootDir: '../../../../', - roots: ['/packages/analytics/shippers/fullstory'], + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/packages/analytics/ebt'], }; diff --git a/packages/analytics/client/kibana.jsonc b/packages/analytics/ebt/kibana.jsonc similarity index 66% rename from packages/analytics/client/kibana.jsonc rename to packages/analytics/ebt/kibana.jsonc index a027e7ee866b5..947d6224b6933 100644 --- a/packages/analytics/client/kibana.jsonc +++ b/packages/analytics/ebt/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", - "id": "@kbn/analytics-client", + "id": "@kbn/ebt", "owner": "@elastic/kibana-core" } diff --git a/packages/analytics/client/package.json b/packages/analytics/ebt/package.json similarity index 59% rename from packages/analytics/client/package.json rename to packages/analytics/ebt/package.json index 6db911a1bac86..aee987a4c179b 100644 --- a/packages/analytics/client/package.json +++ b/packages/analytics/ebt/package.json @@ -1,7 +1,6 @@ { - "name": "@kbn/analytics-client", + "name": "@kbn/ebt", "private": true, "version": "1.0.0", - "author": "Kibana Core", "license": "SSPL-1.0 OR Elastic License 2.0" } \ No newline at end of file diff --git a/packages/analytics/shippers/README.md b/packages/analytics/ebt/shippers/README.md similarity index 91% rename from packages/analytics/shippers/README.md rename to packages/analytics/ebt/shippers/README.md index 5ab85d08ff2ca..3f312d1ecefe3 100644 --- a/packages/analytics/shippers/README.md +++ b/packages/analytics/ebt/shippers/README.md @@ -1,4 +1,4 @@ -# @kbn/analytics-shippers-* +# @kbn/ebt/shippers/* This directory holds the implementation of the _built-in_ shippers provided by the Analytics client. At the moment, the shippers are: diff --git a/packages/analytics/shippers/elastic_v3/browser/README.md b/packages/analytics/ebt/shippers/elastic_v3/browser/README.md similarity index 79% rename from packages/analytics/shippers/elastic_v3/browser/README.md rename to packages/analytics/ebt/shippers/elastic_v3/browser/README.md index fcdd5e8a3ca2e..1253a9619233d 100644 --- a/packages/analytics/shippers/elastic_v3/browser/README.md +++ b/packages/analytics/ebt/shippers/elastic_v3/browser/README.md @@ -1,13 +1,13 @@ -# @kbn/analytics-shippers-elastic-v3-browser +# @kbn/ebt/shippers/elastic_v3/browser -UI-side implementation of the Elastic V3 shipper for the `@kbn/analytics-client`. +UI-side implementation of the Elastic V3 shipper for the `@kbn/ebt/client`. ## How to use it -This module is intended to be used **on the browser only**. Due to the nature of the UI events, they are usually more scattered in time, and we can assume a much lower load than the server. For that reason, it doesn't apply the necessary backpressure mechanisms to prevent the server from getting overloaded with too many events neither identifies if the server sits behind a firewall to discard any incoming events. Refer to `@kbn/analytics-shippers-elastic-v3-server` for the server-side implementation. +This module is intended to be used **on the browser only**. Due to the nature of the UI events, they are usually more scattered in time, and we can assume a much lower load than the server. For that reason, it doesn't apply the necessary backpressure mechanisms to prevent the server from getting overloaded with too many events neither identifies if the server sits behind a firewall to discard any incoming events. Refer to `@kbn/ebt/shippers/elastic_v3/server` for the server-side implementation. ```typescript -import { ElasticV3BrowserShipper } from "@kbn/analytics-shippers-elastic-v3-browser"; +import { ElasticV3BrowserShipper } from "@kbn/ebt/shippers/elastic_v3/browser"; analytics.registerShipper(ElasticV3BrowserShipper, { channelName: 'myChannel', version: '1.0.0' }); ``` diff --git a/packages/analytics/shippers/elastic_v3/browser/index.ts b/packages/analytics/ebt/shippers/elastic_v3/browser/index.ts similarity index 82% rename from packages/analytics/shippers/elastic_v3/browser/index.ts rename to packages/analytics/ebt/shippers/elastic_v3/browser/index.ts index 22324dc6d6218..05619fba6e6e3 100644 --- a/packages/analytics/shippers/elastic_v3/browser/index.ts +++ b/packages/analytics/ebt/shippers/elastic_v3/browser/index.ts @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -export type { ElasticV3ShipperOptions } from '@kbn/analytics-shippers-elastic-v3-common'; +export type { ElasticV3ShipperOptions } from '../common'; export { ElasticV3BrowserShipper } from './src/browser_shipper'; diff --git a/packages/analytics/shippers/elastic_v3/browser/src/browser_shipper.test.ts b/packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.test.ts similarity index 51% rename from packages/analytics/shippers/elastic_v3/browser/src/browser_shipper.test.ts rename to packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.test.ts index dca98bddaa6ce..fd8d7f680fd3c 100644 --- a/packages/analytics/shippers/elastic_v3/browser/src/browser_shipper.test.ts +++ b/packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.test.ts @@ -7,9 +7,8 @@ */ import { loggerMock } from '@kbn/logging-mocks'; -import type { AnalyticsClientInitContext, Event } from '@kbn/analytics-client'; -import { fakeSchedulers } from 'rxjs-marbles/jest'; import { firstValueFrom } from 'rxjs'; +import type { AnalyticsClientInitContext, Event } from '../../../../client'; import { ElasticV3BrowserShipper } from './browser_shipper'; describe('ElasticV3BrowserShipper', () => { @@ -33,7 +32,7 @@ describe('ElasticV3BrowserShipper', () => { let fetchMock: jest.Mock; beforeEach(() => { - jest.useFakeTimers({ legacyFakeTimers: true }); + jest.useFakeTimers(); fetchMock = jest.fn().mockResolvedValue({ status: 200, @@ -109,37 +108,32 @@ describe('ElasticV3BrowserShipper', () => { expect(fetchMock).not.toHaveBeenCalled(); }); - test( - 'calls to reportEvents do not call `fetch` after 1s because no optIn value is set yet', - fakeSchedulers((advance) => { - shipper.reportEvents(events); - advance(1000); - expect(fetchMock).not.toHaveBeenCalled(); - }) - ); - - test( - 'calls to reportEvents call `fetch` after 1s when optIn value is set to true', - fakeSchedulers(async (advance) => { - shipper.reportEvents(events); - shipper.optIn(true); - const counter = firstValueFrom(shipper.telemetryCounter$); - advance(1000); - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - keepalive: true, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` + test('calls to reportEvents do not call `fetch` after 1s because no optIn value is set yet', async () => { + shipper.reportEvents(events); + await jest.advanceTimersByTimeAsync(1000); + expect(fetchMock).not.toHaveBeenCalled(); + }); + + test('calls to reportEvents call `fetch` after 1s when optIn value is set to true', async () => { + shipper.reportEvents(events); + shipper.optIn(true); + const counter = firstValueFrom(shipper.telemetryCounter$); + await jest.advanceTimersByTimeAsync(1000); + expect(fetchMock).toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { + body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', + headers: { + 'content-type': 'application/x-ndjson', + 'x-elastic-cluster-id': 'UNKNOWN', + 'x-elastic-stack-version': '1.2.3', + }, + keepalive: true, + method: 'POST', + query: { debug: true }, + } + ); + await expect(counter).resolves.toMatchInlineSnapshot(` Object { "code": "200", "count": 1, @@ -148,43 +142,35 @@ describe('ElasticV3BrowserShipper', () => { "type": "succeeded", } `); - }) - ); - - test( - 'calls to reportEvents do not call `fetch` after 1s when optIn value is set to false', - fakeSchedulers((advance) => { - shipper.reportEvents(events); - shipper.optIn(false); - advance(1000); - expect(fetchMock).not.toHaveBeenCalled(); - }) - ); - - test( - 'calls to flush forces the client to send all the pending events', - fakeSchedulers(async (advance) => { - shipper.optIn(true); - shipper.reportEvents(events); - const counter = firstValueFrom(shipper.telemetryCounter$); - const promise = shipper.flush(); - advance(0); // bufferWhen requires some sort of fake scheduling to advance (but we are not advancing 1s) - await promise; - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - keepalive: true, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` + }); + + test('calls to reportEvents do not call `fetch` after 1s when optIn value is set to false', async () => { + shipper.reportEvents(events); + shipper.optIn(false); + await jest.advanceTimersByTimeAsync(1000); + expect(fetchMock).not.toHaveBeenCalled(); + }); + + test('calls to flush forces the client to send all the pending events', async () => { + shipper.optIn(true); + shipper.reportEvents(events); + const counter = firstValueFrom(shipper.telemetryCounter$); + await shipper.flush(); + expect(fetchMock).toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { + body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', + headers: { + 'content-type': 'application/x-ndjson', + 'x-elastic-cluster-id': 'UNKNOWN', + 'x-elastic-stack-version': '1.2.3', + }, + keepalive: true, + method: 'POST', + query: { debug: true }, + } + ); + await expect(counter).resolves.toMatchInlineSnapshot(` Object { "code": "200", "count": 1, @@ -193,8 +179,7 @@ describe('ElasticV3BrowserShipper', () => { "type": "succeeded", } `); - }) - ); + }); test('calls to flush resolve immediately if there is nothing to send', async () => { shipper.optIn(true); @@ -243,55 +228,50 @@ describe('ElasticV3BrowserShipper', () => { `); }); - test( - 'does not add the query.debug: true property to the request if the shipper is not set with the debug flag', - fakeSchedulers((advance) => { - shipper = new ElasticV3BrowserShipper( - { version: '1.2.3', channelName: 'test-channel' }, - initContext - ); - shipper.reportEvents(events); - shipper.optIn(true); - advance(1000); - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - keepalive: true, - method: 'POST', - } - ); - }) - ); - - test( - 'handles when the fetch request fails', - fakeSchedulers(async (advance) => { - fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); - shipper.reportEvents(events); - shipper.optIn(true); - const counter = firstValueFrom(shipper.telemetryCounter$); - advance(1000); - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - keepalive: true, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` + test('does not add the query.debug: true property to the request if the shipper is not set with the debug flag', async () => { + shipper = new ElasticV3BrowserShipper( + { version: '1.2.3', channelName: 'test-channel' }, + initContext + ); + shipper.reportEvents(events); + shipper.optIn(true); + await jest.advanceTimersByTimeAsync(1000); + expect(fetchMock).toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { + body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', + headers: { + 'content-type': 'application/x-ndjson', + 'x-elastic-cluster-id': 'UNKNOWN', + 'x-elastic-stack-version': '1.2.3', + }, + keepalive: true, + method: 'POST', + } + ); + }); + + test('handles when the fetch request fails', async () => { + fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); + shipper.reportEvents(events); + shipper.optIn(true); + const counter = firstValueFrom(shipper.telemetryCounter$); + await jest.advanceTimersByTimeAsync(1000); + expect(fetchMock).toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { + body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', + headers: { + 'content-type': 'application/x-ndjson', + 'x-elastic-cluster-id': 'UNKNOWN', + 'x-elastic-stack-version': '1.2.3', + }, + keepalive: true, + method: 'POST', + query: { debug: true }, + } + ); + await expect(counter).resolves.toMatchInlineSnapshot(` Object { "code": "Failed to fetch", "count": 1, @@ -300,36 +280,33 @@ describe('ElasticV3BrowserShipper', () => { "type": "failed", } `); - }) - ); - - test( - 'handles when the fetch request fails (request completes but not OK response)', - fakeSchedulers(async (advance) => { - fetchMock.mockResolvedValue({ - ok: false, - status: 400, - text: () => Promise.resolve('{"status": "not ok"}'), - }); - shipper.reportEvents(events); - shipper.optIn(true); - const counter = firstValueFrom(shipper.telemetryCounter$); - advance(1000); - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - keepalive: true, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` + }); + + test('handles when the fetch request fails (request completes but not OK response)', async () => { + fetchMock.mockResolvedValue({ + ok: false, + status: 400, + text: () => Promise.resolve('{"status": "not ok"}'), + }); + shipper.reportEvents(events); + shipper.optIn(true); + const counter = firstValueFrom(shipper.telemetryCounter$); + await jest.advanceTimersByTimeAsync(1000); + expect(fetchMock).toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { + body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', + headers: { + 'content-type': 'application/x-ndjson', + 'x-elastic-cluster-id': 'UNKNOWN', + 'x-elastic-stack-version': '1.2.3', + }, + keepalive: true, + method: 'POST', + query: { debug: true }, + } + ); + await expect(counter).resolves.toMatchInlineSnapshot(` Object { "code": "400", "count": 1, @@ -338,6 +315,5 @@ describe('ElasticV3BrowserShipper', () => { "type": "failed", } `); - }) - ); + }); }); diff --git a/packages/analytics/shippers/elastic_v3/browser/src/browser_shipper.ts b/packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.ts similarity index 95% rename from packages/analytics/shippers/elastic_v3/browser/src/browser_shipper.ts rename to packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.ts index 185ce37072be0..c063dcf86a95f 100644 --- a/packages/analytics/shippers/elastic_v3/browser/src/browser_shipper.ts +++ b/packages/analytics/ebt/shippers/elastic_v3/browser/src/browser_shipper.ts @@ -23,14 +23,9 @@ import type { EventContext, IShipper, TelemetryCounter, -} from '@kbn/analytics-client'; -import { ElasticV3ShipperOptions, ErrorWithCode } from '@kbn/analytics-shippers-elastic-v3-common'; -import { - buildHeaders, - buildUrl, - createTelemetryCounterHelper, - eventsToNDJSON, -} from '@kbn/analytics-shippers-elastic-v3-common'; +} from '../../../../client'; +import { ElasticV3ShipperOptions, ErrorWithCode } from '../../common'; +import { buildHeaders, buildUrl, createTelemetryCounterHelper, eventsToNDJSON } from '../../common'; /** * Elastic V3 shipper to use in the browser. diff --git a/packages/analytics/shippers/elastic_v3/common/README.md b/packages/analytics/ebt/shippers/elastic_v3/common/README.md similarity index 73% rename from packages/analytics/shippers/elastic_v3/common/README.md rename to packages/analytics/ebt/shippers/elastic_v3/common/README.md index 6684027b1ed7f..f212c75961d86 100644 --- a/packages/analytics/shippers/elastic_v3/common/README.md +++ b/packages/analytics/ebt/shippers/elastic_v3/common/README.md @@ -1,4 +1,4 @@ -# @kbn/analytics-shippers-elastic-v3-common +# @kbn/ebt/shippers/elastic_v3/common This package holds the common code for the Elastic V3 shippers: @@ -7,4 +7,4 @@ This package holds the common code for the Elastic V3 shippers: - `eventsToNdjson` utility converts any array of events to NDJSON format. - `reportTelemetryCounters` helps with building the TelemetryCounter to emit after processing an event. -It should be considered an internal package and should not be used other than by the shipper implementations: `@kbn/analytics-shippers-elastic-v3-browser` and `@kbn/analytics-shippers-elastic-v3-server` +It should be considered an internal package and should not be used other than by the shipper implementations: `@kbn/ebt/shippers/elastic_v3/browser` and `@kbn/ebt/shippers/elastic_v3/server` diff --git a/packages/analytics/shippers/elastic_v3/common/index.ts b/packages/analytics/ebt/shippers/elastic_v3/common/index.ts similarity index 100% rename from packages/analytics/shippers/elastic_v3/common/index.ts rename to packages/analytics/ebt/shippers/elastic_v3/common/index.ts diff --git a/packages/analytics/shippers/elastic_v3/common/src/build_headers.test.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/build_headers.test.ts similarity index 100% rename from packages/analytics/shippers/elastic_v3/common/src/build_headers.test.ts rename to packages/analytics/ebt/shippers/elastic_v3/common/src/build_headers.test.ts diff --git a/packages/analytics/shippers/elastic_v3/common/src/build_headers.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/build_headers.ts similarity index 100% rename from packages/analytics/shippers/elastic_v3/common/src/build_headers.ts rename to packages/analytics/ebt/shippers/elastic_v3/common/src/build_headers.ts diff --git a/packages/analytics/shippers/elastic_v3/common/src/build_url.test.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/build_url.test.ts similarity index 100% rename from packages/analytics/shippers/elastic_v3/common/src/build_url.test.ts rename to packages/analytics/ebt/shippers/elastic_v3/common/src/build_url.test.ts diff --git a/packages/analytics/shippers/elastic_v3/common/src/build_url.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/build_url.ts similarity index 100% rename from packages/analytics/shippers/elastic_v3/common/src/build_url.ts rename to packages/analytics/ebt/shippers/elastic_v3/common/src/build_url.ts diff --git a/packages/analytics/shippers/elastic_v3/common/src/error_with_code.test.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/error_with_code.test.ts similarity index 100% rename from packages/analytics/shippers/elastic_v3/common/src/error_with_code.test.ts rename to packages/analytics/ebt/shippers/elastic_v3/common/src/error_with_code.test.ts diff --git a/packages/analytics/shippers/elastic_v3/common/src/error_with_code.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/error_with_code.ts similarity index 100% rename from packages/analytics/shippers/elastic_v3/common/src/error_with_code.ts rename to packages/analytics/ebt/shippers/elastic_v3/common/src/error_with_code.ts diff --git a/packages/analytics/shippers/elastic_v3/common/src/events_to_ndjson.test.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/events_to_ndjson.test.ts similarity index 96% rename from packages/analytics/shippers/elastic_v3/common/src/events_to_ndjson.test.ts rename to packages/analytics/ebt/shippers/elastic_v3/common/src/events_to_ndjson.test.ts index 4033bcb862967..c9bcb58bb1977 100644 --- a/packages/analytics/shippers/elastic_v3/common/src/events_to_ndjson.test.ts +++ b/packages/analytics/ebt/shippers/elastic_v3/common/src/events_to_ndjson.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { Event } from '@kbn/analytics-client'; +import type { Event } from '../../../../client'; import { eventsToNDJSON } from './events_to_ndjson'; describe('eventsToNDJSON', () => { diff --git a/packages/analytics/shippers/elastic_v3/common/src/events_to_ndjson.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/events_to_ndjson.ts similarity index 92% rename from packages/analytics/shippers/elastic_v3/common/src/events_to_ndjson.ts rename to packages/analytics/ebt/shippers/elastic_v3/common/src/events_to_ndjson.ts index f5dc22ff84117..bfd5aabc1223a 100644 --- a/packages/analytics/shippers/elastic_v3/common/src/events_to_ndjson.ts +++ b/packages/analytics/ebt/shippers/elastic_v3/common/src/events_to_ndjson.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { Event } from '@kbn/analytics-client'; +import type { Event } from '../../../../client'; /** * Converts an array of events to a single ndjson string. diff --git a/packages/analytics/shippers/elastic_v3/common/src/report_telemetry_counters.test.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/report_telemetry_counters.test.ts similarity index 98% rename from packages/analytics/shippers/elastic_v3/common/src/report_telemetry_counters.test.ts rename to packages/analytics/ebt/shippers/elastic_v3/common/src/report_telemetry_counters.test.ts index 33cba78f8b297..a720a0c0129a6 100644 --- a/packages/analytics/shippers/elastic_v3/common/src/report_telemetry_counters.test.ts +++ b/packages/analytics/ebt/shippers/elastic_v3/common/src/report_telemetry_counters.test.ts @@ -7,7 +7,7 @@ */ import { firstValueFrom, Subject, take, toArray } from 'rxjs'; -import type { Event, TelemetryCounter } from '@kbn/analytics-client'; +import type { Event, TelemetryCounter } from '../../../../client'; import { createTelemetryCounterHelper } from './report_telemetry_counters'; describe('reportTelemetryCounters', () => { diff --git a/packages/analytics/shippers/elastic_v3/common/src/report_telemetry_counters.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/report_telemetry_counters.ts similarity index 98% rename from packages/analytics/shippers/elastic_v3/common/src/report_telemetry_counters.ts rename to packages/analytics/ebt/shippers/elastic_v3/common/src/report_telemetry_counters.ts index 36deae1732f7a..68260e653e78d 100644 --- a/packages/analytics/shippers/elastic_v3/common/src/report_telemetry_counters.ts +++ b/packages/analytics/ebt/shippers/elastic_v3/common/src/report_telemetry_counters.ts @@ -7,7 +7,7 @@ */ import type { Subject } from 'rxjs'; -import type { Event, TelemetryCounter, TelemetryCounterType } from '@kbn/analytics-client'; +import type { Event, TelemetryCounter, TelemetryCounterType } from '../../../../client'; /** * Creates a telemetry counter helper to make it easier to generate them diff --git a/packages/analytics/shippers/elastic_v3/common/src/types.ts b/packages/analytics/ebt/shippers/elastic_v3/common/src/types.ts similarity index 100% rename from packages/analytics/shippers/elastic_v3/common/src/types.ts rename to packages/analytics/ebt/shippers/elastic_v3/common/src/types.ts diff --git a/packages/analytics/shippers/elastic_v3/server/README.md b/packages/analytics/ebt/shippers/elastic_v3/server/README.md similarity index 83% rename from packages/analytics/shippers/elastic_v3/server/README.md rename to packages/analytics/ebt/shippers/elastic_v3/server/README.md index edb4bdffa0dba..0c27cef5a0023 100644 --- a/packages/analytics/shippers/elastic_v3/server/README.md +++ b/packages/analytics/ebt/shippers/elastic_v3/server/README.md @@ -1,13 +1,13 @@ -# @kbn/analytics-shippers-elastic-v3-server +# @kbn/ebt/shippers/elastic_v3/server -Server-side implementation of the Elastic V3 shipper for the `@kbn/analytics-client`. +Server-side implementation of the Elastic V3 shipper for the `@kbn/ebt/client`. ## How to use it -This module is intended to be used **on the server-side only**. It is specially designed to apply the necessary backpressure mechanisms to prevent the server from getting overloaded with too many events and identify if the server sits behind a firewall to discard any incoming events. Refer to `@kbn/analytics-shippers-elastic-v3-browser` for the browser-side implementation. +This module is intended to be used **on the server-side only**. It is specially designed to apply the necessary backpressure mechanisms to prevent the server from getting overloaded with too many events and identify if the server sits behind a firewall to discard any incoming events. Refer to `@kbn/ebt/shippers/elastic_v3/browser` for the browser-side implementation. ```typescript -import { ElasticV3ServerShipper } from "@kbn/analytics-shippers-elastic-v3-server"; +import { ElasticV3ServerShipper } from "@kbn/ebt/shippers/elastic_v3/server"; analytics.registerShipper(ElasticV3ServerShipper, { channelName: 'myChannel', version: '1.0.0' }); ``` diff --git a/packages/analytics/shippers/elastic_v3/server/index.ts b/packages/analytics/ebt/shippers/elastic_v3/server/index.ts similarity index 82% rename from packages/analytics/shippers/elastic_v3/server/index.ts rename to packages/analytics/ebt/shippers/elastic_v3/server/index.ts index d5d0cb04a21ed..c0031aac4b217 100644 --- a/packages/analytics/shippers/elastic_v3/server/index.ts +++ b/packages/analytics/ebt/shippers/elastic_v3/server/index.ts @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -export type { ElasticV3ShipperOptions } from '@kbn/analytics-shippers-elastic-v3-common'; +export type { ElasticV3ShipperOptions } from '../common'; export { ElasticV3ServerShipper } from './src/server_shipper'; diff --git a/packages/analytics/shippers/elastic_v3/server/src/server_shipper.test.mocks.ts b/packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.test.mocks.ts similarity index 100% rename from packages/analytics/shippers/elastic_v3/server/src/server_shipper.test.mocks.ts rename to packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.test.mocks.ts diff --git a/packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.test.ts b/packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.test.ts new file mode 100644 index 0000000000000..1e973ed4c9819 --- /dev/null +++ b/packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.test.ts @@ -0,0 +1,571 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { loggerMock } from '@kbn/logging-mocks'; +import { firstValueFrom } from 'rxjs'; +import type { AnalyticsClientInitContext, Event } from '../../../../client'; +import { fetchMock } from './server_shipper.test.mocks'; +import { ElasticV3ServerShipper } from './server_shipper'; + +const SECONDS = 1000; +const MINUTES = 60 * SECONDS; + +describe('ElasticV3ServerShipper', () => { + const events: Event[] = [ + { + timestamp: '2020-01-01T00:00:00.000Z', + event_type: 'test-event-type', + context: {}, + properties: {}, + }, + ]; + + const initContext: AnalyticsClientInitContext = { + sendTo: 'staging', + isDev: true, + logger: loggerMock.create(), + }; + + let shipper: ElasticV3ServerShipper; + + // eslint-disable-next-line dot-notation + const setLastBatchSent = (ms: number) => (shipper['lastBatchSent'] = ms); + + beforeEach(() => { + jest.useFakeTimers({ legacyFakeTimers: false }); + + shipper = new ElasticV3ServerShipper( + { version: '1.2.3', channelName: 'test-channel', debug: true }, + initContext + ); + // eslint-disable-next-line dot-notation + shipper['firstTimeOffline'] = null; // The tests think connectivity is OK initially for easier testing. + }); + + afterEach(() => { + shipper.shutdown(); + jest.clearAllMocks(); + }); + + test('set optIn should update the isOptedIn$ observable', () => { + // eslint-disable-next-line dot-notation + const getInternalOptIn = () => shipper['isOptedIn$'].value; + + // Initially undefined + expect(getInternalOptIn()).toBeUndefined(); + + shipper.optIn(true); + expect(getInternalOptIn()).toBe(true); + + shipper.optIn(false); + expect(getInternalOptIn()).toBe(false); + }); + + test('clears the queue after optIn: false', () => { + shipper.reportEvents(events); + // eslint-disable-next-line dot-notation + expect(shipper['internalQueue'].length).toBe(1); + + shipper.optIn(false); + // eslint-disable-next-line dot-notation + expect(shipper['internalQueue'].length).toBe(0); + }); + + test('set extendContext should store local values: clusterUuid and licenseId', () => { + // eslint-disable-next-line dot-notation + const getInternalClusterUuid = () => shipper['clusterUuid']; + // eslint-disable-next-line dot-notation + const getInternalLicenseId = () => shipper['licenseId']; + + // Initial values + expect(getInternalClusterUuid()).toBe('UNKNOWN'); + expect(getInternalLicenseId()).toBeUndefined(); + + shipper.extendContext({ cluster_uuid: 'test-cluster-uuid' }); + expect(getInternalClusterUuid()).toBe('test-cluster-uuid'); + expect(getInternalLicenseId()).toBeUndefined(); + + shipper.extendContext({ license_id: 'test-license-id' }); + expect(getInternalClusterUuid()).toBe('test-cluster-uuid'); + expect(getInternalLicenseId()).toBe('test-license-id'); + + shipper.extendContext({ cluster_uuid: 'test-cluster-uuid-2', license_id: 'test-license-id-2' }); + expect(getInternalClusterUuid()).toBe('test-cluster-uuid-2'); + expect(getInternalLicenseId()).toBe('test-license-id-2'); + }); + + test('calls to reportEvents do not call `fetch` straight away', () => { + shipper.reportEvents(events); + expect(fetchMock).not.toHaveBeenCalled(); + }); + + test('calls to reportEvents do not call `fetch` after 10 minutes because no optIn value is set yet', async () => { + shipper.reportEvents(events); + await jest.advanceTimersByTimeAsync(10 * MINUTES); + expect(fetchMock).not.toHaveBeenCalled(); + }); + + test('calls to reportEvents call `fetch` after 10 seconds when optIn value is set to true', async () => { + shipper.reportEvents(events); + shipper.optIn(true); + const counter = firstValueFrom(shipper.telemetryCounter$); + setLastBatchSent(Date.now() - 10 * SECONDS); + await jest.advanceTimersByTimeAsync(1 * SECONDS); // Moving 1 second should be enough to trigger the logic + expect(fetchMock).toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { + body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', + headers: { + 'content-type': 'application/x-ndjson', + 'x-elastic-cluster-id': 'UNKNOWN', + 'x-elastic-stack-version': '1.2.3', + }, + method: 'POST', + query: { debug: true }, + } + ); + await expect(counter).resolves.toMatchInlineSnapshot(` + Object { + "code": "200", + "count": 1, + "event_type": "test-event-type", + "source": "elastic_v3_server", + "type": "succeeded", + } + `); + }); + + test('calls to reportEvents do not call `fetch` after 10 seconds when optIn value is set to false', async () => { + shipper.reportEvents(events); + shipper.optIn(false); + setLastBatchSent(Date.now() - 10 * SECONDS); + await jest.advanceTimersByTimeAsync(1 * SECONDS); // Moving 1 second should be enough to trigger the logic + expect(fetchMock).not.toHaveBeenCalled(); + }); + + test('calls to reportEvents call `fetch` when shutting down if optIn value is set to true', async () => { + shipper.reportEvents(events); + shipper.optIn(true); + const counter = firstValueFrom(shipper.telemetryCounter$); + shipper.shutdown(); + jest.advanceTimersToNextTimer(); // We are handling the shutdown in a promise, so we need to wait for the next tick. + expect(fetchMock).toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { + body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', + headers: { + 'content-type': 'application/x-ndjson', + 'x-elastic-cluster-id': 'UNKNOWN', + 'x-elastic-stack-version': '1.2.3', + }, + method: 'POST', + query: { debug: true }, + } + ); + await expect(counter).resolves.toMatchInlineSnapshot(` + Object { + "code": "200", + "count": 1, + "event_type": "test-event-type", + "source": "elastic_v3_server", + "type": "succeeded", + } + `); + }); + + test('does not add the query.debug: true property to the request if the shipper is not set with the debug flag', async () => { + shipper = new ElasticV3ServerShipper( + { version: '1.2.3', channelName: 'test-channel' }, + initContext + ); + // eslint-disable-next-line dot-notation + shipper['firstTimeOffline'] = null; + shipper.reportEvents(events); + shipper.optIn(true); + setLastBatchSent(Date.now() - 10 * SECONDS); + await jest.advanceTimersByTimeAsync(1 * SECONDS); // Moving 1 second should be enough to trigger the logic + expect(fetchMock).toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { + body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', + headers: { + 'content-type': 'application/x-ndjson', + 'x-elastic-cluster-id': 'UNKNOWN', + 'x-elastic-stack-version': '1.2.3', + }, + method: 'POST', + } + ); + }); + + test('sends when the queue overflows the 10kB leaky bucket one batch every 10s', async () => { + expect.assertions(2 * 9 + 2); + + shipper.reportEvents(new Array(1000).fill(events[0])); + shipper.optIn(true); + + // Due to the size of the test events, it matches 8 rounds. + for (let i = 0; i < 9; i++) { + const counter = firstValueFrom(shipper.telemetryCounter$); + setLastBatchSent(Date.now() - 10 * SECONDS); + await jest.advanceTimersByTimeAsync(1 * SECONDS); // Moving 1 second should be enough to trigger the logic + expect(fetchMock).toHaveBeenNthCalledWith( + i + 1, + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { + body: new Array(103) + .fill( + '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n' + ) + .join(''), + headers: { + 'content-type': 'application/x-ndjson', + 'x-elastic-cluster-id': 'UNKNOWN', + 'x-elastic-stack-version': '1.2.3', + }, + method: 'POST', + query: { debug: true }, + } + ); + await expect(counter).resolves.toMatchInlineSnapshot(` + Object { + "code": "200", + "count": 103, + "event_type": "test-event-type", + "source": "elastic_v3_server", + "type": "succeeded", + } + `); + jest.advanceTimersToNextTimer(); + } + // eslint-disable-next-line dot-notation + expect(shipper['internalQueue'].length).toBe(1000 - 9 * 103); // 73 + + // If we call it again, it should not enqueue all the events (only the ones to fill the queue): + shipper.reportEvents(new Array(1000).fill(events[0])); + // eslint-disable-next-line dot-notation + expect(shipper['internalQueue'].length).toBe(1000); + }); + + test('handles when the fetch request fails', async () => { + fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); + shipper.reportEvents(events); + shipper.optIn(true); + const counter = firstValueFrom(shipper.telemetryCounter$); + setLastBatchSent(Date.now() - 10 * SECONDS); + await jest.advanceTimersByTimeAsync(1 * SECONDS); // Moving 1 second should be enough to trigger the logic + expect(fetchMock).toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { + body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', + headers: { + 'content-type': 'application/x-ndjson', + 'x-elastic-cluster-id': 'UNKNOWN', + 'x-elastic-stack-version': '1.2.3', + }, + method: 'POST', + query: { debug: true }, + } + ); + await expect(counter).resolves.toMatchInlineSnapshot(` + Object { + "code": "Failed to fetch", + "count": 1, + "event_type": "test-event-type", + "source": "elastic_v3_server", + "type": "failed", + } + `); + }); + + test('handles when the fetch request fails (request completes but not OK response)', async () => { + fetchMock.mockResolvedValueOnce({ + ok: false, + status: 400, + text: () => Promise.resolve('{"status": "not ok"}'), + }); + shipper.reportEvents(events); + shipper.optIn(true); + const counter = firstValueFrom(shipper.telemetryCounter$); + setLastBatchSent(Date.now() - 10 * SECONDS); + await jest.advanceTimersByTimeAsync(1 * SECONDS); // Moving 1 second should be enough to trigger the logic + expect(fetchMock).toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { + body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', + headers: { + 'content-type': 'application/x-ndjson', + 'x-elastic-cluster-id': 'UNKNOWN', + 'x-elastic-stack-version': '1.2.3', + }, + method: 'POST', + query: { debug: true }, + } + ); + await expect(counter).resolves.toMatchInlineSnapshot(` + Object { + "code": "400", + "count": 1, + "event_type": "test-event-type", + "source": "elastic_v3_server", + "type": "failed", + } + `); + }); + + describe('Connectivity Checks', () => { + describe('connectivity check when connectivity is confirmed (firstTimeOffline === null)', () => { + test.each([undefined, false, true])('does not run for opt-in %p', async (optInValue) => { + if (optInValue !== undefined) { + shipper.optIn(optInValue); + } + + // From the start, it doesn't check connectivity because already confirmed + expect(fetchMock).not.toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { method: 'OPTIONS' } + ); + + // Wait a big time (1 minute should be enough, but for the sake of tests...) + await jest.advanceTimersByTimeAsync(10 * MINUTES); + + expect(fetchMock).not.toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { method: 'OPTIONS' } + ); + }); + }); + + describe('connectivity check with initial unknown state of the connectivity', () => { + beforeEach(() => { + // eslint-disable-next-line dot-notation + shipper['firstTimeOffline'] = undefined; // Initial unknown state of the connectivity + }); + + test.each([undefined, false])('does not run for opt-in %p', async (optInValue) => { + if (optInValue !== undefined) { + shipper.optIn(optInValue); + } + + // From the start, it doesn't check connectivity because already confirmed + expect(fetchMock).not.toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { method: 'OPTIONS' } + ); + + // Wait a big time (1 minute should be enough, but for the sake of tests...) + await jest.advanceTimersByTimeAsync(10 * MINUTES); + + expect(fetchMock).not.toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { method: 'OPTIONS' } + ); + }); + + test('runs as soon as opt-in is set to true', () => { + shipper.optIn(true); + + // From the start, it doesn't check connectivity because opt-in is not true + expect(fetchMock).toHaveBeenNthCalledWith( + 1, + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { method: 'OPTIONS' } + ); + }); + }); + + describe('connectivity check with the connectivity confirmed to be faulty', () => { + beforeEach(() => { + // eslint-disable-next-line dot-notation + shipper['firstTimeOffline'] = 100; // Failed at some point + }); + + test.each([undefined, false])('does not run for opt-in %p', async (optInValue) => { + if (optInValue !== undefined) { + shipper.optIn(optInValue); + } + + // From the start, it doesn't check connectivity because already confirmed + expect(fetchMock).not.toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { method: 'OPTIONS' } + ); + + // Wait a big time (1 minute should be enough, but for the sake of tests...) + await jest.advanceTimersByTimeAsync(10 * MINUTES); + + expect(fetchMock).not.toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { method: 'OPTIONS' } + ); + }); + + test('runs as soon as opt-in is set to true', () => { + shipper.optIn(true); + + // From the start, it doesn't check connectivity because opt-in is not true + expect(fetchMock).toHaveBeenNthCalledWith( + 1, + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { method: 'OPTIONS' } + ); + }); + }); + + describe('after report failure', () => { + // generate the report failure for each test + beforeEach(async () => { + fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); + shipper.reportEvents(events); + shipper.optIn(true); + const counter = firstValueFrom(shipper.telemetryCounter$); + setLastBatchSent(Date.now() - 10 * SECONDS); + await jest.advanceTimersByTimeAsync(1 * SECONDS); // Moving 1 second should be enough to trigger the logic + expect(fetchMock).toHaveBeenNthCalledWith( + 1, + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { + body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', + headers: { + 'content-type': 'application/x-ndjson', + 'x-elastic-cluster-id': 'UNKNOWN', + 'x-elastic-stack-version': '1.2.3', + }, + method: 'POST', + query: { debug: true }, + } + ); + await expect(counter).resolves.toMatchInlineSnapshot(` + Object { + "code": "Failed to fetch", + "count": 1, + "event_type": "test-event-type", + "source": "elastic_v3_server", + "type": "failed", + } + `); + }); + + test('connectivity check runs periodically', async () => { + fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); + await jest.advanceTimersByTimeAsync(1 * MINUTES); + expect(fetchMock).toHaveBeenNthCalledWith( + 2, + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { method: 'OPTIONS' } + ); + fetchMock.mockResolvedValueOnce({ ok: false }); + await jest.advanceTimersByTimeAsync(2 * MINUTES); + expect(fetchMock).toHaveBeenNthCalledWith( + 3, + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { method: 'OPTIONS' } + ); + }); + }); + + describe('after being offline for longer than 24h', () => { + beforeEach(() => { + shipper.optIn(true); + shipper.reportEvents(events); + // eslint-disable-next-line dot-notation + expect(shipper['internalQueue'].length).toBe(1); + // eslint-disable-next-line dot-notation + shipper['firstTimeOffline'] = 100; + }); + + test('the following connectivity check clears the queue', async () => { + fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); + await jest.advanceTimersByTimeAsync(1 * MINUTES); + expect(fetchMock).toHaveBeenNthCalledWith( + 1, + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { method: 'OPTIONS' } + ); + // eslint-disable-next-line dot-notation + expect(shipper['internalQueue'].length).toBe(0); + }); + + test('new events are not added to the queue', async () => { + fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); + await jest.advanceTimersByTimeAsync(1 * MINUTES); + expect(fetchMock).toHaveBeenNthCalledWith( + 1, + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { method: 'OPTIONS' } + ); + // eslint-disable-next-line dot-notation + expect(shipper['internalQueue'].length).toBe(0); + + shipper.reportEvents(events); + // eslint-disable-next-line dot-notation + expect(shipper['internalQueue'].length).toBe(0); + }); + + test('regains the connection', async () => { + fetchMock.mockResolvedValueOnce({ ok: true }); + await jest.advanceTimersByTimeAsync(1 * MINUTES); + expect(fetchMock).toHaveBeenNthCalledWith( + 1, + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { method: 'OPTIONS' } + ); + // eslint-disable-next-line dot-notation + expect(shipper['firstTimeOffline']).toBe(null); + + await jest.advanceTimersByTimeAsync(10 * MINUTES); + expect(fetchMock).not.toHaveBeenNthCalledWith( + 2, + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { method: 'OPTIONS' } + ); + }); + }); + }); + + describe('flush method', () => { + test('resolves straight away if it should not send anything', async () => { + await expect(shipper.flush()).resolves.toBe(undefined); + }); + + test('resolves when all the ongoing requests are complete', async () => { + shipper.optIn(true); + shipper.reportEvents(events); + expect(fetchMock).toHaveBeenCalledTimes(0); + fetchMock.mockImplementation(async () => { + // eslint-disable-next-line dot-notation + expect(shipper['inFlightRequests$'].value).toBe(1); + }); + await expect(shipper.flush()).resolves.toBe(undefined); + expect(fetchMock).toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/test-channel', + { + body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', + headers: { + 'content-type': 'application/x-ndjson', + 'x-elastic-cluster-id': 'UNKNOWN', + 'x-elastic-stack-version': '1.2.3', + }, + method: 'POST', + query: { debug: true }, + } + ); + }); + + test('calling flush multiple times does not keep hanging', async () => { + await expect(shipper.flush()).resolves.toBe(undefined); + await expect(shipper.flush()).resolves.toBe(undefined); + await Promise.all([shipper.flush(), shipper.flush()]); + }); + + test('calling flush after shutdown does not keep hanging', async () => { + shipper.shutdown(); + await expect(shipper.flush()).resolves.toBe(undefined); + }); + }); +}); diff --git a/packages/analytics/shippers/elastic_v3/server/src/server_shipper.ts b/packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts similarity index 99% rename from packages/analytics/shippers/elastic_v3/server/src/server_shipper.ts rename to packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts index cb6e689dd893f..d3b54da3314cf 100644 --- a/packages/analytics/shippers/elastic_v3/server/src/server_shipper.ts +++ b/packages/analytics/ebt/shippers/elastic_v3/server/src/server_shipper.ts @@ -25,13 +25,6 @@ import { skip, firstValueFrom, } from 'rxjs'; -import type { - AnalyticsClientInitContext, - Event, - EventContext, - IShipper, - TelemetryCounter, -} from '@kbn/analytics-client'; import { type ElasticV3ShipperOptions, buildHeaders, @@ -39,7 +32,14 @@ import { createTelemetryCounterHelper, eventsToNDJSON, ErrorWithCode, -} from '@kbn/analytics-shippers-elastic-v3-common'; +} from '../../common'; +import type { + AnalyticsClientInitContext, + Event, + EventContext, + IShipper, + TelemetryCounter, +} from '../../../../client'; const SECOND = 1000; const MINUTE = 60 * SECOND; diff --git a/packages/analytics/shippers/fullstory/README.md b/packages/analytics/ebt/shippers/fullstory/README.md similarity index 91% rename from packages/analytics/shippers/fullstory/README.md rename to packages/analytics/ebt/shippers/fullstory/README.md index 6d1c443db3d22..a1e557a7600b8 100644 --- a/packages/analytics/shippers/fullstory/README.md +++ b/packages/analytics/ebt/shippers/fullstory/README.md @@ -1,13 +1,13 @@ -# @kbn/analytics-shippers-fullstory +# @kbn/ebt/shippers/fullstory -FullStory implementation as a shipper for the `@kbn/analytics-client`. +FullStory implementation as a shipper for the `@kbn/ebt/client`. ## How to use it This module is intended to be used **on the browser only**. It does not support server-side events. ```typescript -import { FullStoryShipper } from "@kbn/analytics-shippers-fullstory"; +import { FullStoryShipper } from "@kbn/ebt/shippers/fullstory"; analytics.registerShipper(FullStoryShipper, { fullStoryOrgId: '12345' }) ``` diff --git a/packages/analytics/shippers/fullstory/index.ts b/packages/analytics/ebt/shippers/fullstory/index.ts similarity index 100% rename from packages/analytics/shippers/fullstory/index.ts rename to packages/analytics/ebt/shippers/fullstory/index.ts diff --git a/packages/analytics/shippers/fullstory/src/format_payload.test.ts b/packages/analytics/ebt/shippers/fullstory/src/format_payload.test.ts similarity index 100% rename from packages/analytics/shippers/fullstory/src/format_payload.test.ts rename to packages/analytics/ebt/shippers/fullstory/src/format_payload.test.ts diff --git a/packages/analytics/shippers/fullstory/src/format_payload.ts b/packages/analytics/ebt/shippers/fullstory/src/format_payload.ts similarity index 100% rename from packages/analytics/shippers/fullstory/src/format_payload.ts rename to packages/analytics/ebt/shippers/fullstory/src/format_payload.ts diff --git a/packages/analytics/shippers/fullstory/src/fullstory_shipper.test.mocks.ts b/packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.test.mocks.ts similarity index 100% rename from packages/analytics/shippers/fullstory/src/fullstory_shipper.test.mocks.ts rename to packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.test.mocks.ts diff --git a/packages/analytics/shippers/fullstory/src/fullstory_shipper.test.ts b/packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.test.ts similarity index 100% rename from packages/analytics/shippers/fullstory/src/fullstory_shipper.test.ts rename to packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.test.ts diff --git a/packages/analytics/shippers/fullstory/src/fullstory_shipper.ts b/packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts similarity index 96% rename from packages/analytics/shippers/fullstory/src/fullstory_shipper.ts rename to packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts index 96ae3389be017..a091076236a80 100644 --- a/packages/analytics/shippers/fullstory/src/fullstory_shipper.ts +++ b/packages/analytics/ebt/shippers/fullstory/src/fullstory_shipper.ts @@ -6,15 +6,10 @@ * Side Public License, v 1. */ -import type { - AnalyticsClientInitContext, - EventContext, - Event, - IShipper, -} from '@kbn/analytics-client'; import { Subject, distinct, debounceTime, map, filter, Subscription } from 'rxjs'; import { get, has } from 'lodash'; import { set } from '@kbn/safer-lodash-set'; +import type { AnalyticsClientInitContext, EventContext, Event, IShipper } from '../../../client'; import type { FullStoryApi } from './types'; import type { FullStorySnippetConfig } from './load_snippet'; import { formatPayload } from './format_payload'; @@ -56,6 +51,10 @@ export interface FullStoryShipperConfig extends FullStorySnippetConfig { * If this setting is provided, it'll only send the event types specified in this list. */ eventTypesAllowlist?: string[]; + /** + * FullStory only allows calling setVars('page') once per navigation. + * This setting defines how much time to hold from calling the API while additional lazy context is being resolved. + */ pageVarsDebounceTimeMs?: number; } diff --git a/packages/analytics/shippers/fullstory/src/get_parsed_version.test.ts b/packages/analytics/ebt/shippers/fullstory/src/get_parsed_version.test.ts similarity index 100% rename from packages/analytics/shippers/fullstory/src/get_parsed_version.test.ts rename to packages/analytics/ebt/shippers/fullstory/src/get_parsed_version.test.ts diff --git a/packages/analytics/shippers/fullstory/src/get_parsed_version.ts b/packages/analytics/ebt/shippers/fullstory/src/get_parsed_version.ts similarity index 100% rename from packages/analytics/shippers/fullstory/src/get_parsed_version.ts rename to packages/analytics/ebt/shippers/fullstory/src/get_parsed_version.ts diff --git a/packages/analytics/shippers/fullstory/src/load_snippet.test.ts b/packages/analytics/ebt/shippers/fullstory/src/load_snippet.test.ts similarity index 68% rename from packages/analytics/shippers/fullstory/src/load_snippet.test.ts rename to packages/analytics/ebt/shippers/fullstory/src/load_snippet.test.ts index 0b920ef2d22c1..002f113f71f63 100644 --- a/packages/analytics/shippers/fullstory/src/load_snippet.test.ts +++ b/packages/analytics/ebt/shippers/fullstory/src/load_snippet.test.ts @@ -11,25 +11,11 @@ import { loadSnippet } from './load_snippet'; describe('loadSnippet', () => { beforeAll(() => { // Define necessary window and document global variables for the tests - Object.defineProperty(global, 'window', { - writable: true, - value: {}, - }); - - Object.defineProperty(global, 'document', { - writable: true, - value: { - createElement: jest.fn().mockReturnValue({}), - getElementsByTagName: jest - .fn() - .mockReturnValue([{ parentNode: { insertBefore: jest.fn() } }]), - }, - }); - - Object.defineProperty(global, '_fs_script', { - writable: true, - value: '', - }); + jest + .spyOn(global.document, 'getElementsByTagName') + .mockReturnValue([ + { parentNode: { insertBefore: jest.fn() } }, + ] as unknown as HTMLCollectionOf); }); it('should return the FullStory API', () => { diff --git a/packages/analytics/shippers/fullstory/src/load_snippet.ts b/packages/analytics/ebt/shippers/fullstory/src/load_snippet.ts similarity index 100% rename from packages/analytics/shippers/fullstory/src/load_snippet.ts rename to packages/analytics/ebt/shippers/fullstory/src/load_snippet.ts diff --git a/packages/analytics/shippers/fullstory/src/types.ts b/packages/analytics/ebt/shippers/fullstory/src/types.ts similarity index 100% rename from packages/analytics/shippers/fullstory/src/types.ts rename to packages/analytics/ebt/shippers/fullstory/src/types.ts diff --git a/packages/analytics/shippers/fullstory/tsconfig.json b/packages/analytics/ebt/tsconfig.json similarity index 52% rename from packages/analytics/shippers/fullstory/tsconfig.json rename to packages/analytics/ebt/tsconfig.json index 00a680d9c6f2c..38e6caee5d2a1 100644 --- a/packages/analytics/shippers/fullstory/tsconfig.json +++ b/packages/analytics/ebt/tsconfig.json @@ -1,21 +1,23 @@ { - "extends": "../../../../tsconfig.base.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "target/types", "types": [ "jest", - "node" + "node", + "react" ] }, "include": [ - "**/*.ts" + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" ], "kbn_references": [ - "@kbn/analytics-client", "@kbn/logging-mocks", - "@kbn/safer-lodash-set" - ], - "exclude": [ - "target/**/*", + "@kbn/logging", + "@kbn/safer-lodash-set", ] } diff --git a/packages/analytics/shippers/elastic_v3/browser/kibana.jsonc b/packages/analytics/shippers/elastic_v3/browser/kibana.jsonc deleted file mode 100644 index a54bd23df252d..0000000000000 --- a/packages/analytics/shippers/elastic_v3/browser/kibana.jsonc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "shared-common", - "id": "@kbn/analytics-shippers-elastic-v3-browser", - "owner": "@elastic/kibana-core" -} diff --git a/packages/analytics/shippers/elastic_v3/browser/package.json b/packages/analytics/shippers/elastic_v3/browser/package.json deleted file mode 100644 index 88d42d1fd184b..0000000000000 --- a/packages/analytics/shippers/elastic_v3/browser/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@kbn/analytics-shippers-elastic-v3-browser", - "private": true, - "version": "1.0.0", - "author": "Kibana Core", - "license": "SSPL-1.0 OR Elastic License 2.0" -} \ No newline at end of file diff --git a/packages/analytics/shippers/elastic_v3/browser/tsconfig.json b/packages/analytics/shippers/elastic_v3/browser/tsconfig.json deleted file mode 100644 index 7808dee7058f0..0000000000000 --- a/packages/analytics/shippers/elastic_v3/browser/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "extends": "../../../../../tsconfig.base.json", - "compilerOptions": { - "outDir": "target/types", - "types": [ - "jest", - "node" - ] - }, - "include": [ - "**/*.ts" - ], - "kbn_references": [ - "@kbn/analytics-client", - "@kbn/analytics-shippers-elastic-v3-common", - "@kbn/logging-mocks" - ], - "exclude": [ - "target/**/*", - ] -} diff --git a/packages/analytics/shippers/elastic_v3/common/kibana.jsonc b/packages/analytics/shippers/elastic_v3/common/kibana.jsonc deleted file mode 100644 index 30c723c2b5217..0000000000000 --- a/packages/analytics/shippers/elastic_v3/common/kibana.jsonc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "shared-common", - "id": "@kbn/analytics-shippers-elastic-v3-common", - "owner": "@elastic/kibana-core" -} diff --git a/packages/analytics/shippers/elastic_v3/server/kibana.jsonc b/packages/analytics/shippers/elastic_v3/server/kibana.jsonc deleted file mode 100644 index a516db1bbf30e..0000000000000 --- a/packages/analytics/shippers/elastic_v3/server/kibana.jsonc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "shared-common", - "id": "@kbn/analytics-shippers-elastic-v3-server", - "owner": "@elastic/kibana-core" -} diff --git a/packages/analytics/shippers/elastic_v3/server/package.json b/packages/analytics/shippers/elastic_v3/server/package.json deleted file mode 100644 index 3fcbd5062d35f..0000000000000 --- a/packages/analytics/shippers/elastic_v3/server/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@kbn/analytics-shippers-elastic-v3-server", - "private": true, - "version": "1.0.0", - "author": "Kibana Core", - "license": "SSPL-1.0 OR Elastic License 2.0" -} \ No newline at end of file diff --git a/packages/analytics/shippers/elastic_v3/server/src/server_shipper.test.ts b/packages/analytics/shippers/elastic_v3/server/src/server_shipper.test.ts deleted file mode 100644 index 1d5ab760d15f2..0000000000000 --- a/packages/analytics/shippers/elastic_v3/server/src/server_shipper.test.ts +++ /dev/null @@ -1,624 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { loggerMock } from '@kbn/logging-mocks'; -import { firstValueFrom } from 'rxjs'; -import { fakeSchedulers } from 'rxjs-marbles/jest'; -import type { AnalyticsClientInitContext, Event } from '@kbn/analytics-client'; -import { fetchMock } from './server_shipper.test.mocks'; -import { ElasticV3ServerShipper } from './server_shipper'; - -const SECONDS = 1000; -const MINUTES = 60 * SECONDS; - -describe('ElasticV3ServerShipper', () => { - const events: Event[] = [ - { - timestamp: '2020-01-01T00:00:00.000Z', - event_type: 'test-event-type', - context: {}, - properties: {}, - }, - ]; - - const nextTick = () => new Promise((resolve) => setImmediate(resolve)); - - const initContext: AnalyticsClientInitContext = { - sendTo: 'staging', - isDev: true, - logger: loggerMock.create(), - }; - - let shipper: ElasticV3ServerShipper; - - // eslint-disable-next-line dot-notation - const setLastBatchSent = (ms: number) => (shipper['lastBatchSent'] = ms); - - beforeEach(() => { - jest.useFakeTimers({ legacyFakeTimers: true }); - - shipper = new ElasticV3ServerShipper( - { version: '1.2.3', channelName: 'test-channel', debug: true }, - initContext - ); - // eslint-disable-next-line dot-notation - shipper['firstTimeOffline'] = null; // The tests think connectivity is OK initially for easier testing. - }); - - afterEach(() => { - shipper.shutdown(); - jest.clearAllMocks(); - }); - - test('set optIn should update the isOptedIn$ observable', () => { - // eslint-disable-next-line dot-notation - const getInternalOptIn = () => shipper['isOptedIn$'].value; - - // Initially undefined - expect(getInternalOptIn()).toBeUndefined(); - - shipper.optIn(true); - expect(getInternalOptIn()).toBe(true); - - shipper.optIn(false); - expect(getInternalOptIn()).toBe(false); - }); - - test('clears the queue after optIn: false', () => { - shipper.reportEvents(events); - // eslint-disable-next-line dot-notation - expect(shipper['internalQueue'].length).toBe(1); - - shipper.optIn(false); - // eslint-disable-next-line dot-notation - expect(shipper['internalQueue'].length).toBe(0); - }); - - test('set extendContext should store local values: clusterUuid and licenseId', () => { - // eslint-disable-next-line dot-notation - const getInternalClusterUuid = () => shipper['clusterUuid']; - // eslint-disable-next-line dot-notation - const getInternalLicenseId = () => shipper['licenseId']; - - // Initial values - expect(getInternalClusterUuid()).toBe('UNKNOWN'); - expect(getInternalLicenseId()).toBeUndefined(); - - shipper.extendContext({ cluster_uuid: 'test-cluster-uuid' }); - expect(getInternalClusterUuid()).toBe('test-cluster-uuid'); - expect(getInternalLicenseId()).toBeUndefined(); - - shipper.extendContext({ license_id: 'test-license-id' }); - expect(getInternalClusterUuid()).toBe('test-cluster-uuid'); - expect(getInternalLicenseId()).toBe('test-license-id'); - - shipper.extendContext({ cluster_uuid: 'test-cluster-uuid-2', license_id: 'test-license-id-2' }); - expect(getInternalClusterUuid()).toBe('test-cluster-uuid-2'); - expect(getInternalLicenseId()).toBe('test-license-id-2'); - }); - - test('calls to reportEvents do not call `fetch` straight away', () => { - shipper.reportEvents(events); - expect(fetchMock).not.toHaveBeenCalled(); - }); - - test( - 'calls to reportEvents do not call `fetch` after 10 minutes because no optIn value is set yet', - fakeSchedulers((advance) => { - shipper.reportEvents(events); - advance(10 * MINUTES); - expect(fetchMock).not.toHaveBeenCalled(); - }) - ); - - test( - 'calls to reportEvents call `fetch` after 10 seconds when optIn value is set to true', - fakeSchedulers(async (advance) => { - shipper.reportEvents(events); - shipper.optIn(true); - const counter = firstValueFrom(shipper.telemetryCounter$); - setLastBatchSent(Date.now() - 10 * SECONDS); - advance(1 * SECONDS); // Moving 1 second should be enough to trigger the logic - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` - Object { - "code": "200", - "count": 1, - "event_type": "test-event-type", - "source": "elastic_v3_server", - "type": "succeeded", - } - `); - }) - ); - - test( - 'calls to reportEvents do not call `fetch` after 10 seconds when optIn value is set to false', - fakeSchedulers((advance) => { - shipper.reportEvents(events); - shipper.optIn(false); - setLastBatchSent(Date.now() - 10 * SECONDS); - advance(1 * SECONDS); // Moving 1 second should be enough to trigger the logic - expect(fetchMock).not.toHaveBeenCalled(); - }) - ); - - test('calls to reportEvents call `fetch` when shutting down if optIn value is set to true', async () => { - shipper.reportEvents(events); - shipper.optIn(true); - const counter = firstValueFrom(shipper.telemetryCounter$); - shipper.shutdown(); - await nextTick(); // We are handling the shutdown in a promise, so we need to wait for the next tick. - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` - Object { - "code": "200", - "count": 1, - "event_type": "test-event-type", - "source": "elastic_v3_server", - "type": "succeeded", - } - `); - }); - - test( - 'does not add the query.debug: true property to the request if the shipper is not set with the debug flag', - fakeSchedulers((advance) => { - shipper = new ElasticV3ServerShipper( - { version: '1.2.3', channelName: 'test-channel' }, - initContext - ); - // eslint-disable-next-line dot-notation - shipper['firstTimeOffline'] = null; - shipper.reportEvents(events); - shipper.optIn(true); - setLastBatchSent(Date.now() - 10 * SECONDS); - advance(1 * SECONDS); // Moving 1 second should be enough to trigger the logic - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - method: 'POST', - } - ); - }) - ); - - test( - 'sends when the queue overflows the 10kB leaky bucket one batch every 10s', - fakeSchedulers(async (advance) => { - expect.assertions(2 * 9 + 2); - - shipper.reportEvents(new Array(1000).fill(events[0])); - shipper.optIn(true); - - // Due to the size of the test events, it matches 8 rounds. - for (let i = 0; i < 9; i++) { - const counter = firstValueFrom(shipper.telemetryCounter$); - setLastBatchSent(Date.now() - 10 * SECONDS); - advance(1 * SECONDS); // Moving 1 second should be enough to trigger the logic - expect(fetchMock).toHaveBeenNthCalledWith( - i + 1, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: new Array(103) - .fill( - '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n' - ) - .join(''), - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` - Object { - "code": "200", - "count": 103, - "event_type": "test-event-type", - "source": "elastic_v3_server", - "type": "succeeded", - } - `); - await nextTick(); - } - // eslint-disable-next-line dot-notation - expect(shipper['internalQueue'].length).toBe(1000 - 9 * 103); // 73 - - // If we call it again, it should not enqueue all the events (only the ones to fill the queue): - shipper.reportEvents(new Array(1000).fill(events[0])); - // eslint-disable-next-line dot-notation - expect(shipper['internalQueue'].length).toBe(1000); - }) - ); - - test( - 'handles when the fetch request fails', - fakeSchedulers(async (advance) => { - fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); - shipper.reportEvents(events); - shipper.optIn(true); - const counter = firstValueFrom(shipper.telemetryCounter$); - setLastBatchSent(Date.now() - 10 * SECONDS); - advance(1 * SECONDS); // Moving 1 second should be enough to trigger the logic - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` - Object { - "code": "Failed to fetch", - "count": 1, - "event_type": "test-event-type", - "source": "elastic_v3_server", - "type": "failed", - } - `); - }) - ); - - test( - 'handles when the fetch request fails (request completes but not OK response)', - fakeSchedulers(async (advance) => { - fetchMock.mockResolvedValueOnce({ - ok: false, - status: 400, - text: () => Promise.resolve('{"status": "not ok"}'), - }); - shipper.reportEvents(events); - shipper.optIn(true); - const counter = firstValueFrom(shipper.telemetryCounter$); - setLastBatchSent(Date.now() - 10 * SECONDS); - advance(1 * SECONDS); // Moving 1 second should be enough to trigger the logic - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` - Object { - "code": "400", - "count": 1, - "event_type": "test-event-type", - "source": "elastic_v3_server", - "type": "failed", - } - `); - }) - ); - - describe('Connectivity Checks', () => { - describe('connectivity check when connectivity is confirmed (firstTimeOffline === null)', () => { - test.each([undefined, false, true])('does not run for opt-in %p', (optInValue) => - fakeSchedulers(async (advance) => { - if (optInValue !== undefined) { - shipper.optIn(optInValue); - } - - // From the start, it doesn't check connectivity because already confirmed - expect(fetchMock).not.toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - - // Wait a big time (1 minute should be enough, but for the sake of tests...) - advance(10 * MINUTES); - await nextTick(); - - expect(fetchMock).not.toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - })() - ); - }); - - describe('connectivity check with initial unknown state of the connectivity', () => { - beforeEach(() => { - // eslint-disable-next-line dot-notation - shipper['firstTimeOffline'] = undefined; // Initial unknown state of the connectivity - }); - - test.each([undefined, false])('does not run for opt-in %p', (optInValue) => - fakeSchedulers(async (advance) => { - if (optInValue !== undefined) { - shipper.optIn(optInValue); - } - - // From the start, it doesn't check connectivity because already confirmed - expect(fetchMock).not.toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - - // Wait a big time (1 minute should be enough, but for the sake of tests...) - advance(10 * MINUTES); - await nextTick(); - - expect(fetchMock).not.toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - })() - ); - - test('runs as soon as opt-in is set to true', () => { - shipper.optIn(true); - - // From the start, it doesn't check connectivity because opt-in is not true - expect(fetchMock).toHaveBeenNthCalledWith( - 1, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - }); - }); - - describe('connectivity check with the connectivity confirmed to be faulty', () => { - beforeEach(() => { - // eslint-disable-next-line dot-notation - shipper['firstTimeOffline'] = 100; // Failed at some point - }); - - test.each([undefined, false])('does not run for opt-in %p', (optInValue) => - fakeSchedulers(async (advance) => { - if (optInValue !== undefined) { - shipper.optIn(optInValue); - } - - // From the start, it doesn't check connectivity because already confirmed - expect(fetchMock).not.toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - - // Wait a big time (1 minute should be enough, but for the sake of tests...) - advance(10 * MINUTES); - await nextTick(); - - expect(fetchMock).not.toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - })() - ); - - test('runs as soon as opt-in is set to true', () => { - shipper.optIn(true); - - // From the start, it doesn't check connectivity because opt-in is not true - expect(fetchMock).toHaveBeenNthCalledWith( - 1, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - }); - }); - - describe('after report failure', () => { - // generate the report failure for each test - beforeEach( - fakeSchedulers(async (advance) => { - fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); - shipper.reportEvents(events); - shipper.optIn(true); - const counter = firstValueFrom(shipper.telemetryCounter$); - setLastBatchSent(Date.now() - 10 * SECONDS); - advance(1 * SECONDS); // Moving 1 second should be enough to trigger the logic - expect(fetchMock).toHaveBeenNthCalledWith( - 1, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - method: 'POST', - query: { debug: true }, - } - ); - await expect(counter).resolves.toMatchInlineSnapshot(` - Object { - "code": "Failed to fetch", - "count": 1, - "event_type": "test-event-type", - "source": "elastic_v3_server", - "type": "failed", - } - `); - }) - ); - - test( - 'connectivity check runs periodically', - fakeSchedulers(async (advance) => { - fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); - advance(1 * MINUTES); - await nextTick(); - expect(fetchMock).toHaveBeenNthCalledWith( - 2, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - fetchMock.mockResolvedValueOnce({ ok: false }); - advance(2 * MINUTES); - await nextTick(); - expect(fetchMock).toHaveBeenNthCalledWith( - 3, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - }) - ); - }); - - describe('after being offline for longer than 24h', () => { - beforeEach(() => { - shipper.optIn(true); - shipper.reportEvents(events); - // eslint-disable-next-line dot-notation - expect(shipper['internalQueue'].length).toBe(1); - // eslint-disable-next-line dot-notation - shipper['firstTimeOffline'] = 100; - }); - - test( - 'the following connectivity check clears the queue', - fakeSchedulers(async (advance) => { - fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); - advance(1 * MINUTES); - await nextTick(); - expect(fetchMock).toHaveBeenNthCalledWith( - 1, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - // eslint-disable-next-line dot-notation - expect(shipper['internalQueue'].length).toBe(0); - }) - ); - - test( - 'new events are not added to the queue', - fakeSchedulers(async (advance) => { - fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); - advance(1 * MINUTES); - await nextTick(); - expect(fetchMock).toHaveBeenNthCalledWith( - 1, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - // eslint-disable-next-line dot-notation - expect(shipper['internalQueue'].length).toBe(0); - - shipper.reportEvents(events); - // eslint-disable-next-line dot-notation - expect(shipper['internalQueue'].length).toBe(0); - }) - ); - - test( - 'regains the connection', - fakeSchedulers(async (advance) => { - fetchMock.mockResolvedValueOnce({ ok: true }); - advance(1 * MINUTES); - await nextTick(); - expect(fetchMock).toHaveBeenNthCalledWith( - 1, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - // eslint-disable-next-line dot-notation - expect(shipper['firstTimeOffline']).toBe(null); - - advance(10 * MINUTES); - await nextTick(); - expect(fetchMock).not.toHaveBeenNthCalledWith( - 2, - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { method: 'OPTIONS' } - ); - }) - ); - }); - }); - - describe('flush method', () => { - test('resolves straight away if it should not send anything', async () => { - await expect(shipper.flush()).resolves.toBe(undefined); - }); - - test('resolves when all the ongoing requests are complete', async () => { - shipper.optIn(true); - shipper.reportEvents(events); - expect(fetchMock).toHaveBeenCalledTimes(0); - fetchMock.mockImplementation(async () => { - // eslint-disable-next-line dot-notation - expect(shipper['inFlightRequests$'].value).toBe(1); - }); - await expect(shipper.flush()).resolves.toBe(undefined); - expect(fetchMock).toHaveBeenCalledWith( - 'https://telemetry-staging.elastic.co/v3/send/test-channel', - { - body: '{"timestamp":"2020-01-01T00:00:00.000Z","event_type":"test-event-type","context":{},"properties":{}}\n', - headers: { - 'content-type': 'application/x-ndjson', - 'x-elastic-cluster-id': 'UNKNOWN', - 'x-elastic-stack-version': '1.2.3', - }, - method: 'POST', - query: { debug: true }, - } - ); - }); - - test('calling flush multiple times does not keep hanging', async () => { - await expect(shipper.flush()).resolves.toBe(undefined); - await expect(shipper.flush()).resolves.toBe(undefined); - await Promise.all([shipper.flush(), shipper.flush()]); - }); - - test('calling flush after shutdown does not keep hanging', async () => { - shipper.shutdown(); - await expect(shipper.flush()).resolves.toBe(undefined); - }); - }); -}); diff --git a/packages/analytics/shippers/elastic_v3/server/tsconfig.json b/packages/analytics/shippers/elastic_v3/server/tsconfig.json deleted file mode 100644 index 7808dee7058f0..0000000000000 --- a/packages/analytics/shippers/elastic_v3/server/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "extends": "../../../../../tsconfig.base.json", - "compilerOptions": { - "outDir": "target/types", - "types": [ - "jest", - "node" - ] - }, - "include": [ - "**/*.ts" - ], - "kbn_references": [ - "@kbn/analytics-client", - "@kbn/analytics-shippers-elastic-v3-common", - "@kbn/logging-mocks" - ], - "exclude": [ - "target/**/*", - ] -} diff --git a/packages/analytics/shippers/fullstory/kibana.jsonc b/packages/analytics/shippers/fullstory/kibana.jsonc deleted file mode 100644 index d2848e7b3c453..0000000000000 --- a/packages/analytics/shippers/fullstory/kibana.jsonc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "shared-common", - "id": "@kbn/analytics-shippers-fullstory", - "owner": "@elastic/kibana-core" -} diff --git a/packages/content-management/content_editor/src/components/activity_view.test.tsx b/packages/content-management/content_editor/src/components/activity_view.test.tsx new file mode 100644 index 0000000000000..252591aba72ac --- /dev/null +++ b/packages/content-management/content_editor/src/components/activity_view.test.tsx @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; +import { UserProfilesProvider } from '@kbn/content-management-user-profiles'; +import { I18nProvider } from '@kbn/i18n-react'; + +import { ActivityView as ActivityViewComponent, ActivityViewProps } from './activity_view'; + +const mockGetUserProfile = jest.fn(async (uid: string) => ({ + uid, + enabled: true, + data: {}, + user: { username: uid, full_name: uid.toLocaleUpperCase() }, +})); + +const ActivityView = (props: ActivityViewProps) => { + return ( + + + + + + ); +}; + +test('should render activity view', () => { + render(); + + expect(screen.getByTestId('activityView')).toBeVisible(); + + expect(screen.getByTestId('createdByCard')).toHaveTextContent(/Unknown/); + expect(() => screen.getByTestId('updateByCard')).toThrow(); +}); + +test('should render creator card', async () => { + render(); + + await waitFor(() => { + const createdByCard = screen.getByTestId('createdByCard'); + expect(createdByCard).toHaveTextContent(/JOHN/); + expect(createdByCard).toHaveTextContent(/June 13/); + }); +}); + +test('should not render updater card when updatedAt matches createdAt', async () => { + render( + + ); + + expect(screen.getByTestId('createdByCard')).toBeVisible(); + expect(() => screen.getByTestId('updateByCard')).toThrow(); +}); + +test('should render updater card', async () => { + render( + + ); + + await waitFor(() => { + const createdByCard = screen.getByTestId('createdByCard'); + expect(createdByCard).toHaveTextContent(/JOHN/); + expect(createdByCard).toHaveTextContent(/June 13/); + }); + + await waitFor(() => { + const updatedByCard = screen.getByTestId('updatedByCard'); + expect(updatedByCard).toHaveTextContent(/PETE/); + expect(updatedByCard).toHaveTextContent(/June 14/); + }); +}); + +test('should handle managed objects', async () => { + render( + + ); + + await waitFor(() => { + const createdByCard = screen.getByTestId('createdByCard'); + expect(createdByCard).toHaveTextContent(/System/); + expect(createdByCard).toHaveTextContent(/June 13/); + }); + + const updatedByCard = screen.getByTestId('updatedByCard'); + expect(updatedByCard).toHaveTextContent(/System/); + expect(updatedByCard).toHaveTextContent(/June 14/); +}); diff --git a/packages/content-management/content_editor/src/components/activity_view.tsx b/packages/content-management/content_editor/src/components/activity_view.tsx new file mode 100644 index 0000000000000..eb413acb20e36 --- /dev/null +++ b/packages/content-management/content_editor/src/components/activity_view.tsx @@ -0,0 +1,185 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiIconTip, + EuiPanel, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { + UserAvatarTip, + useUserProfile, + NoUpdaterTip, + NoCreatorTip, + ManagedAvatarTip, +} from '@kbn/content-management-user-profiles'; +import { getUserDisplayName } from '@kbn/user-profile-components'; + +import { Item } from '../types'; + +export interface ActivityViewProps { + item: Pick; +} + +export const ActivityView = ({ item }: ActivityViewProps) => { + const showLastUpdated = Boolean(item.updatedAt && item.updatedAt !== item.createdAt); + + const UnknownUserLabel = ( + + ); + + const ManagedUserLabel = ( + <> + {' '} + + + ); + + return ( + + {' '} + + } + /> + + } + fullWidth + data-test-subj={'activityView'} + > + <> + + + + ) : item.managed ? ( + <>{ManagedUserLabel} + ) : ( + <> + {UnknownUserLabel} + + + ) + } + when={item.createdAt} + data-test-subj={'createdByCard'} + /> + + + {showLastUpdated && ( + + ) : item.managed ? ( + <>{ManagedUserLabel} + ) : ( + <> + {UnknownUserLabel} + + + ) + } + when={item.updatedAt} + data-test-subj={'updatedByCard'} + /> + )} + + + + + ); +}; + +const dateFormatter = new Intl.DateTimeFormat(i18n.getLocale(), { + dateStyle: 'long', + timeStyle: 'short', +}); + +const ActivityCard = ({ + what, + when, + who, + 'data-test-subj': dataTestSubj, +}: { + what: string; + who: React.ReactNode; + when?: string; + 'data-test-subj'?: string; +}) => { + return ( + + + {what} + + + + {who} + + {when && ( + <> + + + + + + )} + + ); +}; + +const UserLabel = ({ uid }: { uid: string }) => { + const userQuery = useUserProfile(uid); + + if (!userQuery.data) return null; + + return ( + <> + {getUserDisplayName(userQuery.data.user)} + + ); +}; diff --git a/packages/content-management/content_editor/src/components/editor_flyout_content.tsx b/packages/content-management/content_editor/src/components/editor_flyout_content.tsx index 007cc58c4e5ad..a347dbb2c4d31 100644 --- a/packages/content-management/content_editor/src/components/editor_flyout_content.tsx +++ b/packages/content-management/content_editor/src/components/editor_flyout_content.tsx @@ -28,6 +28,7 @@ import type { Item } from '../types'; import { MetadataForm } from './metadata_form'; import { useMetadataForm } from './use_metadata_form'; import type { CustomValidators } from './use_metadata_form'; +import { ActivityView } from './activity_view'; const getI18nTexts = ({ entityName }: { entityName: string }) => ({ saveButtonLabel: i18n.translate('contentManagement.contentEditor.saveButtonLabel', { @@ -55,6 +56,7 @@ export interface Props { }) => Promise; customValidators?: CustomValidators; onCancel: () => void; + showActivityView?: boolean; } const capitalize = (str: string) => `${str.charAt(0).toLocaleUpperCase()}${str.substring(1)}`; @@ -68,6 +70,7 @@ export const ContentEditorFlyoutContent: FC = ({ onSave, onCancel, customValidators, + showActivityView, }) => { const { euiTheme } = useEuiTheme(); const [isSubmitting, setIsSubmitting] = useState(false); @@ -147,7 +150,9 @@ export const ContentEditorFlyoutContent: FC = ({ tagsReferences={item.tags} TagList={TagList} TagSelector={TagSelector} - /> + > + {showActivityView && } + diff --git a/packages/content-management/content_editor/src/components/editor_flyout_content_container.tsx b/packages/content-management/content_editor/src/components/editor_flyout_content_container.tsx index c1a36406980f9..18094bc04f084 100644 --- a/packages/content-management/content_editor/src/components/editor_flyout_content_container.tsx +++ b/packages/content-management/content_editor/src/components/editor_flyout_content_container.tsx @@ -21,6 +21,7 @@ type CommonProps = Pick< | 'onCancel' | 'entityName' | 'customValidators' + | 'showActivityView' >; export type Props = CommonProps; diff --git a/packages/content-management/content_editor/src/components/inspector_flyout_content.test.tsx b/packages/content-management/content_editor/src/components/inspector_flyout_content.test.tsx index e4668d022d00d..c543acedbae5b 100644 --- a/packages/content-management/content_editor/src/components/inspector_flyout_content.test.tsx +++ b/packages/content-management/content_editor/src/components/inspector_flyout_content.test.tsx @@ -262,5 +262,22 @@ describe('', () => { tags: ['id-3', 'id-4'], // New selection }); }); + + test('should render activity view', async () => { + await act(async () => { + testBed = await setup({ showActivityView: true }); + }); + const { find, component } = testBed!; + + expect(find('activityView').exists()).toBe(true); + expect(find('activityView.createdByCard').exists()).toBe(true); + expect(find('activityView.updatedByCard').exists()).toBe(false); + + testBed.setProps({ + item: { ...savedObjectItem, updatedAt: '2021-01-01T00:00:00Z' }, + }); + component.update(); + expect(find('activityView.updatedByCard').exists()).toBe(true); + }); }); }); diff --git a/packages/content-management/content_editor/src/components/metadata_form.tsx b/packages/content-management/content_editor/src/components/metadata_form.tsx index 26b433edcd161..c5db98dc2811b 100644 --- a/packages/content-management/content_editor/src/components/metadata_form.tsx +++ b/packages/content-management/content_editor/src/components/metadata_form.tsx @@ -6,20 +6,20 @@ * Side Public License, v 1. */ -import React from 'react'; import type { FC } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { + EuiFieldText, EuiForm, EuiFormRow, - EuiFieldText, - EuiTextArea, EuiSpacer, + EuiTextArea, EuiToolTip, } from '@elastic/eui'; import { ContentEditorFlyoutWarningsCallOut } from './editor_flyout_warnings'; -import type { MetadataFormState, Field } from './use_metadata_form'; +import type { Field, MetadataFormState } from './use_metadata_form'; import type { SavedObjectsReference, Services } from '../services'; interface Props { @@ -42,6 +42,7 @@ export const MetadataForm: FC = ({ TagSelector, isReadonly, readonlyReason, + children, }) => { const { title, @@ -137,6 +138,13 @@ export const MetadataForm: FC = ({ )} + + {children && ( + <> + + {children} + + )} ); }; diff --git a/packages/content-management/content_editor/src/open_content_editor.tsx b/packages/content-management/content_editor/src/open_content_editor.tsx index b9bace1b45994..89b73991ba5d6 100644 --- a/packages/content-management/content_editor/src/open_content_editor.tsx +++ b/packages/content-management/content_editor/src/open_content_editor.tsx @@ -15,7 +15,13 @@ import type { ContentEditorFlyoutContentContainerProps } from './components'; export type OpenContentEditorParams = Pick< ContentEditorFlyoutContentContainerProps, - 'item' | 'onSave' | 'isReadonly' | 'readonlyReason' | 'entityName' | 'customValidators' + | 'item' + | 'onSave' + | 'isReadonly' + | 'readonlyReason' + | 'entityName' + | 'customValidators' + | 'showActivityView' >; export function useOpenContentEditor() { diff --git a/packages/content-management/content_editor/src/services.tsx b/packages/content-management/content_editor/src/services.tsx index 6edf58e45c846..8a9938ff51561 100644 --- a/packages/content-management/content_editor/src/services.tsx +++ b/packages/content-management/content_editor/src/services.tsx @@ -8,6 +8,10 @@ import type { FC, PropsWithChildren, ReactNode } from 'react'; import React, { useCallback, useContext, useMemo } from 'react'; +import { + UserProfilesProvider, + useUserProfilesServices, +} from '@kbn/content-management-user-profiles'; import type { EuiComboBoxProps } from '@elastic/eui'; import type { AnalyticsServiceStart } from '@kbn/core-analytics-browser'; @@ -130,11 +134,19 @@ export const ContentEditorKibanaProvider: FC< return Comp; }, [savedObjectsTagging?.ui.components.TagList]); + const userProfilesServices = useUserProfilesServices(); + const openFlyout = useCallback( (node: ReactNode, options: OverlayFlyoutOpenOptions) => { - return coreOpenFlyout(toMountPoint(node, startServices), options); + return coreOpenFlyout( + toMountPoint( + {node}, + startServices + ), + options + ); }, - [coreOpenFlyout, startServices] + [coreOpenFlyout, startServices, userProfilesServices] ); return ( diff --git a/packages/content-management/content_editor/src/types.ts b/packages/content-management/content_editor/src/types.ts index 02607aa70d8bb..f54ad6b562ad8 100644 --- a/packages/content-management/content_editor/src/types.ts +++ b/packages/content-management/content_editor/src/types.ts @@ -13,4 +13,11 @@ export interface Item { title: string; description?: string; tags: SavedObjectsReference[]; + + createdAt?: string; + createdBy?: string; + updatedAt?: string; + updatedBy?: string; + + managed?: boolean; } diff --git a/packages/content-management/content_editor/tsconfig.json b/packages/content-management/content_editor/tsconfig.json index 8a7a1c08ba74a..b4f77e22f1f44 100644 --- a/packages/content-management/content_editor/tsconfig.json +++ b/packages/content-management/content_editor/tsconfig.json @@ -10,7 +10,9 @@ "react", "@kbn/ambient-ui-types", "@kbn/ambient-storybook-types", - "@emotion/react/types/css-prop" + "@emotion/react/types/css-prop", + "@testing-library/jest-dom", + "@testing-library/react" ] }, "include": [ @@ -26,7 +28,9 @@ "@kbn/core-i18n-browser", "@kbn/core-theme-browser", "@kbn/test-jest-helpers", - "@kbn/react-kibana-mount" + "@kbn/react-kibana-mount", + "@kbn/content-management-user-profiles", + "@kbn/user-profile-components" ], "exclude": [ "target/**/*" diff --git a/packages/content-management/table_list_view_common/index.ts b/packages/content-management/table_list_view_common/index.ts index fea9e6a918673..4ca206e196f17 100644 --- a/packages/content-management/table_list_view_common/index.ts +++ b/packages/content-management/table_list_view_common/index.ts @@ -11,6 +11,8 @@ import type { SavedObjectsReference } from '@kbn/content-management-content-edit export interface UserContentCommonSchema { id: string; updatedAt: string; + updatedBy?: string; + createdAt?: string; createdBy?: string; managed?: boolean; references: SavedObjectsReference[]; diff --git a/packages/content-management/table_list_view_table/src/__jest__/created_by_filter.test.tsx b/packages/content-management/table_list_view_table/src/__jest__/created_by_filter.test.tsx index 5593bda387778..bd68993b3f3ba 100644 --- a/packages/content-management/table_list_view_table/src/__jest__/created_by_filter.test.tsx +++ b/packages/content-management/table_list_view_table/src/__jest__/created_by_filter.test.tsx @@ -147,7 +147,7 @@ describe('created_by filter', () => { // wait until first render expect(await screen.findByTestId('itemsInMemTable')).toBeVisible(); - // 5 items in the list + // 4 items in the list expect(screen.getAllByTestId(/userContentListingTitleLink/)).toHaveLength(4); userEvent.click(screen.getByTestId('userFilterPopoverButton')); diff --git a/packages/content-management/table_list_view_table/src/__jest__/tests.helpers.tsx b/packages/content-management/table_list_view_table/src/__jest__/tests.helpers.tsx index a1f35289c26ef..d1f42f0d1d6b1 100644 --- a/packages/content-management/table_list_view_table/src/__jest__/tests.helpers.tsx +++ b/packages/content-management/table_list_view_table/src/__jest__/tests.helpers.tsx @@ -9,12 +9,13 @@ import React from 'react'; import type { ComponentType } from 'react'; import { from } from 'rxjs'; import { ContentEditorProvider } from '@kbn/content-management-content-editor'; +import { UserProfilesProvider, UserProfilesServices } from '@kbn/content-management-user-profiles'; import { TagList } from '../mocks'; import { TableListViewProvider, Services } from '../services'; -export const getMockServices = (overrides?: Partial) => { - const services: Services = { +export const getMockServices = (overrides?: Partial) => { + const services: Services & UserProfilesServices = { canEditAdvancedSettings: true, getListingLimitSettingsUrl: () => 'http://elastic.co', notifyError: () => undefined, @@ -25,24 +26,29 @@ export const getMockServices = (overrides?: Partial) => { itemHasTags: () => true, getTagManagementUrl: () => '', getTagIdsFromReferences: () => [], - bulkGetUserProfiles: jest.fn(() => Promise.resolve([])), - getUserProfile: jest.fn(), isTaggingEnabled: () => true, + bulkGetUserProfiles: async () => [], + getUserProfile: async () => ({ uid: '', enabled: true, data: {}, user: { username: '' } }), ...overrides, }; return services; }; -export function WithServices

(Comp: ComponentType

, overrides: Partial = {}) { +export function WithServices

( + Comp: ComponentType

, + overrides: Partial = {} +) { return (props: P) => { const services = getMockServices(overrides); return ( - undefined}> - - - - + + undefined}> + + + + + ); }; } diff --git a/packages/content-management/table_list_view_table/src/components/user_filter_panel.tsx b/packages/content-management/table_list_view_table/src/components/user_filter_panel.tsx index 2307f449b9c2d..1f53678936065 100644 --- a/packages/content-management/table_list_view_table/src/components/user_filter_panel.tsx +++ b/packages/content-management/table_list_view_table/src/components/user_filter_panel.tsx @@ -11,10 +11,8 @@ import React from 'react'; import { EuiFilterButton, useEuiTheme } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { UserProfile, UserProfilesPopover } from '@kbn/user-profile-components'; -import { useQuery, QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { i18n } from '@kbn/i18n'; -import { useServices } from '../services'; -import { NoUsersTip } from './user_missing_tip'; +import { useUserProfiles, NoCreatorTip } from '@kbn/content-management-user-profiles'; interface Context { enabled: boolean; @@ -25,43 +23,29 @@ interface Context { } const UserFilterContext = React.createContext(null); -const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false, staleTime: 30 * 60 * 1000 } }, -}); export const UserFilterContextProvider: FC = ({ children, ...props }) => { if (!props.enabled) { return <>{children}; } - return ( - - {children} - - ); + return {children}; }; export const NULL_USER = 'no-user'; export const UserFilterPanel: FC<{}> = () => { - const { bulkGetUserProfiles } = useServices(); const { euiTheme } = useEuiTheme(); const componentContext = React.useContext(UserFilterContext); if (!componentContext) throw new Error('UserFilterPanel must be used within a UserFilterContextProvider'); - if (!bulkGetUserProfiles) - throw new Error('UserFilterPanel must be used with a bulkGetUserProfiles function'); const { onSelectedUsersChange, selectedUsers, showNoUserOption } = componentContext; const [isPopoverOpen, setPopoverOpen] = React.useState(false); const [searchTerm, setSearchTerm] = React.useState(''); - const query = useQuery({ - queryKey: ['user-filter-suggestions', componentContext.allUsers], - queryFn: () => bulkGetUserProfiles(componentContext.allUsers), - enabled: isPopoverOpen, - }); + const query = useUserProfiles(componentContext.allUsers, { enabled: isPopoverOpen }); const usersMap = React.useMemo(() => { if (!query.data) return {}; @@ -138,7 +122,7 @@ export const UserFilterPanel: FC<{}> = () => { id="contentManagement.tableList.listing.userFilter.emptyMessage" defaultMessage="None of the dashboards have creators" /> - {} + {}

), nullOptionLabel: i18n.translate( @@ -148,7 +132,7 @@ export const UserFilterPanel: FC<{}> = () => { } ), nullOptionProps: { - append: , + append: , }, clearButtonLabel: ( ( - - } - /> -); diff --git a/packages/content-management/table_list_view_table/src/mocks.tsx b/packages/content-management/table_list_view_table/src/mocks.tsx index a650d544aaecc..f76954fd497a2 100644 --- a/packages/content-management/table_list_view_table/src/mocks.tsx +++ b/packages/content-management/table_list_view_table/src/mocks.tsx @@ -71,8 +71,6 @@ export const getStoryServices = (params: Params, action: ActionFn = () => {}) => itemHasTags: () => true, getTagManagementUrl: () => '', getTagIdsFromReferences: () => [], - bulkGetUserProfiles: () => Promise.resolve([]), - getUserProfile: jest.fn(), isTaggingEnabled: () => true, ...params, }; diff --git a/packages/content-management/table_list_view_table/src/services.tsx b/packages/content-management/table_list_view_table/src/services.tsx index 7be68fd28be91..5275dd35069cd 100644 --- a/packages/content-management/table_list_view_table/src/services.tsx +++ b/packages/content-management/table_list_view_table/src/services.tsx @@ -20,11 +20,10 @@ import type { MountPoint, OverlayRef } from '@kbn/core-mount-utils-browser'; import type { OverlayFlyoutOpenOptions } from '@kbn/core-overlays-browser'; import type { ThemeServiceStart } from '@kbn/core-theme-browser'; import type { UserProfileServiceStart } from '@kbn/core-user-profile-browser'; -import type { UserProfile } from '@kbn/user-profile-components'; import type { FormattedRelative } from '@kbn/i18n-react'; import { toMountPoint } from '@kbn/react-kibana-mount'; import { RedirectAppLinksKibanaProvider } from '@kbn/shared-ux-link-redirect-app'; -import { createBatcher } from './utils/batcher'; +import { UserProfilesKibanaProvider } from '@kbn/content-management-user-profiles'; import { TAG_MANAGEMENT_APP_URL } from './constants'; import type { Tag } from './types'; @@ -69,9 +68,6 @@ export interface Services { /** Handler to return the url to navigate to the kibana tags management */ getTagManagementUrl: () => string; getTagIdsFromReferences: (references: SavedObjectsReference[]) => string[]; - /** resolve user profiles for the user filter and creator functionality */ - bulkGetUserProfiles: (uids: string[]) => Promise; - getUserProfile: (uid: string) => Promise; } const TableListViewContext = React.createContext(null); @@ -229,51 +225,35 @@ export const TableListViewKibanaProvider: FC< [getTagIdsFromReferences] ); - const bulkGetUserProfiles = useCallback<(userProfileIds: string[]) => Promise>( - async (uids: string[]) => { - if (uids.length === 0) return []; - - return core.userProfile.bulkGet({ uids: new Set(uids), dataPath: 'avatar' }); - }, - [core.userProfile] - ); - - const getUserProfile = useMemo(() => { - return createBatcher({ - fetcher: bulkGetUserProfiles, - resolver: (users, id) => users.find((u) => u.uid === id)!, - }).fetch; - }, [bulkGetUserProfiles]); - return ( - - - application.getUrlForApp('management', { - path: `/kibana/settings?query=savedObjects:listingLimit`, - }) - } - notifyError={(title, text) => { - notifications.toasts.addDanger({ title: toMountPoint(title, startServices), text }); - }} - searchQueryParser={searchQueryParser} - DateFormatterComp={(props) => } - currentAppId$={application.currentAppId$} - navigateToUrl={application.navigateToUrl} - isTaggingEnabled={() => Boolean(savedObjectsTagging)} - getTagList={getTagList} - TagList={TagList} - itemHasTags={itemHasTags} - getTagIdsFromReferences={getTagIdsFromReferences} - getTagManagementUrl={() => core.http.basePath.prepend(TAG_MANAGEMENT_APP_URL)} - bulkGetUserProfiles={bulkGetUserProfiles} - getUserProfile={getUserProfile} - > - {children} - - + + + + application.getUrlForApp('management', { + path: `/kibana/settings?query=savedObjects:listingLimit`, + }) + } + notifyError={(title, text) => { + notifications.toasts.addDanger({ title: toMountPoint(title, startServices), text }); + }} + searchQueryParser={searchQueryParser} + DateFormatterComp={(props) => } + currentAppId$={application.currentAppId$} + navigateToUrl={application.navigateToUrl} + isTaggingEnabled={() => Boolean(savedObjectsTagging)} + getTagList={getTagList} + TagList={TagList} + itemHasTags={itemHasTags} + getTagIdsFromReferences={getTagIdsFromReferences} + getTagManagementUrl={() => core.http.basePath.prepend(TAG_MANAGEMENT_APP_URL)} + > + {children} + + + ); }; diff --git a/packages/content-management/table_list_view_table/src/table_list_view_table.tsx b/packages/content-management/table_list_view_table/src/table_list_view_table.tsx index 6cac7f7db8870..9e69c4e0f4434 100644 --- a/packages/content-management/table_list_view_table/src/table_list_view_table.tsx +++ b/packages/content-management/table_list_view_table/src/table_list_view_table.tsx @@ -27,6 +27,11 @@ import { FormattedMessage } from '@kbn/i18n-react'; import type { IHttpFetchError } from '@kbn/core-http-browser'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import { useOpenContentEditor } from '@kbn/content-management-content-editor'; +import { + UserAvatarTip, + ManagedAvatarTip, + NoCreatorTip, +} from '@kbn/content-management-user-profiles'; import type { OpenContentEditorParams, SavedObjectsReference, @@ -47,12 +52,12 @@ import { type SortColumnField, getInitialSorting, saveSorting } from './componen import { useTags } from './use_tags'; import { useInRouterContext, useUrlState } from './use_url_state'; import { RowActions, TableItemsRowActions } from './types'; -import { UserAvatarTip } from './components/user_avatar_tip'; -import { NoUsersTip } from './components/user_missing_tip'; -import { ManagedAvatarTip } from './components/managed_avatar_tip'; interface ContentEditorConfig - extends Pick { + extends Pick< + OpenContentEditorParams, + 'isReadonly' | 'onSave' | 'customValidators' | 'showActivityView' + > { enabled?: boolean; } @@ -506,6 +511,11 @@ function TableListViewTableComp({ title: item.attributes.title, description: item.attributes.description, tags, + createdAt: item.createdAt, + createdBy: item.createdBy, + updatedAt: item.updatedAt, + updatedBy: item.updatedBy, + managed: item.managed, }, entityName, ...contentEditor, @@ -575,7 +585,6 @@ function TableListViewTableComp({ {i18n.translate('contentManagement.tableList.createdByColumnTitle', { defaultMessage: 'Creator', })} - ), render: (field: string, record: { createdBy?: string; managed?: boolean }) => @@ -583,7 +592,9 @@ function TableListViewTableComp({ ) : record.managed ? ( - ) : null, + ) : ( + + ), sortable: false /* createdBy column is not sortable because it doesn't make sense to sort by id*/, width: '100px', @@ -601,7 +612,7 @@ function TableListViewTableComp({ ), sortable: true, - width: '120px', + width: '130px', }); } diff --git a/packages/content-management/table_list_view_table/tsconfig.json b/packages/content-management/table_list_view_table/tsconfig.json index 318c7ce382a9b..09bf8256764d1 100644 --- a/packages/content-management/table_list_view_table/tsconfig.json +++ b/packages/content-management/table_list_view_table/tsconfig.json @@ -33,7 +33,8 @@ "@kbn/content-management-table-list-view-common", "@kbn/user-profile-components", "@kbn/core-user-profile-browser", - "@kbn/react-kibana-mount" + "@kbn/react-kibana-mount", + "@kbn/content-management-user-profiles" ], "exclude": [ "target/**/*" diff --git a/packages/content-management/user_profiles/README.md b/packages/content-management/user_profiles/README.md new file mode 100644 index 0000000000000..e3559c9070f62 --- /dev/null +++ b/packages/content-management/user_profiles/README.md @@ -0,0 +1,3 @@ +# @kbn/content-management-user-profiles + +Shared user profile components for content management components. diff --git a/packages/content-management/user_profiles/index.ts b/packages/content-management/user_profiles/index.ts new file mode 100644 index 0000000000000..bce8d57baddcd --- /dev/null +++ b/packages/content-management/user_profiles/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { UserAvatarTip, NoUpdaterTip, NoCreatorTip, ManagedAvatarTip } from './src/components'; + +export { useUserProfile, useUserProfiles } from './src/queries'; + +export { + UserProfilesKibanaProvider, + type UserProfilesKibanaDependencies, + UserProfilesProvider, + type UserProfilesServices, + useUserProfilesServices, +} from './src/services'; diff --git a/packages/analytics/shippers/elastic_v3/server/jest.config.js b/packages/content-management/user_profiles/jest.config.js similarity index 73% rename from packages/analytics/shippers/elastic_v3/server/jest.config.js rename to packages/content-management/user_profiles/jest.config.js index 4397eb3e50c5a..464585a818055 100644 --- a/packages/analytics/shippers/elastic_v3/server/jest.config.js +++ b/packages/content-management/user_profiles/jest.config.js @@ -7,7 +7,7 @@ */ module.exports = { - preset: '@kbn/test/jest_node', - rootDir: '../../../../../', - roots: ['/packages/analytics/shippers/elastic_v3/server'], + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/packages/content-management/user_profiles'], }; diff --git a/packages/content-management/user_profiles/kibana.jsonc b/packages/content-management/user_profiles/kibana.jsonc new file mode 100644 index 0000000000000..6422efa40b631 --- /dev/null +++ b/packages/content-management/user_profiles/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/content-management-user-profiles", + "owner": "@elastic/appex-sharedux" +} diff --git a/packages/analytics/shippers/fullstory/package.json b/packages/content-management/user_profiles/package.json similarity index 55% rename from packages/analytics/shippers/fullstory/package.json rename to packages/content-management/user_profiles/package.json index 4eca1476236a1..bb5629d86ff0b 100644 --- a/packages/analytics/shippers/fullstory/package.json +++ b/packages/content-management/user_profiles/package.json @@ -1,7 +1,6 @@ { - "name": "@kbn/analytics-shippers-fullstory", + "name": "@kbn/content-management-user-profiles", "private": true, "version": "1.0.0", - "author": "Kibana Core", "license": "SSPL-1.0 OR Elastic License 2.0" } \ No newline at end of file diff --git a/packages/content-management/user_profiles/src/components/index.ts b/packages/content-management/user_profiles/src/components/index.ts new file mode 100644 index 0000000000000..1f6e2116145b9 --- /dev/null +++ b/packages/content-management/user_profiles/src/components/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { UserAvatarTip } from './user_avatar_tip'; +export { NoUpdaterTip, NoCreatorTip } from './user_missing_tip'; +export { ManagedAvatarTip } from './managed_avatar_tip'; diff --git a/packages/content-management/table_list_view_table/src/components/managed_avatar_tip.tsx b/packages/content-management/user_profiles/src/components/managed_avatar_tip.tsx similarity index 70% rename from packages/content-management/table_list_view_table/src/components/managed_avatar_tip.tsx rename to packages/content-management/user_profiles/src/components/managed_avatar_tip.tsx index 1453a5183e9c6..02e22faa629fb 100644 --- a/packages/content-management/table_list_view_table/src/components/managed_avatar_tip.tsx +++ b/packages/content-management/user_profiles/src/components/managed_avatar_tip.tsx @@ -10,10 +10,16 @@ import React from 'react'; import { EuiAvatar, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -export function ManagedAvatarTip({ entityName }: { entityName: string }) { +export function ManagedAvatarTip({ + entityName = i18n.translate('contentManagement.userProfiles.managedAvatarTip.defaultEntityName', { + defaultMessage: 'object', + }), +}: { + entityName?: string; +}) { return ( { - return getUserProfile(props.uid); - }, - { staleTime: Infinity } - ); + const query = useUserProfile(props.uid); if (query.data) { return ( diff --git a/packages/content-management/user_profiles/src/components/user_missing_tip.tsx b/packages/content-management/user_profiles/src/components/user_missing_tip.tsx new file mode 100644 index 0000000000000..758429b781537 --- /dev/null +++ b/packages/content-management/user_profiles/src/components/user_missing_tip.tsx @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiIconTip, IconType } from '@elastic/eui'; +import React from 'react'; + +export const NoCreatorTip = (props: { iconType?: IconType }) => ( + + } + {...props} + /> +); + +export const NoUpdaterTip = (props: { iconType?: string }) => ( + + } + {...props} + /> +); + +const NoUsersTip = ({ + iconType: type = 'questionInCircle', + ...props +}: { + content: React.ReactNode; + iconType?: IconType; +}) => ( + +); diff --git a/packages/content-management/user_profiles/src/queries.ts b/packages/content-management/user_profiles/src/queries.ts new file mode 100644 index 0000000000000..b238a5489d20c --- /dev/null +++ b/packages/content-management/user_profiles/src/queries.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { useQuery } from '@tanstack/react-query'; +import { useUserProfilesServices } from './services'; + +export const userProfileKeys = { + get: (uid: string) => ['user-profile', uid], + bulkGet: (uids: string[]) => ['user-profile', { uids }], +}; + +export const useUserProfile = (uid: string) => { + const { getUserProfile } = useUserProfilesServices(); + const query = useQuery( + userProfileKeys.get(uid), + async () => { + return getUserProfile(uid); + }, + { staleTime: Infinity } + ); + return query; +}; + +export const useUserProfiles = (uids: string[], opts?: { enabled?: boolean }) => { + const { bulkGetUserProfiles } = useUserProfilesServices(); + const query = useQuery({ + queryKey: userProfileKeys.bulkGet(uids), + queryFn: () => bulkGetUserProfiles(uids), + enabled: opts?.enabled ?? true, + }); + return query; +}; diff --git a/packages/content-management/user_profiles/src/services.tsx b/packages/content-management/user_profiles/src/services.tsx new file mode 100644 index 0000000000000..25ea8d4b5acda --- /dev/null +++ b/packages/content-management/user_profiles/src/services.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import type { UserProfileServiceStart } from '@kbn/core-user-profile-browser'; +import React, { FC, PropsWithChildren, useCallback, useContext, useMemo } from 'react'; +import type { UserProfile } from '@kbn/user-profile-components'; +import { createBatcher } from './utils/batcher'; + +export const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false, staleTime: 30 * 60 * 1000 } }, +}); + +export interface UserProfilesKibanaDependencies { + core: { + userProfile: { + bulkGet: UserProfileServiceStart['bulkGet']; + }; + }; +} + +export interface UserProfilesServices { + bulkGetUserProfiles: (uids: string[]) => Promise; + getUserProfile: (uid: string) => Promise; +} + +const UserProfilesContext = React.createContext(null); + +export const UserProfilesProvider: FC> = ({ + children, + ...services +}) => { + return ( + + {children} + + ); +}; + +export const UserProfilesKibanaProvider: FC> = ({ + children, + core, +}) => { + const bulkGetUserProfiles = useCallback<(userProfileIds: string[]) => Promise>( + async (uids: string[]) => { + if (uids.length === 0) return []; + + return core.userProfile.bulkGet({ uids: new Set(uids), dataPath: 'avatar' }); + }, + [core.userProfile] + ); + + const getUserProfile = useMemo(() => { + return createBatcher({ + fetcher: bulkGetUserProfiles, + resolver: (users, id) => users.find((u) => u.uid === id)!, + }).fetch; + }, [bulkGetUserProfiles]); + + return ( + + {children} + + ); +}; + +export function useUserProfilesServices() { + const context = useContext(UserProfilesContext); + + if (!context) { + throw new Error( + 'UserProfilesContext is missing. Ensure your component or React root is wrapped with ' + ); + } + + return context; +} diff --git a/packages/content-management/table_list_view_table/src/utils/batcher.test.tsx b/packages/content-management/user_profiles/src/utils/batcher.test.tsx similarity index 100% rename from packages/content-management/table_list_view_table/src/utils/batcher.test.tsx rename to packages/content-management/user_profiles/src/utils/batcher.test.tsx diff --git a/packages/content-management/table_list_view_table/src/utils/batcher.ts b/packages/content-management/user_profiles/src/utils/batcher.ts similarity index 100% rename from packages/content-management/table_list_view_table/src/utils/batcher.ts rename to packages/content-management/user_profiles/src/utils/batcher.ts diff --git a/packages/content-management/user_profiles/tsconfig.json b/packages/content-management/user_profiles/tsconfig.json new file mode 100644 index 0000000000000..de0aeff18778f --- /dev/null +++ b/packages/content-management/user_profiles/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/user-profile-components", + "@kbn/core-user-profile-browser", + "@kbn/i18n", + "@kbn/i18n-react", + ] +} diff --git a/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts index 7f32ea7ed41a6..ec9d9e05aff23 100644 --- a/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts +++ b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.test.mocks.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { AnalyticsClient } from '@kbn/analytics-client'; +import { AnalyticsClient } from '@kbn/ebt/client'; import { Subject } from 'rxjs'; export const analyticsClientMock: jest.Mocked = { @@ -21,6 +21,6 @@ export const analyticsClientMock: jest.Mocked = { shutdown: jest.fn(), }; -jest.doMock('@kbn/analytics-client', () => ({ +jest.doMock('@kbn/ebt/client', () => ({ createAnalytics: () => analyticsClientMock, })); diff --git a/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts index 25ea7aed65afc..091643741331e 100644 --- a/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts +++ b/packages/core/analytics/core-analytics-browser-internal/src/analytics_service.ts @@ -7,8 +7,8 @@ */ import { of, Subscription } from 'rxjs'; -import type { AnalyticsClient } from '@kbn/analytics-client'; -import { createAnalytics } from '@kbn/analytics-client'; +import type { AnalyticsClient } from '@kbn/ebt/client'; +import { createAnalytics } from '@kbn/ebt/client'; import { registerPerformanceMetricEventType } from '@kbn/ebt-tools'; import type { CoreContext } from '@kbn/core-base-browser-internal'; import type { InternalInjectedMetadataSetup } from '@kbn/core-injected-metadata-browser-internal'; diff --git a/packages/core/analytics/core-analytics-browser-internal/src/track_clicks.ts b/packages/core/analytics/core-analytics-browser-internal/src/track_clicks.ts index 6ca58e7d69149..e545ec0fe7b2a 100644 --- a/packages/core/analytics/core-analytics-browser-internal/src/track_clicks.ts +++ b/packages/core/analytics/core-analytics-browser-internal/src/track_clicks.ts @@ -7,7 +7,7 @@ */ import { fromEvent } from 'rxjs'; -import type { AnalyticsClient } from '@kbn/analytics-client'; +import type { AnalyticsClient } from '@kbn/ebt/client'; /** HTML attributes that should be skipped from reporting because they might contain data we do not wish to collect */ const HTML_ATTRIBUTES_TO_REMOVE = [ diff --git a/packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.ts b/packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.ts index 80826c03864ad..40d479b448b46 100644 --- a/packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.ts +++ b/packages/core/analytics/core-analytics-browser-internal/src/track_performance_measure_entries.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import type { AnalyticsClient } from '@kbn/analytics-client'; +import type { AnalyticsClient } from '@kbn/ebt/client'; import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; export function trackPerformanceMeasureEntries(analytics: AnalyticsClient, isDevMode: boolean) { diff --git a/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts b/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts index 17fab459f2e8f..dd0ebde28addb 100644 --- a/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts +++ b/packages/core/analytics/core-analytics-browser-internal/src/track_viewport_size.ts @@ -7,7 +7,7 @@ */ import { debounceTime, fromEvent, map, merge, of, shareReplay } from 'rxjs'; -import type { AnalyticsClient, RootSchema } from '@kbn/analytics-client'; +import type { AnalyticsClient, RootSchema } from '@kbn/ebt/client'; export interface ViewportSize { viewport_width: number; diff --git a/packages/core/analytics/core-analytics-browser-internal/tsconfig.json b/packages/core/analytics/core-analytics-browser-internal/tsconfig.json index a58f3402e65a9..ecac1746f44a2 100644 --- a/packages/core/analytics/core-analytics-browser-internal/tsconfig.json +++ b/packages/core/analytics/core-analytics-browser-internal/tsconfig.json @@ -6,13 +6,13 @@ }, "include": ["**/*.ts"], "kbn_references": [ - "@kbn/analytics-client", "@kbn/ebt-tools", "@kbn/core-base-browser-internal", "@kbn/core-injected-metadata-browser-internal", "@kbn/core-analytics-browser", "@kbn/core-base-browser-mocks", "@kbn/core-injected-metadata-browser-mocks", + "@kbn/ebt", ], "exclude": ["target/**/*"] } diff --git a/packages/core/analytics/core-analytics-browser/index.ts b/packages/core/analytics/core-analytics-browser/index.ts index 331f1695d9f20..f20ccf31da2c4 100644 --- a/packages/core/analytics/core-analytics-browser/index.ts +++ b/packages/core/analytics/core-analytics-browser/index.ts @@ -11,3 +11,41 @@ export type { AnalyticsServiceStart, KbnAnalyticsWindowApi, } from './src/types'; + +export type { + AnalyticsClient, + AnalyticsClientInitContext, + // Types for the registerShipper API + ShipperClassConstructor, + RegisterShipperOpts, + // Types for the optIn API + OptInConfig, + OptInConfigPerType, + ShipperName, + // Types for the registerContextProvider API + ContextProviderOpts, + ContextProviderName, + // Types for the registerEventType API + EventTypeOpts, + // Events + Event, + EventContext, + EventType, + TelemetryCounter, + TelemetryCounterType, + // Schema + RootSchema, + SchemaObject, + SchemaArray, + SchemaChildValue, + SchemaMeta, + SchemaValue, + SchemaMetaOptional, + PossibleSchemaTypes, + AllowedSchemaBooleanTypes, + AllowedSchemaNumberTypes, + AllowedSchemaStringTypes, + AllowedSchemaTypes, + // Shippers + IShipper, +} from '@kbn/ebt/client'; diff --git a/packages/core/analytics/core-analytics-browser/src/types.ts b/packages/core/analytics/core-analytics-browser/src/types.ts index dbc35043613cd..779172acc9b9d 100644 --- a/packages/core/analytics/core-analytics-browser/src/types.ts +++ b/packages/core/analytics/core-analytics-browser/src/types.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { AnalyticsClient } from '@kbn/analytics-client'; +import type { AnalyticsClient } from '@kbn/ebt/client'; /** * Exposes the public APIs of the AnalyticsClient during the setup phase. diff --git a/packages/core/analytics/core-analytics-browser/tsconfig.json b/packages/core/analytics/core-analytics-browser/tsconfig.json index 9c3a721a57e23..83571abe4bcf4 100644 --- a/packages/core/analytics/core-analytics-browser/tsconfig.json +++ b/packages/core/analytics/core-analytics-browser/tsconfig.json @@ -11,7 +11,7 @@ "**/*.ts" ], "kbn_references": [ - "@kbn/analytics-client" + "@kbn/ebt", ], "exclude": [ "target/**/*", diff --git a/packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.mocks.ts b/packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.mocks.ts index a3bd814f1e32f..d759a9520cc16 100644 --- a/packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.mocks.ts +++ b/packages/core/analytics/core-analytics-server-internal/src/analytics_service.test.mocks.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { AnalyticsClient } from '@kbn/analytics-client'; +import { AnalyticsClient } from '@kbn/ebt/client'; import { Subject } from 'rxjs'; export const analyticsClientMock: jest.Mocked = { @@ -21,6 +21,6 @@ export const analyticsClientMock: jest.Mocked = { flush: jest.fn(), }; -jest.doMock('@kbn/analytics-client', () => ({ +jest.doMock('@kbn/ebt/client', () => ({ createAnalytics: () => analyticsClientMock, })); diff --git a/packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts b/packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts index 141f5b9970c0b..8e0ce76e08b96 100644 --- a/packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts +++ b/packages/core/analytics/core-analytics-server-internal/src/analytics_service.ts @@ -7,8 +7,8 @@ */ import { of } from 'rxjs'; -import type { AnalyticsClient } from '@kbn/analytics-client'; -import { createAnalytics } from '@kbn/analytics-client'; +import type { AnalyticsClient } from '@kbn/ebt/client'; +import { createAnalytics } from '@kbn/ebt/client'; import { registerPerformanceMetricEventType } from '@kbn/ebt-tools'; import type { CoreContext } from '@kbn/core-base-server-internal'; import type { diff --git a/packages/core/analytics/core-analytics-server-internal/tsconfig.json b/packages/core/analytics/core-analytics-server-internal/tsconfig.json index 56292065f7af6..57a0fa3e04362 100644 --- a/packages/core/analytics/core-analytics-server-internal/tsconfig.json +++ b/packages/core/analytics/core-analytics-server-internal/tsconfig.json @@ -11,12 +11,12 @@ "**/*.ts" ], "kbn_references": [ - "@kbn/analytics-client", "@kbn/ebt-tools", "@kbn/core-base-server-internal", "@kbn/core-analytics-server", "@kbn/config-mocks", "@kbn/core-base-server-mocks", + "@kbn/ebt", ], "exclude": [ "target/**/*", diff --git a/packages/core/analytics/core-analytics-server/index.ts b/packages/core/analytics/core-analytics-server/index.ts index c9a91ca3a8866..90341deb693a3 100644 --- a/packages/core/analytics/core-analytics-server/index.ts +++ b/packages/core/analytics/core-analytics-server/index.ts @@ -11,3 +11,41 @@ export type { AnalyticsServiceStart, AnalyticsServicePreboot, } from './src/contracts'; + +export type { + AnalyticsClient, + AnalyticsClientInitContext, + // Types for the registerShipper API + ShipperClassConstructor, + RegisterShipperOpts, + // Types for the optIn API + OptInConfig, + OptInConfigPerType, + ShipperName, + // Types for the registerContextProvider API + ContextProviderOpts, + ContextProviderName, + // Types for the registerEventType API + EventTypeOpts, + // Events + Event, + EventContext, + EventType, + TelemetryCounter, + TelemetryCounterType, + // Schema + RootSchema, + SchemaObject, + SchemaArray, + SchemaChildValue, + SchemaMeta, + SchemaValue, + SchemaMetaOptional, + PossibleSchemaTypes, + AllowedSchemaBooleanTypes, + AllowedSchemaNumberTypes, + AllowedSchemaStringTypes, + AllowedSchemaTypes, + // Shippers + IShipper, +} from '@kbn/ebt/client'; diff --git a/packages/core/analytics/core-analytics-server/src/contracts.ts b/packages/core/analytics/core-analytics-server/src/contracts.ts index 4879b988b1752..531c4ef6afd1f 100644 --- a/packages/core/analytics/core-analytics-server/src/contracts.ts +++ b/packages/core/analytics/core-analytics-server/src/contracts.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { AnalyticsClient } from '@kbn/analytics-client'; +import type { AnalyticsClient } from '@kbn/ebt/client'; /** * Exposes the public APIs of the AnalyticsClient during the preboot phase diff --git a/packages/core/analytics/core-analytics-server/tsconfig.json b/packages/core/analytics/core-analytics-server/tsconfig.json index 9c3a721a57e23..aa81d68980cd9 100644 --- a/packages/core/analytics/core-analytics-server/tsconfig.json +++ b/packages/core/analytics/core-analytics-server/tsconfig.json @@ -11,7 +11,7 @@ "**/*.ts" ], "kbn_references": [ - "@kbn/analytics-client" + "@kbn/ebt" ], "exclude": [ "target/**/*", diff --git a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx index 15ccb7c6a5531..d2922b9a96611 100644 --- a/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx +++ b/packages/core/chrome/core-chrome-browser-internal/src/chrome_service.tsx @@ -11,7 +11,6 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { BehaviorSubject, combineLatest, merge, type Observable, of, ReplaySubject } from 'rxjs'; import { mergeMap, map, takeUntil, filter } from 'rxjs'; import { parse } from 'url'; -import { EuiLink } from '@elastic/eui'; import useObservable from 'react-use/lib/useObservable'; import type { CoreContext } from '@kbn/core-base-browser-internal'; @@ -314,14 +313,6 @@ export class ChromeService { projectNavigation.setProjectName(projectName); }; - const isIE = () => { - const ua = window.navigator.userAgent; - const msie = ua.indexOf('MSIE '); // IE 10 or older - const trident = ua.indexOf('Trident/'); // IE 11 - - return msie > 0 || trident > 0; - }; - if (!this.params.browserSupportsCsp && injectedMetadata.getCspConfig().warnLegacyBrowsers) { notifications.toasts.addWarning({ title: mountReactNode( @@ -333,27 +324,6 @@ export class ChromeService { }); } - if (isIE()) { - notifications.toasts.addWarning({ - title: mountReactNode( - - - - ), - }} - /> - ), - }); - } - const getHeaderComponent = () => { const defaultChromeStyle = chromeStyleSubject$.getValue(); diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/client_config.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/client_config.test.ts index b6526cc201717..de0702b62aa66 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/client_config.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/client_config.test.ts @@ -7,6 +7,7 @@ */ import { duration } from 'moment'; +import { ByteSizeValue } from '@kbn/config-schema'; import type { ElasticsearchClientConfig } from '@kbn/core-elasticsearch-server'; import { parseClientOptions } from './client_config'; import { getDefaultHeaders } from './headers'; @@ -19,6 +20,7 @@ const createConfig = ( compression: false, maxSockets: Infinity, maxIdleSockets: 300, + maxResponseSize: undefined, idleSocketTimeout: duration(30, 'seconds'), sniffOnStart: false, sniffOnConnectionFault: false, @@ -152,6 +154,28 @@ describe('parseClientOptions', () => { }); }); + describe('`maxResponseSize` option', () => { + it('does not set the values on client options when undefined', () => { + const options = parseClientOptions( + createConfig({ maxResponseSize: undefined }), + false, + kibanaVersion + ); + expect(options.maxResponseSize).toBe(undefined); + expect(options.maxCompressedResponseSize).toBe(undefined); + }); + + it('sets the right values on client options when defined', () => { + const options = parseClientOptions( + createConfig({ maxResponseSize: ByteSizeValue.parse('2kb') }), + false, + kibanaVersion + ); + expect(options.maxResponseSize).toBe(2048); + expect(options.maxCompressedResponseSize).toBe(2048); + }); + }); + describe('`compression` option', () => { it('`compression` is true', () => { const options = parseClientOptions( diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/client_config.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/client_config.ts index 58660a9917348..7310cdc2da7cd 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/client_config.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/client_config.ts @@ -47,6 +47,11 @@ export function parseClientOptions( compression: config.compression, }; + if (config.maxResponseSize) { + clientOptions.maxResponseSize = config.maxResponseSize.getValueInBytes(); + clientOptions.maxCompressedResponseSize = config.maxResponseSize.getValueInBytes(); + } + if (config.pingTimeout != null) { clientOptions.pingTimeout = getDurationAsMs(config.pingTimeout); } diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts index fd7eeb2ad536b..7368fe006a29d 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts @@ -14,9 +14,17 @@ import { import type { Client } from '@elastic/elasticsearch'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { httpServerMock, httpServiceMock } from '@kbn/core-http-server-mocks'; -import type { ElasticsearchClientConfig } from '@kbn/core-elasticsearch-server'; +import type { + ElasticsearchClientConfig, + ElasticsearchClient, +} from '@kbn/core-elasticsearch-server'; import { ClusterClient } from './cluster_client'; -import { DEFAULT_HEADERS, getDefaultHeaders } from './headers'; +import { + DEFAULT_HEADERS, + ES_SECONDARY_AUTH_HEADER, + AUTHORIZATION_HEADER, + getDefaultHeaders, +} from './headers'; import { AgentManager } from './agent_manager'; import { duration } from 'moment'; @@ -51,6 +59,7 @@ describe('ClusterClient', () => { let internalClient: jest.Mocked; let scopedClient: jest.Mocked; let agentFactoryProvider: AgentManager; + let client: ElasticsearchClient; const mockTransport = { mockTransport: true }; @@ -125,7 +134,36 @@ describe('ClusterClient', () => { }); }); - describe('#asScoped', () => { + describe('#asScoped().asCurrentUser', () => { + it('lazily instantiate the client when first called', () => { + const clusterClient = new ClusterClient({ + config: createConfig(), + logger, + type: 'custom-type', + authHeaders, + agentFactoryProvider, + kibanaVersion, + }); + const request = httpServerMock.createKibanaRequest(); + + expect(scopedClient.child).not.toHaveBeenCalled(); + + const scopedClusterClient = clusterClient.asScoped(request); + + expect(scopedClient.child).not.toHaveBeenCalled(); + + // trigger client instantiation via getter + client = scopedClusterClient.asCurrentUser; + + expect(scopedClient.child).toHaveBeenCalledTimes(1); + expect(scopedClient.child).toHaveBeenCalledWith({ + headers: expect.any(Object), + Transport: mockTransport, + }); + + expect(client).toBe(scopedClient.child.mock.results[0].value); + }); + it('returns a scoped cluster client bound to the request', () => { const clusterClient = new ClusterClient({ config: createConfig(), @@ -139,6 +177,9 @@ describe('ClusterClient', () => { const scopedClusterClient = clusterClient.asScoped(request); + // trigger client instantiation via getter + client = scopedClusterClient.asCurrentUser; + expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith({ headers: expect.any(Object), @@ -164,7 +205,9 @@ describe('ClusterClient', () => { }); const request = httpServerMock.createKibanaRequest(); - clusterClient.asScoped(request); + const scopedClusterClient = clusterClient.asScoped(request); + // trigger client instantiation via getter + client = scopedClusterClient.asCurrentUser; expect(createTransportMock).toHaveBeenCalledTimes(1); expect(createTransportMock).toHaveBeenCalledWith({ @@ -173,7 +216,7 @@ describe('ClusterClient', () => { }); }); - it('calls `createTransportcreateInternalErrorHandler` lazily', () => { + it('calls `createInternalErrorHandler` lazily', () => { const getExecutionContext = jest.fn(); const getUnauthorizedErrorHandler = jest.fn(); const clusterClient = new ClusterClient({ @@ -188,7 +231,8 @@ describe('ClusterClient', () => { }); const request = httpServerMock.createKibanaRequest(); - clusterClient.asScoped(request); + const scopedClusterClient = clusterClient.asScoped(request); + client = scopedClusterClient.asCurrentUser; expect(createTransportMock).toHaveBeenCalledTimes(1); expect(createTransportMock).toHaveBeenCalledWith({ @@ -224,6 +268,10 @@ describe('ClusterClient', () => { const scopedClusterClient1 = clusterClient.asScoped(request); const scopedClusterClient2 = clusterClient.asScoped(request); + // trigger client instantiation via getter + client = scopedClusterClient1.asCurrentUser; + client = scopedClusterClient2.asCurrentUser; + expect(scopedClient.child).toHaveBeenCalledTimes(2); expect(scopedClusterClient1).not.toBe(scopedClusterClient2); @@ -251,7 +299,9 @@ describe('ClusterClient', () => { }, }); - clusterClient.asScoped(request); + const scopedClusterClient = clusterClient.asScoped(request); + // trigger client instantiation via getter + client = scopedClusterClient.asCurrentUser; expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith( @@ -280,7 +330,9 @@ describe('ClusterClient', () => { }); const request = httpServerMock.createKibanaRequest({}); - clusterClient.asScoped(request); + const scopedClusterClient = clusterClient.asScoped(request); + // trigger client instantiation via getter + client = scopedClusterClient.asCurrentUser; expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith( @@ -318,7 +370,9 @@ describe('ClusterClient', () => { }, }); - clusterClient.asScoped(request); + const scopedClusterClient = clusterClient.asScoped(request); + // trigger client instantiation via getter + client = scopedClusterClient.asCurrentUser; expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith( @@ -353,7 +407,9 @@ describe('ClusterClient', () => { }); const request = httpServerMock.createKibanaRequest({}); - clusterClient.asScoped(request); + const scopedClusterClient = clusterClient.asScoped(request); + // trigger client instantiation via getter + client = scopedClusterClient.asCurrentUser; expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith( @@ -384,7 +440,9 @@ describe('ClusterClient', () => { kibanaRequestState: { requestId: 'my-fake-id', requestUuid: 'ignore-this-id' }, }); - clusterClient.asScoped(request); + const scopedClusterClient = clusterClient.asScoped(request); + // trigger client instantiation via getter + client = scopedClusterClient.asCurrentUser; expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith( @@ -419,7 +477,9 @@ describe('ClusterClient', () => { }); const request = httpServerMock.createKibanaRequest({}); - clusterClient.asScoped(request); + const scopedClusterClient = clusterClient.asScoped(request); + // trigger client instantiation via getter + client = scopedClusterClient.asCurrentUser; expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith( @@ -456,7 +516,9 @@ describe('ClusterClient', () => { headers: { foo: 'request' }, }); - clusterClient.asScoped(request); + const scopedClusterClient = clusterClient.asScoped(request); + // trigger client instantiation via getter + client = scopedClusterClient.asCurrentUser; expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith( @@ -491,7 +553,9 @@ describe('ClusterClient', () => { }); const request = httpServerMock.createKibanaRequest(); - clusterClient.asScoped(request); + const scopedClusterClient = clusterClient.asScoped(request); + // trigger client instantiation via getter + client = scopedClusterClient.asCurrentUser; expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith( @@ -524,7 +588,9 @@ describe('ClusterClient', () => { headers: { [headerKey]: 'foo' }, }); - clusterClient.asScoped(request); + const scopedClusterClient = clusterClient.asScoped(request); + // trigger client instantiation via getter + client = scopedClusterClient.asCurrentUser; expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith( @@ -559,7 +625,9 @@ describe('ClusterClient', () => { kibanaRequestState: { requestId: 'from request', requestUuid: 'ignore-this-id' }, }); - clusterClient.asScoped(request); + const scopedClusterClient = clusterClient.asScoped(request); + // trigger client instantiation via getter + client = scopedClusterClient.asCurrentUser; expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith( @@ -593,7 +661,9 @@ describe('ClusterClient', () => { }, }; - clusterClient.asScoped(request); + const scopedClusterClient = clusterClient.asScoped(request); + // trigger client instantiation via getter + client = scopedClusterClient.asCurrentUser; expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith( @@ -626,7 +696,9 @@ describe('ClusterClient', () => { }, }; - clusterClient.asScoped(request); + const scopedClusterClient = clusterClient.asScoped(request); + // trigger client instantiation via getter + client = scopedClusterClient.asCurrentUser; expect(scopedClient.child).toHaveBeenCalledTimes(1); expect(scopedClient.child).toHaveBeenCalledWith( @@ -637,6 +709,265 @@ describe('ClusterClient', () => { }); }); + describe('#asScoped().asSecondaryAuthUser', () => { + it('lazily instantiate the client when first called', () => { + const clusterClient = new ClusterClient({ + config: createConfig(), + logger, + type: 'custom-type', + authHeaders, + agentFactoryProvider, + kibanaVersion, + }); + const request = httpServerMock.createKibanaRequest(); + + expect(internalClient.child).not.toHaveBeenCalled(); + + const scopedClusterClient = clusterClient.asScoped(request); + + expect(internalClient.child).not.toHaveBeenCalled(); + + // trigger client instantiation via getter + client = scopedClusterClient.asSecondaryAuthUser; + + expect(internalClient.child).toHaveBeenCalledTimes(1); + expect(internalClient.child).toHaveBeenCalledWith({ + headers: expect.any(Object), + }); + + expect(client).toBe(internalClient.child.mock.results[0].value); + }); + + it('returns a scoped cluster client bound to the request', () => { + const clusterClient = new ClusterClient({ + config: createConfig(), + logger, + type: 'custom-type', + authHeaders, + agentFactoryProvider, + kibanaVersion, + }); + const request = httpServerMock.createKibanaRequest(); + + const scopedClusterClient = clusterClient.asScoped(request); + + // trigger client instantiation via getter + client = scopedClusterClient.asSecondaryAuthUser; + + expect(scopedClusterClient.asSecondaryAuthUser).toBe( + internalClient.child.mock.results[0].value + ); + }); + + it('creates a scoped client using the proper `es-secondary-authorization` header', () => { + const config = createConfig({ + requestHeadersWhitelist: ['foo'], + }); + authHeaders.get.mockReturnValue({ + [AUTHORIZATION_HEADER]: 'yes', + }); + + const clusterClient = new ClusterClient({ + config, + logger, + type: 'custom-type', + authHeaders, + agentFactoryProvider, + kibanaVersion, + }); + const request = httpServerMock.createKibanaRequest({ + headers: { + foo: 'bar', + hello: 'dolly', + }, + }); + + const scopedClusterClient = clusterClient.asScoped(request); + // trigger client instantiation via getter + client = scopedClusterClient.asSecondaryAuthUser; + + expect(internalClient.child).toHaveBeenCalledTimes(1); + expect(internalClient.child).toHaveBeenCalledWith( + expect.objectContaining({ + headers: { ...defaultHeaders, [ES_SECONDARY_AUTH_HEADER]: 'yes' }, + }) + ); + expect(internalClient.child).toHaveBeenCalledWith( + expect.not.objectContaining({ + headers: { [AUTHORIZATION_HEADER]: 'yes' }, + }) + ); + }); + + it('throws when used with a request without authorization header', () => { + const config = createConfig({ + requestHeadersWhitelist: ['foo'], + }); + authHeaders.get.mockReturnValue({}); + + const clusterClient = new ClusterClient({ + config, + logger, + type: 'custom-type', + authHeaders, + agentFactoryProvider, + kibanaVersion, + }); + const request = httpServerMock.createKibanaRequest({ + headers: { + foo: 'bar', + hello: 'dolly', + }, + }); + + const scopedClusterClient = clusterClient.asScoped(request); + // trigger client instantiation via getter + expect(() => { + client = scopedClusterClient.asSecondaryAuthUser; + }).toThrowErrorMatchingInlineSnapshot( + `"asSecondaryAuthUser called from a client scoped to a request without 'authorization' header."` + ); + }); + + it('includes the `customHeaders` from the config without filtering them', () => { + const config = createConfig({ + customHeaders: { + foo: 'bar', + hello: 'dolly', + }, + requestHeadersWhitelist: ['authorization'], + }); + authHeaders.get.mockReturnValue({ + [AUTHORIZATION_HEADER]: 'foo', + }); + + const clusterClient = new ClusterClient({ + config, + logger, + type: 'custom-type', + authHeaders, + agentFactoryProvider, + kibanaVersion, + }); + const request = httpServerMock.createKibanaRequest({}); + + const scopedClusterClient = clusterClient.asScoped(request); + // trigger client instantiation via getter + client = scopedClusterClient.asSecondaryAuthUser; + + expect(internalClient.child).toHaveBeenCalledTimes(1); + expect(internalClient.child).toHaveBeenCalledWith( + expect.objectContaining({ + headers: { + ...defaultHeaders, + [ES_SECONDARY_AUTH_HEADER]: 'foo', + foo: 'bar', + hello: 'dolly', + }, + }) + ); + }); + + it('does not add the x-opaque-id header based on the request id', () => { + const config = createConfig(); + authHeaders.get.mockReturnValue({ + [AUTHORIZATION_HEADER]: 'foo', + }); + + const clusterClient = new ClusterClient({ + config, + logger, + type: 'custom-type', + authHeaders, + agentFactoryProvider, + kibanaVersion, + }); + const request = httpServerMock.createKibanaRequest({ + kibanaRequestState: { requestId: 'my-fake-id', requestUuid: 'ignore-this-id' }, + }); + + const scopedClusterClient = clusterClient.asScoped(request); + // trigger client instantiation via getter + client = scopedClusterClient.asSecondaryAuthUser; + + expect(internalClient.child).toHaveBeenCalledTimes(1); + expect(internalClient.child).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.not.objectContaining({ + 'x-opaque-id': expect.any(String), + }), + }) + ); + }); + + it('uses the authorization header from the request when using a `FakeRequest`', () => { + const config = createConfig({ + requestHeadersWhitelist: ['authorization', 'foo'], + }); + authHeaders.get.mockReturnValue({ + [AUTHORIZATION_HEADER]: 'will_not_be_used', + }); + + const clusterClient = new ClusterClient({ + config, + logger, + type: 'custom-type', + authHeaders, + agentFactoryProvider, + kibanaVersion, + }); + const request = { + headers: { + [AUTHORIZATION_HEADER]: 'yes', + hello: 'dolly', + }, + }; + + const scopedClusterClient = clusterClient.asScoped(request); + // trigger client instantiation via getter + client = scopedClusterClient.asSecondaryAuthUser; + + expect(internalClient.child).toHaveBeenCalledTimes(1); + expect(internalClient.child).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ [ES_SECONDARY_AUTH_HEADER]: 'yes' }), + }) + ); + }); + + it('throws when used with a `FakeRequest` without authorization header', () => { + const config = createConfig({ + requestHeadersWhitelist: ['authorization', 'foo'], + }); + authHeaders.get.mockReturnValue({ + [AUTHORIZATION_HEADER]: 'will_not_be_used', + }); + + const clusterClient = new ClusterClient({ + config, + logger, + type: 'custom-type', + authHeaders, + agentFactoryProvider, + kibanaVersion, + }); + const request = { + headers: { + hello: 'dolly', + }, + }; + + const scopedClusterClient = clusterClient.asScoped(request); + + expect(() => { + // trigger client instantiation via getter + client = scopedClusterClient.asSecondaryAuthUser; + }).toThrowErrorMatchingInlineSnapshot( + `"asSecondaryAuthUser called from a client scoped to a request without 'authorization' header."` + ); + }); + }); + describe('#close', () => { it('closes both underlying clients', async () => { const clusterClient = new ClusterClient({ diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.ts index 2a2f6ef1334a2..37e86f98a0a26 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.ts @@ -23,7 +23,7 @@ import type { import type { ElasticsearchClientConfig } from '@kbn/core-elasticsearch-server'; import { configureClient } from './configure_client'; import { ScopedClusterClient } from './scoped_cluster_client'; -import { getDefaultHeaders } from './headers'; +import { getDefaultHeaders, AUTHORIZATION_HEADER, ES_SECONDARY_AUTH_HEADER } from './headers'; import { createInternalErrorHandler, type InternalUnauthorizedErrorHandler, @@ -78,28 +78,43 @@ export class ClusterClient implements ICustomClusterClient { kibanaVersion, }); this.rootScopedClient = configureClient(config, { + scoped: true, logger, type, getExecutionContext, - scoped: true, agentFactoryProvider, kibanaVersion, }); } asScoped(request: ScopeableRequest) { - const scopedHeaders = this.getScopedHeaders(request); + const createScopedClient = () => { + const scopedHeaders = this.getScopedHeaders(request); - const transportClass = createTransport({ - getExecutionContext: this.getExecutionContext, - getUnauthorizedErrorHandler: this.createInternalErrorHandlerAccessor(request), - }); + const transportClass = createTransport({ + getExecutionContext: this.getExecutionContext, + getUnauthorizedErrorHandler: this.createInternalErrorHandlerAccessor(request), + }); + + return this.rootScopedClient.child({ + headers: scopedHeaders, + Transport: transportClass, + }); + }; + + const createSecondaryScopedClient = () => { + const secondaryAuthHeaders = this.getSecondaryAuthHeaders(request); + + return this.asInternalUser.child({ + headers: secondaryAuthHeaders, + }); + }; - const scopedClient = this.rootScopedClient.child({ - headers: scopedHeaders, - Transport: transportClass, + return new ScopedClusterClient({ + asInternalUser: this.asInternalUser, + asCurrentUserFactory: createScopedClient, + asSecondaryAuthUserFactory: createSecondaryScopedClient, }); - return new ScopedClusterClient(this.asInternalUser, scopedClient); } public async close() { @@ -129,7 +144,7 @@ export class ClusterClient implements ICustomClusterClient { if (isRealRequest(request)) { const requestHeaders = ensureRawRequest(request).headers ?? {}; const requestIdHeaders = isKibanaRequest(request) ? { 'x-opaque-id': request.id } : {}; - const authHeaders = this.authHeaders ? this.authHeaders.get(request) : {}; + const authHeaders = this.authHeaders?.get(request) ?? {}; scopedHeaders = { ...filterHeaders(requestHeaders, this.config.requestHeadersWhitelist), @@ -146,4 +161,25 @@ export class ClusterClient implements ICustomClusterClient { ...scopedHeaders, }; } + + private getSecondaryAuthHeaders(request: ScopeableRequest): Headers { + const headerSource = isRealRequest(request) + ? this.authHeaders?.get(request) ?? {} + : request.headers; + const authorizationHeader = Object.entries(headerSource).find(([key, value]) => { + return key.toLowerCase() === AUTHORIZATION_HEADER && value !== undefined; + }); + + if (!authorizationHeader) { + throw new Error( + `asSecondaryAuthUser called from a client scoped to a request without 'authorization' header.` + ); + } + + return { + ...getDefaultHeaders(this.kibanaVersion), + ...this.config.customHeaders, + [ES_SECONDARY_AUTH_HEADER]: authorizationHeader[1], + }; + } } diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/create_transport.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/create_transport.test.ts index ce54d8356f38b..3039119ae3c16 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/create_transport.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/create_transport.test.ts @@ -265,6 +265,79 @@ describe('createTransport', () => { ); }); }); + + describe('maxResponseSize options', () => { + it('does not set values when not provided in the options', async () => { + const transportClass = createTransportClass(); + const transport = new transportClass(baseConstructorParams); + const requestParams = { method: 'GET', path: '/' }; + + await transport.request(requestParams, {}); + + expect(transportRequestMock).toHaveBeenCalledTimes(1); + expect(transportRequestMock).toHaveBeenCalledWith( + expect.any(Object), + expect.not.objectContaining({ + maxResponseSize: expect.any(Number), + maxCompressedResponseSize: expect.any(Number), + }) + ); + }); + + it('uses `maxResponseSize` from the options when provided and when `maxCompressedResponseSize` is not', async () => { + const transportClass = createTransportClass(); + const transport = new transportClass(baseConstructorParams); + const requestParams = { method: 'GET', path: '/' }; + + await transport.request(requestParams, { maxResponseSize: 234 }); + + expect(transportRequestMock).toHaveBeenCalledTimes(1); + expect(transportRequestMock).toHaveBeenCalledWith( + expect.any(Object), + expect.objectContaining({ + maxResponseSize: 234, + maxCompressedResponseSize: 234, + }) + ); + }); + + it('uses `maxCompressedResponseSize` from the options when provided and when `maxResponseSize` is not', async () => { + const transportClass = createTransportClass(); + const transport = new transportClass(baseConstructorParams); + const requestParams = { method: 'GET', path: '/' }; + + await transport.request(requestParams, { maxCompressedResponseSize: 272 }); + + expect(transportRequestMock).toHaveBeenCalledTimes(1); + expect(transportRequestMock).toHaveBeenCalledWith( + expect.any(Object), + expect.objectContaining({ + maxResponseSize: 272, + maxCompressedResponseSize: 272, + }) + ); + }); + + it('uses individual values when both `maxResponseSize` and `maxCompressedResponseSize` are defined', async () => { + const transportClass = createTransportClass(); + const transport = new transportClass(baseConstructorParams); + const requestParams = { method: 'GET', path: '/' }; + + await transport.request(requestParams, { + maxResponseSize: 512, + maxCompressedResponseSize: 272, + }); + + expect(transportRequestMock).toHaveBeenCalledTimes(1); + expect(transportRequestMock).toHaveBeenCalledWith( + expect.any(Object), + expect.objectContaining({ + maxResponseSize: 512, + maxCompressedResponseSize: 272, + }) + ); + }); + }); }); describe('unauthorized error handler', () => { diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/create_transport.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/create_transport.ts index 07ed3833a7af2..ef643faa05c17 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/create_transport.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/create_transport.ts @@ -41,6 +41,20 @@ export const createTransport = ({ async request(params: TransportRequestParams, options?: TransportRequestOptions) { const opts: TransportRequestOptions = options ? { ...options } : {}; + // sync override of maxResponseSize and maxCompressedResponseSize + if (options) { + if ( + options.maxResponseSize !== undefined && + options.maxCompressedResponseSize === undefined + ) { + opts.maxCompressedResponseSize = options.maxResponseSize; + } else if ( + options.maxCompressedResponseSize !== undefined && + options.maxResponseSize === undefined + ) { + opts.maxResponseSize = options.maxCompressedResponseSize; + } + } const opaqueId = getExecutionContext(); if (opaqueId && !opts.opaqueId) { // rewrites headers['x-opaque-id'] if it presents diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/headers.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/headers.ts index cf6755414afd7..1571f18f0788e 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/headers.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/headers.ts @@ -23,6 +23,16 @@ export const PRODUCT_ORIGIN_HEADER = 'x-elastic-product-origin'; */ export const USER_AGENT_HEADER = 'user-agent'; +/** + * @internal + */ +export const AUTHORIZATION_HEADER = 'authorization'; + +/** + * @internal + */ +export const ES_SECONDARY_AUTH_HEADER = 'es-secondary-authorization'; + /** * @internal */ diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/log_query_and_deprecation.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/log_query_and_deprecation.test.ts index edb3ab90e6bc6..e974951da8aef 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/log_query_and_deprecation.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/log_query_and_deprecation.test.ts @@ -1051,4 +1051,41 @@ describe('instrumentQueryAndDeprecationLogger', () => { }); }); }); + + describe('requests aborted due to maximum response size exceeded errors', () => { + const requestAbortedErrorMessage = `The content length (9000) is bigger than the maximum allowed buffer (42)`; + + it('logs warning when the client emits a RequestAbortedError error due to excessive response length ', () => { + instrumentEsQueryAndDeprecationLogger({ + logger, + client, + type: 'test type', + apisToRedactInLogs: [], + }); + + client.diagnostic.emit( + 'response', + new errors.RequestAbortedError(requestAbortedErrorMessage), + null + ); + + expect(loggingSystemMock.collect(logger).warn[0][0]).toMatchInlineSnapshot( + `"Request was aborted: The content length (9000) is bigger than the maximum allowed buffer (42)"` + ); + }); + + it('does not log warning for other type of errors', () => { + instrumentEsQueryAndDeprecationLogger({ + logger, + client, + type: 'test type', + apisToRedactInLogs: [], + }); + + const response = createApiResponse({ body: {} }); + client.diagnostic.emit('response', new errors.TimeoutError('message', response), response); + + expect(loggingSystemMock.collect(logger).warn).toMatchInlineSnapshot(`Array []`); + }); + }); }); diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/log_query_and_deprecation.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/log_query_and_deprecation.ts index 8d7f0f0a5c2dd..753b17a6a7aa3 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/log_query_and_deprecation.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/log_query_and_deprecation.ts @@ -12,7 +12,7 @@ import { stringify } from 'querystring'; import { errors, DiagnosticResult, RequestBody, Client } from '@elastic/elasticsearch'; import numeral from '@elastic/numeral'; import type { Logger } from '@kbn/logging'; -import type { ElasticsearchErrorDetails } from '@kbn/es-errors'; +import { isMaximumResponseSizeExceededError, type ElasticsearchErrorDetails } from '@kbn/es-errors'; import type { ElasticsearchApiToRedactInLogs } from '@kbn/core-elasticsearch-server'; import { getEcsResponseLog } from './get_ecs_response_log'; @@ -171,6 +171,16 @@ function getQueryMessage( } } +function getResponseSizeExceededErrorMessage(error: errors.RequestAbortedError): string { + if (error.meta) { + const params = error.meta.meta.request.params; + return `Request against ${params.method} ${params.path} was aborted: ${error.message}`; + } else { + // in theory meta is always populated for such errors, but better safe than sorry + return `Request was aborted: ${error.message}`; + } +} + export const instrumentEsQueryAndDeprecationLogger = ({ logger, client, @@ -184,6 +194,7 @@ export const instrumentEsQueryAndDeprecationLogger = ({ }) => { const queryLogger = logger.get('query', type); const deprecationLogger = logger.get('deprecation'); + const warningLogger = logger.get('warnings'); // elasticsearch.warnings client.diagnostic.on('response', (error, event) => { // we could check this once and not subscribe to response events if both are disabled, @@ -191,6 +202,10 @@ export const instrumentEsQueryAndDeprecationLogger = ({ const logQuery = queryLogger.isLevelEnabled('debug'); const logDeprecation = deprecationLogger.isLevelEnabled('debug'); + if (error && isMaximumResponseSizeExceededError(error)) { + warningLogger.warn(getResponseSizeExceededErrorMessage(error)); + } + if (event && (logQuery || logDeprecation)) { const bytes = getContentLength(event.headers); const queryMsg = getQueryMessage(bytes, error, event, apisToRedactInLogs); diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/scoped_cluster_client.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/scoped_cluster_client.test.ts index 75d2e9d9e6361..05d7fcedeaf74 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/scoped_cluster_client.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/scoped_cluster_client.test.ts @@ -9,24 +9,82 @@ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { ScopedClusterClient } from './scoped_cluster_client'; -const createEsClient = () => ({} as unknown as ElasticsearchClient); +const createEsClient = () => Symbol('client') as unknown as ElasticsearchClient; describe('ScopedClusterClient', () => { it('uses the internal client passed in the constructor', () => { const internalClient = createEsClient(); const scopedClient = createEsClient(); + const secondaryAuthClient = createEsClient(); - const scopedClusterClient = new ScopedClusterClient(internalClient, scopedClient); + const scopedClusterClient = new ScopedClusterClient({ + asInternalUser: internalClient, + asCurrentUserFactory: () => scopedClient, + asSecondaryAuthUserFactory: () => secondaryAuthClient, + }); expect(scopedClusterClient.asInternalUser).toBe(internalClient); }); - it('uses the scoped client passed in the constructor', () => { + it('uses the primary-auth scoped client factory passed in the constructor', () => { const internalClient = createEsClient(); const scopedClient = createEsClient(); + const secondaryAuthClient = createEsClient(); - const scopedClusterClient = new ScopedClusterClient(internalClient, scopedClient); + const scopedClusterClient = new ScopedClusterClient({ + asInternalUser: internalClient, + asCurrentUserFactory: () => scopedClient, + asSecondaryAuthUserFactory: () => secondaryAuthClient, + }); expect(scopedClusterClient.asCurrentUser).toBe(scopedClient); }); + + it('uses the secondary-auth scoped client factory passed in the constructor', () => { + const internalClient = createEsClient(); + const scopedClient = createEsClient(); + const secondaryAuthClient = createEsClient(); + + const scopedClusterClient = new ScopedClusterClient({ + asInternalUser: internalClient, + asCurrentUserFactory: () => scopedClient, + asSecondaryAuthUserFactory: () => secondaryAuthClient, + }); + + expect(scopedClusterClient.asSecondaryAuthUser).toBe(secondaryAuthClient); + }); + + it('returns the same instance when calling `asCurrentUser` multiple times', () => { + const internalClient = createEsClient(); + const scopedClient = createEsClient(); + const secondaryAuthClient = createEsClient(); + + const scopedClusterClient = new ScopedClusterClient({ + asInternalUser: internalClient, + asCurrentUserFactory: () => scopedClient, + asSecondaryAuthUserFactory: () => secondaryAuthClient, + }); + + const userClient1 = scopedClusterClient.asCurrentUser; + const userClient2 = scopedClusterClient.asCurrentUser; + + expect(userClient1).toBe(userClient2); + }); + + it('returns the same instance when calling `asSecondaryAuthUser` multiple times', () => { + const internalClient = createEsClient(); + const scopedClient = createEsClient(); + const secondaryAuthClient = createEsClient(); + + const scopedClusterClient = new ScopedClusterClient({ + asInternalUser: internalClient, + asCurrentUserFactory: () => scopedClient, + asSecondaryAuthUserFactory: () => secondaryAuthClient, + }); + + const secondaryAuth1 = scopedClusterClient.asSecondaryAuthUser; + const secondaryAuth2 = scopedClusterClient.asSecondaryAuthUser; + + expect(secondaryAuth1).toBe(secondaryAuth2); + }); }); diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/scoped_cluster_client.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/scoped_cluster_client.ts index dedccacf43202..1b5376e560160 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/scoped_cluster_client.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/scoped_cluster_client.ts @@ -10,8 +10,39 @@ import type { ElasticsearchClient, IScopedClusterClient } from '@kbn/core-elasti /** @internal **/ export class ScopedClusterClient implements IScopedClusterClient { - constructor( - public readonly asInternalUser: ElasticsearchClient, - public readonly asCurrentUser: ElasticsearchClient - ) {} + public readonly asInternalUser; + + readonly #asCurrentUserFactory: () => ElasticsearchClient; + readonly #asSecondaryAuthUserFactory: () => ElasticsearchClient; + + #asCurrentUserClient?: ElasticsearchClient; + #asSecondaryAuthUserClient?: ElasticsearchClient; + + constructor({ + asInternalUser, + asCurrentUserFactory, + asSecondaryAuthUserFactory, + }: { + asInternalUser: ElasticsearchClient; + asCurrentUserFactory: () => ElasticsearchClient; + asSecondaryAuthUserFactory: () => ElasticsearchClient; + }) { + this.asInternalUser = asInternalUser; + this.#asCurrentUserFactory = asCurrentUserFactory; + this.#asSecondaryAuthUserFactory = asSecondaryAuthUserFactory; + } + + public get asCurrentUser() { + if (this.#asCurrentUserClient === undefined) { + this.#asCurrentUserClient = this.#asCurrentUserFactory(); + } + return this.#asCurrentUserClient; + } + + public get asSecondaryAuthUser() { + if (this.#asSecondaryAuthUserClient === undefined) { + this.#asSecondaryAuthUserClient = this.#asSecondaryAuthUserFactory(); + } + return this.#asSecondaryAuthUserClient; + } } diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/tsconfig.json b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/tsconfig.json index 2288caa55dea8..de4169d034e65 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/tsconfig.json +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/tsconfig.json @@ -22,6 +22,7 @@ "@kbn/core-logging-server-mocks", "@kbn/core-http-server-mocks", "@kbn/core-metrics-server", + "@kbn/config-schema", ], "exclude": [ "target/**/*", diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/mocks.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/mocks.ts index be3fe6f839dce..be421bba02a24 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/mocks.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/mocks.ts @@ -190,12 +190,14 @@ const createClientMock = (res?: Promise): ElasticsearchClientMock => export interface ScopedClusterClientMock { asInternalUser: ElasticsearchClientMock; asCurrentUser: ElasticsearchClientMock; + asSecondaryAuthUser: ElasticsearchClientMock; } const createScopedClusterClientMock = () => { const mock: ScopedClusterClientMock = { asInternalUser: createClientMock(), asCurrentUser: createClientMock(), + asSecondaryAuthUser: createClientMock(), }; return mock; diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.test.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.test.ts index 58745a0e85d09..cafa50e65d91c 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.test.ts @@ -42,6 +42,7 @@ test('set correct defaults', () => { "idleSocketTimeout": "PT1M", "ignoreVersionMismatch": false, "maxIdleSockets": 256, + "maxResponseSize": undefined, "maxSockets": 800, "password": undefined, "pingTimeout": "PT30S", @@ -127,6 +128,20 @@ describe('#maxSockets', () => { }); }); +describe('#maxResponseSize', () => { + test('accepts `false` value', () => { + const configValue = new ElasticsearchConfig(config.schema.validate({ maxResponseSize: false })); + expect(configValue.maxResponseSize).toBe(undefined); + }); + + test('accepts bytesize value', () => { + const configValue = new ElasticsearchConfig( + config.schema.validate({ maxResponseSize: '200b' }) + ); + expect(configValue.maxResponseSize!.getValueInBytes()).toBe(200); + }); +}); + test('#requestHeadersWhitelist accepts both string and array of strings', () => { let configValue = new ElasticsearchConfig( config.schema.validate({ requestHeadersWhitelist: 'token' }) diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.ts index 1f6d75b2f30e1..010bc3ac1c796 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_config.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import { schema, TypeOf, offeringBasedSchema } from '@kbn/config-schema'; +import { readFileSync } from 'fs'; +import { Duration } from 'moment'; import { readPkcs12Keystore, readPkcs12Truststore } from '@kbn/crypto'; import { i18n } from '@kbn/i18n'; -import { Duration } from 'moment'; -import { readFileSync } from 'fs'; +import { schema, offeringBasedSchema, ByteSizeValue, type TypeOf } from '@kbn/config-schema'; import type { ServiceConfigDescriptor } from '@kbn/core-base-server-internal'; import type { ConfigDeprecationProvider } from '@kbn/config'; import type { @@ -42,6 +42,9 @@ export const configSchema = schema.object({ }), maxSockets: schema.number({ defaultValue: 800, min: 1 }), maxIdleSockets: schema.number({ defaultValue: 256, min: 1 }), + maxResponseSize: schema.oneOf([schema.literal(false), schema.byteSize()], { + defaultValue: false, + }), idleSocketTimeout: schema.duration({ defaultValue: '60s' }), compression: schema.boolean({ defaultValue: false }), username: schema.maybe( @@ -332,6 +335,12 @@ export class ElasticsearchConfig implements IElasticsearchConfig { */ public readonly maxIdleSockets: number; + /** + * The maximum allowed response size (both compressed and uncompressed). + * When defined, responses with a size higher than the set limit will be aborted with an error. + */ + public readonly maxResponseSize?: ByteSizeValue; + /** * The timeout for idle sockets kept open between Kibana and Elasticsearch. If the socket is idle for longer than this timeout, it will be closed. */ @@ -455,6 +464,8 @@ export class ElasticsearchConfig implements IElasticsearchConfig { this.customHeaders = rawConfig.customHeaders; this.maxSockets = rawConfig.maxSockets; this.maxIdleSockets = rawConfig.maxIdleSockets; + this.maxResponseSize = + rawConfig.maxResponseSize !== false ? rawConfig.maxResponseSize : undefined; this.idleSocketTimeout = rawConfig.idleSocketTimeout; this.compression = rawConfig.compression; this.skipStartupConnectionCheck = rawConfig.skipStartupConnectionCheck; diff --git a/packages/core/elasticsearch/core-elasticsearch-server/src/client/client_config.ts b/packages/core/elasticsearch/core-elasticsearch-server/src/client/client_config.ts index 6ef638525c580..e7d119a90622d 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server/src/client/client_config.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server/src/client/client_config.ts @@ -7,6 +7,7 @@ */ import type { Duration } from 'moment'; +import type { ByteSizeValue } from '@kbn/config-schema'; /** * Definition of an API that should redact the requested body in the logs @@ -35,6 +36,7 @@ export interface ElasticsearchClientConfig { requestHeadersWhitelist: string[]; maxSockets: number; maxIdleSockets: number; + maxResponseSize?: ByteSizeValue; idleSocketTimeout: Duration; compression: boolean; sniffOnStart: boolean; diff --git a/packages/core/elasticsearch/core-elasticsearch-server/src/client/scoped_cluster_client.ts b/packages/core/elasticsearch/core-elasticsearch-server/src/client/scoped_cluster_client.ts index 4e3f7b6924e7c..7589bc9e6267c 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server/src/client/scoped_cluster_client.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server/src/client/scoped_cluster_client.ts @@ -22,6 +22,17 @@ export interface IScopedClusterClient { * on behalf of the internal Kibana user. */ readonly asInternalUser: ElasticsearchClient; + + /** + * A {@link ElasticsearchClient | client} to be used to query the elasticsearch cluster + * with the internal Kibana user as primary auth and the current user as secondary auth + * (using the `es-secondary-authorization` header). + * + * Note that only a subset of Elasticsearch APIs support secondary authentication, and that only those endpoints + * should be called with this client. + */ + readonly asSecondaryAuthUser: ElasticsearchClient; + /** * A {@link ElasticsearchClient | client} to be used to query the elasticsearch cluster * on behalf of the user that initiated the request to the Kibana server. diff --git a/packages/core/elasticsearch/core-elasticsearch-server/tsconfig.json b/packages/core/elasticsearch/core-elasticsearch-server/tsconfig.json index 88e18c12e41e0..c3320c12cff9b 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server/tsconfig.json +++ b/packages/core/elasticsearch/core-elasticsearch-server/tsconfig.json @@ -13,7 +13,8 @@ "kbn_references": [ "@kbn/utility-types", "@kbn/es-errors", - "@kbn/core-http-server" + "@kbn/core-http-server", + "@kbn/config-schema" ], "exclude": [ "target/**/*", diff --git a/packages/core/http/core-http-router-server-internal/src/response.test.ts b/packages/core/http/core-http-router-server-internal/src/response.test.ts index 84f22f076528c..b80bcbc305c69 100644 --- a/packages/core/http/core-http-router-server-internal/src/response.test.ts +++ b/packages/core/http/core-http-router-server-internal/src/response.test.ts @@ -6,13 +6,61 @@ * Side Public License, v 1. */ -import { fileResponseFactory } from './response'; +import { IKibanaResponse } from '@kbn/core-http-server'; +import { kibanaResponseFactory } from './response'; + +describe('kibanaResponseFactory', () => { + describe('status codes', () => { + const tests: Array<[string, number, IKibanaResponse]> = [ + ['ok', 200, kibanaResponseFactory.ok()], + ['created', 201, kibanaResponseFactory.created()], + ['accepted', 202, kibanaResponseFactory.accepted()], + ['noContent', 204, kibanaResponseFactory.noContent()], + ['multiStatus', 207, kibanaResponseFactory.multiStatus()], + ['redirected', 302, kibanaResponseFactory.redirected({})], + ['notModified', 304, kibanaResponseFactory.notModified({})], + ['badRequest', 400, kibanaResponseFactory.badRequest()], + ['unauthorized', 401, kibanaResponseFactory.unauthorized()], + ['forbidden', 403, kibanaResponseFactory.forbidden()], + ['notFound', 404, kibanaResponseFactory.notFound()], + ['conflict', 409, kibanaResponseFactory.conflict()], + ['unprocessableContent', 422, kibanaResponseFactory.unprocessableContent()], + [ + 'file', + 200, + kibanaResponseFactory.file({ + filename: 'test.txt', + body: 'content', + }), + ], + [ + 'custom:205', + 205, + kibanaResponseFactory.custom({ + statusCode: 205, + }), + ], + [ + 'customError:505', + 505, + kibanaResponseFactory.customError({ + statusCode: 505, + }), + ], + ]; + + it.each(tests)( + '.%s produces a response with status code %i', + (_name, expectedStatusCode, response) => { + expect(response.status).toEqual(expectedStatusCode); + } + ); + }); -describe('fileResponseFactory', () => { describe('res.file', () => { it('returns a kibana response with attachment', () => { const body = Buffer.from('Attachment content'); - const result = fileResponseFactory.file({ + const result = kibanaResponseFactory.file({ body, filename: 'myfile.test', fileContentSize: 30, @@ -31,7 +79,7 @@ describe('fileResponseFactory', () => { it('converts string body content to buffer in response', () => { const body = 'I am a string'; - const result = fileResponseFactory.file({ body, filename: 'myfile.test' }); + const result = kibanaResponseFactory.file({ body, filename: 'myfile.test' }); expect(result.payload?.toString()).toBe(body); }); @@ -39,7 +87,7 @@ describe('fileResponseFactory', () => { const isMultiByte = (str: string) => [...str].some((c) => (c.codePointAt(0) || 0) > 255); const multuByteCharacters = '日本語ダッシュボード.pdf'; - const result = fileResponseFactory.file({ + const result = kibanaResponseFactory.file({ body: 'content', filename: multuByteCharacters, }); @@ -72,7 +120,7 @@ describe('fileResponseFactory', () => { }; const filename = 'myfile.test'; const fileContent = 'content'; - const result = fileResponseFactory.file({ + const result = kibanaResponseFactory.file({ body: fileContent, filename, headers: { ...extraHeaders, ...overrideHeaders }, @@ -88,15 +136,15 @@ describe('fileResponseFactory', () => { describe('content-type', () => { it('default mime type octet-stream', () => { - const result = fileResponseFactory.file({ body: 'content', filename: 'myfile.unknown' }); + const result = kibanaResponseFactory.file({ body: 'content', filename: 'myfile.unknown' }); expect(result.options.headers).toHaveProperty('content-type', 'application/octet-stream'); }); it('gets mime type from filename', () => { - const result = fileResponseFactory.file({ body: 'content', filename: 'myfile.mp4' }); + const result = kibanaResponseFactory.file({ body: 'content', filename: 'myfile.mp4' }); expect(result.options.headers).toHaveProperty('content-type', 'video/mp4'); }); it('gets accepts contentType override', () => { - const result = fileResponseFactory.file({ + const result = kibanaResponseFactory.file({ body: 'content', filename: 'myfile.mp4', fileContentType: 'custom', diff --git a/packages/core/http/core-http-router-server-internal/src/response.ts b/packages/core/http/core-http-router-server-internal/src/response.ts index 14e2bd10c9c14..b79fd7d812b2b 100644 --- a/packages/core/http/core-http-router-server-internal/src/response.ts +++ b/packages/core/http/core-http-router-server-internal/src/response.ts @@ -40,8 +40,11 @@ export class KibanaResponse const successResponseFactory: KibanaSuccessResponseFactory = { ok: (options: HttpResponseOptions = {}) => new KibanaResponse(200, options.body, options), + created: (options: HttpResponseOptions = {}) => new KibanaResponse(201, options.body, options), accepted: (options: HttpResponseOptions = {}) => new KibanaResponse(202, options.body, options), noContent: (options: HttpResponseOptions = {}) => new KibanaResponse(204, undefined, options), + multiStatus: (options: HttpResponseOptions = {}) => + new KibanaResponse(207, options.body, options), }; const redirectionResponseFactory: KibanaRedirectionResponseFactory = { @@ -63,6 +66,8 @@ const errorResponseFactory: KibanaErrorResponseFactory = { new KibanaResponse(404, options.body || 'Not Found', options), conflict: (options: ErrorHttpResponseOptions = {}) => new KibanaResponse(409, options.body || 'Conflict', options), + unprocessableContent: (options: ErrorHttpResponseOptions = {}) => + new KibanaResponse(422, options.body || 'Unprocessable Content', options), customError: (options: CustomHttpResponseOptions) => { if (!options || !options.statusCode) { throw new Error( diff --git a/packages/core/http/core-http-router-server-internal/src/router.test.ts b/packages/core/http/core-http-router-server-internal/src/router.test.ts index cab011a5f0f72..c9d9c28a88823 100644 --- a/packages/core/http/core-http-router-server-internal/src/router.test.ts +++ b/packages/core/http/core-http-router-server-internal/src/router.test.ts @@ -43,7 +43,15 @@ describe('Router', () => { const router = new Router('', logger, enhanceWithContext, routerOptions); const validation = schema.object({ foo: schema.string() }); router.post( - { path: '/', validate: { body: validation, query: validation, params: validation } }, + { + path: '/', + validate: { body: validation, query: validation, params: validation }, + options: { + deprecated: true, + summary: 'post test summary', + description: 'post test description', + }, + }, (context, req, res) => res.ok() ); const routes = router.getRoutes(); @@ -55,6 +63,11 @@ describe('Router', () => { path: '/', validationSchemas: { body: validation, query: validation, params: validation }, isVersioned: false, + options: { + deprecated: true, + summary: 'post test summary', + description: 'post test description', + }, }); }); diff --git a/packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_router.test.ts b/packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_router.test.ts index 8b5a7d23fa0ee..c29a3edd967af 100644 --- a/packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_router.test.ts +++ b/packages/core/http/core-http-router-server-internal/src/versioned_router/core_versioned_router.test.ts @@ -32,8 +32,13 @@ describe('Versioned router', () => { it('provides the expected metadata', () => { const versionedRouter = CoreVersionedRouter.from({ router }); - versionedRouter.get({ path: '/test/{id}', access: 'internal' }); - versionedRouter.post({ path: '/test', access: 'internal' }); + versionedRouter.get({ path: '/test/{id}', access: 'internal', deprecated: true }); + versionedRouter.post({ + path: '/test', + access: 'internal', + summary: 'Post test', + description: 'Post test description', + }); versionedRouter.delete({ path: '/test', access: 'internal' }); expect(versionedRouter.getRoutes()).toMatchInlineSnapshot(` Array [ @@ -42,6 +47,7 @@ describe('Versioned router', () => { "method": "get", "options": Object { "access": "internal", + "deprecated": true, }, "path": "/test/{id}", }, @@ -50,6 +56,8 @@ describe('Versioned router', () => { "method": "post", "options": Object { "access": "internal", + "description": "Post test description", + "summary": "Post test", }, "path": "/test", }, diff --git a/packages/core/http/core-http-router-server-mocks/src/router.mock.ts b/packages/core/http/core-http-router-server-mocks/src/router.mock.ts index d5904699b5813..dfe95000014e7 100644 --- a/packages/core/http/core-http-router-server-mocks/src/router.mock.ts +++ b/packages/core/http/core-http-router-server-mocks/src/router.mock.ts @@ -119,8 +119,10 @@ function createKibanaRequestMock

({ const createResponseFactoryMock = (): jest.Mocked => ({ ok: jest.fn(), + created: jest.fn(), accepted: jest.fn(), noContent: jest.fn(), + multiStatus: jest.fn(), notModified: jest.fn(), custom: jest.fn(), redirected: jest.fn(), @@ -129,6 +131,7 @@ const createResponseFactoryMock = (): jest.Mocked => ({ forbidden: jest.fn(), notFound: jest.fn(), conflict: jest.fn(), + unprocessableContent: jest.fn(), customError: jest.fn(), file: jest.fn(), }); diff --git a/packages/core/http/core-http-server-mocks/src/http_server.mocks.ts b/packages/core/http/core-http-server-mocks/src/http_server.mocks.ts index 8e7d5543e35eb..0854e43ebbee4 100644 --- a/packages/core/http/core-http-server-mocks/src/http_server.mocks.ts +++ b/packages/core/http/core-http-server-mocks/src/http_server.mocks.ts @@ -22,6 +22,7 @@ const createLifecycleResponseFactoryMock = (): jest.Mocked ): IKibanaResponse; + /** + * The request has succeeded and has led to the creation of a resource. + * Status code: `201`. + * @param options - {@link HttpResponseOptions} configures HTTP response body & headers. + */ + created( + options?: HttpResponseOptions + ): IKibanaResponse; + /** * The request has been accepted for processing. * Status code: `202`. @@ -46,6 +55,13 @@ export interface KibanaSuccessResponseFactory { * @param options - {@link HttpResponseOptions} configures HTTP response body & headers. */ noContent(options?: HttpResponseOptions): IKibanaResponse; + + /** + * The server indicates that there might be a mixture of responses (some tasks succeeded, some failed). + * Status code: `207`. + * @param options - {@link HttpResponseOptions} configures HTTP response body & headers. + */ + multiStatus(options?: HttpResponseOptions): IKibanaResponse; } /** @@ -112,6 +128,13 @@ export interface KibanaErrorResponseFactory { */ conflict(options?: ErrorHttpResponseOptions): IKibanaResponse; + /** + * The server understands the content type of the request entity, and the syntax of the request entity is correct, but it was unable to process the contained instructions. + * Status code: `422`. + * @param options - {@link HttpResponseOptions} configures HTTP response headers, error message and other error details to pass to the client + */ + unprocessableContent(options?: ErrorHttpResponseOptions): IKibanaResponse; + /** * Creates an error response with defined status code and payload. * @param options - {@link CustomHttpResponseOptions} configures HTTP response headers, error message and other error details to pass to the client diff --git a/packages/core/http/core-http-server/src/router/route.ts b/packages/core/http/core-http-server/src/router/route.ts index 983c495a1b541..aa5b8598e0d0b 100644 --- a/packages/core/http/core-http-server/src/router/route.ts +++ b/packages/core/http/core-http-server/src/router/route.ts @@ -198,6 +198,14 @@ export interface RouteConfigOptions { * ``` */ description?: string; + + /** + * Setting this to `true` declares this route to be deprecated. Consumers SHOULD + * refrain from usage of this route. + * + * @remarks This will be surfaced in OAS documentation. + */ + deprecated?: boolean; } /** 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 af3173691415f..5c4eb459196cc 100644 --- a/packages/core/http/core-http-server/src/versioning/types.ts +++ b/packages/core/http/core-http-server/src/versioning/types.ts @@ -32,7 +32,7 @@ export type VersionedRouteConfig = Omit< RouteConfig, 'validate' | 'options' > & { - options?: Omit, 'access' | 'description'>; + options?: Omit, 'access' | 'description' | 'deprecated'>; /** See {@link RouteConfigOptions['access']} */ access: Exclude['access'], undefined>; /** @@ -82,6 +82,14 @@ export type VersionedRouteConfig = Omit< * ``` */ description?: string; + + /** + * Declares this operation to be deprecated. Consumers SHOULD refrain from usage + * of this route. This will be surfaced in OAS documentation. + * + * @default false + */ + deprecated?: boolean; }; /** diff --git a/packages/core/i18n/core-i18n-browser-internal/src/__snapshots__/i18n_service.test.tsx.snap b/packages/core/i18n/core-i18n-browser-internal/src/__snapshots__/i18n_service.test.tsx.snap index fd8ba14e035cf..67fe5f2a46221 100644 --- a/packages/core/i18n/core-i18n-browser-internal/src/__snapshots__/i18n_service.test.tsx.snap +++ b/packages/core/i18n/core-i18n-browser-internal/src/__snapshots__/i18n_service.test.tsx.snap @@ -12,6 +12,7 @@ exports[`#start() returns \`Context\` component 1`] = ` "euiAutoRefresh.autoRefreshLabel": "Auto refresh", "euiAutoRefresh.buttonLabelOff": "Auto refresh is off", "euiAutoRefresh.buttonLabelOn": [Function], + "euiBasicTable.deselectRows": "Deselect rows", "euiBasicTable.noItemsMessage": "No items found", "euiBasicTable.selectAllRows": "Select all rows", "euiBasicTable.selectThisRow": [Function], diff --git a/packages/core/i18n/core-i18n-browser-internal/src/i18n_eui_mapping.tsx b/packages/core/i18n/core-i18n-browser-internal/src/i18n_eui_mapping.tsx index 4d6fcf64ab081..151a94bd3ce22 100644 --- a/packages/core/i18n/core-i18n-browser-internal/src/i18n_eui_mapping.tsx +++ b/packages/core/i18n/core-i18n-browser-internal/src/i18n_eui_mapping.tsx @@ -85,6 +85,9 @@ export const getEuiContextMapping = (): EuiTokensObject => { 'euiBasicTable.noItemsMessage': i18n.translate('core.euiBasicTable.noItemsMessage', { defaultMessage: 'No items found', }), + 'euiBasicTable.deselectRows': i18n.translate('core.euiBasicTable.deselectRows', { + defaultMessage: 'Deselect rows', + }), 'euiBottomBar.customScreenReaderAnnouncement': ({ landmarkHeading }: EuiValues) => i18n.translate('core.euiBottomBar.customScreenReaderAnnouncement', { defaultMessage: diff --git a/packages/core/notifications/core-notifications-browser-internal/src/toasts/telemetry/event_types.ts b/packages/core/notifications/core-notifications-browser-internal/src/toasts/telemetry/event_types.ts index 7c4b08cd8647d..35e012fe2f625 100644 --- a/packages/core/notifications/core-notifications-browser-internal/src/toasts/telemetry/event_types.ts +++ b/packages/core/notifications/core-notifications-browser-internal/src/toasts/telemetry/event_types.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { type RootSchema, type EventTypeOpts } from '@kbn/analytics-client'; +import { type RootSchema, type EventTypeOpts } from '@kbn/ebt/client'; export enum EventMetric { TOAST_DISMISSED = 'global_toast_list_toast_dismissed', diff --git a/packages/core/notifications/core-notifications-browser-internal/tsconfig.json b/packages/core/notifications/core-notifications-browser-internal/tsconfig.json index 0250d4b80488b..88855bd5bd4bb 100644 --- a/packages/core/notifications/core-notifications-browser-internal/tsconfig.json +++ b/packages/core/notifications/core-notifications-browser-internal/tsconfig.json @@ -31,7 +31,7 @@ "@kbn/react-kibana-context-render", "@kbn/core-analytics-browser", "@kbn/core-analytics-browser-mocks", - "@kbn/analytics-client", + "@kbn/ebt", ], "exclude": [ "target/**/*", diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts index 49ae09a16e7bb..cdb8d29dd725a 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts @@ -24,7 +24,6 @@ export { calculateExcludeFilters, checkForUnknownDocs, waitForIndexStatus, - initAction, cloneIndex, waitForTask, updateAndPickupMappings, @@ -40,6 +39,7 @@ export { fetchIndices, waitForReindexTask, waitForPickupUpdatedMappingsTask, + checkClusterRoutingAllocationEnabled, } from './src/actions'; export type { OpenPitResponse, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/README.md b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/README.md index f30b09fca6212..ebeea457bb622 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/README.md +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/README.md @@ -1,95 +1,134 @@ + - [Introduction](#introduction) - [Algorithm steps](#algorithm-steps) - [INIT](#init) - [Next action](#next-action) - [New control state](#new-control-state) - - [CREATE_NEW_TARGET](#create_new_target) + - [CREATE\_NEW\_TARGET](#create_new_target) - [Next action](#next-action-1) - [New control state](#new-control-state-1) - - [LEGACY_SET_WRITE_BLOCK](#legacy_set_write_block) + - [LEGACY\_CHECK\_CLUSTER\_ROUTING\_ALLOCATION](#legacy_check_cluster_routing_allocation) - [Next action](#next-action-2) - [New control state](#new-control-state-2) - - [LEGACY_CREATE_REINDEX_TARGET](#legacy_create_reindex_target) + - [LEGACY\_SET\_WRITE\_BLOCK](#legacy_set_write_block) - [Next action](#next-action-3) - [New control state](#new-control-state-3) - - [LEGACY_REINDEX](#legacy_reindex) + - [LEGACY\_CREATE\_REINDEX\_TARGET](#legacy_create_reindex_target) - [Next action](#next-action-4) - [New control state](#new-control-state-4) - - [LEGACY_REINDEX_WAIT_FOR_TASK](#legacy_reindex_wait_for_task) + - [LEGACY\_REINDEX](#legacy_reindex) - [Next action](#next-action-5) - [New control state](#new-control-state-5) - - [LEGACY_DELETE](#legacy_delete) + - [LEGACY\_REINDEX\_WAIT\_FOR\_TASK](#legacy_reindex_wait_for_task) - [Next action](#next-action-6) - [New control state](#new-control-state-6) - - [WAIT_FOR_MIGRATION_COMPLETION](#wait_for_migration_completion) + - [LEGACY\_DELETE](#legacy_delete) - [Next action](#next-action-7) - [New control state](#new-control-state-7) - - [WAIT_FOR_YELLOW_SOURCE](#wait_for_yellow_source) + - [WAIT\_FOR\_MIGRATION\_COMPLETION](#wait_for_migration_completion) - [Next action](#next-action-8) - [New control state](#new-control-state-8) - - [UPDATE_SOURCE_MAPPINGS_PROPERTIES](#update_source_mappings_properties) + - [WAIT\_FOR\_YELLOW\_SOURCE](#wait_for_yellow_source) - [Next action](#next-action-9) - [New control state](#new-control-state-9) - - [SET_SOURCE_WRITE_BLOCK](#set_source_write_block) + - [UPDATE\_SOURCE\_MAPPINGS\_PROPERTIES](#update_source_mappings_properties) - [Next action](#next-action-10) - [New control state](#new-control-state-10) - - [CREATE_REINDEX_TEMP](#create_reindex_temp) + - [CLEANUP\_UNKNOWN\_AND\_EXCLUDED](#cleanup_unknown_and_excluded) - [Next action](#next-action-11) - [New control state](#new-control-state-11) - - [REINDEX_SOURCE_TO_TEMP_OPEN_PIT](#reindex_source_to_temp_open_pit) + - [CLEANUP\_UNKNOWN\_AND\_EXCLUDED\_WAIT\_FOR\_TASK](#cleanup_unknown_and_excluded_wait_for_task) - [Next action](#next-action-12) - [New control state](#new-control-state-12) - - [REINDEX_SOURCE_TO_TEMP_READ](#reindex_source_to_temp_read) + - [PREPARE\_COMPATIBLE\_MIGRATION](#prepare_compatible_migration) - [Next action](#next-action-13) - [New control state](#new-control-state-13) - - [REINDEX_SOURCE_TO_TEMP_TRANSFORM](#reindex_source_to_temp_transform) + - [REFRESH\_SOURCE](#refresh_source) - [Next action](#next-action-14) - [New control state](#new-control-state-14) - - [REINDEX_SOURCE_TO_TEMP_INDEX_BULK](#reindex_source_to_temp_index_bulk) + - [CHECK\_CLUSTER\_ROUTING\_ALLOCATION](#check_cluster_routing_allocation) - [Next action](#next-action-15) - [New control state](#new-control-state-15) - - [REINDEX_SOURCE_TO_TEMP_CLOSE_PIT](#reindex_source_to_temp_close_pit) + - [CHECK\_UNKNOWN\_DOCUMENTS](#check_unknown_documents) - [Next action](#next-action-16) - - [New control state](#new-control-state-16) - - [SET_TEMP_WRITE_BLOCK](#set_temp_write_block) + - [SET\_SOURCE\_WRITE\_BLOCK](#set_source_write_block) - [Next action](#next-action-17) - - [New control state](#new-control-state-17) - - [CLONE_TEMP_TO_TARGET](#clone_temp_to_target) + - [New control state](#new-control-state-16) + - [CREATE\_REINDEX\_TEMP](#create_reindex_temp) - [Next action](#next-action-18) - - [New control state](#new-control-state-18) - - [OUTDATED_DOCUMENTS_SEARCH](#outdated_documents_search) + - [New control state](#new-control-state-17) + - [REINDEX\_SOURCE\_TO\_TEMP\_OPEN\_PIT](#reindex_source_to_temp_open_pit) - [Next action](#next-action-19) - - [New control state](#new-control-state-19) - - [OUTDATED_DOCUMENTS_TRANSFORM](#outdated_documents_transform) + - [New control state](#new-control-state-18) + - [REINDEX\_SOURCE\_TO\_TEMP\_READ](#reindex_source_to_temp_read) - [Next action](#next-action-20) - - [New control state](#new-control-state-20) - - [CHECK_TARGET_MAPPINGS](#check_target_mappings) + - [New control state](#new-control-state-19) + - [REINDEX\_SOURCE\_TO\_TEMP\_TRANSFORM](#reindex_source_to_temp_transform) - [Next action](#next-action-21) - - [New control state](#new-control-state-21) - - [UPDATE_TARGET_MAPPINGS_PROPERTIES](#update_target_mappings_properties) + - [New control state](#new-control-state-20) + - [REINDEX\_SOURCE\_TO\_TEMP\_INDEX\_BULK](#reindex_source_to_temp_index_bulk) - [Next action](#next-action-22) - - [New control state](#new-control-state-22) - - [UPDATE_TARGET_MAPPINGS_PROPERTIES_WAIT_FOR_TASK](#update_target_mappings_properties_wait_for_task) + - [New control state](#new-control-state-21) + - [REINDEX\_SOURCE\_TO\_TEMP\_CLOSE\_PIT](#reindex_source_to_temp_close_pit) - [Next action](#next-action-23) - - [New control state](#new-control-state-23) - - [CHECK_VERSION_INDEX_READY_ACTIONS](#check_version_index_ready_actions) + - [New control state](#new-control-state-22) + - [SET\_TEMP\_WRITE\_BLOCK](#set_temp_write_block) - [Next action](#next-action-24) - - [New control state](#new-control-state-24) - - [MARK_VERSION_INDEX_READY](#mark_version_index_ready) + - [New control state](#new-control-state-23) + - [CLONE\_TEMP\_TO\_TARGET](#clone_temp_to_target) - [Next action](#next-action-25) - - [New control state](#new-control-state-25) - - [MARK_VERSION_INDEX_READY_CONFLICT](#mark_version_index_ready_conflict) + - [New control state](#new-control-state-24) + - [REFRESH\_TARGET](#refresh_target) - [Next action](#next-action-26) + - [New control state](#new-control-state-25) + - [OUTDATED\_DOCUMENTS\_SEARCH\_OPEN\_PIT](#outdated_documents_search_open_pit) + - [Next action](#next-action-27) - [New control state](#new-control-state-26) + - [OUTDATED\_DOCUMENTS\_SEARCH\_READ](#outdated_documents_search_read) + - [Next action](#next-action-28) + - [New control state](#new-control-state-27) + - [OUTDATED\_DOCUMENTS\_TRANSFORM](#outdated_documents_transform) + - [Next action](#next-action-29) + - [New control state](#new-control-state-28) + - [TRANSFORMED\_DOCUMENTS\_BULK\_INDEX](#transformed_documents_bulk_index) + - [Next action](#next-action-30) + - [New control state](#new-control-state-29) + - [OUTDATED\_DOCUMENTS\_SEARCH\_CLOSE\_PIT](#outdated_documents_search_close_pit) + - [Next action](#next-action-31) + - [New control state](#new-control-state-30) + - [OUTDATED\_DOCUMENTS\_REFRESH](#outdated_documents_refresh) + - [Next action](#next-action-32) + - [New control state](#new-control-state-31) + - [CHECK\_TARGET\_MAPPINGS](#check_target_mappings) + - [Next action](#next-action-33) + - [New control state](#new-control-state-32) + - [UPDATE\_TARGET\_MAPPINGS\_PROPERTIES](#update_target_mappings_properties) + - [Next action](#next-action-34) + - [New control state](#new-control-state-33) + - [UPDATE\_TARGET\_MAPPINGS\_PROPERTIES\_WAIT\_FOR\_TASK](#update_target_mappings_properties_wait_for_task) + - [Next action](#next-action-35) + - [New control state](#new-control-state-34) + - [CHECK\_VERSION\_INDEX\_READY\_ACTIONS](#check_version_index_ready_actions) + - [Next action](#next-action-36) + - [New control state](#new-control-state-35) + - [MARK\_VERSION\_INDEX\_READY](#mark_version_index_ready) + - [Next action](#next-action-37) + - [New control state](#new-control-state-36) + - [MARK\_VERSION\_INDEX\_READY\_CONFLICT](#mark_version_index_ready_conflict) + - [Next action](#next-action-38) + - [New control state](#new-control-state-37) + - [FATAL](#fatal) + - [DONE](#done) - [Manual QA Test Plan](#manual-qa-test-plan) - [1. Legacy pre-migration](#1-legacy-pre-migration) - [2. Plugins enabled/disabled](#2-plugins-enableddisabled) - - [Test scenario 1 (enable a plugin after migration):](#test-scenario-1-enable-a-plugin-after-migration) - - [Test scenario 2 (disable a plugin after migration):](#test-scenario-2-disable-a-plugin-after-migration) - - [Test scenario 3 (multiple instances, enable a plugin after migration):](#test-scenario-3-multiple-instances-enable-a-plugin-after-migration) - - [Test scenario 4 (multiple instances, mixed plugin enabled configs):](#test-scenario-4-multiple-instances-mixed-plugin-enabled-configs) + - [Test scenario 1 (enable a plugin after migration)](#test-scenario-1-enable-a-plugin-after-migration) + - [Test scenario 2 (disable a plugin after migration)](#test-scenario-2-disable-a-plugin-after-migration) + - [Test scenario 3 (multiple instances, enable a plugin after migration)](#test-scenario-3-multiple-instances-enable-a-plugin-after-migration) + - [Test scenario 4 (multiple instances, mixed plugin enabled configs)](#test-scenario-4-multiple-instances-mixed-plugin-enabled-configs) # Introduction + In the past, the risk of downtime caused by Kibana's saved object upgrade migrations have discouraged users from adopting the latest features. v2 migrations aims to solve this problem by minimizing the operational impact on @@ -108,17 +147,18 @@ migrations RFC](https://github.com/elastic/kibana/blob/main/rfcs/text/0013_saved_object_migrations.md). # Algorithm steps + The design goals for the algorithm was to keep downtime below 10 minutes for 100k saved objects while guaranteeing no data loss and keeping steps as simple and explicit as possible. -The algorithm is implemented as a state-action machine based on https://www.microsoft.com/en-us/research/uploads/prod/2016/12/Computation-and-State-Machines.pdf +The algorithm is implemented as a *state-action machine*, based on The state-action machine defines it's behaviour in steps. Each step is a transition from a control state s_i to the contral state s_i+1 caused by an action a_i. -``` +```text s_i -> a_i -> s_i+1 s_i+1 -> a_i+1 -> s_i+2 ``` @@ -129,7 +169,8 @@ response to determine the next state to transition to as defined by the function `model(state, response)`. We can then loosely define a step as: -``` + +```javascript s_i+1 = model(s_i, await next(s_i)()) ``` @@ -138,8 +179,9 @@ terminates such as in the DONE and FATAL control states. What follows is a list of all control states. For each control state the following is described: - - _next action_: the next action triggered by the current control state - - _new control state_: based on the action response, the possible new control states that the machine will transition to + +- *next action*: the next action triggered by the current control state +- *new control state*: based on the action response, the possible new control states that the machine will transition to Since the algorithm runs once for each saved object index the steps below always reference a single saved object index `.kibana`. When Kibana starts up, @@ -147,14 +189,8 @@ all the steps are also repeated for the `.kibana_task_manager` index but this is left out of the description for brevity. ## INIT -### Next action -`initAction` - -Check that replica allocation is enabled from cluster settings (`cluster.routing.allocation.enabled`). Migrations will fail when replica allocation is disabled during the bulk index operation that waits for all active shards. Migrations wait for all active shards to ensure that saved objects are replicated to protect against data loss. -The Elasticsearch documentation mentions switching off replica allocation when restoring a cluster and this is a setting that might be overlooked when a restore is done. Migrations will fail early if replica allocation is incorrectly set to avoid adding a write block to the old index before running into a failure later. - -If replica allocation is set to 'all', the migration continues to fetch the saved object indices: +### Next action `fetchIndices` @@ -163,53 +199,77 @@ and determine whether we’re migrating from a legacy index or a v1 migrations index. ### New control state -1. Two conditions have to be met before migrations begin: - 1. The Elasticsearch shard allocation cluster setting `cluster.routing.allocation.enable` needs to be unset or set to 'all'. When set to 'primaries', 'new_primaries' or 'none', the migration will timeout when waiting for index green status before bulk indexing because the replica cannot be allocated. - As per the Elasticsearch docs https://www.elastic.co/guide/en/elasticsearch/reference/8.2/restart-cluster.html#restart-cluster-rolling when Cloud performs a rolling restart such as during an upgrade, it will temporarily disable shard allocation. Kibana therefore keeps retrying the INIT step to wait for shard allocation to be enabled again. +1. If `.kibana` is pointing to more than one index. - The check only considers persistent and transient settings and does not take static configuration in `elasticsearch.yml` into account since there are no known use cases for doing so. If `cluster.routing.allocation.enable` is configured in `elaticsearch.yml` and not set to the default of 'all', the migration will timeout. Static settings can only be returned from the `nodes/info` API. - → `INIT` + → [FATAL](#fatal) - 2. If `.kibana` is pointing to more than one index. - → `FATAL` - - 3. If `.kibana` is pointing to an index that belongs to a later version of +2. If `.kibana` is pointing to an index that belongs to a later version of Kibana .e.g. a 7.11.0 instance found the `.kibana` alias pointing to `.kibana_7.12.0_001` fail the migration - → `FATAL` -2. If `waitForMigrations` was set we're running on a background-tasks node and + → [FATAL](#fatal) + +3. If `waitForMigrations` was set we're running on a background-tasks node and we should not participate in the migration but instead wait for the ui node(s) to complete the migration. - → `WAIT_FOR_MIGRATION_COMPLETION` -3. If the `.kibana` alias exists we’re migrating from either a v1 or v2 index + → [WAIT_FOR_MIGRATION_COMPLETION](#wait_for_migration_completion) + +4. If the `.kibana` alias exists we’re migrating from either a v1 or v2 index and the migration source index is the index the `.kibana` alias points to. - → `WAIT_FOR_YELLOW_SOURCE` -4. If `.kibana` is a concrete index, we’re migrating from a legacy index - → `LEGACY_SET_WRITE_BLOCK` + → [WAIT_FOR_YELLOW_SOURCE](#wait_for_yellow_source) + +5. If `.kibana` is a concrete index, we’re migrating from a legacy index + + → [LEGACY_SET_WRITE_BLOCK](#legacy_set_write_block) -5. If there are no `.kibana` indices, this is a fresh deployment. Initialize a +6. If there are no `.kibana` indices, this is a fresh deployment. Initialize a new saved objects index - → `CREATE_NEW_TARGET` + + → [CREATE_NEW_TARGET](#create_new_target) ## CREATE_NEW_TARGET + ### Next action + `createIndex` Create the target index. This operation is idempotent, if the index already exist, we wait until its status turns green ### New control state + 1. If the action succeeds - → `MARK_VERSION_INDEX_READY` + + → [MARK_VERSION_INDEX_READY](#mark_version_index_ready) + 2. If the action fails with a `index_not_green_timeout` - → `CREATE_NEW_TARGET` + → [CREATE_NEW_TARGET](#create_new_target) + +## LEGACY_CHECK_CLUSTER_ROUTING_ALLOCATION + +### Next action + +`checkClusterRoutingAllocationCompatible` + +Same description and behavior as [CHECK\_CLUSTER\_ROUTING\_ALLOCATION](#check_cluster_routing_allocation), for legacy flow. + +### New control state + +1. If `cluster.routing.allocation.enabled` has a compatible value. + + → [LEGACY_SET_WRITE_BLOCK](#legacy_set_write_block) + +2. If it has a value that will not allow creating new *saved object* indices. + + → [LEGACY_CHECK_CLUSTER_ROUTING_ALLOCATION](#legacy_check_cluster_routing_allocation) (retry) ## LEGACY_SET_WRITE_BLOCK + ### Next action + `setWriteBlock` Set a write block on the legacy index to prevent any older Kibana instances @@ -217,118 +277,312 @@ from writing to the index while the migration is in progress which could cause lost acknowledged writes. This is the first of a series of `LEGACY_*` control states that will: - - reindex the concrete legacy `.kibana` index into a `.kibana_pre6.5.0_001` index - - delete the concrete `.kibana` _index_ so that we're able to create a `.kibana` _alias_ + +- reindex the concrete legacy `.kibana` index into a `.kibana_pre6.5.0_001` index +- delete the concrete `.kibana` *index* so that we're able to create a `.kibana` *alias* ### New control state + 1. If the write block was successfully added - → `LEGACY_CREATE_REINDEX_TARGET` + + → [LEGACY_CREATE_REINDEX_TARGET](#legacy_create_reindex_target) + 2. If the write block failed because the index doesn't exist, it means another instance already completed the legacy pre-migration. Proceed to the next step. - → `LEGACY_CREATE_REINDEX_TARGET` + + → [LEGACY_CREATE_REINDEX_TARGET](#legacy_create_reindex_target) ## LEGACY_CREATE_REINDEX_TARGET + ### Next action + `createIndex` Create a new `.kibana_pre6.5.0_001` index into which we can reindex the legacy index. (Since the task manager index was converted from a data index into a saved objects index in 7.4 it will be reindexed into `.kibana_pre7.4.0_001`) + ### New control state + 1. If the index creation succeeds - → `LEGACY_REINDEX` + + → [LEGACY_REINDEX](#legacy_reindex) + 2. If the index creation task failed with a `index_not_green_timeout` - → `LEGACY_REINDEX_WAIT_FOR_TASK` + + → [LEGACY_REINDEX_WAIT_FOR_TASK](#legacy_reindex_wait_for_task) + ## LEGACY_REINDEX + ### Next action + `reindex` Let Elasticsearch reindex the legacy index into `.kibana_pre6.5.0_001`. (For the task manager index we specify a `preMigrationScript` to convert the original task manager documents into valid saved objects) + ### New control state - → `LEGACY_REINDEX_WAIT_FOR_TASK` +→ [LEGACY_REINDEX_WAIT_FOR_TASK](#legacy_reindex_wait_for_task) ## LEGACY_REINDEX_WAIT_FOR_TASK + ### Next action + `waitForReindexTask` Wait for up to 60s for the reindex task to complete. + ### New control state + 1. If the reindex task completed - → `LEGACY_DELETE` + + → [LEGACY_DELETE](#legacy_delete) + 2. If the reindex task failed with a `target_index_had_write_block` or `index_not_found_exception` another instance already completed this step - → `LEGACY_DELETE` + + → [LEGACY_DELETE](#legacy_delete) + 3. If the reindex task is still in progress - → `LEGACY_REINDEX_WAIT_FOR_TASK` + + → [LEGACY_REINDEX_WAIT_FOR_TASK](#legacy_reindex_wait_for_task) ## LEGACY_DELETE + ### Next action + `updateAliases` Use the updateAliases API to atomically remove the legacy index and create a new `.kibana` alias that points to `.kibana_pre6.5.0_001`. + ### New control state + 1. If the action succeeds - → `SET_SOURCE_WRITE_BLOCK` + + → [SET_SOURCE_WRITE_BLOCK](#set_source_write_block) + 2. If the action fails with `remove_index_not_a_concrete_index` or `index_not_found_exception` another instance has already completed this step. - → `SET_SOURCE_WRITE_BLOCK` + + → [SET_SOURCE_WRITE_BLOCK](#set_source_write_block) ## WAIT_FOR_MIGRATION_COMPLETION + ### Next action + `fetchIndices` + ### New control state + 1. If the ui node finished the migration - → `DONE` + + → [DONE](#done) + 2. Otherwise wait 2s and check again - → `WAIT_FOR_MIGRATION_COMPLETION` + + → [WAIT_FOR_MIGRATION_COMPLETION](#wait_for_migration_completion) ## WAIT_FOR_YELLOW_SOURCE + ### Next action + `waitForIndexStatus` (status='yellow') Wait for the source index to become yellow. This means the index's primary has been allocated and is ready for reading/searching. On a multi node cluster the replicas for this index might not be ready yet but since we're never writing to the source index it does not matter. ### New control state + 1. If the action succeeds - → `UPDATE_SOURCE_MAPPINGS_PROPERTIES` + + → [UPDATE_SOURCE_MAPPINGS_PROPERTIES](#update_source_mappings_properties) + 2. If the action fails with a `index_not_yellow_timeout` - → `WAIT_FOR_YELLOW_SOURCE` + + → [WAIT_FOR_YELLOW_SOURCE](#wait_for_yellow_source) ## UPDATE_SOURCE_MAPPINGS_PROPERTIES + ### Next action + `updateSourceMappingsProperties` This action checks for source mappings changes. And if there are some, it tries to patch the mappings. + - If there were no changes or the patch was successful, that reports either the changes are compatible or the source is already up to date, depending on the version migration completion state. Either way, it does not require a follow-up reindexing. - If the patch is failed and the version migration is incomplete, it reports an incompatible state that requires reindexing. - If the patch is failed and the version migration is complete, it reports an error as it means an incompatible mappings change in an already migrated environment. The latter usually happens when a new plugin is enabled that brings some incompatible changes or when there are incompatible changes in the development environment. ### New control state + 1. If the mappings are updated and the migration is already completed. - → `OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT` + + → [OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT](#outdated_documents_search_open_pit) + 2. If the mappings are updated and the migration is still in progress. - → `CLEANUP_UNKNOWN_AND_EXCLUDED` + + → [CLEANUP_UNKNOWN_AND_EXCLUDED](#cleanup_unknown_and_excluded) + 3. If the mappings are not updated due to incompatible changes and the migration is still in progress. - → `CHECK_UNKNOWN_DOCUMENTS` + + → [CHECK_CLUSTER_ROUTING_ALLOCATION](#check_cluster_routing_allocation) + 4. If the mappings are not updated due to incompatible changes and the migration is already completed. - → `FATAL` + + → [FATAL](#fatal) + +## CLEANUP_UNKNOWN_AND_EXCLUDED + +### Next action + +`cleanupUnknownAndExcluded` + +This action searches for and deletes *saved objects* which are of unknown or excluded type. + +- Saved objects become unknown when their type is no longer registered in the *typeRegistry*. This can happen when disabling plugins. +- Also, saved objects can be excluded from upgrade with the `excludeOnUpgrade` flag in their type definition. + +In order to allow Kibana to discard unknown saved objects, users must set the [migrations.discardUnknownObjects](https://www.elastic.co/guide/en/kibana/current/resolve-migrations-failures.html#unknown-saved-object-types) flag. + +### New control state + +1. If unknown docs are found and Kibana is not configured to ignore them. + + → [FATAL](#fatal) + +2. If the delete operation is launched and we can wait for it. + + → [CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK](#cleanup_unknown_and_excluded_wait_for_task) + +## CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK + +### Next action + +`waitForDeleteByQueryTask` + +The cleanup task on the previous step is launched asynchronously, tracked by a specific `taskId`. On this step, we actively wait for it to finish, and we do that with a large timeout. + +### New control state + +1. If the task finishes before the timeout. + + → [PREPARE_COMPATIBLE_MIGRATION](#prepare_compatible_migration) + +2. If we hit the timeout whilst waiting for the task to be completed, but we still have some retry attempts left. + + → [CLEANUP_UNKNOWN_AND_EXCLUDED_WAIT_FOR_TASK](#cleanup_unknown_and_excluded_wait_for_task) + +3. If some errors occur whilst cleaning up, there could be other instances performing the cleanup in parallel, deleting the documents that we intend to delete. In that scenario, we will launch the operation again. + + → [CLEANUP_UNKNOWN_AND_EXCLUDED](#cleanup_unknown_and_excluded) + +4. If we hit the timeout and we run out of retries. + + → [FATAL](#fatal) + +## PREPARE_COMPATIBLE_MIGRATION + +### Next action + +`updateAliases` + +At this point, we have successfully updated the index mappings. We are performing a *compatible migration*, aka updating *saved objects* in place on the existing index. In order to prevent other Kibana instances from writing documents whilst we update them, we remove the previous version alias. We also set set the current version alias, which will cause other instances' migrators to directly perform an *up-to-date migration*. + +### New control state + +1. If the aliases are updated successfully and some documents have been deleted on the previous step. + + → [REFRESH_SOURCE](#refresh_source) + +2. If the aliases are updated successfully and we did not delete any documents on the previous step. + + → [OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT](#outdated_documents_search_open_pit) + +3. When unexpected errors occur when updating the aliases. + + → [FATAL](#fatal) + +## REFRESH_SOURCE + +### Next action + +`refreshIndex` + +We are performing a *compatible migration*, and we discarded some unknown and excluded saved object documents. We must refresh the index so that subsequent queries no longer find these removed documents. + +### New control state + +1. If the index is refreshed successfully. + + → [OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT](#outdated_documents_search_open_pit) + +2. When unexpected errors occur during the refresh. + + → [FATAL](#fatal) + +## CHECK_CLUSTER_ROUTING_ALLOCATION + +### Next action + +`checkClusterRoutingAllocationEnabled` + +Check that replica allocation is enabled from cluster settings (`cluster.routing.allocation.enabled`). Migrations will fail when replica allocation is disabled during the bulk index operation that waits for all active shards. Migrations wait for all active shards to ensure that saved objects are replicated to protect against data loss. + +The Elasticsearch documentation mentions switching off replica allocation when restoring a cluster and this is a setting that might be overlooked when a restore is done. Migrations will fail early if replica allocation is incorrectly set to avoid adding a write block to the old index before running into a failure later. + +If replica allocation is set to 'all', the migration continues to fetch the saved object indices. + +### New control state + +The Elasticsearch shard allocation cluster setting `cluster.routing.allocation.enable` needs to be unset or set to 'all'. When set to 'primaries', 'new_primaries' or 'none', the migration will timeout when waiting for index green status before bulk indexing because the replica cannot be allocated. + +As per the Elasticsearch [docs](https://www.elastic.co/guide/en/elasticsearch/reference/8.2/restart-cluster.html#restart-cluster-rolling), when Cloud performs a rolling restart such as during an upgrade, it will temporarily disable shard allocation. Kibana therefore keeps retrying the INIT step to wait for shard allocation to be enabled again. + +The check only considers persistent and transient settings and does not take static configuration in `elasticsearch.yml` into account since there are no known use cases for doing so. If `cluster.routing.allocation.enable` is configured in `elaticsearch.yml` and not set to the default of 'all', the migration will timeout. Static settings can only be returned from the `nodes/info` API. + +1. If `cluster.routing.allocation.enabled` has a compatible value. + + → [CHECK_UNKNOWN_DOCUMENTS](#check_unknown_documents) + +2. If it has a value that will not allow creating new *saved object* indices. + + → [CHECK_CLUSTER_ROUTING_ALLOCATION](#check_cluster_routing_allocation) (retry) + +## CHECK_UNKNOWN_DOCUMENTS + +Saved objects are unknown when their type is not registered in the *typeRegistry*. This can happen when disabling plugins, or when deprecated plugins are removed during a major upgrade. + +During a *reindex migration*, these documents can be discarded if Kibana is configured with the [migrations.discardUnknownObjects](https://www.elastic.co/guide/en/kibana/current/resolve-migrations-failures.html#unknown-saved-object-types) flag. + +### Next action + +1. If no unknown documents are found, or Kibana is configured to discard them. + + → [SET_SOURCE_WRITE_BLOCK](#set_source_write_block) + +2. If some unknown documents are found and Kibana is NOT configured to discard them. + + → [FATAL](#fatal) ## SET_SOURCE_WRITE_BLOCK + ### Next action + `setWriteBlock` Set a write block on the source index to prevent any older Kibana instances from writing to the index while the migration is in progress which could cause lost acknowledged writes. ### New control state - → `CREATE_REINDEX_TEMP` + +→ [CREATE_REINDEX_TEMP](#create_reindex_temp) ## CREATE_REINDEX_TEMP + ### Next action + `createIndex` This operation is idempotent, if the index already exist, we wait until its status turns green. @@ -337,33 +591,49 @@ This operation is idempotent, if the index already exist, we wait until its stat - (Since we never query the temporary index we can potentially disable refresh to speed up indexing performance. Profile to see if gains justify complexity) ### New control state + 1. If the action succeeds - → `REINDEX_SOURCE_TO_TEMP_OPEN_PIT` + + → [REINDEX_SOURCE_TO_TEMP_OPEN_PIT](#reindex_source_to_temp_open_pit) + 2. If the action fails with a `index_not_green_timeout` - → `CREATE_REINDEX_TEMP` + + → [CREATE_REINDEX_TEMP](#create_reindex_temp) ## REINDEX_SOURCE_TO_TEMP_OPEN_PIT + ### Next action + `openPIT` Open a PIT. Since there is a write block on the source index there is basically no overhead to keeping the PIT so we can lean towards a larger `keep_alive` value like 10 minutes. + ### New control state - → `REINDEX_SOURCE_TO_TEMP_READ` + +→ [REINDEX_SOURCE_TO_TEMP_READ](#reindex_source_to_temp_read) ## REINDEX_SOURCE_TO_TEMP_READ + ### Next action + `readNextBatchOfSourceDocuments` Read the next batch of outdated documents from the source index by using search after with our PIT. ### New control state + 1. If the batch contained > 0 documents - → `REINDEX_SOURCE_TO_TEMP_TRANSFORM` + + → [REINDEX_SOURCE_TO_TEMP_TRANSFORM](#reindex_source_to_temp_transform) + 2. If there are no more documents returned - → `REINDEX_SOURCE_TO_TEMP_CLOSE_PIT` + + → [REINDEX_SOURCE_TO_TEMP_CLOSE_PIT](#reindex_source_to_temp_close_pit) ## REINDEX_SOURCE_TO_TEMP_TRANSFORM + ### Next action + `transformRawDocs` Transform the current batch of documents @@ -372,10 +642,15 @@ In order to support sharing saved objects to multiple spaces in 8.0, the transforms will also regenerate document `_id`'s. To ensure that this step remains idempotent, the new `_id` is deterministically generated using UUIDv5 ensuring that each Kibana instance generates the same new `_id` for the same document. + ### New control state - → `REINDEX_SOURCE_TO_TEMP_INDEX_BULK` + +→ [REINDEX_SOURCE_TO_TEMP_INDEX_BULK](#reindex_source_to_temp_index_bulk) + ## REINDEX_SOURCE_TO_TEMP_INDEX_BULK + ### Next action + `bulkIndexTransformedDocuments` Use the bulk API create action to write a batch of up-to-date documents. The @@ -386,32 +661,47 @@ step will ensure that the index is refreshed before we start serving traffic. The following errors are ignored because it means another instance already completed this step: - - documents already exist in the temp index - - temp index has a write block - - temp index is not found + +- documents already exist in the temp index +- temp index has a write block +- temp index is not found + ### New control state + 1. If `currentBatch` is the last batch in `bulkOperationBatches` - → `REINDEX_SOURCE_TO_TEMP_READ` + + → [REINDEX_SOURCE_TO_TEMP_READ](#reindex_source_to_temp_read) + 2. If there are more batches left in `bulkOperationBatches` - → `REINDEX_SOURCE_TO_TEMP_INDEX_BULK` + + → [REINDEX_SOURCE_TO_TEMP_INDEX_BULK](#reindex_source_to_temp_index_bulk) ## REINDEX_SOURCE_TO_TEMP_CLOSE_PIT + ### Next action + `closePIT` ### New control state - → `SET_TEMP_WRITE_BLOCK` + +→ [SET_TEMP_WRITE_BLOCK](#set_temp_write_block) ## SET_TEMP_WRITE_BLOCK + ### Next action + `setWriteBlock` Set a write block on the temporary index so that we can clone it. + ### New control state - → `CLONE_TEMP_TO_TARGET` + +→ [CLONE_TEMP_TO_TARGET](#clone_temp_to_target) ## CLONE_TEMP_TO_TARGET + ### Next action + `cloneIndex` Ask elasticsearch to clone the temporary index into the target index. If the target index already exists (because another node already started the clone operation), wait until the clone is complete by waiting for a green index status. @@ -419,13 +709,56 @@ Ask elasticsearch to clone the temporary index into the target index. If the tar We can’t use the temporary index as our target index because one instance can complete the migration, delete a document, and then a second instance starts the reindex operation and re-creates the deleted document. By cloning the temporary index and only accepting writes/deletes from the cloned target index, we prevent lost acknowledged deletes. ### New control state -1. If the action succeeds - → `OUTDATED_DOCUMENTS_SEARCH` -2. If the action fails with a `index_not_green_timeout` - → `CLONE_TEMP_TO_TARGET` -## OUTDATED_DOCUMENTS_SEARCH +1. If the action succeeds. + + → [REFRESH_TARGET](#refresh_target) + +2. If the action fails with an `index_not_green_timeout`. + + → [CLONE_TEMP_TO_TARGET](#clone_temp_to_target) + +## REFRESH_TARGET + +### Next action + +`refreshIndex` + +We refresh the temporary clone index, to make sure newly added documents are taken into account. + +### New control state + +1. If the index is refreshed successfully. + + → [OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT](#outdated_documents_search_open_pit) + +2. When unexpected errors occur during the refresh. + + → [FATAL](#fatal) + +## OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT + +### Next action + +`openPit` + +Any saved objects that belong to previous versions are updated in the index. +This operation is performed in batches, leveraging the [Point in Time API](https://www.elastic.co/guide/en/elasticsearch/reference/current/point-in-time-api.html). + +### New control state + +1. If the PIT is created successfully. + + → [OUTDATED_DOCUMENTS_SEARCH_READ](#outdated_documents_search_read) + +2. When unexpected errors occur whilst creating the PIT. + + → [FATAL](#fatal) + +## OUTDATED_DOCUMENTS_SEARCH_READ + ### Next action + `readWithPit(outdatedDocumentsQuery)` Search for outdated saved object documents. Will return one batch of @@ -438,19 +771,100 @@ plugins were disabled by the instance that performed the and transform them to ensure that everything is up to date. ### New control state -1. Found outdated documents? - → `OUTDATED_DOCUMENTS_TRANSFORM` -2. All documents up to date - → `UPDATE_TARGET_MAPPINGS_PROPERTIES` + +1. Found outdated documents. + + → [OUTDATED_DOCUMENTS_TRANSFORM](#outdated_documents_transform) + +2. There aren't any outdated documents left to read, and we can proceed with the flow. + + → [OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT](#outdated_documents_search_close_pit) + +3. There aren't any outdated documents left to read, but we encountered *corrupt* documents or *transform errors*, and Kibana is not configured to ignore them (using `migrations.discardCorruptObjects` flag). + + → [FATAL](#fatal) + +4. If we encounter an error of the form `es_response_too_large` whilst reading *saved object* documents, we retry with a smaller batch size. + + → [OUTDATED_DOCUMENTS_SEARCH_READ](#outdated_documents_search_read) ## OUTDATED_DOCUMENTS_TRANSFORM + +### Next action + +`transformDocs` + +### New control state + +1. If all of the outdated documents in the current batch are transformed successfully, or Kibana is configured to ignore *corrupt* documents and *transform* errors. We managed to break down the current set of documents into smaller batches successfully, so we can start indexing them one by one. + + → [TRANSFORMED_DOCUMENTS_BULK_INDEX](#transformed_documents_bulk_index) + +2. If the batch contains corrupt documents or transform errors, and Kibana is not configured to discard them, we do not index them, we simply read the next batch, accumulating encountered errors. + + → [OUTDATED_DOCUMENTS_SEARCH_READ](#outdated_documents_search_read) + +3. If we can't split the set of documents in batches small enough to not exceed the `maxBatchSize`, we fail the migration. + + → [FATAL](#fatal) + +## TRANSFORMED_DOCUMENTS_BULK_INDEX + +### Next action + +`bulkOverwriteTransformedDocuments` + +Once transformed we use an index operation to overwrite the outdated document with the up-to-date version. Optimistic concurrency control ensures that we only overwrite the document once so that any updates/writes by another instance which already completed the migration aren’t overwritten and lost. The transformed documents are split in different batches, and then each batch is bulk indexed. + +### New control state + +1. We have more batches to bulk index. + + → [TRANSFORMED_DOCUMENTS_BULK_INDEX](#transformed_documents_bulk_index) + +2. We have indexed all the batches of the current read operation. Proceed to read more documents. + + → [OUTDATED_DOCUMENTS_SEARCH_READ](#outdated_documents_search_read) + +## OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT + +### Next action + +`closePit` + +After reading, transforming and bulk indexingn all saved objects, we can close our PIT. + +### New control state + +1. If we can close the PIT successfully, and we did update some documents. + + → [OUTDATED_DOCUMENTS_REFRESH](#outdated_documents_refresh) + +2. If we can close the PIT successfully, and we did not update any documents. + + → [CHECK_TARGET_MAPPINGS](#check_target_mappings) + +3. An unexpected error occurred whilst closing the PIT. + + → [FATAL](#fatal) + +## OUTDATED_DOCUMENTS_REFRESH + ### Next action -`transformRawDocs` + `bulkOverwriteTransformedDocuments` -Once transformed we use an index operation to overwrite the outdated document with the up-to-date version. Optimistic concurrency control ensures that we only overwrite the document once so that any updates/writes by another instance which already completed the migration aren’t overwritten and lost. +`refreshIndex` + +We updated some outdated documents, we must refresh the target index to pick up the changes. ### New control state - → `OUTDATED_DOCUMENTS_SEARCH` + +1. If the index is refreshed successfully. + + → [CHECK_TARGET_MAPPINGS](#check_target_mappings) + +2. When unexpected errors occur during the refresh.**** + + → [FATAL](#fatal) ## CHECK_TARGET_MAPPINGS @@ -463,26 +877,35 @@ Compare the calculated mappings' hashes against those stored in the `.map ### New control state 1. If calculated mappings don't match, we must update them. - → `UPDATE_TARGET_MAPPINGS_PROPERTIES` + + → [UPDATE_TARGET_MAPPINGS_PROPERTIES](#update_target_mappings_properties) + 2. If calculated mappings and stored mappings match, we can skip directly to the next step. - → `CHECK_VERSION_INDEX_READY_ACTIONS` + + → [CHECK_VERSION_INDEX_READY_ACTIONS](#check_version_index_ready_actions) ## UPDATE_TARGET_MAPPINGS_PROPERTIES + ### Next action + `updateAndPickupMappings` If another instance has some plugins disabled it will disable the mappings of that plugin's types when creating the temporary index. This action will update the mappings and then use an update_by_query to ensure that all fields are “picked-up” and ready to be searched over. ### New control state - → `UPDATE_TARGET_MAPPINGS_PROPERTIES_WAIT_FOR_TASK` + +→ [UPDATE_TARGET_MAPPINGS_PROPERTIES_WAIT_FOR_TASK](#update_target_mappings_properties_wait_for_task) ## UPDATE_TARGET_MAPPINGS_PROPERTIES_WAIT_FOR_TASK + ### Next action + `waitForPickupUpdatedMappingsTask` ### New control state - → `MARK_VERSION_INDEX_READY` + +→ [MARK_VERSION_INDEX_READY](#mark_version_index_ready) ## CHECK_VERSION_INDEX_READY_ACTIONS @@ -495,12 +918,17 @@ None ### New control state 1. If there are some `versionIndexReadyActions`, we performed a full migration and need to point the aliases to our newly migrated index. - → `MARK_VERSION_INDEX_READY` + + → [MARK_VERSION_INDEX_READY](#mark_version_index_ready) + 2. If there are no `versionIndexReadyActions`, another instance already completed this migration and we only transformed outdated documents and updated the mappings for in case a new plugin was enabled. - → `DONE` + + → [DONE](#done) ## MARK_VERSION_INDEX_READY + ### Next action + `updateAliases` Atomically apply the `versionIndexReadyActions` using the _alias actions API. By performing the following actions we guarantee that if multiple versions of Kibana started the upgrade in parallel, only one version will succeed. @@ -510,40 +938,62 @@ Atomically apply the `versionIndexReadyActions` using the _alias actions API. By 3. Remove the temporary index ### New control state + 1. If all the actions succeed we’re ready to serve traffic - → `DONE` + + → [DONE](#done) + 2. If action (1) fails with alias_not_found_exception or action (3) fails with index_not_found_exception another instance already completed the migration - → `MARK_VERSION_INDEX_READY_CONFLICT` + + → [MARK_VERSION_INDEX_READY_CONFLICT](#mark_version_index_ready_conflict) ## MARK_VERSION_INDEX_READY_CONFLICT + ### Next action + `fetchIndices` Fetch the saved object indices ### New control state + If another instance completed a migration from the same source we need to verify that it is running the same version. 1. If the current and version aliases are pointing to the same index the instance that completed the migration was on the same version and it’s safe to start serving traffic. - → `DONE` + + → [DONE](#done) + 2. If the other instance was running a different version we fail the migration. Once we restart one of two things can happen: the other instance is an older version and we will restart the migration, or, it’s a newer version and we will refuse to start up. - → `FATAL` + + → [FATAL](#fatal) + +## FATAL + +Unfortunately, this migrator failed at some step. Please check the logs and identify the cause. Once addressed, restart Kibana again to restart / resume the migration. + +## DONE + +Congratulations, this migrator finished the saved objects migration for its index. # Manual QA Test Plan + ## 1. Legacy pre-migration + When upgrading from a legacy index additional steps are required before the regular migration process can start. We have the following potential legacy indices: - - v5.x index that wasn't upgraded -> kibana should refuse to start the migration - - v5.x index that was upgraded to v6.x: `.kibana-6` _index_ with `.kibana` _alias_ - - < v6.5 `.kibana` _index_ (Saved Object Migrations were - introduced in v6.5 https://github.com/elastic/kibana/pull/20243) - - TODO: Test versions which introduced the `kibana_index_template` template? - - < v7.4 `.kibana_task_manager` _index_ (Task Manager started - using Saved Objects in v7.4 https://github.com/elastic/kibana/pull/39829) + +- v5.x index that wasn't upgraded -> kibana should refuse to start the migration +- v5.x index that was upgraded to v6.x: `.kibana-6` *index* with `.kibana` *alias* +- < v6.5 `.kibana` *index* (Saved Object Migrations were + introduced in v6.5 ) +- TODO: Test versions which introduced the `kibana_index_template` template? +- < v7.4 `.kibana_task_manager` *index* (Task Manager started + using Saved Objects in v7.4 ) Test plan: + 1. Ensure that the different versions of Kibana listed above can successfully upgrade to 7.11. 2. Ensure that multiple Kibana nodes can migrate a legacy index in parallel @@ -565,6 +1015,7 @@ Test plan: successfully complete the migration. For a successful migration the following behaviour should be observed: + 1. The `.kibana` index should be reindexed into a `.kibana_pre6.5.0` index 2. The `.kibana` index should be deleted 3. The `.kibana_index_template` should be deleted @@ -574,11 +1025,13 @@ For a successful migration the following behaviour should be observed: aliases should point to the `.kibana_7.11.0_001` index. ## 2. Plugins enabled/disabled + Kibana plugins can be disabled/enabled at any point in time. We need to ensure that Saved Object documents are migrated for all the possible sequences of enabling, disabling, before or after a version upgrade. -### Test scenario 1 (enable a plugin after migration): +### Test scenario 1 (enable a plugin after migration) + 1. Start an old version of Kibana (< 7.11) 2. Create a document that we know will be migrated in a later version (i.e. create a `dashboard`) @@ -589,23 +1042,27 @@ enabling, disabling, before or after a version upgrade. 7. Ensure that the document from step (2) has been migrated (`migrationVersion` contains 7.11.0) -### Test scenario 2 (disable a plugin after migration): +### Test scenario 2 (disable a plugin after migration) + 1. Start an old version of Kibana (< 7.11) 2. Create a document that we know will be migrated in a later version (i.e. create a `dashboard`) 3. Upgrade Kibana to v7.11 making sure the plugin in step (3) is enabled. 4. Disable the plugin to which the document belongs (i.e `dashboard` plugin) -6. Restart Kibana -7. Ensure that Kibana logs a warning, but continues to start even though there +5. Restart Kibana +6. Ensure that Kibana logs a warning, but continues to start even though there are saved object documents which don't belong to an enable plugin -### Test scenario 3 (multiple instances, enable a plugin after migration): +### Test scenario 3 (multiple instances, enable a plugin after migration) + Follow the steps from 'Test scenario 1', but perform the migration with multiple instances of Kibana -### Test scenario 4 (multiple instances, mixed plugin enabled configs): +### Test scenario 4 (multiple instances, mixed plugin enabled configs) + We don't support this upgrade scenario, but it's worth making sure we don't have data loss when there's a user error. + 1. Start an old version of Kibana (< 7.11) 2. Create a document that we know will be migrated in a later version (i.e. create a `dashboard`) @@ -615,3 +1072,4 @@ have data loss when there's a user error. other half. 5. Ensure that the document from step (2) has been migrated (`migrationVersion` contains 7.11.0) + diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/initialize_action.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_cluster_routing_allocation.test.ts similarity index 89% rename from packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/initialize_action.test.ts rename to packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_cluster_routing_allocation.test.ts index 04ddcd3b78ce5..bce12671db8e4 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/initialize_action.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_cluster_routing_allocation.test.ts @@ -10,19 +10,13 @@ import * as Either from 'fp-ts/lib/Either'; import { catchRetryableEsClientErrors } from './catch_retryable_es_client_errors'; import { errors as EsErrors } from '@elastic/elasticsearch'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import { initAction, type InitActionParams } from './initialize_action'; +import { checkClusterRoutingAllocationEnabled } from './check_cluster_routing_allocation'; jest.mock('./catch_retryable_es_client_errors'); -describe('initAction', () => { - let initActionParams: Omit; - +describe('checkClusterRoutingAllocationEnabled', () => { beforeEach(() => { jest.clearAllMocks(); - - initActionParams = { - indices: ['.kibana', '.kibana_8.8.0'], - }; }); it('calls catchRetryableEsClientErrors when the promise rejects', async () => { const retryableError = new EsErrors.ResponseError( @@ -34,7 +28,7 @@ describe('initAction', () => { const client = elasticsearchClientMock.createInternalClient( elasticsearchClientMock.createErrorTransportRequestPromise(retryableError) ); - const task = initAction({ ...initActionParams, client }); + const task = checkClusterRoutingAllocationEnabled(client); try { await task(); } catch (e) { @@ -62,7 +56,7 @@ describe('initAction', () => { const client = elasticsearchClientMock.createInternalClient( Promise.resolve(clusterSettingsResponse) ); - const task = initAction({ ...initActionParams, client }); + const task = checkClusterRoutingAllocationEnabled(client); const result = await task(); expect(Either.isLeft(result)).toEqual(true); }); @@ -107,7 +101,7 @@ describe('initAction', () => { const client = elasticsearchClientMock.createInternalClient( Promise.resolve(clusterSettingsResponse) ); - const task = initAction({ ...initActionParams, client }); + const task = checkClusterRoutingAllocationEnabled(client); const result = await task(); expect(Either.isRight(result)).toEqual(true); }); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/initialize_action.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_cluster_routing_allocation.ts similarity index 57% rename from packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/initialize_action.ts rename to packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_cluster_routing_allocation.ts index 8daa548039eeb..a64c03d91e219 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/initialize_action.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_cluster_routing_allocation.ts @@ -8,35 +8,22 @@ import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as Either from 'fp-ts/lib/Either'; -import { pipe } from 'fp-ts/lib/pipeable'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, type RetryableEsClientError, } from './catch_retryable_es_client_errors'; -import { type FetchIndexResponse, fetchIndices } from './fetch_indices'; - -const routingAllocationEnable = 'cluster.routing.allocation.enable'; -export interface ClusterRoutingAllocationEnabled { - clusterRoutingAllocationEnabled: boolean; -} - -export interface InitActionParams { - client: ElasticsearchClient; - indices: string[]; -} +const ROUTING_ALLOCATION_ENABLE = 'cluster.routing.allocation.enable'; export interface IncompatibleClusterRoutingAllocation { type: 'incompatible_cluster_routing_allocation'; } -export const checkClusterRoutingAllocationEnabledTask = - ({ - client, - }: { - client: ElasticsearchClient; - }): TaskEither.TaskEither => +export const checkClusterRoutingAllocationEnabled = + ( + client: ElasticsearchClient + ): TaskEither.TaskEither => () => { return client.cluster .getSettings({ @@ -45,8 +32,8 @@ export const checkClusterRoutingAllocationEnabledTask = .then((settings) => { // transient settings take preference over persistent settings const clusterRoutingAllocation = - settings?.transient?.[routingAllocationEnable] ?? - settings?.persistent?.[routingAllocationEnable]; + settings?.transient?.[ROUTING_ALLOCATION_ENABLE] ?? + settings?.persistent?.[ROUTING_ALLOCATION_ENABLE]; const clusterRoutingAllocationEnabledIsAll = clusterRoutingAllocation === undefined || clusterRoutingAllocation === 'all'; @@ -61,18 +48,3 @@ export const checkClusterRoutingAllocationEnabledTask = }) .catch(catchRetryableEsClientErrors); }; - -export const initAction = ({ - client, - indices, -}: InitActionParams): TaskEither.TaskEither< - RetryableEsClientError | IncompatibleClusterRoutingAllocation, - FetchIndexResponse -> => { - return pipe( - checkClusterRoutingAllocationEnabledTask({ client }), - TaskEither.chainW((value) => { - return fetchIndices({ client, indices }); - }) - ); -}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/clone_index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/clone_index.ts index 7334f17191df0..2271a2773cfab 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/clone_index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/clone_index.ts @@ -8,7 +8,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import { pipe } from 'fp-ts/lib/pipeable'; +import { pipe } from 'fp-ts/lib/function'; import { errors as EsErrors } from '@elastic/elasticsearch'; import type { ElasticsearchClient, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/create_index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/create_index.ts index aea970f1bf5f9..0b38f9c369d5f 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/create_index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/create_index.ts @@ -8,7 +8,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import { pipe } from 'fp-ts/lib/pipeable'; +import { pipe } from 'fp-ts/lib/function'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ElasticsearchClient, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts index 79df66c4b9ccb..35d05e1374667 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts @@ -21,12 +21,15 @@ export { export type { RetryableEsClientError }; // actions/* imports -export type { InitActionParams, IncompatibleClusterRoutingAllocation } from './initialize_action'; -export { initAction } from './initialize_action'; +export type { IncompatibleClusterRoutingAllocation } from './check_cluster_routing_allocation'; +export { checkClusterRoutingAllocationEnabled } from './check_cluster_routing_allocation'; export type { FetchIndexResponse, FetchIndicesParams } from './fetch_indices'; export { fetchIndices } from './fetch_indices'; +export type { SafeWriteBlockParams } from './safe_write_block'; +export { safeWriteBlock } from './safe_write_block'; + export type { SetWriteBlockParams } from './set_write_block'; export { setWriteBlock } from './set_write_block'; @@ -105,7 +108,7 @@ export { } from './update_source_mappings_properties'; import type { UnknownDocsFound } from './check_for_unknown_docs'; -import type { IncompatibleClusterRoutingAllocation } from './initialize_action'; +import type { IncompatibleClusterRoutingAllocation } from './check_cluster_routing_allocation'; import type { ClusterShardLimitExceeded } from './create_index'; import type { SynchronizationFailed } from './synchronize_migrators'; import type { IndexMappingsIncomplete, TypesChanged } from './check_target_mappings'; @@ -158,6 +161,11 @@ export interface EsResponseTooLargeError { contentLength: number; } +export interface SourceEqualsTarget { + type: 'source_equals_target'; + index: string; +} + /** @internal */ export interface AcknowledgeResponse { acknowledged: boolean; @@ -185,6 +193,7 @@ export interface ActionErrorTypeMap { index_mappings_incomplete: IndexMappingsIncomplete; types_changed: TypesChanged; operation_not_supported: OperationNotSupported; + source_equals_target: SourceEqualsTarget; } /** diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/safe_write_block.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/safe_write_block.test.ts new file mode 100644 index 0000000000000..40e60a7afd4c4 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/safe_write_block.test.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import * as Either from 'fp-ts/lib/Either'; +import * as TaskEither from 'fp-ts/lib/TaskEither'; +import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; +import { safeWriteBlock } from './safe_write_block'; + +jest.mock('./set_write_block'); +import { setWriteBlock } from './set_write_block'; + +const setWriteBlockMock = setWriteBlock as jest.MockedFn; + +describe('safeWriteBlock', () => { + beforeEach(() => { + setWriteBlockMock.mockReset(); + setWriteBlockMock.mockReturnValueOnce( + TaskEither.fromEither(Either.right('set_write_block_succeeded' as const)) + ); + }); + + const client = elasticsearchClientMock.createInternalClient(); + it('returns a Left response if source and target indices match', async () => { + const task = safeWriteBlock({ + client, + sourceIndex: '.kibana_8.15.0_001', + targetIndex: '.kibana_8.15.0_001', + }); + const res = await task(); + expect(res).toEqual(Either.left({ type: 'source_equals_target', index: '.kibana_8.15.0_001' })); + expect(setWriteBlockMock).not.toHaveBeenCalled(); + }); + + it('calls setWriteBlock if indices are different', async () => { + const task = safeWriteBlock({ + client, + sourceIndex: '.kibana_7.13.0_001', + targetIndex: '.kibana_8.15.0_001', + timeout: '28s', + }); + const res = await task(); + expect(res).toEqual(Either.right('set_write_block_succeeded' as const)); + expect(setWriteBlockMock).toHaveBeenCalledTimes(1); + expect(setWriteBlockMock).toHaveBeenCalledWith({ + client, + index: '.kibana_7.13.0_001', + timeout: '28s', + }); + }); +}); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/safe_write_block.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/safe_write_block.ts new file mode 100644 index 0000000000000..cb1270428f5dd --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/safe_write_block.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as Either from 'fp-ts/lib/Either'; +import * as TaskEither from 'fp-ts/lib/TaskEither'; +import { pipe } from 'fp-ts/lib/function'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { RetryableEsClientError } from './catch_retryable_es_client_errors'; +import { DEFAULT_TIMEOUT, type SourceEqualsTarget, type IndexNotFound } from '.'; +import { setWriteBlock } from './set_write_block'; + +/** @internal */ +export interface SafeWriteBlockParams { + client: ElasticsearchClient; + sourceIndex: string; + targetIndex: string; + timeout?: string; +} + +export const safeWriteBlock = ({ + client, + sourceIndex, + targetIndex, + timeout = DEFAULT_TIMEOUT, +}: SafeWriteBlockParams): TaskEither.TaskEither< + SourceEqualsTarget | IndexNotFound | RetryableEsClientError, + 'set_write_block_succeeded' +> => { + const assertSourceAndTargetDifferTask: TaskEither.TaskEither< + SourceEqualsTarget, + 'source_and_target_differ' + > = TaskEither.fromEither( + sourceIndex === targetIndex + ? Either.left({ type: 'source_equals_target' as const, index: sourceIndex }) + : Either.right('source_and_target_differ' as const) + ); + + return pipe( + assertSourceAndTargetDifferTask, + TaskEither.chainW(() => setWriteBlock({ client, index: sourceIndex, timeout })) + ); +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts index 8478ad08247a5..9cd55f88a066c 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts @@ -8,7 +8,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import { pipe } from 'fp-ts/lib/pipeable'; +import { pipe } from 'fp-ts/lib/function'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_source_mappings_properties.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_source_mappings_properties.ts index 3a73f948efa20..1a47992074af8 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_source_mappings_properties.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_source_mappings_properties.ts @@ -8,7 +8,7 @@ import { omit } from 'lodash'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import { pipe } from 'fp-ts/lib/pipeable'; +import { pipe } from 'fp-ts/lib/function'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { IndexMapping, VirtualVersionMap } from '@kbn/core-saved-objects-base-server-internal'; import { diffMappings } from '../core/diff_mappings'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts index f06e9f836f2c5..961bc08e735ee 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts @@ -39,7 +39,6 @@ import type { OutdatedDocumentsSearchOpenPit, OutdatedDocumentsSearchRead, OutdatedDocumentsTransform, - PostInitState, PrepareCompatibleMigration, RefreshTarget, ReindexSourceToTempClosePit, @@ -57,6 +56,8 @@ import type { WaitForYellowSourceState, ReadyToReindexSyncState, DoneReindexingSyncState, + LegacyCheckClusterRoutingAllocationState, + CheckClusterRoutingAllocationState, } from '../state'; import { type TransformErrorObjects, TransformSavedObjectDocumentError } from '../core'; import type { AliasAction, RetryableEsClientError } from '../actions'; @@ -175,6 +176,7 @@ describe('migrations v2 model', () => { type: 'retryable_es_client_error', message: 'snapshot_in_progress_exception', }; + test('increments retryCount, exponential retryDelay if an action fails with a retryable_es_client_error', () => { const states = new Array(10).fill(1).map(() => { state = model(state, Either.left(retryableError)); @@ -301,49 +303,59 @@ describe('migrations v2 model', () => { }, } as const; - describe('if waitForMigrationCompletion=true', () => { - const initState = Object.assign({}, initBaseState, { - waitForMigrationCompletion: true, + test("INIT -> FATAL when .kibana points to newer version's index", () => { + const res: ResponseType<'INIT'> = Either.right({ + '.kibana_7.12.0_001': { + aliases: { + '.kibana': {}, + '.kibana_7.12.0': {}, + }, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, + settings: {}, + }, + '.kibana_7.11.0_001': { + aliases: { '.kibana_7.11.0': {} }, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, + settings: {}, + }, }); - test('INIT -> INIT when cluster routing allocation is incompatible', () => { - const res: ResponseType<'INIT'> = Either.left({ - type: 'incompatible_cluster_routing_allocation', - }); - const newState = model(initState, res) as FatalState; + const newState = model(initBaseState, res) as FatalState; - expect(newState.controlState).toEqual('INIT'); - expect(newState.retryCount).toEqual(1); - expect(newState.retryDelay).toEqual(2000); - expect(newState.logs[0]).toMatchInlineSnapshot(` - Object { - "level": "error", - "message": "Action failed with '[incompatible_cluster_routing_allocation] Incompatible Elasticsearch cluster settings detected. Remove the persistent and transient Elasticsearch cluster setting 'cluster.routing.allocation.enable' or set it to a value of 'all' to allow migrations to proceed. Refer to routingAllocationDisabled for more information on how to resolve the issue.'. Retrying attempt 1 in 2 seconds.", - } - `); - }); - test("INIT -> FATAL when .kibana points to newer version's index", () => { - const res: ResponseType<'INIT'> = Either.right({ - '.kibana_7.12.0_001': { - aliases: { - '.kibana': {}, - '.kibana_7.12.0': {}, - }, - mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, - settings: {}, - }, - '.kibana_7.11.0_001': { - aliases: { '.kibana_7.11.0': {} }, - mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, - settings: {}, + expect(newState.controlState).toEqual('FATAL'); + expect(newState.reason).toMatchInlineSnapshot( + `"The .kibana alias is pointing to a newer version of Kibana: v7.12.0"` + ); + }); + + test('INIT -> FATAL when .kibana points to multiple indices', () => { + const res: ResponseType<'INIT'> = Either.right({ + '.kibana_7.12.0_001': { + aliases: { + '.kibana': {}, + '.kibana_7.12.0': {}, }, - }); - const newState = model(initState, res) as FatalState; + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, + settings: {}, + }, + '.kibana_7.11.0_001': { + aliases: { '.kibana': {}, '.kibana_7.11.0': {} }, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, + settings: {}, + }, + }); + const newState = model(initBaseState, res) as FatalState; - expect(newState.controlState).toEqual('FATAL'); - expect(newState.reason).toMatchInlineSnapshot( - `"The .kibana alias is pointing to a newer version of Kibana: v7.12.0"` - ); + expect(newState.controlState).toEqual('FATAL'); + expect(newState.reason).toMatchInlineSnapshot( + `"The .kibana alias is pointing to multiple indices: .kibana_7.12.0_001,.kibana_7.11.0_001."` + ); + }); + + describe('if waitForMigrationCompletion=true', () => { + const initState = Object.assign({}, initBaseState, { + waitForMigrationCompletion: true, }); + test('INIT -> FATAL when later version alias exists', () => { const res: ResponseType<'INIT'> = Either.right({ '.kibana_7.11.0_001': { @@ -359,29 +371,7 @@ describe('migrations v2 model', () => { `"The .kibana_7.12.0 alias refers to a newer version of Kibana: v7.12.0"` ); }); - test('INIT -> FATAL when .kibana points to multiple indices', () => { - const res: ResponseType<'INIT'> = Either.right({ - '.kibana_7.12.0_001': { - aliases: { - '.kibana': {}, - '.kibana_7.12.0': {}, - }, - mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, - settings: {}, - }, - '.kibana_7.11.0_001': { - aliases: { '.kibana': {}, '.kibana_7.11.0': {} }, - mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, - settings: {}, - }, - }); - const newState = model(initState, res) as FatalState; - expect(newState.controlState).toEqual('FATAL'); - expect(newState.reason).toMatchInlineSnapshot( - `"The .kibana alias is pointing to multiple indices: .kibana_7.12.0_001,.kibana_7.11.0_001."` - ); - }); test('INIT -> WAIT_FOR_MIGRATION_COMPLETION when .kibana points to an index with an invalid version', () => { // If users tamper with our index version naming scheme we can no // longer accurately detect a newer version. Older Kibana versions @@ -405,11 +395,12 @@ describe('migrations v2 model', () => { settings: {}, }, }); - const newState = model(initState, res) as WaitForYellowSourceState; + const newState = model(initState, res); expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); expect(newState.retryDelay).toBe(2000); }); + test('INIT -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a v2 migrations index (>= 7.11.0)', () => { const res: ResponseType<'INIT'> = Either.right({ '.kibana_7.11.0_001': { @@ -431,11 +422,12 @@ describe('migrations v2 model', () => { versionIndex: '.kibana_7.12.0_001', }, res - ) as WaitForYellowSourceState; + ); expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); expect(newState.retryDelay).toEqual(2000); }); + test('INIT -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a v1 migrations index (>= 6.5 < 7.11.0)', () => { const res: ResponseType<'INIT'> = Either.right({ '.kibana_3': { @@ -446,11 +438,12 @@ describe('migrations v2 model', () => { settings: {}, }, }); - const newState = model(initState, res) as WaitForYellowSourceState; + const newState = model(initState, res); expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); expect(newState.retryDelay).toEqual(2000); }); + test('INIT -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a legacy index (>= 6.0.0 < 6.5)', () => { const res: ResponseType<'INIT'> = Either.right({ '.kibana': { @@ -464,6 +457,7 @@ describe('migrations v2 model', () => { expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); expect(newState.retryDelay).toEqual(2000); }); + test('INIT -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a custom kibana.index name (>= 6.5 < 7.11.0)', () => { const res: ResponseType<'INIT'> = Either.right({ 'my-saved-objects_3': { @@ -483,11 +477,12 @@ describe('migrations v2 model', () => { versionIndex: 'my-saved-objects_7.11.0_001', }, res - ) as WaitForYellowSourceState; + ); expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); expect(newState.retryDelay).toEqual(2000); }); + test('INIT -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a custom kibana.index v2 migrations index (>= 7.11.0)', () => { const res: ResponseType<'INIT'> = Either.right({ 'my-saved-objects_7.11.0': { @@ -508,11 +503,12 @@ describe('migrations v2 model', () => { versionIndex: 'my-saved-objects_7.12.0_001', }, res - ) as WaitForYellowSourceState; + ); expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); expect(newState.retryDelay).toEqual(2000); }); + test('INIT -> WAIT_FOR_MIGRATION_COMPLETION when no indices/aliases exist', () => { const res: ResponseType<'INIT'> = Either.right({}); const newState = model(initState, res); @@ -526,68 +522,7 @@ describe('migrations v2 model', () => { const initState = Object.assign({}, initBaseState, { waitForMigrationCompletion: false, }); - test('INIT -> INIT when cluster routing allocation is incompatible', () => { - const res: ResponseType<'INIT'> = Either.left({ - type: 'incompatible_cluster_routing_allocation', - }); - const newState = model(initState, res) as FatalState; - - expect(newState.controlState).toEqual('INIT'); - expect(newState.retryCount).toEqual(1); - expect(newState.retryDelay).toEqual(2000); - expect(newState.logs[0]).toMatchInlineSnapshot(` - Object { - "level": "error", - "message": "Action failed with '[incompatible_cluster_routing_allocation] Incompatible Elasticsearch cluster settings detected. Remove the persistent and transient Elasticsearch cluster setting 'cluster.routing.allocation.enable' or set it to a value of 'all' to allow migrations to proceed. Refer to routingAllocationDisabled for more information on how to resolve the issue.'. Retrying attempt 1 in 2 seconds.", - } - `); - }); - test("INIT -> FATAL when .kibana points to newer version's index", () => { - const res: ResponseType<'INIT'> = Either.right({ - '.kibana_7.12.0_001': { - aliases: { - '.kibana': {}, - '.kibana_7.12.0': {}, - }, - mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, - settings: {}, - }, - '.kibana_7.11.0_001': { - aliases: { '.kibana_7.11.0': {} }, - mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, - settings: {}, - }, - }); - const newState = model(initState, res) as FatalState; - - expect(newState.controlState).toEqual('FATAL'); - expect(newState.reason).toMatchInlineSnapshot( - `"The .kibana alias is pointing to a newer version of Kibana: v7.12.0"` - ); - }); - test('INIT -> FATAL when .kibana points to multiple indices', () => { - const res: ResponseType<'INIT'> = Either.right({ - '.kibana_7.12.0_001': { - aliases: { - '.kibana': {}, - '.kibana_7.12.0': {}, - }, - mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, - settings: {}, - }, - '.kibana_7.11.0_001': { - aliases: { '.kibana': {}, '.kibana_7.11.0': {} }, - mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, - settings: {}, - }, - }); - const newState = model(initState, res) as FatalState; - expect(newState.controlState).toEqual('FATAL'); - expect(newState.reason).toMatchInlineSnapshot( - `"The .kibana alias is pointing to multiple indices: .kibana_7.12.0_001,.kibana_7.11.0_001."` - ); - }); test('INIT -> WAIT_FOR_YELLOW_SOURCE when .kibana points to an index with an invalid version', () => { // If users tamper with our index version naming scheme we can no // longer accurately detect a newer version. Older Kibana versions @@ -675,7 +610,8 @@ describe('migrations v2 model', () => { '.kibana': '.kibana_3', }); }); - test('INIT -> LEGACY_SET_WRITE_BLOCK when migrating from a legacy index (>= 6.0.0 < 6.5)', () => { + + test('INIT -> LEGACY_CHECK_CLUSTER_ROUTING_ALLOCATION when migrating from a legacy index (>= 6.0.0 < 6.5)', () => { const res: ResponseType<'INIT'> = Either.right({ '.kibana': { aliases: {}, @@ -686,7 +622,7 @@ describe('migrations v2 model', () => { const newState = model(initState, res); expect(newState).toMatchObject({ - controlState: 'LEGACY_SET_WRITE_BLOCK', + controlState: 'LEGACY_CHECK_CLUSTER_ROUTING_ALLOCATION', sourceIndex: Option.some('.kibana_pre6.5.0_001'), targetIndex: '.kibana_7.11.0_001', }); @@ -714,6 +650,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('INIT -> WAIT_FOR_YELLOW_SOURCE when migrating from a custom kibana.index name (>= 6.5 < 7.11.0)', () => { const res: ResponseType<'INIT'> = Either.right({ 'my-saved-objects_3': { @@ -740,6 +677,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('INIT -> WAIT_FOR_YELLOW_SOURCE when migrating from a custom kibana.index v2 migrations index (>= 7.11.0)', () => { const res: ResponseType<'INIT'> = Either.right({ 'my-saved-objects_7.11.0': { @@ -768,6 +706,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('INIT -> CREATE_NEW_TARGET when the index does not exist and the migrator is NOT involved in a relocation', () => { const res: ResponseType<'INIT'> = Either.right({}); const newState = model(initState, res); @@ -780,6 +719,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('INIT -> CREATE_REINDEX_TEMP when the index does not exist and the migrator is involved in a relocation', () => { const res: ResponseType<'INIT'> = Either.right({}); const newState = model( @@ -837,11 +777,12 @@ describe('migrations v2 model', () => { settings: {}, }, }); - const newState = model(waitForMState, res) as WaitForYellowSourceState; + const newState = model(waitForMState, res); expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); expect(newState.retryDelay).toBe(2000); }); + test('WAIT_FOR_MIGRATION_COMPLETION -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a v2 migrations index (>= 7.11.0)', () => { const res: ResponseType<'WAIT_FOR_MIGRATION_COMPLETION'> = Either.right({ '.kibana_7.11.0_001': { @@ -865,11 +806,12 @@ describe('migrations v2 model', () => { }, }, res - ) as WaitForYellowSourceState; + ); expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); expect(newState.retryDelay).toEqual(2000); }); + test('WAIT_FOR_MIGRATION_COMPLETION -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a v1 migrations index (>= 6.5 < 7.11.0)', () => { const res: ResponseType<'WAIT_FOR_MIGRATION_COMPLETION'> = Either.right({ '.kibana_3': { @@ -880,11 +822,12 @@ describe('migrations v2 model', () => { settings: {}, }, }); - const newState = model(waitForMState, res) as WaitForYellowSourceState; + const newState = model(waitForMState, res); expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); expect(newState.retryDelay).toEqual(2000); }); + test('WAIT_FOR_MIGRATION_COMPLETION -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a legacy index (>= 6.0.0 < 6.5)', () => { const res: ResponseType<'WAIT_FOR_MIGRATION_COMPLETION'> = Either.right({ '.kibana': { @@ -898,6 +841,7 @@ describe('migrations v2 model', () => { expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); expect(newState.retryDelay).toEqual(2000); }); + test('WAIT_FOR_MIGRATION_COMPLETION -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a custom kibana.index name (>= 6.5 < 7.11.0)', () => { const res: ResponseType<'WAIT_FOR_MIGRATION_COMPLETION'> = Either.right({ 'my-saved-objects_3': { @@ -916,11 +860,12 @@ describe('migrations v2 model', () => { versionIndex: 'my-saved-objects_7.11.0_001', }, res - ) as WaitForYellowSourceState; + ); expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); expect(newState.retryDelay).toEqual(2000); }); + test('WAIT_FOR_MIGRATION_COMPLETION -> WAIT_FOR_MIGRATION_COMPLETION when migrating from a custom kibana.index v2 migrations index (>= 7.11.0)', () => { const res: ResponseType<'WAIT_FOR_MIGRATION_COMPLETION'> = Either.right({ 'my-saved-objects_7.11.0': { @@ -940,11 +885,12 @@ describe('migrations v2 model', () => { versionIndex: 'my-saved-objects_7.12.0_001', }, res - ) as WaitForYellowSourceState; + ); expect(newState.controlState).toBe('WAIT_FOR_MIGRATION_COMPLETION'); expect(newState.retryDelay).toEqual(2000); }); + test('WAIT_FOR_MIGRATION_COMPLETION -> WAIT_FOR_MIGRATION_COMPLETION when no indices/aliases exist', () => { const res: ResponseType<'WAIT_FOR_MIGRATION_COMPLETION'> = Either.right({}); const newState = model(waitForMState, res); @@ -970,6 +916,37 @@ describe('migrations v2 model', () => { }); }); + describe('LEGACY_CHECK_CLUSTER_ROUTING_ALLOCATION', () => { + const legacyCheckClusterRoutingAllocationState: LegacyCheckClusterRoutingAllocationState = { + ...postInitState, + controlState: 'LEGACY_CHECK_CLUSTER_ROUTING_ALLOCATION', + sourceIndex: Option.some('.kibana') as Option.Some, + sourceIndexMappings: Option.some({ properties: {} }) as Option.Some, + legacyPreMigrationDoneActions: [], + legacyIndex: '', + }; + + test('LEGACY_CHECK_CLUSTER_ROUTING_ALLOCATION -> LEGACY_CHECK_CLUSTER_ROUTING_ALLOCATION when cluster allocation is not compatible', () => { + const res: ResponseType<'LEGACY_CHECK_CLUSTER_ROUTING_ALLOCATION'> = Either.left({ + type: 'incompatible_cluster_routing_allocation', + }); + const newState = model(legacyCheckClusterRoutingAllocationState, res); + + expect(newState.controlState).toBe('LEGACY_CHECK_CLUSTER_ROUTING_ALLOCATION'); + expect(newState.retryCount).toEqual(1); + expect(newState.retryDelay).toEqual(2000); + }); + + test('LEGACY_CHECK_CLUSTER_ROUTING_ALLOCATION -> LEGACY_SET_WRITE_BLOCK when cluster allocation is compatible', () => { + const res: ResponseType<'LEGACY_CHECK_CLUSTER_ROUTING_ALLOCATION'> = Either.right({}); + const newState = model(legacyCheckClusterRoutingAllocationState, res); + + expect(newState.controlState).toBe('LEGACY_SET_WRITE_BLOCK'); + expect(newState.retryCount).toEqual(0); + expect(newState.retryDelay).toEqual(0); + }); + }); + describe('LEGACY_SET_WRITE_BLOCK', () => { const legacySetWriteBlockState: LegacySetWriteBlockState = { ...postInitState, @@ -979,6 +956,7 @@ describe('migrations v2 model', () => { legacyPreMigrationDoneActions: [], legacyIndex: '', }; + test('LEGACY_SET_WRITE_BLOCK -> LEGACY_SET_WRITE_BLOCK if action fails with set_write_block_failed', () => { const res: ResponseType<'LEGACY_SET_WRITE_BLOCK'> = Either.left({ type: 'retryable_es_client_error', @@ -989,6 +967,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(1); expect(newState.retryDelay).toEqual(2000); }); + test('LEGACY_SET_WRITE_BLOCK -> LEGACY_CREATE_REINDEX_TARGET if action fails with index_not_found_exception', () => { const res: ResponseType<'LEGACY_SET_WRITE_BLOCK'> = Either.left({ type: 'index_not_found_exception', @@ -999,6 +978,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('LEGACY_SET_WRITE_BLOCK -> LEGACY_CREATE_REINDEX_TARGET if action succeeds with set_write_block_succeeded', () => { const res: ResponseType<'LEGACY_SET_WRITE_BLOCK'> = Either.right( 'set_write_block_succeeded' @@ -1019,6 +999,7 @@ describe('migrations v2 model', () => { legacyPreMigrationDoneActions: [], legacyIndex: '', }; + test('LEGACY_CREATE_REINDEX_TARGET -> LEGACY_REINDEX', () => { const res: ResponseType<'LEGACY_CREATE_REINDEX_TARGET'> = Either.right('create_index_succeeded'); @@ -1027,6 +1008,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('LEGACY_CREATE_REINDEX_TARGET -> LEGACY_CREATE_REINDEX_TARGET if action fails with index_not_green_timeout', () => { const res: ResponseType<'LEGACY_CREATE_REINDEX_TARGET'> = Either.left({ message: '[index_not_green_timeout] Timeout waiting for ...', @@ -1043,6 +1025,7 @@ describe('migrations v2 model', () => { } `); }); + test('LEGACY_CREATE_REINDEX_TARGET -> LEGACY_REINDEX resets retry count and retry delay if action succeeds', () => { const res: ResponseType<'LEGACY_CREATE_REINDEX_TARGET'> = Either.right('create_index_succeeded'); @@ -1056,13 +1039,14 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('LEGACY_CREATE_REINDEX_TARGET -> FATAL if action fails with cluster_shard_limit_exceeded', () => { const res: ResponseType<'LEGACY_CREATE_REINDEX_TARGET'> = Either.left({ type: 'cluster_shard_limit_exceeded', }); - const newState = model(legacyCreateReindexTargetState, res); + const newState = model(legacyCreateReindexTargetState, res) as FatalState; expect(newState.controlState).toEqual('FATAL'); - expect((newState as FatalState).reason).toMatchInlineSnapshot( + expect(newState.reason).toMatchInlineSnapshot( `"[cluster_shard_limit_exceeded] Upgrading Kibana requires adding a small number of new shards. Ensure that Kibana is able to add 10 more shards by increasing the cluster.max_shards_per_node setting, or removing indices to clear up resources. See clusterShardLimitExceeded"` ); }); @@ -1077,6 +1061,7 @@ describe('migrations v2 model', () => { legacyPreMigrationDoneActions: [], legacyIndex: '', }; + test('LEGACY_REINDEX -> LEGACY_REINDEX_WAIT_FOR_TASK', () => { const res: ResponseType<'LEGACY_REINDEX'> = Either.right({ taskId: 'task id' }); const newState = model(legacyReindexState, res); @@ -1096,6 +1081,7 @@ describe('migrations v2 model', () => { legacyIndex: 'legacy_index_name', legacyReindexTaskId: 'test_task_id', }; + test('LEGACY_REINDEX_WAIT_FOR_TASK -> LEGACY_DELETE if action succeeds', () => { const res: ResponseType<'LEGACY_REINDEX_WAIT_FOR_TASK'> = Either.right('reindex_succeeded'); const newState = model(legacyReindexWaitForTaskState, res); @@ -1103,6 +1089,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('LEGACY_REINDEX_WAIT_FOR_TASK -> LEGACY_DELETE if action fails with index_not_found_exception for reindex source', () => { const res: ResponseType<'LEGACY_REINDEX_WAIT_FOR_TASK'> = Either.left({ type: 'index_not_found_exception', @@ -1113,6 +1100,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('LEGACY_REINDEX_WAIT_FOR_TASK -> LEGACY_DELETE if action fails with target_index_had_write_block', () => { const res: ResponseType<'LEGACY_REINDEX_WAIT_FOR_TASK'> = Either.left({ type: 'target_index_had_write_block', @@ -1122,6 +1110,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('LEGACY_REINDEX_WAIT_FOR_TASK -> LEGACY_REINDEX_WAIT_FOR_TASK if action fails with wait_for_task_completion_timeout', () => { const res: ResponseType<'LEGACY_REINDEX_WAIT_FOR_TASK'> = Either.left({ message: '[timeout_exception] Timeout waiting for ...', @@ -1132,6 +1121,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(1); expect(newState.retryDelay).toEqual(2000); }); + test('LEGACY_REINDEX_WAIT_FOR_TASK -> LEGACY_REINDEX_WAIT_FOR_TASK with incremented retryCount if action fails with wait_for_task_completion_timeout a second time', () => { const state = Object.assign({}, legacyReindexWaitForTaskState, { retryCount: 1 }); const res: ResponseType<'LEGACY_REINDEX_WAIT_FOR_TASK'> = Either.left({ @@ -1154,6 +1144,7 @@ describe('migrations v2 model', () => { legacyPreMigrationDoneActions: [], legacyIndex: 'legacy_index_name', }; + test('LEGACY_DELETE -> SET_SOURCE_WRITE_BLOCK if action succeeds', () => { const res: ResponseType<'LEGACY_DELETE'> = Either.right('update_aliases_succeeded'); const newState = model(legacyDeleteState, res); @@ -1161,6 +1152,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('LEGACY_DELETE -> SET_SOURCE_WRITE_BLOCK if action fails with index_not_found_exception for legacy index', () => { const res: ResponseType<'LEGACY_REINDEX_WAIT_FOR_TASK'> = Either.left({ type: 'index_not_found_exception', @@ -1171,6 +1163,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('LEGACY_DELETE -> SET_SOURCE_WRITE_BLOCK if action fails with remove_index_not_a_concrete_index', () => { const res: ResponseType<'LEGACY_DELETE'> = Either.left({ type: 'remove_index_not_a_concrete_index', @@ -1209,25 +1202,20 @@ describe('migrations v2 model', () => { test('WAIT_FOR_YELLOW_SOURCE -> UPDATE_SOURCE_MAPPINGS_PROPERTIES', () => { const res: ResponseType<'WAIT_FOR_YELLOW_SOURCE'> = Either.right({}); const newState = model(waitForYellowSourceState, res); - - expect(newState).toMatchObject({ - controlState: 'UPDATE_SOURCE_MAPPINGS_PROPERTIES', - }); + expect(newState.controlState).toEqual('UPDATE_SOURCE_MAPPINGS_PROPERTIES'); }); }); describe('if the migrator is involved in a relocation', () => { // no need to attempt to update the mappings, we are going to reindex - test('WAIT_FOR_YELLOW_SOURCE -> CHECK_UNKNOWN_DOCUMENTS', () => { + test('WAIT_FOR_YELLOW_SOURCE -> CHECK_CLUSTER_ROUTING_ALLOCATION', () => { const res: ResponseType<'WAIT_FOR_YELLOW_SOURCE'> = Either.right({}); const newState = model( { ...waitForYellowSourceState, mustRelocateDocuments: true }, res ); - expect(newState).toMatchObject({ - controlState: 'CHECK_UNKNOWN_DOCUMENTS', - }); + expect(newState.controlState).toEqual('CHECK_CLUSTER_ROUTING_ALLOCATION'); }); }); }); @@ -1272,10 +1260,7 @@ describe('migrations v2 model', () => { 'update_mappings_succeeded' ); const newState = model(updateSourceMappingsPropertiesState, res); - - expect(newState).toMatchObject({ - controlState: 'CLEANUP_UNKNOWN_AND_EXCLUDED', - }); + expect(newState.controlState).toEqual('CLEANUP_UNKNOWN_AND_EXCLUDED'); }); test('UPDATE_SOURCE_MAPPINGS_PROPERTIES -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT if mappings changes are compatible and index is already migrated', () => { @@ -1305,15 +1290,12 @@ describe('migrations v2 model', () => { }); describe('if action fails', () => { - test('UPDATE_SOURCE_MAPPINGS_PROPERTIES -> CHECK_UNKNOWN_DOCUMENTS if mappings changes are incompatible', () => { + test('UPDATE_SOURCE_MAPPINGS_PROPERTIES -> CHECK_CLUSTER_ROUTING_ALLOCATION if mappings changes are incompatible', () => { const res: ResponseType<'UPDATE_SOURCE_MAPPINGS_PROPERTIES'> = Either.left({ type: 'incompatible_mapping_exception', }); const newState = model(updateSourceMappingsPropertiesState, res); - - expect(newState).toMatchObject({ - controlState: 'CHECK_UNKNOWN_DOCUMENTS', - }); + expect(newState.controlState).toEqual('CHECK_CLUSTER_ROUTING_ALLOCATION'); }); test('UPDATE_SOURCE_MAPPINGS_PROPERTIES -> FATAL', () => { @@ -1326,11 +1308,12 @@ describe('migrations v2 model', () => { .set(['aliases', '.kibana'], '.kibana_7.11.0_001') .value(), res - ); + ) as FatalState; - expect(newState).toMatchObject({ - controlState: 'FATAL', - }); + expect(newState.controlState).toEqual('FATAL'); + expect(newState.reason).toMatchInlineSnapshot( + `"Incompatible mappings change on already migrated Kibana instance."` + ); }); }); }); @@ -1485,6 +1468,35 @@ describe('migrations v2 model', () => { }); }); + describe('CHECK_CLUSTER_ROUTING_ALLOCATION', () => { + const checkClusterRoutingAllocationState: CheckClusterRoutingAllocationState = { + ...postInitState, + controlState: 'CHECK_CLUSTER_ROUTING_ALLOCATION', + sourceIndex: Option.some('.kibana') as Option.Some, + sourceIndexMappings: Option.some({}) as Option.Some, + }; + + test('CHECK_CLUSTER_ROUTING_ALLOCATION -> CHECK_CLUSTER_ROUTING_ALLOCATION when cluster allocation is not compatible', () => { + const res: ResponseType<'CHECK_CLUSTER_ROUTING_ALLOCATION'> = Either.left({ + type: 'incompatible_cluster_routing_allocation', + }); + const newState = model(checkClusterRoutingAllocationState, res); + + expect(newState.controlState).toBe('CHECK_CLUSTER_ROUTING_ALLOCATION'); + expect(newState.retryCount).toEqual(1); + expect(newState.retryDelay).toEqual(2000); + }); + + test('CHECK_CLUSTER_ROUTING_ALLOCATION -> CHECK_UNKNOWN_DOCUMENTS when cluster allocation is compatible', () => { + const res: ResponseType<'CHECK_CLUSTER_ROUTING_ALLOCATION'> = Either.right({}); + const newState = model(checkClusterRoutingAllocationState, res); + + expect(newState.controlState).toBe('CHECK_UNKNOWN_DOCUMENTS'); + expect(newState.retryCount).toEqual(0); + expect(newState.retryDelay).toEqual(0); + }); + }); + describe('CHECK_UNKNOWN_DOCUMENTS', () => { const mappingsWithUnknownType = { properties: { @@ -1626,6 +1638,7 @@ describe('migrations v2 model', () => { sourceIndex: Option.some('.kibana') as Option.Some, sourceIndexMappings: Option.some({}) as Option.Some, }; + test('SET_SOURCE_WRITE_BLOCK -> SET_SOURCE_WRITE_BLOCK if action fails with set_write_block_failed', () => { const res: ResponseType<'SET_SOURCE_WRITE_BLOCK'> = Either.left({ type: 'retryable_es_client_error', @@ -1636,6 +1649,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(1); expect(newState.retryDelay).toEqual(2000); }); + test('SET_SOURCE_WRITE_BLOCK -> CALCULATE_EXCLUDE_FILTERS if action succeeds with set_write_block_succeeded', () => { const res: ResponseType<'SET_SOURCE_WRITE_BLOCK'> = Either.right( 'set_write_block_succeeded' @@ -1645,6 +1659,15 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('SET_SOURCE_WRITE_BLOCK -> REFRESH_TARGET if source index matches target index', () => { + const index = `.kibana_${setWriteBlockState.kibanaVersion}_001`; + const res: ResponseType<'SET_SOURCE_WRITE_BLOCK'> = Either.left({ + type: 'source_equals_target' as const, + index, + }); + const newState = model(setWriteBlockState, res); + expect(newState.controlState).toEqual('REFRESH_TARGET'); + }); }); describe('CALCULATE_EXCLUDE_FILTERS', () => { @@ -1655,6 +1678,7 @@ describe('migrations v2 model', () => { sourceIndexMappings: Option.some({}) as Option.Some, tempIndexMappings: { properties: {} }, }; + test('CALCULATE_EXCLUDE_FILTERS -> CALCULATE_EXCLUDE_FILTERS if action fails with retryable error', () => { const res: ResponseType<'CALCULATE_EXCLUDE_FILTERS'> = Either.left({ type: 'retryable_es_client_error', @@ -1755,13 +1779,14 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('CREATE_REINDEX_TEMP -> FATAL if action fails with cluster_shard_limit_exceeded', () => { const res: ResponseType<'CREATE_REINDEX_TEMP'> = Either.left({ type: 'cluster_shard_limit_exceeded', }); - const newState = model(state, res); + const newState = model(state, res) as FatalState; expect(newState.controlState).toEqual('FATAL'); - expect((newState as FatalState).reason).toMatchInlineSnapshot( + expect(newState.reason).toMatchInlineSnapshot( `"[cluster_shard_limit_exceeded] Upgrading Kibana requires adding a small number of new shards. Ensure that Kibana is able to add 10 more shards by increasing the cluster.max_shards_per_node setting, or removing indices to clear up resources. See clusterShardLimitExceeded"` ); }); @@ -1807,9 +1832,9 @@ describe('migrations v2 model', () => { type: 'synchronization_failed', error: new Error('Other migrators failed to reach the synchronization point'), }); - const newState = model(state, res); + const newState = model(state, res) as FatalState; expect(newState.controlState).toEqual('FATAL'); - expect((newState as FatalState).reason).toMatchInlineSnapshot( + expect(newState.reason).toMatchInlineSnapshot( `"An error occurred whilst waiting for other migrators to get to this step."` ); }); @@ -1946,12 +1971,13 @@ describe('migrations v2 model', () => { contentLength: 2345, }); const newState = model({ ...state, batchSize: 1 }, res) as FatalState; - expect(newState.controlState).toBe('FATAL'); - expect(newState.batchSize).toBe(1); // don't halve the batch size or go below 1 - expect(newState.maxBatchSize).toBe(1000); // leaves maxBatchSize unchanged - expect(newState.reason).toMatchInlineSnapshot( - `"After reducing the read batch size to a single document, the Elasticsearch response content length was 2345bytes which still exceeded migrations.maxReadBatchSizeBytes. Increase migrations.maxReadBatchSizeBytes and try again."` - ); + expect(newState).toMatchObject({ + controlState: 'FATAL', + batchSize: 1, + maxBatchSize: 1000, + reason: + 'After reducing the read batch size to a single document, the Elasticsearch response content length was 2345bytes which still exceeded migrations.maxReadBatchSizeBytes. Increase migrations.maxReadBatchSizeBytes and try again.', + }); }); it('REINDEX_SOURCE_TO_TEMP_READ -> REINDEX_SOURCE_TO_TEMP_CLOSE_PIT if no outdated documents to reindex', () => { @@ -2064,14 +2090,15 @@ describe('migrations v2 model', () => { const newState = model(state, res); expect(newState.controlState).toEqual('SET_TEMP_WRITE_BLOCK'); }); + test('DONE_REINDEXING_SYNC -> FATAL if the synchronization between migrators fails', () => { const res: ResponseType<'DONE_REINDEXING_SYNC'> = Either.left({ type: 'synchronization_failed', error: new Error('Other migrators failed to reach the synchronization point'), }); - const newState = model(state, res); + const newState = model(state, res) as FatalState; expect(newState.controlState).toEqual('FATAL'); - expect((newState as FatalState).reason).toMatchInlineSnapshot( + expect(newState.reason).toMatchInlineSnapshot( `"An error occurred whilst waiting for other migrators to get to this step."` ); }); @@ -2168,6 +2195,7 @@ describe('migrations v2 model', () => { corruptDocumentIds: [], progress: createInitialProgress(), }; + test('REINDEX_SOURCE_TO_TEMP_INDEX_BULK -> REINDEX_SOURCE_TO_TEMP_READ if action succeeded', () => { const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_INDEX_BULK'> = Either.right('bulk_index_succeeded'); @@ -2176,6 +2204,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('REINDEX_SOURCE_TO_TEMP_INDEX_BULK -> REINDEX_SOURCE_TO_TEMP_CLOSE_PIT if response is left target_index_had_write_block', () => { const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_INDEX_BULK'> = Either.left({ type: 'target_index_had_write_block', @@ -2185,6 +2214,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('REINDEX_SOURCE_TO_TEMP_INDEX_BULK -> REINDEX_SOURCE_TO_TEMP_CLOSE_PIT if response is left index_not_found_exception', () => { const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_INDEX_BULK'> = Either.left({ type: 'index_not_found_exception', @@ -2195,6 +2225,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('REINDEX_SOURCE_TO_TEMP_INDEX_BULK -> FATAL if action returns left request_entity_too_large_exception', () => { const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_INDEX_BULK'> = Either.left({ type: 'request_entity_too_large_exception', @@ -2205,6 +2236,7 @@ describe('migrations v2 model', () => { `"While indexing a batch of saved objects, Elasticsearch returned a 413 Request Entity Too Large exception. Ensure that the Kibana configuration option 'migrations.maxBatchSizeBytes' is set to a value that is lower than or equal to the Elasticsearch 'http.max_content_length' configuration option."` ); }); + test('REINDEX_SOURCE_TO_TEMP_INDEX_BULK should throw a throwBadResponse error if action failed', () => { const res: ResponseType<'REINDEX_SOURCE_TO_TEMP_INDEX_BULK'> = Either.left({ type: 'retryable_es_client_error', @@ -2224,6 +2256,7 @@ describe('migrations v2 model', () => { sourceIndex: Option.some('.kibana') as Option.Some, sourceIndexMappings: Option.some({}) as Option.Some, }; + test('SET_TEMP_WRITE_BLOCK -> CLONE_TEMP_TO_TARGET when response is right', () => { const res: ResponseType<'SET_TEMP_WRITE_BLOCK'> = Either.right('set_write_block_succeeded'); const newState = model(state, res); @@ -2290,13 +2323,14 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toBe(0); expect(newState.retryDelay).toBe(0); }); + test('CLONE_TEMP_TO_TARGET -> FATAL if action fails with cluster_shard_limit_exceeded', () => { const res: ResponseType<'CLONE_TEMP_TO_TARGET'> = Either.left({ type: 'cluster_shard_limit_exceeded', }); - const newState = model(state, res); + const newState = model(state, res) as FatalState; expect(newState.controlState).toEqual('FATAL'); - expect((newState as FatalState).reason).toMatchInlineSnapshot( + expect(newState.reason).toMatchInlineSnapshot( `"[cluster_shard_limit_exceeded] Upgrading Kibana requires adding a small number of new shards. Ensure that Kibana is able to add 10 more shards by increasing the cluster.max_shards_per_node setting, or removing indices to clear up resources. See clusterShardLimitExceeded"` ); }); @@ -2678,7 +2712,7 @@ describe('migrations v2 model', () => { it('REFRESH_TARGET -> OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT if action succeeded', () => { const res: ResponseType<'REFRESH_TARGET'> = Either.right({ refreshed: true }); - const newState = model(state, res) as UpdateTargetMappingsPropertiesState; + const newState = model(state, res); expect(newState.controlState).toBe('OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT'); }); }); @@ -2707,6 +2741,7 @@ describe('migrations v2 model', () => { hasTransformedDocs: false, progress: createInitialProgress(), }; + describe('OUTDATED_DOCUMENTS_TRANSFORM if action succeeds', () => { test('OUTDATED_DOCUMENTS_TRANSFORM -> TRANSFORMED_DOCUMENTS_BULK_INDEX if action succeeds', () => { const res: ResponseType<'OUTDATED_DOCUMENTS_TRANSFORM'> = Either.right({ processedDocs }); @@ -2721,6 +2756,7 @@ describe('migrations v2 model', () => { expect(newState.retryDelay).toEqual(0); expect(newState.progress.processed).toBe(outdatedDocuments.length); }); + test('OUTDATED_DOCUMENTS_TRANSFORM -> OUTDATED_DOCUMENTS_SEARCH_READ if there are are existing documents that failed transformation', () => { const outdatedDocumentsTransformStateWithFailedDocuments: OutdatedDocumentsTransform = { ...outdatedDocumentsTransformState, @@ -2738,6 +2774,7 @@ describe('migrations v2 model', () => { expect(newState.retryDelay).toEqual(0); expect(newState.progress.processed).toBe(outdatedDocuments.length); }); + test('OUTDATED_DOCUMENTS_TRANSFORM -> OUTDATED_DOCUMENTS_SEARCH_READ if there are are existing documents that failed transformation because of transform errors', () => { const outdatedDocumentsTransformStateWithFailedDocuments: OutdatedDocumentsTransform = { ...outdatedDocumentsTransformState, @@ -2757,6 +2794,7 @@ describe('migrations v2 model', () => { expect(newState.progress.processed).toBe(outdatedDocuments.length); }); }); + describe('OUTDATED_DOCUMENTS_TRANSFORM if action fails', () => { test('OUTDATED_DOCUMENTS_TRANSFORM -> OUTDATED_DOCUMENTS_SEARCH_READ adding newly failed documents to state if documents failed the transform', () => { const res: ResponseType<'OUTDATED_DOCUMENTS_TRANSFORM'> = Either.left({ @@ -2773,6 +2811,7 @@ describe('migrations v2 model', () => { expect(newState.corruptDocumentIds).toEqual(corruptDocumentIds); expect(newState.progress.processed).toBe(outdatedDocuments.length); }); + test('OUTDATED_DOCUMENTS_TRANSFORM -> OUTDATED_DOCUMENTS_SEARCH_READ combines newly failed documents with those already on state if documents failed the transform', () => { const newFailedTransformDocumentIds = ['b:other', 'c:__']; const outdatedDocumentsTransformStateWithFailedDocuments: OutdatedDocumentsTransform = { @@ -2908,6 +2947,7 @@ describe('migrations v2 model', () => { }, }), }; + test('UPDATE_TARGET_MAPPINGS_PROPERTIES -> UPDATE_TARGET_MAPPINGS_PROPERTIES_WAIT_FOR_TASK', () => { const res: ResponseType<'UPDATE_TARGET_MAPPINGS_PROPERTIES'> = Either.right({ taskId: 'update target mappings task', @@ -2952,10 +2992,7 @@ describe('migrations v2 model', () => { message: '[timeout_exception] Timeout waiting for ...', type: 'wait_for_task_completion_timeout', }); - const newState = model( - updateTargetMappingsWaitForTaskState, - res - ) as UpdateTargetMappingsPropertiesWaitForTaskState; + const newState = model(updateTargetMappingsWaitForTaskState, res); expect(newState.controlState).toEqual('UPDATE_TARGET_MAPPINGS_PROPERTIES_WAIT_FOR_TASK'); expect(newState.retryCount).toEqual(1); expect(newState.retryDelay).toEqual(2000); @@ -2967,7 +3004,7 @@ describe('migrations v2 model', () => { message: '[timeout_exception] Timeout waiting for ...', type: 'wait_for_task_completion_timeout', }); - const newState = model(state, res) as UpdateTargetMappingsPropertiesWaitForTaskState; + const newState = model(state, res); expect(newState.controlState).toEqual('UPDATE_TARGET_MAPPINGS_PROPERTIES_WAIT_FOR_TASK'); expect(newState.retryCount).toEqual(2); expect(newState.retryDelay).toEqual(4000); @@ -3014,7 +3051,7 @@ describe('migrations v2 model', () => { versionIndexReadyActions, }, res - ) as PostInitState; + ); expect(newState.controlState).toEqual('MARK_VERSION_INDEX_READY'); expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); @@ -3032,14 +3069,14 @@ describe('migrations v2 model', () => { versionIndexReadyActions, }, res - ) as PostInitState; + ); expect(newState.controlState).toEqual('MARK_VERSION_INDEX_READY_SYNC'); expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); test('CHECK_VERSION_INDEX_READY_ACTIONS -> DONE if none versionIndexReadyActions', () => { - const newState = model(сheckVersionIndexReadyActionsState, res) as PostInitState; + const newState = model(сheckVersionIndexReadyActionsState, res); expect(newState.controlState).toEqual('DONE'); expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); @@ -3057,6 +3094,7 @@ describe('migrations v2 model', () => { sourceIndex: Option.none as Option.None, targetIndex: '.kibana_7.11.0_001', }; + test('CREATE_NEW_TARGET -> CHECK_VERSION_INDEX_READY_ACTIONS', () => { const res: ResponseType<'CREATE_NEW_TARGET'> = Either.right('create_index_succeeded'); const newState = model(createNewTargetState, res); @@ -3064,6 +3102,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('CREATE_NEW_TARGET -> CREATE_NEW_TARGET if action fails with index_not_green_timeout', () => { const res: ResponseType<'CREATE_NEW_TARGET'> = Either.left({ message: '[index_not_green_timeout] Timeout waiting for ...', @@ -3074,6 +3113,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(1); expect(newState.retryDelay).toEqual(2000); }); + test('CREATE_NEW_TARGET -> CHECK_VERSION_INDEX_READY_ACTIONS resets the retry count and delay', () => { const res: ResponseType<'CREATE_NEW_TARGET'> = Either.right('create_index_succeeded'); const testState = { @@ -3087,13 +3127,14 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('CREATE_NEW_TARGET -> FATAL if action fails with cluster_shard_limit_exceeded', () => { const res: ResponseType<'CREATE_NEW_TARGET'> = Either.left({ type: 'cluster_shard_limit_exceeded', }); - const newState = model(createNewTargetState, res); + const newState = model(createNewTargetState, res) as FatalState; expect(newState.controlState).toEqual('FATAL'); - expect((newState as FatalState).reason).toMatchInlineSnapshot( + expect(newState.reason).toMatchInlineSnapshot( `"[cluster_shard_limit_exceeded] Upgrading Kibana requires adding a small number of new shards. Ensure that Kibana is able to add 10 more shards by increasing the cluster.max_shards_per_node setting, or removing indices to clear up resources. See clusterShardLimitExceeded"` ); }); @@ -3109,6 +3150,7 @@ describe('migrations v2 model', () => { versionIndexReadyActions: aliasActions, targetIndex: '.kibana_7.11.0_001', }; + test('MARK_VERSION_INDEX_READY -> DONE if the action succeeded', () => { const res: ResponseType<'MARK_VERSION_INDEX_READY'> = Either.right( 'update_aliases_succeeded' @@ -3118,6 +3160,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('MARK_VERSION_INDEX_READY -> MARK_VERSION_INDEX_CONFLICT if another removed the current alias from the source index', () => { const res: ResponseType<'MARK_VERSION_INDEX_READY'> = Either.left({ type: 'alias_not_found_exception', @@ -3127,6 +3170,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('MARK_VERSION_INDEX_READY -> MARK_VERSION_INDEX_CONFLICT if another node removed the temporary index', () => { const res: ResponseType<'MARK_VERSION_INDEX_READY'> = Either.left({ type: 'index_not_found_exception', @@ -3149,6 +3193,7 @@ describe('migrations v2 model', () => { versionIndexReadyActions: aliasActions, targetIndex: '.kibana_7.11.0_001', }; + test('MARK_VERSION_INDEX_CONFLICT -> DONE if the current alias is pointing to the version alias', () => { const res: ResponseType<'MARK_VERSION_INDEX_READY_CONFLICT'> = Either.right({ '.kibana_7.11.0_001': { @@ -3170,6 +3215,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('MARK_VERSION_INDEX_READY_CONFLICT -> FATAL if the current alias is pointing to a different version index', () => { const res: ResponseType<'MARK_VERSION_INDEX_READY_CONFLICT'> = Either.right({ '.kibana_7.11.0_001': { @@ -3194,6 +3240,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); + test('MARK_VERSION_INDEX_READY_CONFLICT -> FATAL if the current alias is pointing to a multiple indices', () => { const res: ResponseType<'MARK_VERSION_INDEX_READY_CONFLICT'> = Either.right({ '.kibana_7.11.0_001': { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts index e25d48af4ed25..cf0fe5fa3396b 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts @@ -82,193 +82,181 @@ export const model = (currentState: State, resW: ResponseType): if (stateP.controlState === 'INIT') { const res = resW as ExcludeRetryableEsError>; - if (Either.isLeft(res)) { - const left = res.left; - if (isTypeof(left, 'incompatible_cluster_routing_allocation')) { - const retryErrorMessage = `[${left.type}] Incompatible Elasticsearch cluster settings detected. Remove the persistent and transient Elasticsearch cluster setting 'cluster.routing.allocation.enable' or set it to a value of 'all' to allow migrations to proceed. Refer to ${stateP.migrationDocLinks.routingAllocationDisabled} for more information on how to resolve the issue.`; - return delayRetryState(stateP, retryErrorMessage, stateP.retryAttempts); - } else { - throwBadResponse(stateP, left); - } - } else if (Either.isRight(res)) { - // cluster routing allocation is enabled and we can continue with the migration as normal - const indices = res.right; - const aliasesRes = getAliases(indices); + // cluster routing allocation is enabled and we can continue with the migration as normal + const indices = res.right; + const aliasesRes = getAliases(indices); - if (Either.isLeft(aliasesRes)) { - return { - ...stateP, - controlState: 'FATAL', - reason: `The ${ - aliasesRes.left.alias - } alias is pointing to multiple indices: ${aliasesRes.left.indices.join(',')}.`, - }; - } + if (Either.isLeft(aliasesRes)) { + return { + ...stateP, + controlState: 'FATAL', + reason: `The ${ + aliasesRes.left.alias + } alias is pointing to multiple indices: ${aliasesRes.left.indices.join(',')}.`, + }; + } - const aliases = aliasesRes.right; + const aliases = aliasesRes.right; - if ( - // `.kibana` is pointing to an index that belongs to a later - // version of Kibana .e.g. a 7.11.0 instance found the `.kibana` alias - // pointing to `.kibana_7.12.0_001` - indexBelongsToLaterVersion(stateP.kibanaVersion, aliases[stateP.currentAlias]) - ) { - return { - ...stateP, - controlState: 'FATAL', - reason: `The ${ - stateP.currentAlias - } alias is pointing to a newer version of Kibana: v${indexVersion( - aliases[stateP.currentAlias] - )}`, - }; - } + if ( + // `.kibana` is pointing to an index that belongs to a later + // version of Kibana .e.g. a 7.11.0 instance found the `.kibana` alias + // pointing to `.kibana_7.12.0_001` + indexBelongsToLaterVersion(stateP.kibanaVersion, aliases[stateP.currentAlias]) + ) { + return { + ...stateP, + controlState: 'FATAL', + reason: `The ${ + stateP.currentAlias + } alias is pointing to a newer version of Kibana: v${indexVersion( + aliases[stateP.currentAlias] + )}`, + }; + } - const laterVersionAlias = hasLaterVersionAlias(stateP.kibanaVersion, aliases); - if ( - // a `.kibana_` alias exist, which refers to a later version of Kibana - // e.g. `.kibana_8.7.0` exists, and current stack version is 8.6.1 - // see https://github.com/elastic/kibana/issues/155136 - laterVersionAlias - ) { - return { - ...stateP, - controlState: 'FATAL', - reason: `The ${laterVersionAlias} alias refers to a newer version of Kibana: v${aliasVersion( - laterVersionAlias - )}`, - }; - } + const laterVersionAlias = hasLaterVersionAlias(stateP.kibanaVersion, aliases); + if ( + // a `.kibana_` alias exist, which refers to a later version of Kibana + // e.g. `.kibana_8.7.0` exists, and current stack version is 8.6.1 + // see https://github.com/elastic/kibana/issues/155136 + laterVersionAlias + ) { + return { + ...stateP, + controlState: 'FATAL', + reason: `The ${laterVersionAlias} alias refers to a newer version of Kibana: v${aliasVersion( + laterVersionAlias + )}`, + }; + } - // The source index .kibana is pointing to. E.g: ".kibana_8.7.0_001" - const source = aliases[stateP.currentAlias]; - // The target index .kibana WILL be pointing to if we reindex. E.g: ".kibana_8.8.0_001" - const newVersionTarget = stateP.versionIndex; + // The source index .kibana is pointing to. E.g: ".kibana_8.7.0_001" + const source = aliases[stateP.currentAlias]; + // The target index .kibana WILL be pointing to if we reindex. E.g: ".kibana_8.8.0_001" + const newVersionTarget = stateP.versionIndex; - const postInitState = { - aliases, - sourceIndex: Option.fromNullable(source), - sourceIndexMappings: Option.fromNullable(source ? indices[source]?.mappings : undefined), - versionIndexReadyActions: Option.none, - }; + const postInitState = { + aliases, + sourceIndex: Option.fromNullable(source), + sourceIndexMappings: Option.fromNullable(source ? indices[source]?.mappings : undefined), + versionIndexReadyActions: Option.none, + }; - if ( - // Don't actively participate in this migration but wait for another instance to complete it - stateP.waitForMigrationCompletion === true - ) { - return { - ...stateP, - ...postInitState, - sourceIndex: Option.none, - targetIndex: newVersionTarget, - controlState: 'WAIT_FOR_MIGRATION_COMPLETION', - // Wait for 2s before checking again if the migration has completed - retryDelay: 2000, - logs: [ - ...stateP.logs, - { - level: 'info', - message: `Migration required. Waiting until another Kibana instance completes the migration.`, - }, - ], - }; - } else if ( - // If the `.kibana` alias exists - Option.isSome(postInitState.sourceIndex) - ) { - return { - ...stateP, - ...postInitState, - controlState: 'WAIT_FOR_YELLOW_SOURCE', - sourceIndex: postInitState.sourceIndex, - sourceIndexMappings: postInitState.sourceIndexMappings as Option.Some, - targetIndex: postInitState.sourceIndex.value, // We preserve the same index, source == target (E.g: ".xx8.7.0_001") - }; - } else if (indices[stateP.legacyIndex] != null) { - // Migrate from a legacy index + if ( + // Don't actively participate in this migration but wait for another instance to complete it + stateP.waitForMigrationCompletion === true + ) { + return { + ...stateP, + ...postInitState, + sourceIndex: Option.none, + targetIndex: newVersionTarget, + controlState: 'WAIT_FOR_MIGRATION_COMPLETION', + // Wait for 2s before checking again if the migration has completed + retryDelay: 2000, + logs: [ + ...stateP.logs, + { + level: 'info', + message: `Migration required. Waiting until another Kibana instance completes the migration.`, + }, + ], + }; + } else if ( + // If the `.kibana` alias exists + Option.isSome(postInitState.sourceIndex) + ) { + return { + ...stateP, + ...postInitState, + controlState: 'WAIT_FOR_YELLOW_SOURCE', + sourceIndex: postInitState.sourceIndex, + sourceIndexMappings: postInitState.sourceIndexMappings as Option.Some, + targetIndex: postInitState.sourceIndex.value, // We preserve the same index, source == target (E.g: ".xx8.7.0_001") + }; + } else if (indices[stateP.legacyIndex] != null) { + // Migrate from a legacy index - // If the user used default index names we can narrow the version - // number we use when creating a backup index. This is purely to help - // users more easily identify how "old" and index is so that they can - // decide if it's safe to delete these rollback backups. Because - // backups are kept for rollback, a version number is more useful than - // a date. - let legacyVersion = ''; - if (stateP.indexPrefix === '.kibana') { - legacyVersion = 'pre6.5.0'; - } else if (stateP.indexPrefix === '.kibana_task_manager') { - legacyVersion = 'pre7.4.0'; - } else { - legacyVersion = 'pre' + stateP.kibanaVersion; - } + // If the user used default index names we can narrow the version + // number we use when creating a backup index. This is purely to help + // users more easily identify how "old" and index is so that they can + // decide if it's safe to delete these rollback backups. Because + // backups are kept for rollback, a version number is more useful than + // a date. + let legacyVersion = ''; + if (stateP.indexPrefix === '.kibana') { + legacyVersion = 'pre6.5.0'; + } else if (stateP.indexPrefix === '.kibana_task_manager') { + legacyVersion = 'pre7.4.0'; + } else { + legacyVersion = 'pre' + stateP.kibanaVersion; + } - const legacyReindexTarget = `${stateP.indexPrefix}_${legacyVersion}_001`; + const legacyReindexTarget = `${stateP.indexPrefix}_${legacyVersion}_001`; - return { - ...stateP, - ...postInitState, - controlState: 'LEGACY_SET_WRITE_BLOCK', - sourceIndex: Option.some(legacyReindexTarget) as Option.Some, - sourceIndexMappings: Option.some( - indices[stateP.legacyIndex].mappings - ) as Option.Some, - targetIndex: newVersionTarget, - legacyPreMigrationDoneActions: [ - { remove_index: { index: stateP.legacyIndex } }, - { - add: { - index: legacyReindexTarget, - alias: stateP.currentAlias, - }, + return { + ...stateP, + ...postInitState, + controlState: 'LEGACY_CHECK_CLUSTER_ROUTING_ALLOCATION', + sourceIndex: Option.some(legacyReindexTarget) as Option.Some, + sourceIndexMappings: Option.some( + indices[stateP.legacyIndex].mappings + ) as Option.Some, + targetIndex: newVersionTarget, + legacyPreMigrationDoneActions: [ + { remove_index: { index: stateP.legacyIndex } }, + { + add: { + index: legacyReindexTarget, + alias: stateP.currentAlias, }, - ], - versionIndexReadyActions: Option.some([ - { - remove: { - index: legacyReindexTarget, - alias: stateP.currentAlias, - must_exist: true, - }, + }, + ], + versionIndexReadyActions: Option.some([ + { + remove: { + index: legacyReindexTarget, + alias: stateP.currentAlias, + must_exist: true, }, - { add: { index: newVersionTarget, alias: stateP.currentAlias } }, - { add: { index: newVersionTarget, alias: stateP.versionAlias } }, - { remove_index: { index: stateP.tempIndex } }, - ]), - }; - } else if ( - // if we must relocate documents to this migrator's index, but the index does NOT yet exist: - // this migrator must create a temporary index and synchronize with other migrators - // this is a similar flow to the reindex one, but this migrator will not reindexing anything - stateP.mustRelocateDocuments - ) { - return { - ...stateP, - ...postInitState, - controlState: 'CREATE_REINDEX_TEMP', - sourceIndex: Option.none as Option.None, - targetIndex: newVersionTarget, - versionIndexReadyActions: Option.some([ - { add: { index: newVersionTarget, alias: stateP.currentAlias } }, - { add: { index: newVersionTarget, alias: stateP.versionAlias } }, - { remove_index: { index: stateP.tempIndex } }, - ]), - }; - } else { - // no need to copy anything over from other indices, we can start with a clean, empty index - return { - ...stateP, - ...postInitState, - controlState: 'CREATE_NEW_TARGET', - sourceIndex: Option.none as Option.None, - targetIndex: newVersionTarget, - versionIndexReadyActions: Option.some([ - { add: { index: newVersionTarget, alias: stateP.currentAlias } }, - { add: { index: newVersionTarget, alias: stateP.versionAlias } }, - ]) as Option.Some, - }; - } + }, + { add: { index: newVersionTarget, alias: stateP.currentAlias } }, + { add: { index: newVersionTarget, alias: stateP.versionAlias } }, + { remove_index: { index: stateP.tempIndex } }, + ]), + }; + } else if ( + // if we must relocate documents to this migrator's index, but the index does NOT yet exist: + // this migrator must create a temporary index and synchronize with other migrators + // this is a similar flow to the reindex one, but this migrator will not reindexing anything + stateP.mustRelocateDocuments + ) { + return { + ...stateP, + ...postInitState, + controlState: 'CREATE_REINDEX_TEMP', + sourceIndex: Option.none as Option.None, + targetIndex: newVersionTarget, + versionIndexReadyActions: Option.some([ + { add: { index: newVersionTarget, alias: stateP.currentAlias } }, + { add: { index: newVersionTarget, alias: stateP.versionAlias } }, + { remove_index: { index: stateP.tempIndex } }, + ]), + }; } else { - throwBadResponse(stateP, res); + // no need to copy anything over from other indices, we can start with a clean, empty index + return { + ...stateP, + ...postInitState, + controlState: 'CREATE_NEW_TARGET', + sourceIndex: Option.none as Option.None, + targetIndex: newVersionTarget, + versionIndexReadyActions: Option.some([ + { add: { index: newVersionTarget, alias: stateP.currentAlias } }, + { add: { index: newVersionTarget, alias: stateP.versionAlias } }, + ]) as Option.Some, + }; } } else if (stateP.controlState === 'WAIT_FOR_MIGRATION_COMPLETION') { const res = resW as ExcludeRetryableEsError>; @@ -306,6 +294,22 @@ export const model = (currentState: State, resW: ResponseType): ], }; } + } else if (stateP.controlState === 'LEGACY_CHECK_CLUSTER_ROUTING_ALLOCATION') { + const res = resW as ExcludeRetryableEsError>; + if (Either.isRight(res)) { + return { + ...stateP, + controlState: 'LEGACY_SET_WRITE_BLOCK', + }; + } else { + const left = res.left; + if (isTypeof(left, 'incompatible_cluster_routing_allocation')) { + const retryErrorMessage = `[${left.type}] Incompatible Elasticsearch cluster settings detected. Remove the persistent and transient Elasticsearch cluster setting 'cluster.routing.allocation.enable' or set it to a value of 'all' to allow migrations to proceed. Refer to ${stateP.migrationDocLinks.routingAllocationDisabled} for more information on how to resolve the issue.`; + return delayRetryState(stateP, retryErrorMessage, stateP.retryAttempts); + } else { + throwBadResponse(stateP, left); + } + } } else if (stateP.controlState === 'LEGACY_SET_WRITE_BLOCK') { const res = resW as ExcludeRetryableEsError>; // If the write block is successfully in place @@ -457,7 +461,7 @@ export const model = (currentState: State, resW: ResponseType): // we must reindex and synchronize with other migrators return { ...stateP, - controlState: 'CHECK_UNKNOWN_DOCUMENTS', + controlState: 'CHECK_CLUSTER_ROUTING_ALLOCATION', }; } else { // this migrator is not involved in a relocation, we can proceed with the standard flow @@ -502,7 +506,7 @@ export const model = (currentState: State, resW: ResponseType): case MigrationType.Incompatible: return { ...stateP, - controlState: 'CHECK_UNKNOWN_DOCUMENTS', + controlState: 'CHECK_CLUSTER_ROUTING_ALLOCATION', }; case MigrationType.Unnecessary: return { @@ -636,7 +640,7 @@ export const model = (currentState: State, resW: ResponseType): ...stateP, controlState: stateP.mustRefresh ? 'REFRESH_SOURCE' : 'OUTDATED_DOCUMENTS_SEARCH_OPEN_PIT', }; - } else if (Either.isLeft(res)) { + } else { const left = res.left; // Note: if multiple newer Kibana versions are competing with each other to perform a migration, // it might happen that another Kibana instance has deleted this instance's version index. @@ -661,11 +665,8 @@ export const model = (currentState: State, resW: ResponseType): // step). throwBadResponse(stateP, left as never); } else { - // TODO update to handle 2 more cases throwBadResponse(stateP, left); } - } else { - throwBadResponse(stateP, res); } } else if (stateP.controlState === 'REFRESH_SOURCE') { const res = resW as ExcludeRetryableEsError>; @@ -677,6 +678,22 @@ export const model = (currentState: State, resW: ResponseType): } else { throwBadResponse(stateP, res); } + } else if (stateP.controlState === 'CHECK_CLUSTER_ROUTING_ALLOCATION') { + const res = resW as ExcludeRetryableEsError>; + if (Either.isRight(res)) { + return { + ...stateP, + controlState: 'CHECK_UNKNOWN_DOCUMENTS', + }; + } else { + const left = res.left; + if (isTypeof(left, 'incompatible_cluster_routing_allocation')) { + const retryErrorMessage = `[${left.type}] Incompatible Elasticsearch cluster settings detected. Remove the persistent and transient Elasticsearch cluster setting 'cluster.routing.allocation.enable' or set it to a value of 'all' to allow migrations to proceed. Refer to ${stateP.migrationDocLinks.routingAllocationDisabled} for more information on how to resolve the issue.`; + return delayRetryState(stateP, retryErrorMessage, stateP.retryAttempts); + } else { + throwBadResponse(stateP, left); + } + } } else if (stateP.controlState === 'CHECK_UNKNOWN_DOCUMENTS') { const res = resW as ExcludeRetryableEsError>; @@ -736,6 +753,14 @@ export const model = (currentState: State, resW: ResponseType): ...stateP, controlState: 'CALCULATE_EXCLUDE_FILTERS', }; + } else if (isTypeof(res.left, 'source_equals_target')) { + // As part of a reindex-migration, we wanted to block the source index to prevent updates + // However, this migrator's source index matches the target index. + // Thus, another instance's migrator is ahead of us. We skip the clone steps and continue the flow + return { + ...stateP, + controlState: 'REFRESH_TARGET', + }; } else if (isTypeof(res.left, 'index_not_found_exception')) { // We don't handle the following errors as the migration algorithm // will never cause them to occur: @@ -1296,11 +1321,11 @@ export const model = (currentState: State, resW: ResponseType): Either.isRight(res) || (isTypeof(res.left, 'documents_transform_failed') && stateP.discardCorruptObjects) ) { - // we might have some transformation errors, but user has chosen to discard them if ( (stateP.corruptDocumentIds.length === 0 && stateP.transformErrors.length === 0) || stateP.discardCorruptObjects ) { + // we might have some transformation errors from previous iterations, but user has chosen to discard them const documents = Either.isRight(res) ? res.right.processedDocs : res.left.processedDocs; let corruptDocumentIds = stateP.corruptDocumentIds; @@ -1338,8 +1363,10 @@ export const model = (currentState: State, resW: ResponseType): }; } } else { - // We have seen corrupt documents and/or transformation errors - // skip indexing and go straight to reading and transforming more docs + // At this point, there are some corrupt documents and/or transformation errors + // from previous iterations and we're not discarding them. + // Also, the current batch of SEARCH_READ documents has been transformed successfully + // so there is no need to append them to the lists of corruptDocumentIds, transformErrors. return { ...stateP, controlState: 'OUTDATED_DOCUMENTS_SEARCH_READ', diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts index 9cd7f92ee4355..2ab4a0d927b84 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts @@ -80,7 +80,7 @@ export const nextActionMap = ( ) => { return { INIT: (state: InitState) => - Actions.initAction({ client, indices: [state.currentAlias, state.versionAlias] }), + Actions.fetchIndices({ client, indices: [state.currentAlias, state.versionAlias] }), WAIT_FOR_MIGRATION_COMPLETION: (state: WaitForMigrationCompletionState) => Actions.fetchIndices({ client, indices: [state.currentAlias, state.versionAlias] }), WAIT_FOR_YELLOW_SOURCE: (state: WaitForYellowSourceState) => @@ -117,6 +117,7 @@ export const nextActionMap = ( Actions.updateAliases({ client, aliasActions: state.preTransformDocsActions }), REFRESH_SOURCE: (state: RefreshSource) => Actions.refreshIndex({ client, index: state.sourceIndex.value }), + CHECK_CLUSTER_ROUTING_ALLOCATION: () => Actions.checkClusterRoutingAllocationEnabled(client), CHECK_UNKNOWN_DOCUMENTS: (state: CheckUnknownDocumentsState) => Actions.checkForUnknownDocs({ client, @@ -125,7 +126,11 @@ export const nextActionMap = ( knownTypes: state.knownTypes, }), SET_SOURCE_WRITE_BLOCK: (state: SetSourceWriteBlockState) => - Actions.setWriteBlock({ client, index: state.sourceIndex.value }), + Actions.safeWriteBlock({ + client, + sourceIndex: state.sourceIndex.value, + targetIndex: state.targetIndex, + }), CALCULATE_EXCLUDE_FILTERS: (state: CalculateExcludeFiltersState) => Actions.calculateExcludeFilters({ client, @@ -283,6 +288,8 @@ export const nextActionMap = ( ), MARK_VERSION_INDEX_READY_CONFLICT: (state: MarkVersionIndexReadyConflict) => Actions.fetchIndices({ client, indices: [state.currentAlias, state.versionAlias] }), + LEGACY_CHECK_CLUSTER_ROUTING_ALLOCATION: () => + Actions.checkClusterRoutingAllocationEnabled(client), LEGACY_SET_WRITE_BLOCK: (state: LegacySetWriteBlockState) => Actions.setWriteBlock({ client, index: state.legacyIndex }), LEGACY_CREATE_REINDEX_TARGET: (state: LegacyCreateReindexTargetState) => diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts index b26088adc53e9..fc32f88ddd910 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts @@ -384,6 +384,10 @@ export interface RefreshTarget extends PostInitState { readonly targetIndex: string; } +export interface CheckClusterRoutingAllocationState extends SourceExistsState { + readonly controlState: 'CHECK_CLUSTER_ROUTING_ALLOCATION'; +} + export interface CheckTargetTypesMappingsState extends PostInitState { readonly controlState: 'CHECK_TARGET_MAPPINGS'; } @@ -510,6 +514,10 @@ export interface LegacyBaseState extends SourceExistsState { readonly legacyPreMigrationDoneActions: AliasAction[]; } +export interface LegacyCheckClusterRoutingAllocationState extends LegacyBaseState { + readonly controlState: 'LEGACY_CHECK_CLUSTER_ROUTING_ALLOCATION'; +} + export interface LegacySetWriteBlockState extends LegacyBaseState { /** Set a write block on the legacy index to prevent any further writes */ readonly controlState: 'LEGACY_SET_WRITE_BLOCK'; @@ -549,6 +557,7 @@ export interface LegacyDeleteState extends LegacyBaseState { export type State = Readonly< | CalculateExcludeFiltersState + | CheckClusterRoutingAllocationState | CheckTargetTypesMappingsState | CheckUnknownDocumentsState | CheckVersionIndexReadyActions @@ -561,6 +570,7 @@ export type State = Readonly< | DoneState | FatalState | InitState + | LegacyCheckClusterRoutingAllocationState | LegacyCreateReindexTargetState | LegacyDeleteState | LegacyReindexState diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/actions/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/actions/index.ts index a3db45a3748cc..c586d9f3b95df 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/actions/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/actions/index.ts @@ -9,7 +9,7 @@ import type { ActionErrorTypeMap as BaseActionErrorTypeMap } from '../../actions'; export { - initAction as init, + fetchIndices, waitForIndexStatus, createIndex, updateAliases, @@ -25,7 +25,6 @@ export { transformDocs, bulkOverwriteTransformedDocuments, noop, - type InitActionParams, type IncompatibleClusterRoutingAllocation, type RetryableEsClientError, type WaitForTaskCompletionTimeout, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/model.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/model.test.ts index 0ba01a714b99d..36eb685226ce4 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/model.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/model.test.ts @@ -9,7 +9,7 @@ import './model.test.mocks'; import * as Either from 'fp-ts/lib/Either'; import { createContextMock, MockedMigratorContext } from '../test_helpers'; -import type { RetryableEsClientError } from '../../actions'; +import type { FetchIndexResponse, RetryableEsClientError } from '../../actions'; import type { State, BaseState, FatalState, AllActionStates } from '../state'; import type { StateActionResponse } from './types'; import { model, modelStageMap } from './model'; @@ -89,7 +89,7 @@ describe('model', () => { mappings: { properties: {} }, settings: {}, }, - }); + }) as Either.Right; const newState = model(state, res, context); expect(newState.retryCount).toEqual(0); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.test.ts index 892ac57c7e79c..383b8dd583d41 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.test.ts @@ -75,24 +75,12 @@ describe('Stage: init', () => { }); }); - it('INIT -> INIT when cluster routing allocation is incompatible', () => { - const state = createState(); - const res: StateActionResponse<'INIT'> = Either.left({ - type: 'incompatible_cluster_routing_allocation', - }); - - const newState = init(state, res, context); - - expect(newState.controlState).toEqual('INIT'); - expect(newState.retryCount).toEqual(1); - expect(newState.retryDelay).toEqual(2000); - expect(newState.logs).toHaveLength(1); - }); - it('calls getCurrentIndex with the correct parameters', () => { const state = createState(); const fetchIndexResponse = createResponse(); - const res: StateActionResponse<'INIT'> = Either.right(fetchIndexResponse); + const res: StateActionResponse<'INIT'> = Either.right( + fetchIndexResponse + ) as Either.Right; const aliases = { '.foo': '.bar' }; getAliasesMock.mockReturnValue(Either.right(aliases)); @@ -110,7 +98,9 @@ describe('Stage: init', () => { it('calls checkVersionCompatibility with the correct parameters', () => { const state = createState(); const fetchIndexResponse = createResponse(); - const res: StateActionResponse<'INIT'> = Either.right(fetchIndexResponse); + const res: StateActionResponse<'INIT'> = Either.right( + fetchIndexResponse + ) as Either.Right; init(state, res, context); @@ -130,7 +120,9 @@ describe('Stage: init', () => { it('adds a log entry about the algo check', () => { const state = createState(); - const res: StateActionResponse<'INIT'> = Either.right(createResponse()); + const res: StateActionResponse<'INIT'> = Either.right( + createResponse() + ) as Either.Right; const newState = init(state, res, context); @@ -142,7 +134,9 @@ describe('Stage: init', () => { it('INIT -> FATAL', () => { const state = createState(); const fetchIndexResponse = createResponse(); - const res: StateActionResponse<'INIT'> = Either.right(fetchIndexResponse); + const res: StateActionResponse<'INIT'> = Either.right( + fetchIndexResponse + ) as Either.Right; const newState = init(state, res, context); @@ -162,7 +156,9 @@ describe('Stage: init', () => { it('adds a log entry about the algo check', () => { const state = createState(); - const res: StateActionResponse<'INIT'> = Either.right(createResponse()); + const res: StateActionResponse<'INIT'> = Either.right( + createResponse() + ) as Either.Right; const newState = init(state, res, context); @@ -174,7 +170,9 @@ describe('Stage: init', () => { it('INIT -> FATAL', () => { const state = createState(); const fetchIndexResponse = createResponse(); - const res: StateActionResponse<'INIT'> = Either.right(fetchIndexResponse); + const res: StateActionResponse<'INIT'> = Either.right( + fetchIndexResponse + ) as Either.Right; const newState = init(state, res, context); @@ -195,7 +193,9 @@ describe('Stage: init', () => { it('calls buildIndexMappings with the correct parameters', () => { const state = createState(); - const res: StateActionResponse<'INIT'> = Either.right(createResponse()); + const res: StateActionResponse<'INIT'> = Either.right( + createResponse() + ) as Either.Right; init(state, res, context); @@ -207,7 +207,9 @@ describe('Stage: init', () => { it('adds a log entry about the algo check', () => { const state = createState(); - const res: StateActionResponse<'INIT'> = Either.right(createResponse()); + const res: StateActionResponse<'INIT'> = Either.right( + createResponse() + ) as Either.Right; const newState = init(state, res, context); @@ -219,7 +221,9 @@ describe('Stage: init', () => { it('INIT -> UPDATE_INDEX_MAPPINGS', () => { const state = createState(); const fetchIndexResponse = createResponse(); - const res: StateActionResponse<'INIT'> = Either.right(fetchIndexResponse); + const res: StateActionResponse<'INIT'> = Either.right( + fetchIndexResponse + ) as Either.Right; const mockMappings = { properties: { someMappings: 'string' } }; buildIndexMappingsMock.mockReturnValue(mockMappings); @@ -248,7 +252,9 @@ describe('Stage: init', () => { it('adds a log entry about the algo check', () => { const state = createState(); - const res: StateActionResponse<'INIT'> = Either.right(createResponse()); + const res: StateActionResponse<'INIT'> = Either.right( + createResponse() + ) as Either.Right; const newState = init(state, res, context); @@ -260,7 +266,9 @@ describe('Stage: init', () => { it('INIT -> UPDATE_INDEX_MAPPINGS', () => { const state = createState(); const fetchIndexResponse = createResponse(); - const res: StateActionResponse<'INIT'> = Either.right(fetchIndexResponse); + const res: StateActionResponse<'INIT'> = Either.right( + fetchIndexResponse + ) as Either.Right; const mockMappings = { properties: { someMappings: 'string' } }; buildIndexMappingsMock.mockReturnValue(mockMappings); @@ -285,7 +293,9 @@ describe('Stage: init', () => { it('adds a log entry about the algo check', () => { const state = createState(); - const res: StateActionResponse<'INIT'> = Either.right(createResponse()); + const res: StateActionResponse<'INIT'> = Either.right( + createResponse() + ) as Either.Right; const newState = init(state, res, context); @@ -302,7 +312,9 @@ describe('Stage: init', () => { it('calls buildIndexMappings with the correct parameters', () => { const state = createState(); const fetchIndexResponse = createResponse(); - const res: StateActionResponse<'INIT'> = Either.right(fetchIndexResponse); + const res: StateActionResponse<'INIT'> = Either.right( + fetchIndexResponse + ) as Either.Right; init(state, res, context); @@ -315,7 +327,9 @@ describe('Stage: init', () => { it('calls getCreationAliases with the correct parameters', () => { const state = createState(); const fetchIndexResponse = createResponse(); - const res: StateActionResponse<'INIT'> = Either.right(fetchIndexResponse); + const res: StateActionResponse<'INIT'> = Either.right( + fetchIndexResponse + ) as Either.Right; init(state, res, context); @@ -329,7 +343,9 @@ describe('Stage: init', () => { it('INIT -> CREATE_TARGET_INDEX', () => { const state = createState(); const fetchIndexResponse = createResponse(); - const res: StateActionResponse<'INIT'> = Either.right(fetchIndexResponse); + const res: StateActionResponse<'INIT'> = Either.right( + fetchIndexResponse + ) as Either.Right; const mockMappings = { properties: { someMappings: 'string' } }; buildIndexMappingsMock.mockReturnValue(mockMappings); @@ -354,7 +370,9 @@ describe('Stage: init', () => { it('calls generateAdditiveMappingDiff with the correct parameters', () => { const state = createState(); const fetchIndexResponse = createResponse(); - const res: StateActionResponse<'INIT'> = Either.right(fetchIndexResponse); + const res: StateActionResponse<'INIT'> = Either.right( + fetchIndexResponse + ) as Either.Right; checkVersionCompatibilityMock.mockReturnValue({ status: 'greater', @@ -373,7 +391,9 @@ describe('Stage: init', () => { it('INIT -> UPDATE_INDEX_MAPPINGS', () => { const state = createState(); const fetchIndexResponse = createResponse(); - const res: StateActionResponse<'INIT'> = Either.right(fetchIndexResponse); + const res: StateActionResponse<'INIT'> = Either.right( + fetchIndexResponse + ) as Either.Right; checkVersionCompatibilityMock.mockReturnValue({ status: 'greater', @@ -396,7 +416,9 @@ describe('Stage: init', () => { it('adds a log entry about the version check', () => { const state = createState(); - const res: StateActionResponse<'INIT'> = Either.right(createResponse()); + const res: StateActionResponse<'INIT'> = Either.right( + createResponse() + ) as Either.Right; checkVersionCompatibilityMock.mockReturnValue({ status: 'greater', @@ -414,7 +436,9 @@ describe('Stage: init', () => { it('INIT -> UPDATE_ALIASES if alias actions are not empty', () => { const state = createState(); const fetchIndexResponse = createResponse(); - const res: StateActionResponse<'INIT'> = Either.right(fetchIndexResponse); + const res: StateActionResponse<'INIT'> = Either.right( + fetchIndexResponse + ) as Either.Right; checkVersionCompatibilityMock.mockReturnValue({ status: 'equal', @@ -437,7 +461,9 @@ describe('Stage: init', () => { it('INIT -> INDEX_STATE_UPDATE_DONE if alias actions are empty', () => { const state = createState(); const fetchIndexResponse = createResponse(); - const res: StateActionResponse<'INIT'> = Either.right(fetchIndexResponse); + const res: StateActionResponse<'INIT'> = Either.right( + fetchIndexResponse + ) as Either.Right; checkVersionCompatibilityMock.mockReturnValue({ status: 'equal', @@ -459,7 +485,9 @@ describe('Stage: init', () => { it('adds a log entry about the version check', () => { const state = createState(); - const res: StateActionResponse<'INIT'> = Either.right(createResponse()); + const res: StateActionResponse<'INIT'> = Either.right( + createResponse() + ) as Either.Right; checkVersionCompatibilityMock.mockReturnValue({ status: 'equal', @@ -477,7 +505,9 @@ describe('Stage: init', () => { it('INIT -> INDEX_STATE_UPDATE_DONE', () => { const state = createState(); const fetchIndexResponse = createResponse(); - const res: StateActionResponse<'INIT'> = Either.right(fetchIndexResponse); + const res: StateActionResponse<'INIT'> = Either.right( + fetchIndexResponse + ) as Either.Right; checkVersionCompatibilityMock.mockReturnValue({ status: 'lesser', @@ -494,7 +524,9 @@ describe('Stage: init', () => { it('adds a log entry about the version check', () => { const state = createState(); - const res: StateActionResponse<'INIT'> = Either.right(createResponse()); + const res: StateActionResponse<'INIT'> = Either.right( + createResponse() + ) as Either.Right; checkVersionCompatibilityMock.mockReturnValue({ status: 'lesser', @@ -512,7 +544,9 @@ describe('Stage: init', () => { it('INIT -> FATAL', () => { const state = createState(); const fetchIndexResponse = createResponse(); - const res: StateActionResponse<'INIT'> = Either.right(fetchIndexResponse); + const res: StateActionResponse<'INIT'> = Either.right( + fetchIndexResponse + ) as Either.Right; checkVersionCompatibilityMock.mockReturnValue({ status: 'conflict', @@ -530,7 +564,9 @@ describe('Stage: init', () => { it('adds a log entry about the version check', () => { const state = createState(); - const res: StateActionResponse<'INIT'> = Either.right(createResponse()); + const res: StateActionResponse<'INIT'> = Either.right( + createResponse() + ) as Either.Right; checkVersionCompatibilityMock.mockReturnValue({ status: 'conflict', diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.ts index 7405515c88b56..317090b41354b 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.ts @@ -8,10 +8,7 @@ import { cloneDeep } from 'lodash'; import * as Either from 'fp-ts/lib/Either'; -import { delayRetryState } from '../../../model/retry_state'; -import { throwBadResponse } from '../../../model/helpers'; import type { MigrationLog } from '../../../types'; -import { isTypeof } from '../../actions'; import { getAliases } from '../../../model/helpers'; import { getCurrentIndex, @@ -33,16 +30,6 @@ export const init: ModelStage< | 'INDEX_STATE_UPDATE_DONE' | 'FATAL' > = (state, res, context) => { - if (Either.isLeft(res)) { - const left = res.left; - if (isTypeof(left, 'incompatible_cluster_routing_allocation')) { - const retryErrorMessage = `[${left.type}] Incompatible Elasticsearch cluster settings detected. Remove the persistent and transient Elasticsearch cluster setting 'cluster.routing.allocation.enable' or set it to a value of 'all' to allow migrations to proceed. Refer to ${context.migrationDocLinks.routingAllocationDisabled} for more information on how to resolve the issue.`; - return delayRetryState(state, retryErrorMessage, context.maxRetryAttempts); - } else { - return throwBadResponse(state, left); - } - } - const types = context.types.map((type) => context.typeRegistry.getType(type)!); const logs: MigrationLog[] = [...state.logs]; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/next.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/next.ts index 3073dd66e73f1..56ebb8d721cfa 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/next.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/next.ts @@ -57,7 +57,7 @@ export const nextActionMap = (context: MigratorContext) => { const client = context.elasticsearchClient; return { INIT: (state: InitState) => - Actions.init({ + Actions.fetchIndices({ client, indices: [`${context.indexPrefix}_*`], }), diff --git a/packages/core/saved-objects/docs/openapi/README.md b/packages/core/saved-objects/docs/openapi/README.md index 71b3ceebec0b3..a64b4289935f8 100644 --- a/packages/core/saved-objects/docs/openapi/README.md +++ b/packages/core/saved-objects/docs/openapi/README.md @@ -16,19 +16,23 @@ To validate and bundle the docs, you can use [Redocly](https://redocly.com/docs/ For example, run the following commands from the `packages/core/saved-objects/docs/openapi/` folder: - ```bash - npx swagger-cli validate entrypoint.yaml - ``` +```bash +npx swagger-cli validate entrypoint.yaml +npx swagger-cli validate entrypoint-serverless.yaml +``` Then you can generate the `bundled` files by running the following commands: - ```bash - npx @redocly/cli bundle entrypoint.yaml --output bundled.yaml --ext yaml - npx @redocly/cli bundle entrypoint.yaml --output bundled.json --ext json - ``` +```bash +npx @redocly/cli bundle entrypoint.yaml --output bundled.yaml --ext yaml +npx @redocly/cli bundle entrypoint.yaml --output bundled.json --ext json +npx @redocly/cli bundle entrypoint_serverless.yaml --output bundled_serverless.yaml --ext yaml +npx @redocly/cli bundle entrypoint_serverless.yaml --output bundled_serverless.json --ext json +``` After generating the json bundle ensure that it is also valid by running the following command: - ```bash - npx @redocly/cli lint bundled.json - ``` +```bash +npx @redocly/cli lint bundled.json +npx @redocly/cli lint bundled_serverless.json +``` diff --git a/packages/core/saved-objects/docs/openapi/bundled.json b/packages/core/saved-objects/docs/openapi/bundled.json index 4382ee9b62d98..32e429e0d9c51 100644 --- a/packages/core/saved-objects/docs/openapi/bundled.json +++ b/packages/core/saved-objects/docs/openapi/bundled.json @@ -1,5 +1,5 @@ { - "openapi": "3.1.0", + "openapi": "3.0.3", "info": { "title": "Saved objects", "description": "OpenAPI schema for saved object endpoints", @@ -34,7 +34,7 @@ "paths": { "/api/encrypted_saved_objects/_rotate_key": { "post": { - "summary": "Rotate the encryption key for encrypted saved objects.", + "summary": "Rotate a key for encrypted saved objects", "operationId": "rotateEncryptionKey", "description": "Superuser role required.\n\nIf a saved object cannot be decrypted using the primary encryption key, then Kibana will attempt to decrypt it using the specified decryption-only keys. In most of the cases this overhead is negligible, but if you're dealing with a large number of saved objects and experiencing performance issues, you may want to rotate the encryption key.\n\nThis functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", "tags": [ @@ -116,7 +116,7 @@ }, "/api/saved_objects/_bulk_create": { "post": { - "summary": "Create multiple Kibana saved objects.", + "summary": "Create saved objects", "operationId": "bulkCreateSavedObjects", "deprecated": true, "tags": [ @@ -174,7 +174,7 @@ }, "/api/saved_objects/_bulk_delete": { "post": { - "summary": "Remove multiple Kibana saved objects.", + "summary": "Delete saved objects", "operationId": "bulkDeleteSavedObjects", "description": "WARNING: When you delete a saved object, it cannot be recovered.\n", "deprecated": true, @@ -233,7 +233,7 @@ }, "/api/saved_objects/_bulk_get": { "post": { - "summary": "Retrieve multiple Kibana saved objects by identifier.", + "summary": "Get saved objects", "operationId": "bulkGetSavedObjects", "deprecated": true, "tags": [ @@ -283,10 +283,10 @@ }, "/api/saved_objects/_bulk_resolve": { "post": { - "summary": "Retrieve multiple Kibana saved objects by identifier using any legacy URL aliases if they exist.", + "summary": "Resolve saved objects", "operationId": "bulkResolveSavedObjects", "deprecated": true, - "description": "Under certain circumstances when Kibana is upgraded, saved object migrations may necessitate regenerating some object IDs to enable new features. When an object's ID is regenerated, a legacy URL alias is created for that object, preserving its old ID. In such a scenario, that object can be retrieved by the bulk resolve API using either its new ID or its old ID.\n", + "description": "Retrieve multiple Kibana saved objects by identifier using any legacy URL aliases if they exist. Under certain circumstances when Kibana is upgraded, saved object migrations may necessitate regenerating some object IDs to enable new features. When an object's ID is regenerated, a legacy URL alias is created for that object, preserving its old ID. In such a scenario, that object can be retrieved by the bulk resolve API using either its new ID or its old ID.\n", "tags": [ "saved objects" ], @@ -334,8 +334,9 @@ }, "/api/saved_objects/_bulk_update": { "post": { - "summary": "Update the attributes for multiple Kibana saved objects.", + "summary": "Update saved objects", "operationId": "bulkUpdateSavedObjects", + "description": "Update the attributes for multiple Kibana saved objects.", "deprecated": true, "tags": [ "saved objects" @@ -384,9 +385,9 @@ }, "/api/saved_objects/_export": { "post": { - "summary": "Retrieve sets of saved objects that you want to import into Kibana.", - "operationId": "exportSavedObjects", - "description": "\nYou must include `type` or `objects` in the request body.\n\nNOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be exported.\n\nThis functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "summary": "Export saved objects in the default space", + "operationId": "exportSavedObjectsDefault", + "description": "Retrieve sets of saved objects that you want to import into Kibana.\nYou must include `type` or `objects` in the request body.\n\nNOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be exported.\n\nThis functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", "tags": [ "saved objects" ], @@ -474,8 +475,9 @@ }, "/api/saved_objects/_find": { "get": { - "summary": "Retrieve a paginated set of Kibana saved objects.", + "summary": "Search for saved objects", "operationId": "findSavedObjects", + "description": "Retrieve a paginated set of Kibana saved objects.", "deprecated": true, "tags": [ "saved objects" @@ -642,9 +644,9 @@ }, "/api/saved_objects/_import": { "post": { - "summary": "Create sets of Kibana saved objects from a file created by the export API.", - "operationId": "importSavedObjects", - "description": "Saved objects can be imported only into the same version, a newer minor on the same major, or the next major. Exported saved objects are not backwards compatible and cannot be imported into an older version of Kibana.\n\nThis functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "summary": "Import saved objects in the default space", + "operationId": "importSavedObjectsDefault", + "description": "Create sets of Kibana saved objects from a file created by the export API.\nSaved objects can be imported only into the same version, a newer minor on the same major, or the next major. Exported saved objects are not backwards compatible and cannot be imported into an older version of Kibana.\n\nThis functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", "tags": [ "saved objects" ], @@ -755,9 +757,9 @@ }, "/api/saved_objects/_resolve_import_errors": { "post": { - "summary": "Resolve errors from the Import objects API.", + "summary": "Resolve import errors", "operationId": "resolveImportErrors", - "description": "To resolve errors, you can:\n\n* Retry certain saved objects\n* Overwrite specific saved objects\n* Change references to different saved objects\n\nThis functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "description": "To resolve errors from the Import objects API, you can:\n\n* Retry certain saved objects\n* Overwrite specific saved objects\n* Change references to different saved objects\n\nThis functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", "tags": [ "saved objects" ], @@ -915,8 +917,9 @@ }, "/api/saved_objects/{type}": { "post": { - "summary": "Create Kibana saved objects with randomly generated identifiers.", + "summary": "Create a saved object", "operationId": "createSavedObject", + "description": "Create a Kibana saved object with a randomly generated identifier.", "deprecated": true, "tags": [ "saved objects" @@ -987,8 +990,9 @@ }, "/api/saved_objects/{type}/{id}": { "get": { - "summary": "Retrieve a single Kibana saved object by identifier.", + "summary": "Get a saved object", "operationId": "getSavedObject", + "description": "Retrieve a single Kibana saved object by identifier.", "deprecated": true, "tags": [ "saved objects" @@ -1025,8 +1029,9 @@ } }, "post": { - "summary": "Create Kibana saved objects.", + "summary": "Create a saved object", "operationId": "createSavedObjectId", + "description": "Create a Kibana saved object and specify its identifier instead of using a randomly generated ID.", "deprecated": true, "tags": [ "saved objects" @@ -1098,8 +1103,9 @@ } }, "put": { - "summary": "Update the attributes for Kibana saved objects.", + "summary": "Update a saved object", "operationId": "updateSavedObject", + "description": "Update the attributes for Kibana saved objects.", "deprecated": true, "tags": [ "saved objects" @@ -1161,9 +1167,9 @@ }, "/api/saved_objects/resolve/{type}/{id}": { "get": { - "summary": "Retrieve a single Kibana saved object by identifier using any legacy URL alias if it exists.", + "summary": "Resolve a saved object", "operationId": "resolveSavedObject", - "description": "Under certain circumstances, when Kibana is upgraded, saved object migrations may necessitate regenerating some object IDs to enable new features. When an object's ID is regenerated, a legacy URL alias is created for that object, preserving its old ID. In such a scenario, that object can be retrieved using either its new ID or its old ID.\n", + "description": "Retrieve a single Kibana saved object by identifier using any legacy URL alias if it exists. Under certain circumstances, when Kibana is upgraded, saved object migrations may necessitate regenerating some object IDs to enable new features. When an object's ID is regenerated, a legacy URL alias is created for that object, preserving its old ID. In such a scenario, that object can be retrieved using either its new ID or its old ID.\n", "deprecated": true, "tags": [ "saved objects" diff --git a/packages/core/saved-objects/docs/openapi/bundled.yaml b/packages/core/saved-objects/docs/openapi/bundled.yaml index a12e326ccdbe4..53874b9a469cf 100644 --- a/packages/core/saved-objects/docs/openapi/bundled.yaml +++ b/packages/core/saved-objects/docs/openapi/bundled.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.0 +openapi: 3.0.3 info: title: Saved objects description: OpenAPI schema for saved object endpoints @@ -19,7 +19,7 @@ tags: paths: /api/encrypted_saved_objects/_rotate_key: post: - summary: Rotate the encryption key for encrypted saved objects. + summary: Rotate a key for encrypted saved objects operationId: rotateEncryptionKey description: | Superuser role required. @@ -84,7 +84,7 @@ paths: type: object /api/saved_objects/_bulk_create: post: - summary: Create multiple Kibana saved objects. + summary: Create saved objects operationId: bulkCreateSavedObjects deprecated: true tags: @@ -119,7 +119,7 @@ paths: $ref: '#/components/schemas/400_response' /api/saved_objects/_bulk_delete: post: - summary: Remove multiple Kibana saved objects. + summary: Delete saved objects operationId: bulkDeleteSavedObjects description: | WARNING: When you delete a saved object, it cannot be recovered. @@ -158,7 +158,7 @@ paths: $ref: '#/components/schemas/400_response' /api/saved_objects/_bulk_get: post: - summary: Retrieve multiple Kibana saved objects by identifier. + summary: Get saved objects operationId: bulkGetSavedObjects deprecated: true tags: @@ -188,11 +188,11 @@ paths: $ref: '#/components/schemas/400_response' /api/saved_objects/_bulk_resolve: post: - summary: Retrieve multiple Kibana saved objects by identifier using any legacy URL aliases if they exist. + summary: Resolve saved objects operationId: bulkResolveSavedObjects deprecated: true description: | - Under certain circumstances when Kibana is upgraded, saved object migrations may necessitate regenerating some object IDs to enable new features. When an object's ID is regenerated, a legacy URL alias is created for that object, preserving its old ID. In such a scenario, that object can be retrieved by the bulk resolve API using either its new ID or its old ID. + Retrieve multiple Kibana saved objects by identifier using any legacy URL aliases if they exist. Under certain circumstances when Kibana is upgraded, saved object migrations may necessitate regenerating some object IDs to enable new features. When an object's ID is regenerated, a legacy URL alias is created for that object, preserving its old ID. In such a scenario, that object can be retrieved by the bulk resolve API using either its new ID or its old ID. tags: - saved objects parameters: @@ -221,8 +221,9 @@ paths: $ref: '#/components/schemas/400_response' /api/saved_objects/_bulk_update: post: - summary: Update the attributes for multiple Kibana saved objects. + summary: Update saved objects operationId: bulkUpdateSavedObjects + description: Update the attributes for multiple Kibana saved objects. deprecated: true tags: - saved objects @@ -252,10 +253,10 @@ paths: $ref: '#/components/schemas/400_response' /api/saved_objects/_export: post: - summary: Retrieve sets of saved objects that you want to import into Kibana. - operationId: exportSavedObjects + summary: Export saved objects in the default space + operationId: exportSavedObjectsDefault description: | - + Retrieve sets of saved objects that you want to import into Kibana. You must include `type` or `objects` in the request body. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be exported. @@ -313,8 +314,9 @@ paths: $ref: '#/components/schemas/400_response' /api/saved_objects/_find: get: - summary: Retrieve a paginated set of Kibana saved objects. + summary: Search for saved objects operationId: findSavedObjects + description: Retrieve a paginated set of Kibana saved objects. deprecated: true tags: - saved objects @@ -414,9 +416,10 @@ paths: $ref: '#/components/schemas/400_response' /api/saved_objects/_import: post: - summary: Create sets of Kibana saved objects from a file created by the export API. - operationId: importSavedObjects + summary: Import saved objects in the default space + operationId: importSavedObjectsDefault description: | + Create sets of Kibana saved objects from a file created by the export API. Saved objects can be imported only into the same version, a newer minor on the same major, or the next major. Exported saved objects are not backwards compatible and cannot be imported into an older version of Kibana. This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -500,10 +503,10 @@ paths: $ref: '#/components/schemas/400_response' /api/saved_objects/_resolve_import_errors: post: - summary: Resolve errors from the Import objects API. + summary: Resolve import errors operationId: resolveImportErrors description: | - To resolve errors, you can: + To resolve errors from the Import objects API, you can: * Retry certain saved objects * Overwrite specific saved objects @@ -623,8 +626,9 @@ paths: $ref: '#/components/schemas/400_response' /api/saved_objects/{type}: post: - summary: Create Kibana saved objects with randomly generated identifiers. + summary: Create a saved object operationId: createSavedObject + description: Create a Kibana saved object with a randomly generated identifier. deprecated: true tags: - saved objects @@ -666,8 +670,9 @@ paths: type: object /api/saved_objects/{type}/{id}: get: - summary: Retrieve a single Kibana saved object by identifier. + summary: Get a saved object operationId: getSavedObject + description: Retrieve a single Kibana saved object by identifier. deprecated: true tags: - saved objects @@ -688,8 +693,9 @@ paths: schema: $ref: '#/components/schemas/400_response' post: - summary: Create Kibana saved objects. + summary: Create a saved object operationId: createSavedObjectId + description: Create a Kibana saved object and specify its identifier instead of using a randomly generated ID. deprecated: true tags: - saved objects @@ -731,8 +737,9 @@ paths: schema: type: object put: - summary: Update the attributes for Kibana saved objects. + summary: Update a saved object operationId: updateSavedObject + description: Update the attributes for Kibana saved objects. deprecated: true tags: - saved objects @@ -767,10 +774,10 @@ paths: type: object /api/saved_objects/resolve/{type}/{id}: get: - summary: Retrieve a single Kibana saved object by identifier using any legacy URL alias if it exists. + summary: Resolve a saved object operationId: resolveSavedObject description: | - Under certain circumstances, when Kibana is upgraded, saved object migrations may necessitate regenerating some object IDs to enable new features. When an object's ID is regenerated, a legacy URL alias is created for that object, preserving its old ID. In such a scenario, that object can be retrieved using either its new ID or its old ID. + Retrieve a single Kibana saved object by identifier using any legacy URL alias if it exists. Under certain circumstances, when Kibana is upgraded, saved object migrations may necessitate regenerating some object IDs to enable new features. When an object's ID is regenerated, a legacy URL alias is created for that object, preserving its old ID. In such a scenario, that object can be retrieved using either its new ID or its old ID. deprecated: true tags: - saved objects diff --git a/packages/core/saved-objects/docs/openapi/bundled_serverless.json b/packages/core/saved-objects/docs/openapi/bundled_serverless.json new file mode 100644 index 0000000000000..9fdd5303eaf46 --- /dev/null +++ b/packages/core/saved-objects/docs/openapi/bundled_serverless.json @@ -0,0 +1,603 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Serverless saved objects", + "description": "OpenAPI schema for serverless saved object endpoints", + "version": "0.1", + "contact": { + "name": "Kibana Core Team" + }, + "license": { + "name": "Elastic License 2.0", + "url": "https://www.elastic.co/licensing/elastic-license" + } + }, + "servers": [ + { + "url": "https://{kibana_url}", + "variables": { + "kibana_url": { + "default": "localhost:5601" + } + } + } + ], + "security": [ + { + "apiKeyAuth": [] + } + ], + "tags": [ + { + "name": "saved objects", + "description": "Manage Kibana saved objects, including dashboards, visualizations, and more." + } + ], + "paths": { + "/api/saved_objects/_export": { + "post": { + "summary": "Export saved objects in the default space", + "operationId": "exportSavedObjectsDefault", + "description": "Retrieve sets of saved objects that you want to import into Kibana.\nYou must include `type` or `objects` in the request body.\n\nNOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be exported.\n\nThis functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "tags": [ + "saved objects" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "excludeExportDetails": { + "description": "Do not add export details entry at the end of the stream.", + "type": "boolean", + "default": false + }, + "includeReferencesDeep": { + "description": "Includes all of the referenced objects in the exported objects.", + "type": "boolean" + }, + "objects": { + "description": "A list of objects to export.", + "type": "array", + "items": { + "type": "object" + } + }, + "type": { + "description": "The saved object types to include in the export. Use `*` to export all the types.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + } + }, + "examples": { + "exportSavedObjectsRequest": { + "$ref": "#/components/examples/export_objects_request" + } + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/x-ndjson": { + "schema": { + "type": "object", + "additionalProperties": true + }, + "examples": { + "exportSavedObjectsResponse": { + "$ref": "#/components/examples/export_objects_response" + } + } + } + } + }, + "400": { + "description": "Bad request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + } + } + } + }, + "/api/saved_objects/_import": { + "post": { + "summary": "Import saved objects in the default space", + "operationId": "importSavedObjectsDefault", + "description": "Create sets of Kibana saved objects from a file created by the export API.\nSaved objects can be imported only into the same version, a newer minor on the same major, or the next major. Exported saved objects are not backwards compatible and cannot be imported into an older version of Kibana.\n\nThis functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "tags": [ + "saved objects" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "in": "query", + "name": "createNewCopies", + "schema": { + "type": "boolean" + }, + "required": false, + "description": "Creates copies of saved objects, regenerates each object ID, and resets the origin. When used, potential conflict errors are avoided. NOTE: This option cannot be used with the `overwrite` and `compatibilityMode` options.\n" + }, + { + "in": "query", + "name": "overwrite", + "schema": { + "type": "boolean" + }, + "required": false, + "description": "Overwrites saved objects when they already exist. When used, potential conflict errors are automatically resolved by overwriting the destination object. NOTE: This option cannot be used with the `createNewCopies` option.\n" + }, + { + "in": "query", + "name": "compatibilityMode", + "schema": { + "type": "boolean" + }, + "required": false, + "description": "Applies various adjustments to the saved objects that are being imported to maintain compatibility between different Kibana versions. Use this option only if you encounter issues with imported saved objects. NOTE: This option cannot be used with the `createNewCopies` option.\n" + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "file": { + "description": "A file exported using the export API. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be included in this file. Similarly, the `savedObjects.maxImportPayloadBytes` setting limits the overall size of the file that can be imported.\n" + } + } + }, + "examples": { + "importObjectsRequest": { + "$ref": "#/components/examples/import_objects_request" + } + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "description": "Indicates when the import was successfully completed. When set to false, some objects may not have been created. For additional information, refer to the `errors` and `successResults` properties.\n" + }, + "successCount": { + "type": "integer", + "description": "Indicates the number of successfully imported records." + }, + "errors": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Indicates the import was unsuccessful and specifies the objects that failed to import.\n\nNOTE: One object may result in multiple errors, which requires separate steps to resolve. For instance, a `missing_references` error and conflict error.\n" + }, + "successResults": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Indicates the objects that are successfully imported, with any metadata if applicable.\n\nNOTE: Objects are created only when all resolvable errors are addressed, including conflicts and missing references. If objects are created as new copies, each entry in the `successResults` array includes a `destinationId` attribute.\n" + } + } + }, + "examples": { + "importObjectsResponse": { + "$ref": "#/components/examples/import_objects_response" + } + } + } + } + }, + "400": { + "description": "Bad request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + } + } + } + }, + "/s/{spaceId}/api/saved_objects/_export": { + "post": { + "summary": "Export saved objects", + "operationId": "exportSavedObjects", + "description": "Retrieves sets of saved objects that you want to import into Kibana.\nYou must include `type` or `objects` in the request body.\n\nNOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be exported.\n\nThis functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "tags": [ + "saved objects" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/export_objects_request" + }, + "examples": { + "exportSavedObjectsRequest": { + "$ref": "#/components/examples/export_objects_request" + } + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/x-ndjson": { + "schema": { + "type": "object", + "additionalProperties": true + }, + "examples": { + "exportSavedObjectsResponse": { + "$ref": "#/components/examples/export_objects_response" + } + } + } + } + }, + "400": { + "description": "Bad request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + } + } + } + }, + "/s/{spaceId}/api/saved_objects/_import": { + "post": { + "summary": "Import saved objects", + "operationId": "importSavedObjects", + "description": "Creates sets of Kibana saved objects from a file created by the export API.\nSaved objects can be imported only into the same version, a newer minor on the same major, or the next major. Exported saved objects are not backwards compatible and cannot be imported into an older version of Kibana.\n\nThis functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "tags": [ + "saved objects" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + }, + { + "$ref": "#/components/parameters/compatibility_mode" + }, + { + "$ref": "#/components/parameters/create_new_copies" + }, + { + "$ref": "#/components/parameters/overwrite" + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/import_objects_request" + }, + "examples": { + "importObjectsRequest": { + "$ref": "#/components/examples/import_objects_request" + } + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/200_import_objects_response" + }, + "examples": { + "importObjectsResponse": { + "$ref": "#/components/examples/import_objects_response" + } + } + } + } + }, + "400": { + "description": "Bad request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + } + } + } + } + }, + "components": { + "securitySchemes": { + "apiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "Authorization", + "description": "Serverless APIs support only key-based authentication. You must create an API key and use the encoded value in the request header. For example: 'Authorization: ApiKey base64AccessApiKey'.\n" + } + }, + "parameters": { + "kbn_xsrf": { + "schema": { + "type": "string" + }, + "in": "header", + "name": "kbn-xsrf", + "description": "Cross-site request forgery protection", + "required": true + }, + "space_id": { + "in": "path", + "name": "spaceId", + "description": "An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used.", + "required": true, + "schema": { + "type": "string", + "example": "default" + } + }, + "compatibility_mode": { + "in": "query", + "name": "compatibilityMode", + "schema": { + "type": "boolean" + }, + "required": false, + "description": "Applies various adjustments to the saved objects that are being imported to maintain compatibility between different Kibana versions. Use this option only if you encounter issues with imported saved objects. NOTE: This option cannot be used with the `createNewCopies` option.\n" + }, + "create_new_copies": { + "in": "query", + "name": "createNewCopies", + "schema": { + "type": "boolean" + }, + "required": false, + "description": "Creates copies of saved objects, regenerates each object ID, and resets the origin. When used, potential conflict errors are avoided. NOTE: This option cannot be used with the `overwrite` and `compatibilityMode` options.\n" + }, + "overwrite": { + "in": "query", + "name": "overwrite", + "schema": { + "type": "boolean" + }, + "required": false, + "description": "Overwrites saved objects when they already exist. When used, potential conflict errors are automatically resolved by overwriting the destination object. NOTE: This option cannot be used with the `createNewCopies` option.\n" + } + }, + "examples": { + "export_objects_request": { + "summary": "Export a specific saved object.", + "value": { + "objects": [ + { + "type": "map", + "id": "de71f4f0-1902-11e9-919b-ffe5949a18d2" + } + ], + "includeReferencesDeep": false, + "excludeExportDetails": true + } + }, + "export_objects_response": { + "summary": "The export objects API response contains a JSON record for each exported object.", + "value": { + "attributes": { + "description": "", + "layerListJSON": "[{\"id\":\"0hmz5\",\"alpha\":1,\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"isAutoSelect\":true,\"lightModeDefault\":\"road_map_desaturated\"},\"visible\":true,\"style\":{},\"type\":\"EMS_VECTOR_TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"edh66\",\"label\":\"Total Requests by Destination\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.5,\"sourceDescriptor\":{\"type\":\"EMS_FILE\",\"id\":\"world_countries\",\"tooltipProperties\":[\"name\",\"iso2\"]},\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"name\":\"__kbnjoin__count__673ff994-fc75-4c67-909b-69fcb0e1060e\",\"origin\":\"join\"},\"color\":\"Greys\",\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}}},\"type\":\"GEOJSON_VECTOR\",\"joins\":[{\"leftField\":\"iso2\",\"right\":{\"type\":\"ES_TERM_SOURCE\",\"id\":\"673ff994-fc75-4c67-909b-69fcb0e1060e\",\"indexPatternTitle\":\"kibana_sample_data_logs\",\"term\":\"geo.dest\",\"indexPatternRefName\":\"layer_1_join_0_index_pattern\",\"metrics\":[{\"type\":\"count\",\"label\":\"web logs count\"}],\"applyGlobalQuery\":true}}]},{\"id\":\"gaxya\",\"label\":\"Actual Requests\",\"minZoom\":9,\"maxZoom\":24,\"alpha\":1,\"sourceDescriptor\":{\"id\":\"b7486535-171b-4d3b-bb2e-33c1a0a2854c\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[\"clientip\",\"timestamp\",\"host\",\"request\",\"response\",\"machine.os\",\"agent\",\"bytes\"],\"indexPatternRefName\":\"layer_2_source_index_pattern\",\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\"},\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#2200ff\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":2}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"name\":\"bytes\",\"origin\":\"source\"},\"minSize\":1,\"maxSize\":23,\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}}},\"type\":\"GEOJSON_VECTOR\"},{\"id\":\"tfi3f\",\"label\":\"Total Requests and Bytes\",\"minZoom\":0,\"maxZoom\":9,\"alpha\":1,\"sourceDescriptor\":{\"type\":\"ES_GEO_GRID\",\"resolution\":\"COARSE\",\"id\":\"8aaa65b5-a4e9-448b-9560-c98cb1c5ac5b\",\"geoField\":\"geo.coordinates\",\"requestType\":\"point\",\"metrics\":[{\"type\":\"count\",\"label\":\"web logs count\"},{\"type\":\"sum\",\"field\":\"bytes\"}],\"indexPatternRefName\":\"layer_3_source_index_pattern\",\"applyGlobalQuery\":true},\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"name\":\"doc_count\",\"origin\":\"source\"},\"color\":\"Blues\",\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"name\":\"sum_of_bytes\",\"origin\":\"source\"},\"minSize\":7,\"maxSize\":25,\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"labelText\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"name\":\"doc_count\",\"origin\":\"source\"},\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"labelSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":12,\"maxSize\":24,\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}}},\"type\":\"GEOJSON_VECTOR\"}]", + "mapStateJSON": "{\"zoom\":3.64,\"center\":{\"lon\":-88.92107,\"lat\":42.16337},\"timeFilters\":{\"from\":\"now-7d\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"settings\":{\"autoFitToDataBounds\":false}}", + "title": "[Logs] Total Requests and Bytes", + "uiStateJSON": "{\"isDarkMode\":false}" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2023-08-23T20:03:32.204Z", + "id": "de71f4f0-1902-11e9-919b-ffe5949a18d2", + "managed": false, + "references": [ + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "layer_1_join_0_index_pattern", + "type": "index-pattern" + }, + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "layer_2_source_index_pattern", + "type": "index-pattern" + }, + { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "layer_3_source_index_pattern", + "type": "index-pattern" + } + ], + "type": "map", + "typeMigrationVersion": "8.4.0", + "updated_at": "2023-08-23T20:03:32.204Z", + "version": "WzEzLDFd" + } + }, + "import_objects_request": { + "value": { + "file": "file.ndjson" + } + }, + "import_objects_response": { + "summary": "The import objects API response indicates a successful import and the objects are created. Since these objects are created as new copies, each entry in the successResults array includes a destinationId attribute.", + "value": { + "successCount": 1, + "success": true, + "successResults": [ + { + "type": "index-pattern", + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "meta": { + "title": "Kibana Sample Data Logs", + "icon": "indexPatternApp" + }, + "managed": false, + "destinationId": "82d2760c-468f-49cf-83aa-b9a35b6a8943" + } + ] + } + } + }, + "schemas": { + "400_response": { + "title": "Bad request", + "type": "object", + "required": [ + "error", + "message", + "statusCode" + ], + "properties": { + "error": { + "type": "string", + "enum": [ + "Bad Request" + ] + }, + "message": { + "type": "string" + }, + "statusCode": { + "type": "integer", + "enum": [ + 400 + ] + } + } + }, + "export_objects_request": { + "type": "object", + "properties": { + "excludeExportDetails": { + "description": "Do not add export details entry at the end of the stream.", + "type": "boolean", + "default": false + }, + "includeReferencesDeep": { + "description": "Includes all of the referenced objects in the exported objects.", + "type": "boolean" + }, + "objects": { + "description": "A list of objects to export.", + "type": "array", + "items": { + "type": "object" + } + }, + "type": { + "description": "The saved object types to include in the export. Use `*` to export all the types.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + } + }, + "import_objects_request": { + "type": "object", + "properties": { + "file": { + "description": "A file exported using the export API. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be included in this file. Similarly, the `savedObjects.maxImportPayloadBytes` setting limits the overall size of the file that can be imported.\n" + } + } + }, + "200_import_objects_response": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "description": "Indicates when the import was successfully completed. When set to false, some objects may not have been created. For additional information, refer to the `errors` and `successResults` properties.\n" + }, + "successCount": { + "type": "integer", + "description": "Indicates the number of successfully imported records." + }, + "errors": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Indicates the import was unsuccessful and specifies the objects that failed to import.\n\nNOTE: One object may result in multiple errors, which requires separate steps to resolve. For instance, a `missing_references` error and conflict error.\n" + }, + "successResults": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Indicates the objects that are successfully imported, with any metadata if applicable.\n\nNOTE: Objects are created only when all resolvable errors are addressed, including conflicts and missing references. If objects are created as new copies, each entry in the `successResults` array includes a `destinationId` attribute.\n" + } + } + } + } + } +} \ No newline at end of file diff --git a/packages/core/saved-objects/docs/openapi/bundled_serverless.yaml b/packages/core/saved-objects/docs/openapi/bundled_serverless.yaml new file mode 100644 index 0000000000000..cf07e2b72669c --- /dev/null +++ b/packages/core/saved-objects/docs/openapi/bundled_serverless.yaml @@ -0,0 +1,426 @@ +openapi: 3.0.3 +info: + title: Serverless saved objects + description: OpenAPI schema for serverless saved object endpoints + version: '0.1' + contact: + name: Kibana Core Team + license: + name: Elastic License 2.0 + url: https://www.elastic.co/licensing/elastic-license +servers: + - url: https://{kibana_url} + variables: + kibana_url: + default: localhost:5601 +security: + - apiKeyAuth: [] +tags: + - name: saved objects + description: Manage Kibana saved objects, including dashboards, visualizations, and more. +paths: + /api/saved_objects/_export: + post: + summary: Export saved objects in the default space + operationId: exportSavedObjectsDefault + description: | + Retrieve sets of saved objects that you want to import into Kibana. + You must include `type` or `objects` in the request body. + + NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be exported. + + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - saved objects + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + excludeExportDetails: + description: Do not add export details entry at the end of the stream. + type: boolean + default: false + includeReferencesDeep: + description: Includes all of the referenced objects in the exported objects. + type: boolean + objects: + description: A list of objects to export. + type: array + items: + type: object + type: + description: The saved object types to include in the export. Use `*` to export all the types. + oneOf: + - type: string + - type: array + items: + type: string + examples: + exportSavedObjectsRequest: + $ref: '#/components/examples/export_objects_request' + responses: + '200': + description: Indicates a successful call. + content: + application/x-ndjson: + schema: + type: object + additionalProperties: true + examples: + exportSavedObjectsResponse: + $ref: '#/components/examples/export_objects_response' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/400_response' + /api/saved_objects/_import: + post: + summary: Import saved objects in the default space + operationId: importSavedObjectsDefault + description: | + Create sets of Kibana saved objects from a file created by the export API. + Saved objects can be imported only into the same version, a newer minor on the same major, or the next major. Exported saved objects are not backwards compatible and cannot be imported into an older version of Kibana. + + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - saved objects + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - in: query + name: createNewCopies + schema: + type: boolean + required: false + description: | + Creates copies of saved objects, regenerates each object ID, and resets the origin. When used, potential conflict errors are avoided. NOTE: This option cannot be used with the `overwrite` and `compatibilityMode` options. + - in: query + name: overwrite + schema: + type: boolean + required: false + description: | + Overwrites saved objects when they already exist. When used, potential conflict errors are automatically resolved by overwriting the destination object. NOTE: This option cannot be used with the `createNewCopies` option. + - in: query + name: compatibilityMode + schema: + type: boolean + required: false + description: | + Applies various adjustments to the saved objects that are being imported to maintain compatibility between different Kibana versions. Use this option only if you encounter issues with imported saved objects. NOTE: This option cannot be used with the `createNewCopies` option. + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: + description: | + A file exported using the export API. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be included in this file. Similarly, the `savedObjects.maxImportPayloadBytes` setting limits the overall size of the file that can be imported. + examples: + importObjectsRequest: + $ref: '#/components/examples/import_objects_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + description: | + Indicates when the import was successfully completed. When set to false, some objects may not have been created. For additional information, refer to the `errors` and `successResults` properties. + successCount: + type: integer + description: Indicates the number of successfully imported records. + errors: + type: array + items: + type: object + description: | + Indicates the import was unsuccessful and specifies the objects that failed to import. + + NOTE: One object may result in multiple errors, which requires separate steps to resolve. For instance, a `missing_references` error and conflict error. + successResults: + type: array + items: + type: object + description: | + Indicates the objects that are successfully imported, with any metadata if applicable. + + NOTE: Objects are created only when all resolvable errors are addressed, including conflicts and missing references. If objects are created as new copies, each entry in the `successResults` array includes a `destinationId` attribute. + examples: + importObjectsResponse: + $ref: '#/components/examples/import_objects_response' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/400_response' + /s/{spaceId}/api/saved_objects/_export: + post: + summary: Export saved objects + operationId: exportSavedObjects + description: | + Retrieves sets of saved objects that you want to import into Kibana. + You must include `type` or `objects` in the request body. + + NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be exported. + + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - saved objects + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/space_id' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/export_objects_request' + examples: + exportSavedObjectsRequest: + $ref: '#/components/examples/export_objects_request' + responses: + '200': + description: Indicates a successful call. + content: + application/x-ndjson: + schema: + type: object + additionalProperties: true + examples: + exportSavedObjectsResponse: + $ref: '#/components/examples/export_objects_response' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/400_response' + /s/{spaceId}/api/saved_objects/_import: + post: + summary: Import saved objects + operationId: importSavedObjects + description: | + Creates sets of Kibana saved objects from a file created by the export API. + Saved objects can be imported only into the same version, a newer minor on the same major, or the next major. Exported saved objects are not backwards compatible and cannot be imported into an older version of Kibana. + + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - saved objects + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/space_id' + - $ref: '#/components/parameters/compatibility_mode' + - $ref: '#/components/parameters/create_new_copies' + - $ref: '#/components/parameters/overwrite' + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/import_objects_request' + examples: + importObjectsRequest: + $ref: '#/components/examples/import_objects_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '#/components/schemas/200_import_objects_response' + examples: + importObjectsResponse: + $ref: '#/components/examples/import_objects_response' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '#/components/schemas/400_response' +components: + securitySchemes: + apiKeyAuth: + type: apiKey + in: header + name: Authorization + description: | + Serverless APIs support only key-based authentication. You must create an API key and use the encoded value in the request header. For example: 'Authorization: ApiKey base64AccessApiKey'. + parameters: + kbn_xsrf: + schema: + type: string + in: header + name: kbn-xsrf + description: Cross-site request forgery protection + required: true + space_id: + in: path + name: spaceId + description: An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used. + required: true + schema: + type: string + example: default + compatibility_mode: + in: query + name: compatibilityMode + schema: + type: boolean + required: false + description: | + Applies various adjustments to the saved objects that are being imported to maintain compatibility between different Kibana versions. Use this option only if you encounter issues with imported saved objects. NOTE: This option cannot be used with the `createNewCopies` option. + create_new_copies: + in: query + name: createNewCopies + schema: + type: boolean + required: false + description: | + Creates copies of saved objects, regenerates each object ID, and resets the origin. When used, potential conflict errors are avoided. NOTE: This option cannot be used with the `overwrite` and `compatibilityMode` options. + overwrite: + in: query + name: overwrite + schema: + type: boolean + required: false + description: | + Overwrites saved objects when they already exist. When used, potential conflict errors are automatically resolved by overwriting the destination object. NOTE: This option cannot be used with the `createNewCopies` option. + examples: + export_objects_request: + summary: Export a specific saved object. + value: + objects: + - type: map + id: de71f4f0-1902-11e9-919b-ffe5949a18d2 + includeReferencesDeep: false + excludeExportDetails: true + export_objects_response: + summary: The export objects API response contains a JSON record for each exported object. + value: + attributes: + description: '' + layerListJSON: '[{"id":"0hmz5","alpha":1,"sourceDescriptor":{"type":"EMS_TMS","isAutoSelect":true,"lightModeDefault":"road_map_desaturated"},"visible":true,"style":{},"type":"EMS_VECTOR_TILE","minZoom":0,"maxZoom":24},{"id":"edh66","label":"Total Requests by Destination","minZoom":0,"maxZoom":24,"alpha":0.5,"sourceDescriptor":{"type":"EMS_FILE","id":"world_countries","tooltipProperties":["name","iso2"]},"visible":true,"style":{"type":"VECTOR","properties":{"fillColor":{"type":"DYNAMIC","options":{"field":{"name":"__kbnjoin__count__673ff994-fc75-4c67-909b-69fcb0e1060e","origin":"join"},"color":"Greys","fieldMetaOptions":{"isEnabled":false,"sigma":3}}},"lineColor":{"type":"STATIC","options":{"color":"#FFFFFF"}},"lineWidth":{"type":"STATIC","options":{"size":1}},"iconSize":{"type":"STATIC","options":{"size":10}},"symbolizeAs":{"options":{"value":"circle"}},"icon":{"type":"STATIC","options":{"value":"marker"}}}},"type":"GEOJSON_VECTOR","joins":[{"leftField":"iso2","right":{"type":"ES_TERM_SOURCE","id":"673ff994-fc75-4c67-909b-69fcb0e1060e","indexPatternTitle":"kibana_sample_data_logs","term":"geo.dest","indexPatternRefName":"layer_1_join_0_index_pattern","metrics":[{"type":"count","label":"web logs count"}],"applyGlobalQuery":true}}]},{"id":"gaxya","label":"Actual Requests","minZoom":9,"maxZoom":24,"alpha":1,"sourceDescriptor":{"id":"b7486535-171b-4d3b-bb2e-33c1a0a2854c","type":"ES_SEARCH","geoField":"geo.coordinates","limit":2048,"filterByMapBounds":true,"tooltipProperties":["clientip","timestamp","host","request","response","machine.os","agent","bytes"],"indexPatternRefName":"layer_2_source_index_pattern","applyGlobalQuery":true,"scalingType":"LIMIT"},"visible":true,"style":{"type":"VECTOR","properties":{"fillColor":{"type":"STATIC","options":{"color":"#2200ff"}},"lineColor":{"type":"STATIC","options":{"color":"#FFFFFF"}},"lineWidth":{"type":"STATIC","options":{"size":2}},"iconSize":{"type":"DYNAMIC","options":{"field":{"name":"bytes","origin":"source"},"minSize":1,"maxSize":23,"fieldMetaOptions":{"isEnabled":false,"sigma":3}}},"symbolizeAs":{"options":{"value":"circle"}},"icon":{"type":"STATIC","options":{"value":"marker"}}}},"type":"GEOJSON_VECTOR"},{"id":"tfi3f","label":"Total Requests and Bytes","minZoom":0,"maxZoom":9,"alpha":1,"sourceDescriptor":{"type":"ES_GEO_GRID","resolution":"COARSE","id":"8aaa65b5-a4e9-448b-9560-c98cb1c5ac5b","geoField":"geo.coordinates","requestType":"point","metrics":[{"type":"count","label":"web logs count"},{"type":"sum","field":"bytes"}],"indexPatternRefName":"layer_3_source_index_pattern","applyGlobalQuery":true},"visible":true,"style":{"type":"VECTOR","properties":{"fillColor":{"type":"DYNAMIC","options":{"field":{"name":"doc_count","origin":"source"},"color":"Blues","fieldMetaOptions":{"isEnabled":false,"sigma":3}}},"lineColor":{"type":"STATIC","options":{"color":"#cccccc"}},"lineWidth":{"type":"STATIC","options":{"size":1}},"iconSize":{"type":"DYNAMIC","options":{"field":{"name":"sum_of_bytes","origin":"source"},"minSize":7,"maxSize":25,"fieldMetaOptions":{"isEnabled":false,"sigma":3}}},"labelText":{"type":"DYNAMIC","options":{"field":{"name":"doc_count","origin":"source"},"fieldMetaOptions":{"isEnabled":false,"sigma":3}}},"labelSize":{"type":"DYNAMIC","options":{"field":{"name":"doc_count","origin":"source"},"minSize":12,"maxSize":24,"fieldMetaOptions":{"isEnabled":false,"sigma":3}}},"symbolizeAs":{"options":{"value":"circle"}},"icon":{"type":"STATIC","options":{"value":"marker"}}}},"type":"GEOJSON_VECTOR"}]' + mapStateJSON: '{"zoom":3.64,"center":{"lon":-88.92107,"lat":42.16337},"timeFilters":{"from":"now-7d","to":"now"},"refreshConfig":{"isPaused":true,"interval":0},"query":{"language":"kuery","query":""},"settings":{"autoFitToDataBounds":false}}' + title: '[Logs] Total Requests and Bytes' + uiStateJSON: '{"isDarkMode":false}' + coreMigrationVersion: 8.8.0 + created_at: '2023-08-23T20:03:32.204Z' + id: de71f4f0-1902-11e9-919b-ffe5949a18d2 + managed: false + references: + - id: 90943e30-9a47-11e8-b64d-95841ca0b247 + name: layer_1_join_0_index_pattern + type: index-pattern + - id: 90943e30-9a47-11e8-b64d-95841ca0b247 + name: layer_2_source_index_pattern + type: index-pattern + - id: 90943e30-9a47-11e8-b64d-95841ca0b247 + name: layer_3_source_index_pattern + type: index-pattern + type: map + typeMigrationVersion: 8.4.0 + updated_at: '2023-08-23T20:03:32.204Z' + version: WzEzLDFd + import_objects_request: + value: + file: file.ndjson + import_objects_response: + summary: The import objects API response indicates a successful import and the objects are created. Since these objects are created as new copies, each entry in the successResults array includes a destinationId attribute. + value: + successCount: 1 + success: true + successResults: + - type: index-pattern + id: 90943e30-9a47-11e8-b64d-95841ca0b247 + meta: + title: Kibana Sample Data Logs + icon: indexPatternApp + managed: false + destinationId: 82d2760c-468f-49cf-83aa-b9a35b6a8943 + schemas: + 400_response: + title: Bad request + type: object + required: + - error + - message + - statusCode + properties: + error: + type: string + enum: + - Bad Request + message: + type: string + statusCode: + type: integer + enum: + - 400 + export_objects_request: + type: object + properties: + excludeExportDetails: + description: Do not add export details entry at the end of the stream. + type: boolean + default: false + includeReferencesDeep: + description: Includes all of the referenced objects in the exported objects. + type: boolean + objects: + description: A list of objects to export. + type: array + items: + type: object + type: + description: The saved object types to include in the export. Use `*` to export all the types. + oneOf: + - type: string + - type: array + items: + type: string + import_objects_request: + type: object + properties: + file: + description: | + A file exported using the export API. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be included in this file. Similarly, the `savedObjects.maxImportPayloadBytes` setting limits the overall size of the file that can be imported. + 200_import_objects_response: + type: object + properties: + success: + type: boolean + description: | + Indicates when the import was successfully completed. When set to false, some objects may not have been created. For additional information, refer to the `errors` and `successResults` properties. + successCount: + type: integer + description: Indicates the number of successfully imported records. + errors: + type: array + items: + type: object + description: | + Indicates the import was unsuccessful and specifies the objects that failed to import. + + NOTE: One object may result in multiple errors, which requires separate steps to resolve. For instance, a `missing_references` error and conflict error. + successResults: + type: array + items: + type: object + description: | + Indicates the objects that are successfully imported, with any metadata if applicable. + + NOTE: Objects are created only when all resolvable errors are addressed, including conflicts and missing references. If objects are created as new copies, each entry in the `successResults` array includes a `destinationId` attribute. diff --git a/packages/core/saved-objects/docs/openapi/components/parameters/compatibility_mode.yaml b/packages/core/saved-objects/docs/openapi/components/parameters/compatibility_mode.yaml new file mode 100644 index 0000000000000..d191ef97bfbc8 --- /dev/null +++ b/packages/core/saved-objects/docs/openapi/components/parameters/compatibility_mode.yaml @@ -0,0 +1,7 @@ +in: query +name: compatibilityMode +schema: + type: boolean +required: false +description: | + Applies various adjustments to the saved objects that are being imported to maintain compatibility between different Kibana versions. Use this option only if you encounter issues with imported saved objects. NOTE: This option cannot be used with the `createNewCopies` option. \ No newline at end of file diff --git a/packages/core/saved-objects/docs/openapi/components/parameters/create_new_copies.yaml b/packages/core/saved-objects/docs/openapi/components/parameters/create_new_copies.yaml new file mode 100644 index 0000000000000..b159fe894bc3d --- /dev/null +++ b/packages/core/saved-objects/docs/openapi/components/parameters/create_new_copies.yaml @@ -0,0 +1,7 @@ +in: query +name: createNewCopies +schema: + type: boolean +required: false +description: | + Creates copies of saved objects, regenerates each object ID, and resets the origin. When used, potential conflict errors are avoided. NOTE: This option cannot be used with the `overwrite` and `compatibilityMode` options. diff --git a/packages/core/saved-objects/docs/openapi/components/parameters/overwrite.yaml b/packages/core/saved-objects/docs/openapi/components/parameters/overwrite.yaml new file mode 100644 index 0000000000000..dcc6100438671 --- /dev/null +++ b/packages/core/saved-objects/docs/openapi/components/parameters/overwrite.yaml @@ -0,0 +1,7 @@ +in: query +name: overwrite +schema: + type: boolean +required: false +description: | + Overwrites saved objects when they already exist. When used, potential conflict errors are automatically resolved by overwriting the destination object. NOTE: This option cannot be used with the `createNewCopies` option. diff --git a/packages/core/saved-objects/docs/openapi/components/parameters/space_id.yaml b/packages/core/saved-objects/docs/openapi/components/parameters/space_id.yaml new file mode 100644 index 0000000000000..0a9fba457e3e7 --- /dev/null +++ b/packages/core/saved-objects/docs/openapi/components/parameters/space_id.yaml @@ -0,0 +1,7 @@ +in: path +name: spaceId +description: An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used. +required: true +schema: + type: string + example: default diff --git a/packages/core/saved-objects/docs/openapi/components/schemas/200_import_objects_response.yaml b/packages/core/saved-objects/docs/openapi/components/schemas/200_import_objects_response.yaml new file mode 100644 index 0000000000000..27f7c9df2dd9b --- /dev/null +++ b/packages/core/saved-objects/docs/openapi/components/schemas/200_import_objects_response.yaml @@ -0,0 +1,25 @@ +type: object +properties: + success: + type: boolean + description: | + Indicates when the import was successfully completed. When set to false, some objects may not have been created. For additional information, refer to the `errors` and `successResults` properties. + successCount: + type: integer + description: Indicates the number of successfully imported records. + errors: + type: array + items: + type: object + description: | + Indicates the import was unsuccessful and specifies the objects that failed to import. + + NOTE: One object may result in multiple errors, which requires separate steps to resolve. For instance, a `missing_references` error and conflict error. + successResults: + type: array + items: + type: object + description: | + Indicates the objects that are successfully imported, with any metadata if applicable. + + NOTE: Objects are created only when all resolvable errors are addressed, including conflicts and missing references. If objects are created as new copies, each entry in the `successResults` array includes a `destinationId` attribute. diff --git a/packages/core/saved-objects/docs/openapi/components/schemas/export_objects_request.yaml b/packages/core/saved-objects/docs/openapi/components/schemas/export_objects_request.yaml new file mode 100644 index 0000000000000..2820b5a9b3bfe --- /dev/null +++ b/packages/core/saved-objects/docs/openapi/components/schemas/export_objects_request.yaml @@ -0,0 +1,21 @@ +type: object +properties: + excludeExportDetails: + description: Do not add export details entry at the end of the stream. + type: boolean + default: false + includeReferencesDeep: + description: Includes all of the referenced objects in the exported objects. + type: boolean + objects: + description: A list of objects to export. + type: array + items: + type: object + type: + description: The saved object types to include in the export. Use `*` to export all the types. + oneOf: + - type: string + - type: array + items: + type: string \ No newline at end of file diff --git a/packages/core/saved-objects/docs/openapi/components/schemas/import_objects_request.yaml b/packages/core/saved-objects/docs/openapi/components/schemas/import_objects_request.yaml new file mode 100644 index 0000000000000..15a855ecb277b --- /dev/null +++ b/packages/core/saved-objects/docs/openapi/components/schemas/import_objects_request.yaml @@ -0,0 +1,5 @@ +type: object +properties: + file: + description: | + A file exported using the export API. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be included in this file. Similarly, the `savedObjects.maxImportPayloadBytes` setting limits the overall size of the file that can be imported. \ No newline at end of file diff --git a/packages/core/saved-objects/docs/openapi/entrypoint.yaml b/packages/core/saved-objects/docs/openapi/entrypoint.yaml index b4e58a6ef2171..9fbd1f0ab70cb 100644 --- a/packages/core/saved-objects/docs/openapi/entrypoint.yaml +++ b/packages/core/saved-objects/docs/openapi/entrypoint.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.0 +openapi: 3.0.3 info: title: Saved objects description: OpenAPI schema for saved object endpoints diff --git a/packages/core/saved-objects/docs/openapi/entrypoint_serverless.yaml b/packages/core/saved-objects/docs/openapi/entrypoint_serverless.yaml new file mode 100644 index 0000000000000..b9500ccdbc570 --- /dev/null +++ b/packages/core/saved-objects/docs/openapi/entrypoint_serverless.yaml @@ -0,0 +1,39 @@ +openapi: 3.0.3 +info: + title: Serverless saved objects + description: OpenAPI schema for serverless saved object endpoints + version: '0.1' + contact: + name: Kibana Core Team + license: + name: Elastic License 2.0 + url: https://www.elastic.co/licensing/elastic-license +tags: + - name: saved objects + description: Manage Kibana saved objects, including dashboards, visualizations, and more. +servers: + - url: 'https://{kibana_url}' + variables: + kibana_url: + default: localhost:5601 +paths: + '/api/saved_objects/_export': + $ref: 'paths/api@saved_objects@_export.yaml' + '/api/saved_objects/_import': + $ref: 'paths/api@saved_objects@_import.yaml' + '/s/{spaceId}/api/saved_objects/_export': + $ref: 'paths/s@{spaceid}@api@saved_objects@_export.yaml' + '/s/{spaceId}/api/saved_objects/_import': + $ref: 'paths/s@{spaceid}@api@saved_objects@_import.yaml' +components: + securitySchemes: + apiKeyAuth: + type: apiKey + in: header + name: Authorization + description: > + Serverless APIs support only key-based authentication. + You must create an API key and use the encoded value in the request header. + For example: 'Authorization: ApiKey base64AccessApiKey'. +security: + - apiKeyAuth: [] diff --git a/packages/core/saved-objects/docs/openapi/paths/api@encrypted_saved_objects@_rotate_key.yaml b/packages/core/saved-objects/docs/openapi/paths/api@encrypted_saved_objects@_rotate_key.yaml index a863358622e3e..ae65d4da8539c 100644 --- a/packages/core/saved-objects/docs/openapi/paths/api@encrypted_saved_objects@_rotate_key.yaml +++ b/packages/core/saved-objects/docs/openapi/paths/api@encrypted_saved_objects@_rotate_key.yaml @@ -1,5 +1,5 @@ post: - summary: Rotate the encryption key for encrypted saved objects. + summary: Rotate a key for encrypted saved objects operationId: rotateEncryptionKey description: | Superuser role required. diff --git a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_create.yaml b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_create.yaml index ab22ae13481fb..a35d61964c3b5 100644 --- a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_create.yaml +++ b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_create.yaml @@ -1,5 +1,5 @@ post: - summary: Create multiple Kibana saved objects. + summary: Create saved objects operationId: bulkCreateSavedObjects deprecated: true tags: diff --git a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_delete.yaml b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_delete.yaml index bbbb848f94578..8b251d6d2fb14 100644 --- a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_delete.yaml +++ b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_delete.yaml @@ -1,5 +1,5 @@ post: - summary: Remove multiple Kibana saved objects. + summary: Delete saved objects operationId: bulkDeleteSavedObjects description: > WARNING: When you delete a saved object, it cannot be recovered. diff --git a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_get.yaml b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_get.yaml index aba99de4bf4b5..007b51a2745b9 100644 --- a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_get.yaml +++ b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_get.yaml @@ -1,5 +1,5 @@ post: - summary: Retrieve multiple Kibana saved objects by identifier. + summary: Get saved objects operationId: bulkGetSavedObjects deprecated: true tags: diff --git a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_resolve.yaml b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_resolve.yaml index 96ecd2eb0f13d..2be5950aca879 100644 --- a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_resolve.yaml +++ b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_resolve.yaml @@ -1,8 +1,9 @@ post: - summary: Retrieve multiple Kibana saved objects by identifier using any legacy URL aliases if they exist. + summary: Resolve saved objects operationId: bulkResolveSavedObjects deprecated: true description: > + Retrieve multiple Kibana saved objects by identifier using any legacy URL aliases if they exist. Under certain circumstances when Kibana is upgraded, saved object migrations may necessitate regenerating some object IDs to enable new features. When an object's ID is regenerated, a legacy URL alias is created for that object, preserving its old ID. In such a scenario, that object can be retrieved by the bulk resolve API using either its new ID or its old ID. diff --git a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_update.yaml b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_update.yaml index 497cae0e5b958..2d5c8fac7a65b 100644 --- a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_update.yaml +++ b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_bulk_update.yaml @@ -1,6 +1,7 @@ post: - summary: Update the attributes for multiple Kibana saved objects. + summary: Update saved objects operationId: bulkUpdateSavedObjects + description: Update the attributes for multiple Kibana saved objects. deprecated: true tags: - saved objects diff --git a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_export.yaml b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_export.yaml index ad6817b5e2ebf..6b21922a0cabb 100644 --- a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_export.yaml +++ b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_export.yaml @@ -1,8 +1,8 @@ post: - summary: Retrieve sets of saved objects that you want to import into Kibana. - operationId: exportSavedObjects + summary: Export saved objects in the default space + operationId: exportSavedObjectsDefault description: | - + Retrieve sets of saved objects that you want to import into Kibana. You must include `type` or `objects` in the request body. NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be exported. diff --git a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_find.yaml b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_find.yaml index 673baa1f33f09..b129a20ad0071 100644 --- a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_find.yaml +++ b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_find.yaml @@ -1,6 +1,7 @@ get: - summary: Retrieve a paginated set of Kibana saved objects. + summary: Search for saved objects operationId: findSavedObjects + description: Retrieve a paginated set of Kibana saved objects. deprecated: true tags: - saved objects diff --git a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_import.yaml b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_import.yaml index c1df23e8d11e0..6d61af2f85147 100644 --- a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_import.yaml +++ b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_import.yaml @@ -1,7 +1,8 @@ post: - summary: Create sets of Kibana saved objects from a file created by the export API. - operationId: importSavedObjects + summary: Import saved objects in the default space + operationId: importSavedObjectsDefault description: | + Create sets of Kibana saved objects from a file created by the export API. Saved objects can be imported only into the same version, a newer minor on the same major, or the next major. Exported saved objects are not backwards compatible and cannot be imported into an older version of Kibana. This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. diff --git a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_resolve_import_errors.yaml b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_resolve_import_errors.yaml index aa64b0f3b08af..bfca82dbe7274 100644 --- a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_resolve_import_errors.yaml +++ b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@_resolve_import_errors.yaml @@ -1,8 +1,8 @@ post: - summary: Resolve errors from the Import objects API. + summary: Resolve import errors operationId: resolveImportErrors description: | - To resolve errors, you can: + To resolve errors from the Import objects API, you can: * Retry certain saved objects * Overwrite specific saved objects diff --git a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@resolve@{type}@{id}.yaml b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@resolve@{type}@{id}.yaml index 45f8695d401a8..3f02bd5fc80bc 100644 --- a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@resolve@{type}@{id}.yaml +++ b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@resolve@{type}@{id}.yaml @@ -1,7 +1,8 @@ get: - summary: Retrieve a single Kibana saved object by identifier using any legacy URL alias if it exists. + summary: Resolve a saved object operationId: resolveSavedObject description: > + Retrieve a single Kibana saved object by identifier using any legacy URL alias if it exists. Under certain circumstances, when Kibana is upgraded, saved object migrations may necessitate regenerating some object IDs to enable new features. When an object's ID is regenerated, a legacy URL alias is created for that object, preserving its old ID. In such a scenario, that object can be retrieved using either its new ID or its old ID. diff --git a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@{type}.yaml b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@{type}.yaml index 6fb4a6a2b046e..d58b1543021eb 100644 --- a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@{type}.yaml +++ b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@{type}.yaml @@ -1,6 +1,7 @@ post: - summary: Create Kibana saved objects with randomly generated identifiers. + summary: Create a saved object operationId: createSavedObject + description: Create a Kibana saved object with a randomly generated identifier. deprecated: true tags: - saved objects diff --git a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@{type}@{id}.yaml b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@{type}@{id}.yaml index dc34bc339e2bb..fbbd772eeb836 100644 --- a/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@{type}@{id}.yaml +++ b/packages/core/saved-objects/docs/openapi/paths/api@saved_objects@{type}@{id}.yaml @@ -1,6 +1,7 @@ get: - summary: Retrieve a single Kibana saved object by identifier. + summary: Get a saved object operationId: getSavedObject + description: Retrieve a single Kibana saved object by identifier. deprecated: true tags: - saved objects @@ -22,8 +23,9 @@ get: $ref: '../components/schemas/400_response.yaml' post: - summary: Create Kibana saved objects. + summary: Create a saved object operationId: createSavedObjectId + description: Create a Kibana saved object and specify its identifier instead of using a randomly generated ID. deprecated: true tags: - saved objects @@ -66,8 +68,9 @@ post: type: object put: - summary: Update the attributes for Kibana saved objects. + summary: Update a saved object operationId: updateSavedObject + description: Update the attributes for Kibana saved objects. deprecated: true tags: - saved objects diff --git a/packages/core/saved-objects/docs/openapi/paths/s@{spaceid}@api@saved_objects@_export.yaml b/packages/core/saved-objects/docs/openapi/paths/s@{spaceid}@api@saved_objects@_export.yaml new file mode 100644 index 0000000000000..914c872ec05e8 --- /dev/null +++ b/packages/core/saved-objects/docs/openapi/paths/s@{spaceid}@api@saved_objects@_export.yaml @@ -0,0 +1,41 @@ +post: + summary: Export saved objects + operationId: exportSavedObjects + description: | + Retrieves sets of saved objects that you want to import into Kibana. + You must include `type` or `objects` in the request body. + + NOTE: The `savedObjects.maxImportExportSize` configuration setting limits the number of saved objects which may be exported. + + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - saved objects + parameters: + - $ref: '../components/headers/kbn_xsrf.yaml' + - $ref: '../components/parameters/space_id.yaml' + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/export_objects_request.yaml' + examples: + exportSavedObjectsRequest: + $ref: '../components/examples/export_objects_request.yaml' + responses: + '200': + description: Indicates a successful call. + content: + application/x-ndjson: + schema: + type: object + additionalProperties: true + examples: + exportSavedObjectsResponse: + $ref: '../components/examples/export_objects_response.yaml' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '../components/schemas/400_response.yaml' diff --git a/packages/core/saved-objects/docs/openapi/paths/s@{spaceid}@api@saved_objects@_import.yaml b/packages/core/saved-objects/docs/openapi/paths/s@{spaceid}@api@saved_objects@_import.yaml new file mode 100644 index 0000000000000..eac1b3810e0fd --- /dev/null +++ b/packages/core/saved-objects/docs/openapi/paths/s@{spaceid}@api@saved_objects@_import.yaml @@ -0,0 +1,41 @@ +post: + summary: Import saved objects + operationId: importSavedObjects + description: | + Creates sets of Kibana saved objects from a file created by the export API. + Saved objects can be imported only into the same version, a newer minor on the same major, or the next major. Exported saved objects are not backwards compatible and cannot be imported into an older version of Kibana. + + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - saved objects + parameters: + - $ref: '../components/headers/kbn_xsrf.yaml' + - $ref: '../components/parameters/space_id.yaml' + - $ref: '../components/parameters/compatibility_mode.yaml' + - $ref: '../components/parameters/create_new_copies.yaml' + - $ref: '../components/parameters/overwrite.yaml' + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: '../components/schemas/import_objects_request.yaml' + examples: + importObjectsRequest: + $ref: '../components/examples/import_objects_request.yaml' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '../components/schemas/200_import_objects_response.yaml' + examples: + importObjectsResponse: + $ref: '../components/examples/import_objects_response.yaml' + '400': + description: Bad request. + content: + application/json: + schema: + $ref: '../components/schemas/400_response.yaml' diff --git a/packages/core/status/core-status-server-internal/src/status_service.ts b/packages/core/status/core-status-server-internal/src/status_service.ts index 3947f04c61574..a24153342543c 100644 --- a/packages/core/status/core-status-server-internal/src/status_service.ts +++ b/packages/core/status/core-status-server-internal/src/status_service.ts @@ -18,7 +18,7 @@ import { import { map, distinctUntilChanged, shareReplay, takeUntil, debounceTime } from 'rxjs'; import { isDeepStrictEqual } from 'util'; -import type { RootSchema } from '@kbn/analytics-client'; +import type { RootSchema } from '@kbn/ebt/client'; import type { Logger, LogMeta } from '@kbn/logging'; import type { CoreContext, CoreService } from '@kbn/core-base-server-internal'; import type { PluginName } from '@kbn/core-base-common'; diff --git a/packages/core/status/core-status-server-internal/tsconfig.json b/packages/core/status/core-status-server-internal/tsconfig.json index 0cc9f82b84794..42d4e92c57e0a 100644 --- a/packages/core/status/core-status-server-internal/tsconfig.json +++ b/packages/core/status/core-status-server-internal/tsconfig.json @@ -16,7 +16,6 @@ "@kbn/std", "@kbn/logging", "@kbn/i18n", - "@kbn/analytics-client", "@kbn/core-base-common", "@kbn/core-base-server-internal", "@kbn/core-http-server", @@ -42,6 +41,7 @@ "@kbn/core-analytics-server-mocks", "@kbn/core-logging-server-internal", "@kbn/core-logging-server-mocks", + "@kbn/ebt", ], "exclude": [ "target/**/*", diff --git a/packages/deeplinks/search/constants.ts b/packages/deeplinks/search/constants.ts index fb8a0c459cbd0..3fdcc78bb68a1 100644 --- a/packages/deeplinks/search/constants.ts +++ b/packages/deeplinks/search/constants.ts @@ -8,6 +8,7 @@ export const ENTERPRISE_SEARCH_APP_ID = 'enterpriseSearch'; export const ENTERPRISE_SEARCH_CONTENT_APP_ID = 'enterpriseSearchContent'; +export const ENTERPRISE_SEARCH_INFERENCE_ENDPOINTS_APP_ID = 'enterpriseSearchInferenceEndpoints'; export const ENTERPRISE_SEARCH_APPLICATIONS_APP_ID = 'enterpriseSearchApplications'; export const ENTERPRISE_SEARCH_ANALYTICS_APP_ID = 'enterpriseSearchAnalytics'; export const ENTERPRISE_SEARCH_APPSEARCH_APP_ID = 'appSearch'; @@ -15,3 +16,5 @@ export const ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID = 'workplaceSearch'; export const SERVERLESS_ES_APP_ID = 'serverlessElasticsearch'; export const SERVERLESS_ES_CONNECTORS_ID = 'serverlessConnectors'; export const SERVERLESS_ES_SEARCH_PLAYGROUND_ID = 'searchPlayground'; +export const SERVERLESS_ES_SEARCH_INFERENCE_ENDPOINTS_ID = 'searchInferenceEndpoints'; +export const SEARCH_HOMEPAGE = 'searchHomepage'; diff --git a/packages/deeplinks/search/deep_links.ts b/packages/deeplinks/search/deep_links.ts index 352640e6d4947..f004d1b2c9dd6 100644 --- a/packages/deeplinks/search/deep_links.ts +++ b/packages/deeplinks/search/deep_links.ts @@ -16,6 +16,8 @@ import { ENTERPRISE_SEARCH_APPSEARCH_APP_ID, ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID, SERVERLESS_ES_SEARCH_PLAYGROUND_ID, + SERVERLESS_ES_SEARCH_INFERENCE_ENDPOINTS_ID, + SEARCH_HOMEPAGE, } from './constants'; export type EnterpriseSearchApp = typeof ENTERPRISE_SEARCH_APP_ID; @@ -27,6 +29,8 @@ export type EnterpriseSearchWorkplaceSearchApp = typeof ENTERPRISE_SEARCH_WORKPL export type ServerlessSearchApp = typeof SERVERLESS_ES_APP_ID; export type ConnectorsId = typeof SERVERLESS_ES_CONNECTORS_ID; export type SearchPlaygroundId = typeof SERVERLESS_ES_SEARCH_PLAYGROUND_ID; +export type SearchInferenceEndpointsId = typeof SERVERLESS_ES_SEARCH_INFERENCE_ENDPOINTS_ID; +export type SearchHomepage = typeof SEARCH_HOMEPAGE; export type ContentLinkId = 'searchIndices' | 'connectors' | 'webCrawlers'; @@ -44,6 +48,8 @@ export type DeepLinkId = | ServerlessSearchApp | ConnectorsId | SearchPlaygroundId + | SearchInferenceEndpointsId + | SearchHomepage | `${EnterpriseSearchContentApp}:${ContentLinkId}` | `${EnterpriseSearchApplicationsApp}:${ApplicationsLinkId}` | `${EnterpriseSearchAppsearchApp}:${AppsearchLinkId}`; diff --git a/packages/deeplinks/search/index.ts b/packages/deeplinks/search/index.ts index ba15b8c4890c3..663d625e9fd72 100644 --- a/packages/deeplinks/search/index.ts +++ b/packages/deeplinks/search/index.ts @@ -9,6 +9,7 @@ export { ENTERPRISE_SEARCH_APP_ID, ENTERPRISE_SEARCH_CONTENT_APP_ID, + ENTERPRISE_SEARCH_INFERENCE_ENDPOINTS_APP_ID, ENTERPRISE_SEARCH_APPLICATIONS_APP_ID, ENTERPRISE_SEARCH_ANALYTICS_APP_ID, ENTERPRISE_SEARCH_APPSEARCH_APP_ID, diff --git a/packages/deeplinks/security/deep_links.ts b/packages/deeplinks/security/deep_links.ts index ea1d818cf8010..c127a8bfafdf2 100644 --- a/packages/deeplinks/security/deep_links.ts +++ b/packages/deeplinks/security/deep_links.ts @@ -86,4 +86,5 @@ export enum SecurityPageName { entityAnalyticsManagement = 'entity_analytics-management', entityAnalyticsAssetClassification = 'entity_analytics-asset-classification', coverageOverview = 'coverage-overview', + notesManagement = 'notes-management', } diff --git a/packages/kbn-alerting-types/alerting_framework_health_types.ts b/packages/kbn-alerting-types/alerting_framework_health_types.ts new file mode 100644 index 0000000000000..8bba85300fa2a --- /dev/null +++ b/packages/kbn-alerting-types/alerting_framework_health_types.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export enum HealthStatus { + OK = 'ok', + Warning = 'warn', + Error = 'error', +} + +export interface AlertsHealth { + decryptionHealth: { + status: HealthStatus; + timestamp: string; + }; + executionHealth: { + status: HealthStatus; + timestamp: string; + }; + readHealth: { + status: HealthStatus; + timestamp: string; + }; +} + +export interface AlertingFrameworkHealth { + isSufficientlySecure: boolean; + hasPermanentEncryptionKey: boolean; + alertingFrameworkHealth: AlertsHealth; +} diff --git a/packages/kbn-alerting-types/index.ts b/packages/kbn-alerting-types/index.ts index 8d9ecf1422806..1b82df61427e1 100644 --- a/packages/kbn-alerting-types/index.ts +++ b/packages/kbn-alerting-types/index.ts @@ -13,4 +13,5 @@ export * from './alert_type'; export * from './rule_notify_when_type'; export * from './r_rule_types'; export * from './rule_types'; +export * from './alerting_framework_health_types'; export * from './action_variable'; diff --git a/packages/kbn-alerting-types/rule_types.ts b/packages/kbn-alerting-types/rule_types.ts index c6fc66788cb0d..c0cb500740cb8 100644 --- a/packages/kbn-alerting-types/rule_types.ts +++ b/packages/kbn-alerting-types/rule_types.ts @@ -6,12 +6,17 @@ * Side Public License, v 1. */ -import type { SavedObjectAttributes } from '@kbn/core/server'; +import type { + SavedObjectAttribute, + SavedObjectAttributes, + SavedObjectsResolveResponse, +} from '@kbn/core/server'; import type { Filter } from '@kbn/es-query'; import type { RuleNotifyWhenType, RRuleParams } from '.'; export type RuleTypeParams = Record; export type RuleActionParams = SavedObjectAttributes; +export type RuleActionParam = SavedObjectAttribute; export const ISO_WEEKDAYS = [1, 2, 3, 4, 5, 6, 7] as const; export type IsoWeekday = typeof ISO_WEEKDAYS[number]; @@ -239,3 +244,9 @@ export type SanitizedRule = Omit< Rule, 'apiKey' | 'actions' > & { actions: SanitizedRuleAction[] }; + +export type ResolvedSanitizedRule = SanitizedRule & + Omit & { + outcome: string; + alias_target_id?: string; + }; diff --git a/packages/kbn-alerts-as-data-utils/src/schemas/generated/stack_schema.ts b/packages/kbn-alerts-as-data-utils/src/schemas/generated/stack_schema.ts index 94ba544e4c96f..1d85ffcb1f714 100644 --- a/packages/kbn-alerts-as-data-utils/src/schemas/generated/stack_schema.ts +++ b/packages/kbn-alerts-as-data-utils/src/schemas/generated/stack_schema.ts @@ -11,6 +11,7 @@ import * as rt from 'io-ts'; import { Either } from 'fp-ts/lib/Either'; import { AlertSchema } from './alert_schema'; +import { EcsSchema } from './ecs_schema'; const ISO_DATE_PATTERN = /^d{4}-d{2}-d{2}Td{2}:d{2}:d{2}.d{3}Z$/; export const IsoDateString = new rt.Type( 'IsoDateString', @@ -77,6 +78,6 @@ const StackAlertOptional = rt.partial({ }); // prettier-ignore -export const StackAlertSchema = rt.intersection([StackAlertRequired, StackAlertOptional, AlertSchema]); +export const StackAlertSchema = rt.intersection([StackAlertRequired, StackAlertOptional, AlertSchema, EcsSchema]); // prettier-ignore export type StackAlert = rt.TypeOf; diff --git a/packages/kbn-alerts-ui-shared/index.ts b/packages/kbn-alerts-ui-shared/index.ts index eb3eae1496144..a93fbd476165b 100644 --- a/packages/kbn-alerts-ui-shared/index.ts +++ b/packages/kbn-alerts-ui-shared/index.ts @@ -18,4 +18,4 @@ export type { AlertsSearchBarProps } from './src/alerts_search_bar/types'; export * from './src/alert_fields_table'; export * from './src/alert_filter_controls/types'; -export * from './src/common/hooks'; +export * from './src/common/types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.test.ts b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/create_rule.test.ts similarity index 89% rename from x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.test.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/create_rule/create_rule.test.ts index cf32f04a2bc23..8735a9bebca73 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.test.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/create_rule.test.ts @@ -1,13 +1,15 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { httpServiceMock } from '@kbn/core/public/mocks'; -import { RuleUpdates } from '../../../types'; -import { createRule } from './create'; +import { RuleTypeParams } from '../../types'; +import { createRule } from './create_rule'; +import { CreateRuleBody } from './types'; const http = httpServiceMock.createStartContract(); @@ -62,10 +64,7 @@ describe('createRule', () => { }, }; - const ruleToCreate: Omit< - RuleUpdates, - 'createdBy' | 'updatedBy' | 'muteAll' | 'mutedInstanceIds' | 'executionStatus' - > = { + const ruleToCreate: CreateRuleBody = { params: { aggType: 'count', termSize: 5, @@ -106,17 +105,14 @@ describe('createRule', () => { actionTypeId: '.system-action', }, ], - createdAt: new Date('2021-04-01T21:33:13.247Z'), - updatedAt: new Date('2021-04-01T21:33:13.247Z'), - apiKeyOwner: '', - revision: 0, + notifyWhen: 'onActionGroupChange', alertDelay: { active: 10, }, }; http.post.mockResolvedValueOnce(resolvedValue); - const result = await createRule({ http, rule: ruleToCreate }); + const result = await createRule({ http, rule: ruleToCreate as CreateRuleBody }); expect(result).toEqual({ actions: [ { diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/create_rule.ts b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/create_rule.ts new file mode 100644 index 0000000000000..556d0beb74a1f --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/create_rule.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { HttpSetup } from '@kbn/core/public'; +import type { AsApiContract } from '@kbn/actions-types'; +import type { Rule } from '../../types'; +import { CreateRuleBody, transformCreateRuleBody } from '.'; +import { BASE_ALERTING_API_PATH } from '../../constants'; +import { transformRule } from '../../transformations'; + +export async function createRule({ + http, + rule, +}: { + http: HttpSetup; + rule: CreateRuleBody; +}): Promise { + const res = await http.post>(`${BASE_ALERTING_API_PATH}/rule`, { + body: JSON.stringify(transformCreateRuleBody(rule)), + }); + return transformRule(res); +} diff --git a/packages/kbn-field-utils/src/components/field_description_icon_button/index.ts b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/index.ts similarity index 74% rename from packages/kbn-field-utils/src/components/field_description_icon_button/index.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/create_rule/index.ts index 36ebc6ef3e114..8bf74c5def039 100644 --- a/packages/kbn-field-utils/src/components/field_description_icon_button/index.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/index.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -export { - FieldDescriptionIconButton, - type FieldDescriptionIconButtonProps, -} from './field_description_icon_button'; +export * from './types'; +export * from './create_rule'; +export * from './transform_create_rule_body'; diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/transform_create_rule_body.test.ts b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/transform_create_rule_body.test.ts new file mode 100644 index 0000000000000..8b41e38d14ec1 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/transform_create_rule_body.test.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { transformCreateRuleBody } from './transform_create_rule_body'; +import type { RuleTypeParams } from '../../types'; +import type { CreateRuleBody } from './types'; + +const ruleToCreate: CreateRuleBody = { + params: { + aggType: 'count', + termSize: 5, + thresholdComparator: '>', + timeWindowSize: 5, + timeWindowUnit: 'm', + groupBy: 'all', + threshold: [1000], + index: ['.kibana'], + timeField: 'alert.executionStatus.lastExecutionDate', + }, + consumer: 'alerts', + schedule: { interval: '1m' }, + tags: [], + name: 'test', + enabled: true, + throttle: null, + ruleTypeId: '.index-threshold', + actions: [ + { + group: 'threshold met', + id: '83d4d860-9316-11eb-a145-93ab369a4461', + params: { + level: 'info', + message: 'test-message', + }, + actionTypeId: '.server-log', + frequency: { + notifyWhen: 'onActionGroupChange', + throttle: null, + summary: false, + }, + useAlertDataForTemplate: true, + }, + { + id: '.test-system-action', + params: {}, + actionTypeId: '.system-action', + }, + ], + notifyWhen: 'onActionGroupChange', + alertDelay: { + active: 10, + }, +}; + +describe('transformCreateRuleBody', () => { + test('should transform create rule body', () => { + expect(transformCreateRuleBody(ruleToCreate)).toEqual({ + params: { + aggType: 'count', + termSize: 5, + thresholdComparator: '>', + timeWindowSize: 5, + timeWindowUnit: 'm', + groupBy: 'all', + threshold: [1000], + index: ['.kibana'], + timeField: 'alert.executionStatus.lastExecutionDate', + }, + consumer: 'alerts', + schedule: { interval: '1m' }, + tags: [], + name: 'test', + enabled: true, + throttle: null, + notifyWhen: 'onActionGroupChange', + rule_type_id: '.index-threshold', + actions: [ + { + group: 'threshold met', + id: '83d4d860-9316-11eb-a145-93ab369a4461', + params: { + level: 'info', + message: 'test-message', + }, + frequency: { + notify_when: 'onActionGroupChange', + summary: false, + throttle: null, + }, + use_alert_data_for_template: true, + }, + { id: '.test-system-action', params: {} }, + ], + + alert_delay: { active: 10 }, + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/transform_create_rule_body.ts similarity index 55% rename from x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/create_rule/transform_create_rule_body.ts index ad4768dc9fee3..261641d2c18a9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/transform_create_rule_body.ts @@ -1,26 +1,15 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ -import { HttpSetup } from '@kbn/core/public'; -import { AsApiContract, RewriteResponseCase } from '@kbn/actions-plugin/common'; -import { Rule, RuleUpdates } from '../../../types'; -import { BASE_ALERTING_API_PATH } from '../../constants'; -import { transformRule } from './common_transformations'; -type RuleCreateBody = Omit< - RuleUpdates, - | 'createdBy' - | 'updatedBy' - | 'muteAll' - | 'mutedInstanceIds' - | 'executionStatus' - | 'lastRun' - | 'nextRun' ->; -export const rewriteBodyRequest: RewriteResponseCase = ({ +import { RewriteResponseCase } from '@kbn/actions-types'; +import { CreateRuleBody } from './types'; + +export const transformCreateRuleBody: RewriteResponseCase = ({ ruleTypeId, actions, alertDelay, @@ -56,16 +45,3 @@ export const rewriteBodyRequest: RewriteResponseCase = ({ }), ...(alertDelay ? { alert_delay: alertDelay } : {}), }); - -export async function createRule({ - http, - rule, -}: { - http: HttpSetup; - rule: RuleCreateBody; -}): Promise { - const res = await http.post>(`${BASE_ALERTING_API_PATH}/rule`, { - body: JSON.stringify(rewriteBodyRequest(rule)), - }); - return transformRule(res); -} diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/types.ts b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/types.ts new file mode 100644 index 0000000000000..dfea6fb77de32 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/create_rule/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 { Rule, RuleTypeParams } from '../../types'; + +export interface CreateRuleBody { + name: Rule['name']; + ruleTypeId: Rule['ruleTypeId']; + enabled: Rule['enabled']; + consumer: Rule['consumer']; + tags: Rule['tags']; + throttle?: Rule['throttle']; + params: Rule['params']; + schedule: Rule['schedule']; + actions: Rule['actions']; + notifyWhen?: Rule['notifyWhen']; + alertDelay?: Rule['alertDelay']; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/health.test.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/fetch_alerting_framework_health.test.ts similarity index 74% rename from x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/health.test.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/fetch_alerting_framework_health.test.ts index 84a9b86fc55d3..b67e16e2d1a41 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/health.test.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/fetch_alerting_framework_health.test.ts @@ -1,16 +1,17 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { httpServiceMock } from '@kbn/core/public/mocks'; -import { alertingFrameworkHealth } from './health'; +import { fetchAlertingFrameworkHealth } from '.'; -describe('alertingFrameworkHealth', () => { +describe('fetchAlertingFrameworkHealth', () => { const http = httpServiceMock.createStartContract(); - test('should call alertingFrameworkHealth API', async () => { + test('should call fetchAlertingFrameworkHealth API', async () => { http.get.mockResolvedValueOnce({ is_sufficiently_secure: true, has_permanent_encryption_key: true, @@ -20,7 +21,7 @@ describe('alertingFrameworkHealth', () => { read_health: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' }, }, }); - const result = await alertingFrameworkHealth({ http }); + const result = await fetchAlertingFrameworkHealth({ http }); expect(result).toEqual({ alertingFrameworkHealth: { decryptionHealth: { status: 'ok', timestamp: '2021-04-01T21:29:22.991Z' }, diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/fetch_alerting_framework_health.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/fetch_alerting_framework_health.ts new file mode 100644 index 0000000000000..44d717730e2c9 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/fetch_alerting_framework_health.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { HttpSetup } from '@kbn/core/public'; +import { AsApiContract } from '@kbn/actions-types'; +import { AlertingFrameworkHealth, AlertsHealth } from '@kbn/alerting-types'; +import { BASE_ALERTING_API_PATH } from '../../constants'; +import { transformAlertingFrameworkHealthResponse, transformAlertsHealthResponse } from '.'; + +export async function fetchAlertingFrameworkHealth({ + http, +}: { + http: HttpSetup; +}): Promise { + const res = await http.get>( + `${BASE_ALERTING_API_PATH}/_health` + ); + const alertingFrameworkHealthRewrited = transformAlertsHealthResponse( + res.alerting_framework_health as unknown as AsApiContract + ); + return { + ...transformAlertingFrameworkHealthResponse(res), + alertingFrameworkHealth: alertingFrameworkHealthRewrited, + }; +} diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/index.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/index.ts new file mode 100644 index 0000000000000..c73e798034a51 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './fetch_alerting_framework_health'; +export * from './transform_alerting_framework_health_response'; diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/transform_alerting_framework_health.test.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/transform_alerting_framework_health.test.ts new file mode 100644 index 0000000000000..3710b6cdcdf73 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/transform_alerting_framework_health.test.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { HealthStatus } from '@kbn/alerting-types'; +import { + transformAlertsHealthResponse, + transformAlertingFrameworkHealthResponse, +} from './transform_alerting_framework_health_response'; + +describe('transformAlertingFrameworkHealth', () => { + test('should transform alerts health response', () => { + expect( + transformAlertsHealthResponse({ + decryption_health: { + status: HealthStatus.OK, + timestamp: new Date('01-01-2024').toISOString(), + }, + execution_health: { + status: HealthStatus.OK, + timestamp: new Date('01-02-2024').toISOString(), + }, + read_health: { + status: HealthStatus.OK, + timestamp: new Date('01-03-2024').toISOString(), + }, + }) + ).toEqual({ + decryptionHealth: { + status: 'ok', + timestamp: new Date('01-01-2024').toISOString(), + }, + executionHealth: { + status: 'ok', + timestamp: new Date('01-02-2024').toISOString(), + }, + readHealth: { + status: 'ok', + timestamp: new Date('01-03-2024').toISOString(), + }, + }); + }); + + test('should transform alerting framework health response', () => { + expect( + transformAlertingFrameworkHealthResponse({ + is_sufficiently_secure: true, + has_permanent_encryption_key: true, + alerting_framework_health: { + decryptionHealth: { + status: HealthStatus.OK, + timestamp: new Date('01-01-2024').toISOString(), + }, + executionHealth: { + status: HealthStatus.OK, + timestamp: new Date('01-02-2024').toISOString(), + }, + readHealth: { + status: HealthStatus.OK, + timestamp: new Date('01-03-2024').toISOString(), + }, + }, + }) + ).toEqual({ + alertingFrameworkHealth: { + decryptionHealth: { + status: 'ok', + timestamp: new Date('01-01-2024').toISOString(), + }, + executionHealth: { + status: 'ok', + timestamp: new Date('01-02-2024').toISOString(), + }, + readHealth: { + status: 'ok', + timestamp: new Date('01-03-2024').toISOString(), + }, + }, + hasPermanentEncryptionKey: true, + isSufficientlySecure: true, + }); + }); +}); diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/transform_alerting_framework_health_response.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/transform_alerting_framework_health_response.ts new file mode 100644 index 0000000000000..29e054b7e94d1 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_alerting_framework_health/transform_alerting_framework_health_response.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { AsApiContract, RewriteRequestCase } from '@kbn/actions-types'; +import { AlertingFrameworkHealth, AlertsHealth } from '@kbn/alerting-types'; + +export const transformAlertsHealthResponse: RewriteRequestCase = ({ + decryption_health: decryptionHealth, + execution_health: executionHealth, + read_health: readHealth, + ...res +}: AsApiContract) => ({ + decryptionHealth, + executionHealth, + readHealth, + ...res, +}); + +export const transformAlertingFrameworkHealthResponse: RewriteRequestCase< + AlertingFrameworkHealth +> = ({ + is_sufficiently_secure: isSufficientlySecure, + has_permanent_encryption_key: hasPermanentEncryptionKey, + + alerting_framework_health: alertingFrameworkHealth, + ...res +}: AsApiContract) => ({ + isSufficientlySecure, + hasPermanentEncryptionKey, + alertingFrameworkHealth, + ...res, +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/config_api.test.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/fetch_ui_config.test.ts similarity index 57% rename from x-pack/plugins/triggers_actions_ui/public/common/lib/config_api.test.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/fetch_ui_config.test.ts index 9f117b51c9cf8..0fcdcab8ab296 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/config_api.test.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/fetch_ui_config.test.ts @@ -1,18 +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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { httpServiceMock } from '@kbn/core/public/mocks'; -import { triggersActionsUiConfig } from './config_api'; +import { fetchUiConfig } from '.'; -describe('triggersActionsUiConfig', () => { +describe('fetchUiConfig', () => { const http = httpServiceMock.createStartContract(); - test('should call triggersActionsUiConfig API', async () => { - const result = await triggersActionsUiConfig({ http }); + test('should call fetchUiConfig API', async () => { + const result = await fetchUiConfig({ http }); expect(result).toEqual(undefined); expect(http.get.mock.calls).toMatchInlineSnapshot(` Array [ diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/fetch_ui_config.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/fetch_ui_config.ts new file mode 100644 index 0000000000000..bcf724bb11226 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/fetch_ui_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 and the Server Side Public License, v 1; you may not 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 { HttpStart } from '@kbn/core-http-browser'; +import { BASE_TRIGGERS_ACTIONS_UI_API_PATH } from '../../constants'; +import { UiConfig } from '.'; + +export const fetchUiConfig = async ({ http }: { http: HttpStart }): Promise => { + return http.get(`${BASE_TRIGGERS_ACTIONS_UI_API_PATH}/_config`); +}; diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/start_api_ftr_execution.js b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/index.ts similarity index 80% rename from .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/start_api_ftr_execution.js rename to packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/index.ts index 2ee5715a0fe2b..e0ad7744f20d4 100644 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/start_api_ftr_execution.js +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/index.ts @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -require('../../../../../src/setup_node_env'); -require('./api_ftr_execution').cli(); +export * from './types'; +export * from './fetch_ui_config'; diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/types.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/types.ts new file mode 100644 index 0000000000000..8bc4245f713c5 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_config/types.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export interface UiConfig { + isUsingSecurity: boolean; + minimumScheduleInterval?: { + value: string; + enforce: boolean; + }; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/health_api.test.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/fetch_ui_health_status.test.ts similarity index 56% rename from x-pack/plugins/triggers_actions_ui/public/common/lib/health_api.test.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/fetch_ui_health_status.test.ts index b668d58f5b7da..84388fb63305e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/health_api.test.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/fetch_ui_health_status.test.ts @@ -1,18 +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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { httpServiceMock } from '@kbn/core/public/mocks'; -import { triggersActionsUiHealth } from './health_api'; +import { fetchUiHealthStatus } from '.'; -describe('triggersActionsUiHealth', () => { +describe('fetchUiHealthStatus', () => { const http = httpServiceMock.createStartContract(); - test('should call triggersActionsUiHealth API', async () => { - const result = await triggersActionsUiHealth({ http }); + test('should call fetchUiHealthStatus API', async () => { + const result = await fetchUiHealthStatus({ http }); expect(result).toEqual(undefined); expect(http.get.mock.calls).toMatchInlineSnapshot(` Array [ diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/fetch_ui_health_status.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/fetch_ui_health_status.ts new file mode 100644 index 0000000000000..28eb211ac056c --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/fetch_ui_health_status.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { HttpStart } from '@kbn/core-http-browser'; +import type { UiHealthCheck, UiHealthCheckResponse } from './types'; +import { BASE_TRIGGERS_ACTIONS_UI_API_PATH } from '../../constants'; + +export const fetchUiHealthStatus = async ({ + http, +}: { + http: HttpStart; +}): Promise => { + const result = await http.get( + `${BASE_TRIGGERS_ACTIONS_UI_API_PATH}/_health` + ); + if (result) { + return { + isRulesAvailable: result.isAlertsAvailable, + }; + } + return result; +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/index.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/index.ts new file mode 100644 index 0000000000000..ec5fa3329dc53 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './types'; +export * from './fetch_ui_health_status'; diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/types.ts b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/types.ts new file mode 100644 index 0000000000000..bf0ee678d8949 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/fetch_ui_health_status/types.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export interface UiHealthCheck { + isRulesAvailable: boolean; +} + +export interface UiHealthCheckResponse { + isAlertsAvailable: boolean; +} diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/resolve_rule/index.ts b/packages/kbn-alerts-ui-shared/src/common/apis/resolve_rule/index.ts new file mode 100644 index 0000000000000..aef77ab4a00f1 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/resolve_rule/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './resolve_rule'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/resolve_rule.test.ts b/packages/kbn-alerts-ui-shared/src/common/apis/resolve_rule/resolve_rule.test.ts similarity index 93% rename from x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/resolve_rule.test.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/resolve_rule/resolve_rule.test.ts index 0803f14f4a7f1..ffedc60aac0d6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/resolve_rule.test.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/resolve_rule/resolve_rule.test.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { httpServiceMock } from '@kbn/core/public/mocks'; @@ -64,7 +65,7 @@ describe('resolveRule', () => { }; http.get.mockResolvedValueOnce(resolvedValue); - expect(await resolveRule({ http, ruleId })).toEqual({ + expect(await resolveRule({ http, id: ruleId })).toEqual({ id: '1/', params: { aggType: 'count', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/resolve_rule.ts b/packages/kbn-alerts-ui-shared/src/common/apis/resolve_rule/resolve_rule.ts similarity index 53% rename from x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/resolve_rule.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/resolve_rule/resolve_rule.ts index 1842ad2d8e01a..153b5d8cc763f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/resolve_rule.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/resolve_rule/resolve_rule.ts @@ -1,24 +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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ -import { HttpSetup } from '@kbn/core/public'; -import { AsApiContract } from '@kbn/actions-plugin/common'; -import { ResolvedRule } from '../../../types'; + +import { HttpSetup } from '@kbn/core-http-browser'; +import { AsApiContract } from '@kbn/actions-types'; import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants'; -import { transformResolvedRule } from './common_transformations'; +import { transformResolvedRule } from '../../transformations'; +import { ResolvedRule } from '../../types'; export async function resolveRule({ http, - ruleId, + id, }: { http: HttpSetup; - ruleId: string; + id: string; }): Promise { const res = await http.get>( - `${INTERNAL_BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(ruleId)}/_resolve` + `${INTERNAL_BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}/_resolve` ); return transformResolvedRule(res); } diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/index.ts b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/index.ts new file mode 100644 index 0000000000000..de7e98d72bd78 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './types'; +export * from './transform_update_rule_body'; +export * from './update_rule'; diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/transform_update_rule_body.test.ts b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/transform_update_rule_body.test.ts new file mode 100644 index 0000000000000..c30807884a646 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/transform_update_rule_body.test.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { RuleTypeParams } from '../../types'; +import { transformUpdateRuleBody } from './transform_update_rule_body'; +import { UpdateRuleBody } from './types'; + +const ruleToUpdate: UpdateRuleBody = { + params: { + aggType: 'count', + termSize: 5, + thresholdComparator: '>', + timeWindowSize: 5, + timeWindowUnit: 'm', + groupBy: 'all', + threshold: [1000], + index: ['.kibana'], + timeField: 'alert.executionStatus.lastExecutionDate', + }, + schedule: { interval: '1m' }, + tags: [], + name: 'test', + throttle: null, + actions: [ + { + group: 'threshold met', + id: '83d4d860-9316-11eb-a145-93ab369a4461', + params: { + level: 'info', + message: 'test-message', + }, + actionTypeId: '.server-log', + frequency: { + notifyWhen: 'onActionGroupChange', + throttle: null, + summary: false, + }, + useAlertDataForTemplate: true, + }, + { + id: '.test-system-action', + params: {}, + actionTypeId: '.system-action', + }, + ], + notifyWhen: 'onActionGroupChange', + alertDelay: { + active: 10, + }, +}; + +describe('transformUpdateRuleBody', () => { + test('should transform update rule body', () => { + expect(transformUpdateRuleBody(ruleToUpdate)).toEqual({ + actions: [ + { + frequency: { + notify_when: 'onActionGroupChange', + summary: false, + throttle: null, + }, + group: 'threshold met', + id: '83d4d860-9316-11eb-a145-93ab369a4461', + params: { + level: 'info', + message: 'test-message', + }, + use_alert_data_for_template: true, + }, + { + id: '.test-system-action', + params: {}, + }, + ], + alert_delay: { + active: 10, + }, + name: 'test', + notifyWhen: 'onActionGroupChange', + params: { + aggType: 'count', + groupBy: 'all', + index: ['.kibana'], + termSize: 5, + threshold: [1000], + thresholdComparator: '>', + timeField: 'alert.executionStatus.lastExecutionDate', + timeWindowSize: 5, + timeWindowUnit: 'm', + }, + schedule: { + interval: '1m', + }, + tags: [], + throttle: null, + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/transform_update_rule_body.ts similarity index 50% rename from x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/update_rule/transform_update_rule_body.ts index 40b24be836dc6..f0493a1c164f2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/transform_update_rule_body.ts @@ -1,30 +1,15 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ -import { HttpSetup } from '@kbn/core/public'; -import { pick } from 'lodash'; -import { RewriteResponseCase, AsApiContract } from '@kbn/actions-plugin/common'; -import { BASE_ALERTING_API_PATH } from '../../constants'; -import { Rule, RuleUpdates } from '../../../types'; -import { transformRule } from './common_transformations'; -type RuleUpdatesBody = Pick< - RuleUpdates, - 'name' | 'tags' | 'schedule' | 'actions' | 'params' | 'alertDelay' ->; -export const UPDATE_FIELDS: Array = [ - 'name', - 'tags', - 'schedule', - 'params', - 'actions', - 'alertDelay', -]; +import { RewriteResponseCase } from '@kbn/actions-types'; +import { UpdateRuleBody } from './types'; -export const rewriteBodyRequest: RewriteResponseCase = ({ +export const transformUpdateRuleBody: RewriteResponseCase = ({ actions, alertDelay, ...res @@ -57,21 +42,3 @@ export const rewriteBodyRequest: RewriteResponseCase = ({ }), ...(alertDelay ? { alert_delay: alertDelay } : {}), }); - -export async function updateRule({ - http, - rule, - id, -}: { - http: HttpSetup; - rule: RuleUpdatesBody; - id: string; -}): Promise { - const res = await http.put>( - `${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}`, - { - body: JSON.stringify(rewriteBodyRequest(pick(rule, UPDATE_FIELDS))), - } - ); - return transformRule(res); -} diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/types.ts b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/types.ts new file mode 100644 index 0000000000000..bca55de97dc29 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/types.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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 { Rule, RuleTypeParams } from '../../types'; + +export interface UpdateRuleBody { + name: Rule['name']; + tags: Rule['tags']; + schedule: Rule['schedule']; + throttle?: Rule['throttle']; + params: Rule['params']; + actions: Rule['actions']; + notifyWhen?: Rule['notifyWhen']; + alertDelay?: Rule['alertDelay']; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.test.ts b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/update_rule.test.ts similarity index 91% rename from x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.test.ts rename to packages/kbn-alerts-ui-shared/src/common/apis/update_rule/update_rule.test.ts index ebe187ba88ed0..64f9bf686ba2d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.test.ts +++ b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/update_rule.test.ts @@ -1,13 +1,14 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ -import { Rule } from '../../../types'; import { httpServiceMock } from '@kbn/core/public/mocks'; -import { updateRule } from './update'; +import { Rule } from '../../types'; +import { updateRule, UpdateRuleBody } from '.'; const http = httpServiceMock.createStartContract(); @@ -89,7 +90,7 @@ describe('updateRule', () => { ], }); - const result = await updateRule({ http, id: '12/3', rule: ruleToUpdate }); + const result = await updateRule({ http, id: '12/3', rule: ruleToUpdate as UpdateRuleBody }); expect(result).toEqual({ ...resolvedValue, diff --git a/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/update_rule.ts b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/update_rule.ts new file mode 100644 index 0000000000000..bae2fc52f5180 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/apis/update_rule/update_rule.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { HttpSetup } from '@kbn/core/public'; +import { pick } from 'lodash'; +import { AsApiContract } from '@kbn/actions-types'; +import { UpdateRuleBody } from './types'; +import { BASE_ALERTING_API_PATH } from '../../constants'; +import { transformUpdateRuleBody } from './transform_update_rule_body'; +import { transformRule } from '../../transformations'; +import { Rule } from '../../types'; + +export const UPDATE_FIELDS: Array = [ + 'name', + 'tags', + 'schedule', + 'params', + 'alertDelay', + 'actions', +]; + +export async function updateRule({ + http, + rule, + id, +}: { + http: HttpSetup; + rule: UpdateRuleBody; + id: string; +}): Promise { + const res = await http.put>( + `${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}`, + { + body: JSON.stringify(transformUpdateRuleBody(pick(rule, UPDATE_FIELDS))), + } + ); + return transformRule(res); +} diff --git a/packages/kbn-alerts-ui-shared/src/common/constants.ts b/packages/kbn-alerts-ui-shared/src/common/constants.ts index de25fbe1e76f9..fcbf498988a66 100644 --- a/packages/kbn-alerts-ui-shared/src/common/constants.ts +++ b/packages/kbn-alerts-ui-shared/src/common/constants.ts @@ -8,4 +8,6 @@ export const ALERTS_FEATURE_ID = 'alerts'; export const BASE_ALERTING_API_PATH = '/api/alerting'; +export const INTERNAL_BASE_ALERTING_API_PATH = '/internal/alerting'; export const BASE_RAC_ALERTS_API_PATH = '/internal/rac/alerts'; +export const BASE_TRIGGERS_ACTIONS_UI_API_PATH = '/internal/triggers_actions_ui'; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/index.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/index.ts index 027c825d1cee6..322d466b715ab 100644 --- a/packages/kbn-alerts-ui-shared/src/common/hooks/index.ts +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/index.ts @@ -7,3 +7,9 @@ */ export * from './use_load_rule_types_query'; +export * from './use_load_ui_config'; +export * from './use_load_ui_health'; +export * from './use_load_alerting_framework_health'; +export * from './use_create_rule'; +export * from './use_update_rule'; +export * from './use_resolve_rule'; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.test.tsx new file mode 100644 index 0000000000000..a32fec7185b46 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.test.tsx @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook } from '@testing-library/react-hooks/dom'; +import { waitFor } from '@testing-library/react'; +import type { HttpStart } from '@kbn/core-http-browser'; + +import { useCreateRule } from './use_create_rule'; +import { CreateRuleBody } from '../apis/create_rule'; +import { RuleTypeParams } from '../types'; + +const ruleToCreate: CreateRuleBody = { + params: { + aggType: 'count', + termSize: 5, + thresholdComparator: '>', + timeWindowSize: 5, + timeWindowUnit: 'm', + groupBy: 'all', + threshold: [1000], + index: ['.kibana'], + timeField: 'alert.executionStatus.lastExecutionDate', + }, + consumer: 'alerts', + schedule: { interval: '1m' }, + tags: [], + name: 'test', + enabled: true, + throttle: null, + ruleTypeId: '.index-threshold', + actions: [ + { + group: 'threshold met', + id: '83d4d860-9316-11eb-a145-93ab369a4461', + params: { + level: 'info', + message: 'test-message', + }, + actionTypeId: '.server-log', + frequency: { + notifyWhen: 'onActionGroupChange', + throttle: null, + summary: false, + }, + useAlertDataForTemplate: true, + }, + { + id: '.test-system-action', + params: {}, + actionTypeId: '.system-action', + }, + ], + notifyWhen: 'onActionGroupChange', + alertDelay: { + active: 10, + }, +}; + +const queryClient = new QueryClient(); + +const wrapper = ({ children }: { children: Node }) => ( + {children} +); + +jest.mock('../apis/create_rule/create_rule', () => ({ + createRule: jest.fn(), +})); + +const { createRule } = jest.requireMock('../apis/create_rule/create_rule'); + +const httpMock = jest.fn(); +const onSuccessMock = jest.fn(); +const onErrorMock = jest.fn(); + +describe('useCreateRule', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should call onSuccess if request succeeds', async () => { + createRule.mockResolvedValueOnce({}); + + const { result } = renderHook( + () => { + return useCreateRule({ + http: httpMock as unknown as HttpStart, + onSuccess: onSuccessMock, + onError: onErrorMock, + }); + }, + { wrapper } + ); + + result.current.mutate({ formData: ruleToCreate }); + + await waitFor(() => { + return expect(createRule).toHaveBeenCalledWith({ http: httpMock, rule: ruleToCreate }); + }); + + expect(onSuccessMock).toHaveBeenCalled(); + }); + + test('should call onError if request fails', async () => { + createRule.mockRejectedValueOnce({}); + + const { result } = renderHook( + () => { + return useCreateRule({ + http: httpMock as unknown as HttpStart, + onSuccess: onSuccessMock, + onError: onErrorMock, + }); + }, + { wrapper } + ); + + result.current.mutate({ formData: ruleToCreate }); + + await waitFor(() => { + return expect(createRule).toHaveBeenCalledWith({ http: httpMock, rule: ruleToCreate }); + }); + + expect(onErrorMock).toHaveBeenCalled(); + }); +}); diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.ts new file mode 100644 index 0000000000000..012e9b50e2807 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_create_rule.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useMutation } from '@tanstack/react-query'; +import type { HttpStart, IHttpFetchError } from '@kbn/core-http-browser'; +import { createRule, CreateRuleBody } from '../apis/create_rule'; + +export interface UseCreateRuleProps { + http: HttpStart; + onSuccess?: (formData: CreateRuleBody) => void; + onError?: (error: IHttpFetchError<{ message: string }>) => void; +} + +export const useCreateRule = (props: UseCreateRuleProps) => { + const { http, onSuccess, onError } = props; + + const mutationFn = ({ formData }: { formData: CreateRuleBody }) => { + return createRule({ + http, + rule: formData, + }); + }; + + return useMutation({ + mutationKey: ['useUpdateRule'], + mutationFn, + onSuccess, + onError, + }); +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_alerting_framework_health.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_alerting_framework_health.ts new file mode 100644 index 0000000000000..547e2457ff9ea --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_alerting_framework_health.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useQuery } from '@tanstack/react-query'; +import type { HttpStart } from '@kbn/core-http-browser'; +import { fetchAlertingFrameworkHealth } from '../apis/fetch_alerting_framework_health'; + +export interface UseLoadAlertingFrameworkHealthProps { + http: HttpStart; +} + +export const useLoadAlertingFrameworkHealth = (props: UseLoadAlertingFrameworkHealthProps) => { + const { http } = props; + + const queryFn = () => { + return fetchAlertingFrameworkHealth({ http }); + }; + + const { data, isSuccess, isFetching, isLoading, isInitialLoading, isError, error } = useQuery({ + queryKey: ['useLoadAlertingFrameworkHealth'], + queryFn, + refetchOnWindowFocus: false, + }); + + return { + data, + isLoading: isLoading || isFetching, + isInitialLoading, + isSuccess, + isError, + error, + }; +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_ui_config.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_ui_config.ts new file mode 100644 index 0000000000000..565ac558dfd7e --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_ui_config.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useQuery } from '@tanstack/react-query'; +import type { HttpStart } from '@kbn/core-http-browser'; +import { fetchUiConfig } from '../apis/fetch_ui_config'; + +export interface UseLoadUiConfigProps { + http: HttpStart; +} + +export const useLoadUiConfig = (props: UseLoadUiConfigProps) => { + const { http } = props; + + const queryFn = () => { + return fetchUiConfig({ http }); + }; + + const { data, isSuccess, isLoading, isFetching, isInitialLoading, isError, error } = useQuery({ + queryKey: ['useLoadUiConfig'], + queryFn, + refetchOnWindowFocus: false, + }); + + return { + data, + isLoading: isLoading || isFetching, + isInitialLoading, + isSuccess, + isError, + error, + }; +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_ui_health.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_ui_health.ts new file mode 100644 index 0000000000000..c4efc95575f2d --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_load_ui_health.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useQuery } from '@tanstack/react-query'; +import type { HttpStart } from '@kbn/core-http-browser'; +import { fetchUiHealthStatus } from '../apis/fetch_ui_health_status'; + +export interface UseLoadUiHealthProps { + http: HttpStart; +} + +export const useLoadUiHealth = (props: UseLoadUiHealthProps) => { + const { http } = props; + + const queryFn = () => { + return fetchUiHealthStatus({ http }); + }; + + const { data, isSuccess, isLoading, isFetching, isInitialLoading, isError, error } = useQuery({ + queryKey: ['useLoadUiHealth'], + queryFn, + refetchOnWindowFocus: false, + }); + + return { + data, + isLoading: isLoading || isFetching, + isInitialLoading, + isSuccess, + isError, + error, + }; +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.test.tsx new file mode 100644 index 0000000000000..aeaabe64ef97f --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.test.tsx @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook } from '@testing-library/react-hooks/dom'; +import { waitFor } from '@testing-library/react'; +import type { HttpStart } from '@kbn/core-http-browser'; + +import { useResolveRule } from './use_resolve_rule'; + +const queryClient = new QueryClient(); + +const wrapper = ({ children }: { children: Node }) => ( + {children} +); + +jest.mock('../apis/resolve_rule/resolve_rule', () => ({ + resolveRule: jest.fn(), +})); + +const { resolveRule } = jest.requireMock('../apis/resolve_rule/resolve_rule'); + +const httpMock = jest.fn(); + +describe('useResolveRule', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should call resolve rule API if ID is passed in', async () => { + resolveRule.mockResolvedValueOnce({}); + const { result } = renderHook( + () => { + return useResolveRule({ + id: 'test-id', + http: httpMock as unknown as HttpStart, + }); + }, + { wrapper } + ); + + await waitFor(() => { + return expect(result.current.isInitialLoading).toBeFalsy(); + }); + expect(result.current.data).not.toBeFalsy(); + expect(resolveRule).toHaveBeenCalled(); + }); + + test('should not call resolve rule API if ID is not passed in', async () => { + resolveRule.mockResolvedValueOnce({}); + const { result } = renderHook( + () => { + return useResolveRule({ + http: httpMock as unknown as HttpStart, + }); + }, + { wrapper } + ); + + await waitFor(() => { + return expect(result.current.isInitialLoading).toBeFalsy(); + }); + expect(result.current.data).toBeFalsy(); + expect(resolveRule).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.ts new file mode 100644 index 0000000000000..3f70cb8388d0a --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_resolve_rule.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useQuery } from '@tanstack/react-query'; +import type { HttpStart } from '@kbn/core-http-browser'; +import { resolveRule } from '../apis/resolve_rule'; +import { RuleFormData } from '../../rule_form'; + +export interface UseResolveProps { + http: HttpStart; + id?: string; +} + +export const useResolveRule = (props: UseResolveProps) => { + const { id, http } = props; + + const queryFn = () => { + if (id) { + return resolveRule({ http, id }); + } + }; + + const { data, isSuccess, isFetching, isLoading, isInitialLoading, isError, error } = useQuery({ + queryKey: ['useResolveRule', id], + queryFn, + enabled: !!id, + select: (rule): RuleFormData | null => { + if (!rule) { + return null; + } + return { + ...rule, + ...(rule.alertDelay ? { alertDelay: rule.alertDelay } : {}), + ...(rule.notifyWhen ? { notifyWhen: rule.notifyWhen } : {}), + }; + }, + refetchOnWindowFocus: false, + }); + + return { + data, + isLoading: isLoading || isFetching, + isInitialLoading, + isSuccess, + isError, + error, + }; +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.test.tsx b/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.test.tsx new file mode 100644 index 0000000000000..7941fa86c5825 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.test.tsx @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook } from '@testing-library/react-hooks/dom'; +import { waitFor } from '@testing-library/react'; +import type { HttpStart } from '@kbn/core-http-browser'; + +import { useUpdateRule } from './use_update_rule'; +import { UpdateRuleBody } from '../apis/update_rule'; +import { RuleTypeParams } from '../types'; + +const ruleToUpdate: UpdateRuleBody = { + params: { + aggType: 'count', + termSize: 5, + thresholdComparator: '>', + timeWindowSize: 5, + timeWindowUnit: 'm', + groupBy: 'all', + threshold: [1000], + index: ['.kibana'], + timeField: 'alert.executionStatus.lastExecutionDate', + }, + schedule: { interval: '1m' }, + tags: [], + name: 'test', + throttle: null, + actions: [ + { + group: 'threshold met', + id: '83d4d860-9316-11eb-a145-93ab369a4461', + params: { + level: 'info', + message: 'test-message', + }, + actionTypeId: '.server-log', + frequency: { + notifyWhen: 'onActionGroupChange', + throttle: null, + summary: false, + }, + useAlertDataForTemplate: true, + }, + { + id: '.test-system-action', + params: {}, + actionTypeId: '.system-action', + }, + ], + notifyWhen: 'onActionGroupChange', + alertDelay: { + active: 10, + }, +}; + +const queryClient = new QueryClient(); + +const wrapper = ({ children }: { children: Node }) => ( + {children} +); + +jest.mock('../apis/update_rule/update_rule', () => ({ + updateRule: jest.fn(), +})); + +const { updateRule } = jest.requireMock('../apis/update_rule/update_rule'); + +const httpMock = jest.fn(); +const onSuccessMock = jest.fn(); +const onErrorMock = jest.fn(); + +describe('useUpdateRule', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should call onSuccess if request succeeds', async () => { + updateRule.mockResolvedValueOnce({}); + + const { result } = renderHook( + () => { + return useUpdateRule({ + http: httpMock as unknown as HttpStart, + onSuccess: onSuccessMock, + onError: onErrorMock, + }); + }, + { wrapper } + ); + + result.current.mutate({ id: 'test-rule', formData: ruleToUpdate }); + + await waitFor(() => { + return expect(updateRule).toHaveBeenCalledWith({ + http: httpMock, + id: 'test-rule', + rule: ruleToUpdate, + }); + }); + + expect(onSuccessMock).toHaveBeenCalled(); + }); + + test('should call onError if request fails', async () => { + updateRule.mockRejectedValueOnce({}); + + const { result } = renderHook( + () => { + return useUpdateRule({ + http: httpMock as unknown as HttpStart, + onSuccess: onSuccessMock, + onError: onErrorMock, + }); + }, + { wrapper } + ); + + result.current.mutate({ id: 'test-rule', formData: ruleToUpdate }); + + await waitFor(() => { + return expect(updateRule).toHaveBeenCalledWith({ + http: httpMock, + id: 'test-rule', + rule: ruleToUpdate, + }); + }); + + expect(onErrorMock).toHaveBeenCalled(); + }); +}); diff --git a/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.ts b/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.ts new file mode 100644 index 0000000000000..ae480a9dd205a --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/hooks/use_update_rule.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { useMutation } from '@tanstack/react-query'; +import type { HttpStart, IHttpFetchError } from '@kbn/core-http-browser'; +import { updateRule, UpdateRuleBody } from '../apis/update_rule'; + +export interface UseUpdateRuleProps { + http: HttpStart; + onSuccess?: (formData: UpdateRuleBody) => void; + onError?: (error: IHttpFetchError<{ message: string }>) => void; +} + +export const useUpdateRule = (props: UseUpdateRuleProps) => { + const { http, onSuccess, onError } = props; + + const mutationFn = ({ id, formData }: { id: string; formData: UpdateRuleBody }) => { + return updateRule({ + id, + http, + rule: formData, + }); + }; + + return useMutation({ + mutationKey: ['useUpdateRule'], + mutationFn, + onSuccess, + onError, + }); +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/index.ts b/packages/kbn-alerts-ui-shared/src/common/index.ts new file mode 100644 index 0000000000000..12594660136d8 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './types'; diff --git a/packages/kbn-alerts-ui-shared/src/common/test_utils/action_type_registry.mock.ts b/packages/kbn-alerts-ui-shared/src/common/test_utils/action_type_registry.mock.ts new file mode 100644 index 0000000000000..e9d6928a29490 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/test_utils/action_type_registry.mock.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { lazy } from 'react'; +import { v4 as uuidv4 } from 'uuid'; +import { ActionTypeModel, ActionTypeRegistryContract } from '../types'; + +const createActionTypeRegistryMock = () => { + const mocked: jest.Mocked = { + has: jest.fn((x) => true), + register: jest.fn(), + get: jest.fn(), + list: jest.fn(), + }; + return mocked; +}; + +const mockedActionParamsFields = lazy(async () => ({ + default() { + return React.createElement(React.Fragment); + }, +})); + +const createMockActionTypeModel = (actionType: Partial = {}): ActionTypeModel => { + const id = uuidv4(); + return { + id, + iconClass: `iconClass-${id}`, + selectMessage: `selectMessage-${id}`, + validateParams: jest.fn(), + actionConnectorFields: null, + actionParamsFields: mockedActionParamsFields, + ...actionType, + }; +}; + +export const actionTypeRegistryMock = { + create: createActionTypeRegistryMock, + createMockActionTypeModel, +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/transformations/index.ts b/packages/kbn-alerts-ui-shared/src/common/transformations/index.ts new file mode 100644 index 0000000000000..58e55db753aca --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/transformations/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './transform_action'; +export * from './transform_rule'; diff --git a/packages/kbn-alerts-ui-shared/src/common/transformations/transform_action.ts b/packages/kbn-alerts-ui-shared/src/common/transformations/transform_action.ts new file mode 100644 index 0000000000000..af6cf4c939388 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/transformations/transform_action.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { RewriteRequestCase } from '@kbn/actions-types'; +import { RuleUiAction } from '..'; + +export const transformAction: RewriteRequestCase = (action) => { + const { uuid, id, connector_type_id: actionTypeId, params } = action; + return { + ...('group' in action && action.group ? { group: action.group } : {}), + id, + params, + actionTypeId, + ...('use_alert_data_for_template' in action && + typeof action.use_alert_data_for_template !== 'undefined' + ? { useAlertDataForTemplate: action.use_alert_data_for_template } + : {}), + ...('frequency' in action && action.frequency + ? { + frequency: { + summary: action.frequency.summary, + notifyWhen: action.frequency.notify_when, + throttle: action.frequency.throttle, + }, + } + : {}), + ...('alerts_filter' in action && action.alerts_filter + ? { alertsFilter: action.alerts_filter } + : {}), + ...(uuid && { uuid }), + }; +}; diff --git a/packages/kbn-alerts-ui-shared/src/common/transformations/transform_rule.ts b/packages/kbn-alerts-ui-shared/src/common/transformations/transform_rule.ts new file mode 100644 index 0000000000000..46717052d70b2 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/transformations/transform_rule.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { AsApiContract, RewriteRequestCase } from '@kbn/actions-types'; +import { RuleExecutionStatus, RuleLastRun } from '@kbn/alerting-types'; +import type { ResolvedRule, RuleUiAction, Rule } from '..'; +import { transformAction } from '.'; + +const transformExecutionStatus: RewriteRequestCase = ({ + last_execution_date: lastExecutionDate, + last_duration: lastDuration, + ...rest +}) => ({ + lastExecutionDate, + lastDuration, + ...rest, +}); + +const transformLastRun: RewriteRequestCase = ({ + outcome_msg: outcomeMsg, + outcome_order: outcomeOrder, + alerts_count: alertsCount, + ...rest +}) => ({ + outcomeMsg, + outcomeOrder, + alertsCount, + ...rest, +}); + +export const transformRule: RewriteRequestCase = ({ + rule_type_id: ruleTypeId, + created_by: createdBy, + updated_by: updatedBy, + created_at: createdAt, + updated_at: updatedAt, + api_key_owner: apiKeyOwner, + api_key_created_by_user: apiKeyCreatedByUser, + notify_when: notifyWhen, + mute_all: muteAll, + muted_alert_ids: mutedInstanceIds, + scheduled_task_id: scheduledTaskId, + execution_status: executionStatus, + actions: actions, + snooze_schedule: snoozeSchedule, + is_snoozed_until: isSnoozedUntil, + active_snoozes: activeSnoozes, + last_run: lastRun, + next_run: nextRun, + alert_delay: alertDelay, + ...rest +}: any) => ({ + ruleTypeId, + createdBy, + updatedBy, + createdAt, + updatedAt, + apiKeyOwner, + notifyWhen, + muteAll, + mutedInstanceIds, + snoozeSchedule, + executionStatus: executionStatus ? transformExecutionStatus(executionStatus) : undefined, + actions: actions + ? actions.map((action: AsApiContract) => transformAction(action)) + : [], + scheduledTaskId, + isSnoozedUntil, + activeSnoozes, + ...(lastRun ? { lastRun: transformLastRun(lastRun) } : {}), + ...(nextRun ? { nextRun } : {}), + ...(apiKeyCreatedByUser !== undefined ? { apiKeyCreatedByUser } : {}), + ...(alertDelay ? { alertDelay } : {}), + ...rest, +}); + +export const transformResolvedRule: RewriteRequestCase = ({ + // eslint-disable-next-line @typescript-eslint/naming-convention + alias_target_id, + // eslint-disable-next-line @typescript-eslint/naming-convention + alias_purpose, + outcome, + ...rest +}: any) => { + return { + ...transformRule(rest), + alias_target_id, + alias_purpose, + outcome, + }; +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts b/packages/kbn-alerts-ui-shared/src/common/type_registry.test.ts similarity index 91% rename from x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts rename to packages/kbn-alerts-ui-shared/src/common/type_registry.test.ts index 1d30eaab2a234..53ee8b3e0a354 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts +++ b/packages/kbn-alerts-ui-shared/src/common/type_registry.test.ts @@ -1,18 +1,14 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import { TypeRegistry } from './type_registry'; -import { - ValidationResult, - RuleTypeModel, - ActionTypeModel, - GenericValidationResult, -} from '../types'; -import { actionTypeRegistryMock } from './action_type_registry.mock'; +import { ActionTypeModel, GenericValidationResult, RuleTypeModel, ValidationResult } from './types'; +import { actionTypeRegistryMock } from './test_utils/action_type_registry.mock'; export const ExpressionComponent: React.FunctionComponent = () => { return null; diff --git a/packages/kbn-alerts-ui-shared/src/common/type_registry.ts b/packages/kbn-alerts-ui-shared/src/common/type_registry.ts new file mode 100644 index 0000000000000..1a8cbbd1e0f8a --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/type_registry.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; + +interface BaseObjectType { + id: string; +} + +export class TypeRegistry { + private readonly objectTypes: Map = new Map(); + + /** + * Returns if the object type registry has the given type registered + */ + public has(id: string) { + return this.objectTypes.has(id); + } + + /** + * Registers an object type to the type registry + */ + public register(objectType: T) { + if (this.has(objectType.id)) { + throw new Error( + i18n.translate('alertsUIShared.typeRegistry.register.duplicateObjectTypeErrorMessage', { + defaultMessage: 'Object type "{id}" is already registered.', + values: { + id: objectType.id, + }, + }) + ); + } + this.objectTypes.set(objectType.id, objectType); + } + + /** + * Returns an object type, throw error if not registered + */ + public get(id: string): T { + if (!this.has(id)) { + throw new Error( + i18n.translate('alertsUIShared.typeRegistry.get.missingActionTypeErrorMessage', { + defaultMessage: 'Object type "{id}" is not registered.', + values: { + id, + }, + }) + ); + } + return this.objectTypes.get(id)!; + } + + public list() { + return Array.from(this.objectTypes).map(([id, objectType]) => objectType); + } +} diff --git a/packages/kbn-alerts-ui-shared/src/common/types/action_types.ts b/packages/kbn-alerts-ui-shared/src/common/types/action_types.ts new file mode 100644 index 0000000000000..9fd39aebfc270 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/types/action_types.ts @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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 { ComponentType, ReactNode } from 'react'; +import type { RuleActionParam, ActionVariable } from '@kbn/alerting-types'; +import { IconType, RecursivePartial } from '@elastic/eui'; +import { PublicMethodsOf } from '@kbn/utility-types'; +import { TypeRegistry } from '../type_registry'; +import { RuleFormErrors } from '.'; + +export interface GenericValidationResult { + errors: Record, string[] | unknown>; +} + +export interface ConnectorValidationError { + message: ReactNode; +} + +export type ConnectorValidationFunc = () => Promise; + +export interface ActionConnectorFieldsProps { + readOnly: boolean; + isEdit: boolean; + registerPreSubmitValidator: (validator: ConnectorValidationFunc) => void; +} + +export interface ActionConnectorProps { + secrets: Secrets; + id: string; + actionTypeId: string; + name: string; + referencedByCount?: number; + config: Config; + isPreconfigured: boolean; + isDeprecated: boolean; + isSystemAction: boolean; + isMissingSecrets?: boolean; +} + +export type SystemAction = Omit, 'config' | 'secrets'> & { + isSystemAction: true; + isPreconfigured: false; +}; + +export type PreConfiguredActionConnector = Omit< + ActionConnectorProps, + 'config' | 'secrets' +> & { + isPreconfigured: true; + isSystemAction: false; +}; + +export type UserConfiguredActionConnector = ActionConnectorProps< + Config, + Secrets +> & { + isPreconfigured: false; + isSystemAction: false; +}; + +export type ActionConnector, Secrets = Record> = + | PreConfiguredActionConnector + | SystemAction + | UserConfiguredActionConnector; + +export enum ActionConnectorMode { + Test = 'test', + ActionForm = 'actionForm', +} + +export interface ActionParamsProps { + actionParams: Partial; + index: number; + editAction: (key: string, value: RuleActionParam, index: number) => void; + errors: RuleFormErrors; + ruleTypeId?: string; + messageVariables?: ActionVariable[]; + defaultMessage?: string; + useDefaultMessage?: boolean; + actionConnector?: ActionConnector; + isLoading?: boolean; + isDisabled?: boolean; + selectedActionGroupId?: string; + showEmailSubjectAndMessage?: boolean; + executionMode?: ActionConnectorMode; + onBlur?: (field?: string) => void; + producerId?: string; +} + +export interface ActionReadOnlyElementProps { + connectorId: string; + connectorName: string; +} + +export interface CustomConnectorSelectionItem { + getText: (actionConnector: ActionConnector) => string; + getComponent: ( + actionConnector: ActionConnector + ) => React.LazyExoticComponent> | undefined; +} + +export interface ActionTypeModel { + id: string; + iconClass: IconType; + selectMessage: string; + actionTypeTitle?: string; + validateParams: ( + actionParams: ActionParams + ) => Promise | unknown>>; + actionConnectorFields: React.LazyExoticComponent< + ComponentType + > | null; + actionParamsFields: React.LazyExoticComponent>>; + actionReadOnlyExtraComponent?: React.LazyExoticComponent< + ComponentType + >; + defaultActionParams?: RecursivePartial; + defaultRecoveredActionParams?: RecursivePartial; + customConnectorSelectItem?: CustomConnectorSelectionItem; + isExperimental?: boolean; + subtype?: Array<{ id: string; name: string }>; + convertParamsBetweenGroups?: (params: ActionParams) => ActionParams | {}; + hideInUi?: boolean; + modalWidth?: number; + isSystemActionType?: boolean; +} + +export type ActionTypeRegistryContract = PublicMethodsOf< + TypeRegistry> +>; diff --git a/packages/kbn-alerts-ui-shared/src/common/types/index.ts b/packages/kbn-alerts-ui-shared/src/common/types/index.ts new file mode 100644 index 0000000000000..08756c70290b1 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/common/types/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './rule_types'; +export * from './action_types'; diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/types/index.ts b/packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts similarity index 77% rename from packages/kbn-alerts-ui-shared/src/rule_form/types/index.ts rename to packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts index ed3241974fa79..26c854d95b00e 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/types/index.ts +++ b/packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts @@ -16,9 +16,17 @@ import type { RuleNotifyWhenType, ActionGroup, SanitizedRule as AlertingSanitizedRule, - RuleAction, + SanitizedRuleAction as RuleAction, RuleSystemAction, + ResolvedSanitizedRule, } from '@kbn/alerting-types'; +import { RuleType } from '@kbn/triggers-actions-ui-types'; +import { PublicMethodsOf } from '@kbn/utility-types'; +import { TypeRegistry } from '../type_registry'; + +export type RuleTypeWithDescription = RuleType & { description?: string }; + +export type RuleTypeIndexWithDescriptions = Map; export type RuleTypeParams = Record; @@ -35,11 +43,11 @@ export interface ValidationResult { errors: Record; } -type RuleUiAction = RuleAction | RuleSystemAction; +export type RuleUiAction = RuleAction | RuleSystemAction; // In Triggers and Actions we treat all `Alert`s as `SanitizedRule` // so the `Params` is a black-box of Record -type SanitizedRule = Omit< +export type SanitizedRule = Omit< AlertingSanitizedRule, 'alertTypeId' | 'actions' | 'systemActions' > & { @@ -47,10 +55,15 @@ type SanitizedRule = Omit< actions: RuleUiAction[]; }; -type Rule = SanitizedRule; +export type ResolvedRule = Omit< + ResolvedSanitizedRule, + 'alertTypeId' | 'actions' | 'systemActions' +> & { + ruleTypeId: ResolvedSanitizedRule['alertTypeId']; + actions: RuleUiAction[]; +}; -export type InitialRule = Partial & - Pick; +export type Rule = SanitizedRule; export interface RuleTypeParamsExpressionProps< Params extends RuleTypeParams = RuleTypeParams, @@ -95,3 +108,5 @@ export interface RuleTypeModel { | React.FunctionComponent | React.LazyExoticComponent>; } + +export type RuleTypeRegistryContract = PublicMethodsOf>; diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_alert_delay.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_alert_delay.tsx index 7418215c71755..7d2a226d073b6 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_alert_delay.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_alert_delay.tsx @@ -8,15 +8,14 @@ import React, { useCallback } from 'react'; import { EuiFieldNumber, EuiFormRow } from '@elastic/eui'; -import type { SanitizedRule, RuleTypeParams } from '@kbn/alerting-types'; import { ALERT_DELAY_TITLE_PREFIX, ALERT_DELAY_TITLE_SUFFIX } from '../translations'; -import { RuleFormErrors } from '../types'; +import { RuleFormErrors, Rule, RuleTypeParams } from '../../common'; const INTEGER_REGEX = /^[1-9][0-9]*$/; const INVALID_KEYS = ['-', '+', '.', 'e', 'E']; export interface RuleAlertDelayProps { - alertDelay?: SanitizedRule['alertDelay'] | null; + alertDelay?: Rule['alertDelay'] | null; errors?: RuleFormErrors; onChange: (property: string, value: unknown) => void; } diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_consumer_selection.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_consumer_selection.tsx index 957d5c0152220..e8bc0993734d3 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_consumer_selection.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_consumer_selection.tsx @@ -10,7 +10,7 @@ import React, { useMemo, useCallback } from 'react'; import { EuiComboBox, EuiFormRow, EuiComboBoxOptionOption } from '@elastic/eui'; import { AlertConsumers, RuleCreationValidConsumer } from '@kbn/rule-data-utils'; import { FEATURE_NAME_MAP, CONSUMER_SELECT_COMBO_BOX_TITLE } from '../translations'; -import { RuleFormErrors } from '../types'; +import { RuleFormErrors } from '../../common'; export const VALID_CONSUMERS: RuleCreationValidConsumer[] = [ AlertConsumers.LOGS, diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.test.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.test.tsx index 9ff8a704a728a..a0070e8025ad9 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.test.tsx @@ -16,8 +16,8 @@ import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/ import type { DocLinksStart } from '@kbn/core-doc-links-browser'; import { RuleDefinition } from './rule_definition'; -import { RuleTypeModel } from '../types'; -import { RuleType } from '@kbn/alerting-types'; +import { RuleTypeModel } from '../../common'; +import { RuleType } from '@kbn/triggers-actions-ui-types'; import { ALERT_DELAY_TITLE } from '../translations'; const ruleType = { diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.tsx index adec6e0966cbd..08f74ba8fc46b 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_definition.tsx @@ -33,9 +33,14 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { DocLinksStart } from '@kbn/core-doc-links-browser'; -import type { SanitizedRule, RuleTypeParams } from '@kbn/alerting-types'; import type { RuleType } from '@kbn/triggers-actions-ui-types'; -import type { RuleTypeModel, RuleFormErrors, MinimumScheduleInterval } from '../types'; +import type { + RuleTypeModel, + RuleFormErrors, + MinimumScheduleInterval, + Rule, + RuleTypeParams, +} from '../../common'; import { DOC_LINK_TITLE, LOADING_RULE_TYPE_PARAMS_TITLE, @@ -68,12 +73,12 @@ interface RuleDefinitionProps { docLinks: DocLinksStart; }; formValues: { - id?: SanitizedRule['id']; - params: SanitizedRule['params']; - schedule: SanitizedRule['schedule']; - alertDelay?: SanitizedRule['alertDelay']; - notifyWhen?: SanitizedRule['notifyWhen']; - consumer?: SanitizedRule['consumer']; + id?: Rule['id']; + params: Rule['params']; + schedule: Rule['schedule']; + alertDelay?: Rule['alertDelay']; + notifyWhen?: Rule['notifyWhen']; + consumer?: Rule['consumer']; }; minimumScheduleInterval?: MinimumScheduleInterval; errors?: RuleFormErrors; diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_schedule.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_schedule.tsx index 5af00de31b695..8bdadc28cee45 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_schedule.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_definition/rule_schedule.tsx @@ -15,7 +15,7 @@ import { getDurationNumberInItsUnit, } from '../utils/parse_duration'; import { getTimeOptions } from '../utils/get_time_options'; -import { MinimumScheduleInterval, RuleFormErrors } from '../types'; +import { MinimumScheduleInterval, RuleFormErrors } from '../../common'; import { SCHEDULE_TITLE_PREFIX, INTERVAL_MINIMUM_TEXT, diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_details/rule_details.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_details/rule_details.tsx index a2cd9b6b02bcf..30af5dfa16ed5 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_details/rule_details.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_details/rule_details.tsx @@ -15,8 +15,7 @@ import { EuiComboBoxOptionOption, EuiText, } from '@elastic/eui'; -import type { SanitizedRule, RuleTypeParams } from '@kbn/alerting-types'; -import type { RuleFormErrors } from '../types'; +import type { RuleFormErrors, Rule, RuleTypeParams } from '../../common'; import { RULE_DETAILS_TITLE, RULE_DETAILS_DESCRIPTION, @@ -26,8 +25,8 @@ import { export interface RuleDetailsProps { formValues: { - tags?: SanitizedRule['tags']; - name: SanitizedRule['name']; + tags?: Rule['tags']; + name: Rule['name']; }; errors?: RuleFormErrors; onChange: (property: string, value: unknown) => void; diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/types.ts b/packages/kbn-alerts-ui-shared/src/rule_form/types.ts new file mode 100644 index 0000000000000..dff86d5ce61fd --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/rule_form/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 { Rule, RuleTypeParams } from '../common'; + +export interface RuleFormData { + name: Rule['name']; + tags: Rule['tags']; + params: Rule['params']; + schedule: Rule['schedule']; + consumer: Rule['consumer']; + alertDelay?: Rule['alertDelay']; + notifyWhen?: Rule['notifyWhen']; + ruleTypeId?: Rule['ruleTypeId']; +} + +export type InitialRule = Partial & + Pick; diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/utils/get_errors.ts b/packages/kbn-alerts-ui-shared/src/rule_form/utils/get_errors.ts index 445d900d3f56c..b0ff1f1fd067b 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/utils/get_errors.ts +++ b/packages/kbn-alerts-ui-shared/src/rule_form/utils/get_errors.ts @@ -7,12 +7,11 @@ */ import { - InitialRule, RuleTypeModel, RuleFormErrors, ValidationResult, MinimumScheduleInterval, -} from '../types'; +} from '../../common'; import { parseDuration, formatDuration } from './parse_duration'; import { NAME_REQUIRED_TEXT, @@ -22,6 +21,7 @@ import { INTERVAL_MINIMUM_TEXT, RULE_ALERT_DELAY_BELOW_MINIMUM_TEXT, } from '../translations'; +import { InitialRule } from '../types'; export function validateBaseProperties({ rule, diff --git a/packages/kbn-alerts-ui-shared/tsconfig.json b/packages/kbn-alerts-ui-shared/tsconfig.json index dc2e78cbd49bb..c22c1ac1beac7 100644 --- a/packages/kbn-alerts-ui-shared/tsconfig.json +++ b/packages/kbn-alerts-ui-shared/tsconfig.json @@ -37,5 +37,6 @@ "@kbn/core-doc-links-browser", "@kbn/charts-plugin", "@kbn/data-plugin", + "@kbn/utility-types", ] } diff --git a/packages/kbn-apm-synthtrace-client/src/lib/infra/docker_container.ts b/packages/kbn-apm-synthtrace-client/src/lib/infra/docker_container.ts index b0dab3da9dabb..2ed426a2a6898 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/infra/docker_container.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/infra/docker_container.ts @@ -13,14 +13,22 @@ import { Serializable } from '../serializable'; interface DockerContainerDocument extends Fields { 'container.id': string; 'metricset.name'?: string; + 'container.name'?: string; + 'container.image.name'?: string; + 'container.runtime'?: string; + 'host.name'?: string; + 'cloud.provider'?: string; + 'cloud.instance.id'?: string; + 'cloud.image.id'?: string; + 'event.dataset'?: string; } export class DockerContainer extends Entity { metrics() { return new DockerContainerMetrics({ ...this.fields, - 'docker.cpu.total.pct': 25, - 'docker.memory.usage.pct': 20, + 'docker.cpu.total.pct': 0.25, + 'docker.memory.usage.pct': 0.2, 'docker.network.inbound.bytes': 100, 'docker.network.outbound.bytes': 200, 'docker.diskio.read.ops': 10, diff --git a/packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts b/packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts index 136b3cf227a9d..b4425156e6780 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts @@ -16,6 +16,7 @@ interface HostDocument extends Fields { 'host.name': string; 'metricset.name'?: string; 'event.module'?: string; + 'service.name'?: string; } class Host extends Entity { diff --git a/packages/kbn-apm-synthtrace-client/src/lib/infra/k8s_container.ts b/packages/kbn-apm-synthtrace-client/src/lib/infra/k8s_container.ts index 00d4f0a998960..d2036555919c3 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/infra/k8s_container.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/infra/k8s_container.ts @@ -15,6 +15,14 @@ interface K8sContainerDocument extends Fields { 'kubernetes.pod.uid': string; 'kubernetes.node.name': string; 'metricset.name'?: string; + 'container.name'?: string; + 'container.image.name'?: string; + 'container.runtime'?: string; + 'host.name'?: string; + 'cloud.provider'?: string; + 'cloud.instance.id'?: string; + 'cloud.image.id'?: string; + 'event.dataset'?: string; } export class K8sContainer extends Entity { diff --git a/packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts b/packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts index a649d60df57a2..489221eea3d7a 100644 --- a/packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts +++ b/packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts @@ -25,11 +25,14 @@ export type LogDocument = Fields & 'host.name'?: string; 'container.id'?: string; 'trace.id'?: string; + 'transaction.id'?: string; 'agent.id'?: string; 'agent.name'?: string; 'orchestrator.cluster.name'?: string; 'orchestrator.cluster.id'?: string; 'orchestrator.resource.id'?: string; + 'kubernetes.pod.uid'?: string; + 'aws.s3.bucket.name'?: string; 'orchestrator.namespace'?: string; 'container.name'?: string; 'cloud.provider'?: string; @@ -40,6 +43,7 @@ export type LogDocument = Fields & 'error.stack_trace'?: string; 'error.exception.stacktrace'?: string; 'error.log.stacktrace'?: string; + 'log.custom': Record; }>; class Log extends Serializable { diff --git a/packages/kbn-apm-synthtrace/src/cli/utils/get_service_urls.ts b/packages/kbn-apm-synthtrace/src/cli/utils/get_service_urls.ts index 3f13cae5e039c..3967040a569af 100644 --- a/packages/kbn-apm-synthtrace/src/cli/utils/get_service_urls.ts +++ b/packages/kbn-apm-synthtrace/src/cli/utils/get_service_urls.ts @@ -12,7 +12,7 @@ import { Logger } from '../../lib/utils/create_logger'; import { RunOptions } from './parse_run_cli_flags'; async function discoverAuth(parsedTarget: Url) { - const possibleCredentials = [`admin:changeme`, `elastic:changeme`]; + const possibleCredentials = [`admin:changeme`, `elastic:changeme`, `elastic_serverless:changeme`]; for (const auth of possibleCredentials) { const url = format({ ...parsedTarget, diff --git a/packages/kbn-apm-synthtrace/src/scenarios/infra_docker_containers.ts b/packages/kbn-apm-synthtrace/src/scenarios/infra_docker_containers.ts index c020d2de83db9..6df70ae0641df 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/infra_docker_containers.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/infra_docker_containers.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { InfraDocument, infra } from '@kbn/apm-synthtrace-client'; +import { InfraDocument, infra, generateShortId } from '@kbn/apm-synthtrace-client'; import { Scenario } from '../cli/scenario'; import { withClient } from '../lib/utils/with_client'; @@ -19,7 +19,20 @@ const scenario: Scenario = async (runOptions) => { const CONTAINERS = Array(numContainers) .fill(0) - .map((_, idx) => infra.dockerContainer(`container-${idx}`)); + .map((_, idx) => { + const id = generateShortId(); + return infra.dockerContainer(id).defaults({ + 'container.name': `container-${idx}`, + 'container.id': id, + 'container.runtime': 'docker', + 'container.image.name': 'image-1', + 'host.name': 'host-1', + 'cloud.instance.id': 'instance-1', + 'cloud.image.id': 'image-1', + 'cloud.provider': 'aws', + 'event.dataset': 'docker.container', + }); + }); const containers = range .interval('30s') diff --git a/packages/kbn-apm-synthtrace/src/scenarios/infra_k8s_containers.ts b/packages/kbn-apm-synthtrace/src/scenarios/infra_k8s_containers.ts index 39d2b36b1a03f..340da647bb9d7 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/infra_k8s_containers.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/infra_k8s_containers.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { InfraDocument, infra } from '@kbn/apm-synthtrace-client'; +import { InfraDocument, infra, generateShortId } from '@kbn/apm-synthtrace-client'; import { Scenario } from '../cli/scenario'; import { withClient } from '../lib/utils/with_client'; @@ -19,7 +19,22 @@ const scenario: Scenario = async (runOptions) => { const CONTAINERS = Array(numContainers) .fill(0) - .map((_, idx) => infra.k8sContainer(`container-${idx}`, `pod-${idx}`, `node-${idx}`)); + .map((_, idx) => { + const id = generateShortId(); + return infra.k8sContainer(id, `pod-${idx}`, `node-${idx}`).defaults({ + 'container.id': id, + 'kubernetes.pod.uid': `pod-${idx}`, + 'kubernetes.node.name': `node-${idx}`, + 'container.name': `container-${idx}`, + 'container.runtime': 'docker', + 'container.image.name': 'image-1', + 'host.name': 'host-1', + 'cloud.instance.id': 'instance-1', + 'cloud.image.id': 'image-1', + 'cloud.provider': 'aws', + 'event.dataset': 'kubernetes.container', + }); + }); const containers = range .interval('30s') diff --git a/packages/kbn-apm-synthtrace/src/scenarios/logs_traces_hosts.ts b/packages/kbn-apm-synthtrace/src/scenarios/logs_traces_hosts.ts new file mode 100644 index 0000000000000..a745ef6d2bca2 --- /dev/null +++ b/packages/kbn-apm-synthtrace/src/scenarios/logs_traces_hosts.ts @@ -0,0 +1,467 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + log, + LogDocument, + InfraDocument, + apm, + Instance, + infra, + ApmFields, + generateShortId, +} from '@kbn/apm-synthtrace-client'; +import { Scenario } from '../cli/scenario'; +import { Logger } from '../lib/utils/create_logger'; +import { withClient } from '../lib/utils/with_client'; +import { getSynthtraceEnvironment } from '../lib/utils/get_synthtrace_environment'; + +const ENVIRONMENT = getSynthtraceEnvironment(__filename); + +// Use e.g. # ... --scenarioOpts.numServices=5 --scenarioOpts.customFieldPrefix="metric". See https://github.com/elastic/kibana/pull/184804 for more details. +const DEFAULT_SCENARIO_OPTS = { + numSpaces: 1, + numServices: 10, + numHosts: 10, + numAgents: 5, + numDatasets: 6, // Won't be used if `datasets` option is provided + datasets: undefined, // Provide a list of datasets --scenarioOpts.datasets="apache.access" --scenarioOpts.datasets="nginx.error" to override the default list + degradedRatio: 0.25, // Percentage of logs that are malformed (over limit or mapping conflict) + numCustomFields: 50, // Number of custom field (e.g. `log.custom.field-1: "abc"`) per document + customFieldPrefix: 'field', // Prefix for custom fields (e.g. `log.custom.field-1: "abc"`) + logsInterval: '1m', + logsRate: 1, + ingestHosts: true, + ingestTraces: true, +}; + +const scenario: Scenario = async (runOptions) => { + return { + generate: ({ range, clients: { logsEsClient, infraEsClient, apmEsClient } }) => { + const { + numSpaces, + numServices, + numHosts, + numAgents, + numDatasets, + datasets, + degradedRatio, + numCustomFields, + customFieldPrefix, + logsInterval, + logsRate, + ingestHosts, + ingestTraces, + } = { ...DEFAULT_SCENARIO_OPTS, ...(runOptions.scenarioOpts || {}) }; + const { logger } = runOptions; + + killIfUnknownScenarioOptions(logger, runOptions.scenarioOpts || {}); + + // Infra Hosts + const infraHosts = Array(numHosts) + .fill(0) + .map((_, idx) => infra.host(getRotatedItem(idx, HOSTS, numHosts))); + + const hosts = range + .interval('1m') + .rate(1) + .generator((timestamp) => { + const agent = getRotatedItem(timestamp, AGENTS, numAgents); + const service = getRotatedItem(timestamp, SERVICE_NAMES, numServices); + + return infraHosts.flatMap((host) => + [ + host.cpu().timestamp(timestamp), + host.memory().timestamp(timestamp), + host.network().timestamp(timestamp), + host.load().timestamp(timestamp), + host.filesystem().timestamp(timestamp), + host.diskio().timestamp(timestamp), + ].map((metric) => + metric.defaults({ + 'host.name': host.fields['host.name'], + 'host.hostname': host.fields['host.name'], + 'agent.id': agent.id, + 'service.name': service, + 'system.memory.actual.free': 500 + Math.floor(Math.random() * 500), + 'system.memory.total': 1000, + 'system.cpu.total.norm.pct': 0.5 + Math.random() * 0.25, + }) + ) + ); + }); + + // APM Traces + const instances = [...Array(numServices).keys()].map((index) => { + const agent = getRotatedItem(index, AGENTS, numAgents); + + return apm + .service({ + name: getRotatedItem(index, SERVICE_NAMES, numServices), + environment: ENVIRONMENT, + agentName: agent.name, + }) + .instance(getRotatedItem(index, HOSTS, numHosts)); + }); + const instanceSpans = (instance: Instance) => { + const successfulTraceEvents = range + .interval('1m') + .rate(1) + .generator((timestamp) => { + const isError = Math.random() < 0.5; + const cloudRegion = getRotatedItem(timestamp, CLOUD_REGION, 3); + const agent = getRotatedItem(timestamp, AGENTS, numAgents); + + const transaction = instance + .transaction({ transactionName: getRotatedItem(timestamp, TRANSACTION_NAMES, 3) }) + .timestamp(timestamp) + .duration(1000) + .defaults({ + 'trace.id': `trace-id-${getTimestampBlock(timestamp, 3 * 60 * 1000)}`, + 'transaction.id': `transaction-id-${getTimestampBlock(timestamp, 2 * 60 * 1000)}`, + 'span.id': `span-id-${getTimestampBlock(timestamp, 60 * 1000)}`, + 'agent.name': agent.name, + 'cloud.region': cloudRegion, + 'cloud.provider': getRotatedItem(timestamp, CLOUD_PROVIDERS, 3), + 'cloud.project.id': generateShortId(), + 'cloud.availability_zone': `${cloudRegion}a`, + 'service.name': getRotatedItem(timestamp, SERVICE_NAMES, numServices), + 'service.environment': ENVIRONMENT, + }); + + if (isError) { + transaction.failure().errors( + instance + .error({ + message: '[ResponseError] index_not_found_exception', + type: 'ResponseError', + }) + .timestamp(timestamp) + ); + } else { + transaction.success().children( + instance + .span({ + spanName: 'GET apm-*/_search', + spanType: 'db', + spanSubtype: 'elasticsearch', + }) + .duration(1000) + .success() + .destination('elasticsearch') + .timestamp(timestamp), + instance + .span({ spanName: 'custom_operation', spanType: 'custom' }) + .duration(100) + .success() + .timestamp(timestamp) + ); + } + + return transaction; + }); + + return [successfulTraceEvents]; + }; + + // Logs + const logDatasets = datasets ? (Array.isArray(datasets) ? datasets : [datasets]) : DATASETS; + const numLogDatasets = datasets ? logDatasets.length : numDatasets; + const cloudProjectId = `cloud-project-${generateShortId()}`; + const logs = range + .interval(logsInterval) + .rate(logsRate) + .generator((timestamp, index) => { + const isMalformed = index > 0 && Math.random() < degradedRatio; // `index > 0` to wait for dynamic templates + const cloudRegion = getRotatedItem(timestamp, CLOUD_REGION, 3); + const dataset = getRotatedItem(timestamp, logDatasets, numLogDatasets); + const space = getRotatedItem(timestamp, NAMESPACES, numSpaces); + const hostName = getRotatedItem(timestamp, HOSTS, numHosts); + const agent = getRotatedItem(timestamp, AGENTS, numAgents); + const service = getRotatedItem(timestamp, SERVICE_NAMES, numServices); + const logLevel = getRotatedItem(timestamp, LOG_LEVELS, LOG_LEVELS.length); + const message = `Log message for logs-${dataset}-${space}. logLevel: ${logLevel}. isMalformed: ${isMalformed}. dataset: ${dataset}. space: ${space}. cloudRegion: ${cloudRegion}.`; + const customFields = getExtraFields(numCustomFields, isMalformed, customFieldPrefix); + + return log + .create() + .dataset(dataset) + .message(message) + .logLevel(logLevel) + .namespace(space) + .hostName(hostName) + .service(service) + .containerId(`container.${Math.random() > 0.5 ? 1 : 2}.${hostName}`) + .defaults({ + 'trace.id': `trace-id-${getTimestampBlock(timestamp, 3 * 60 * 1000)}`, + 'transaction.id': `transaction-id-${getTimestampBlock(timestamp, 2 * 60 * 1000)}`, + 'agent.id': agent.id, + 'agent.name': agent.name, + 'container.id': generateShortId(), + 'orchestrator.cluster.name': getRotatedItem(timestamp, CLUSTERS, 3).clusterName, + 'orchestrator.cluster.id': getRotatedItem(timestamp, CLUSTERS, 3).clusterId, + 'orchestrator.resource.id': generateShortId(), + 'kubernetes.pod.uid': `kb.${Math.random() > 0.5 ? 1 : 2}.${hostName}`, + 'aws.s3.bucket.name': `aws.bk.${Math.random() > 0.5 ? 1 : 2}.${hostName}`, + 'cloud.provider': getRotatedItem(timestamp, CLOUD_PROVIDERS, 3), + 'cloud.region': cloudRegion, + 'cloud.availability_zone': `${cloudRegion}a`, + 'cloud.project.id': cloudProjectId, + 'cloud.instance.id': getRotatedItem(timestamp, CLUSTERS, 3).clusterId, + 'log.file.path': `/logs/${generateShortId()}/${logLevel}.txt`, + 'log.custom': customFields, + }) + .timestamp(timestamp); + }); + + return [ + ...(ingestHosts + ? [ + withClient( + infraEsClient, + logger.perf('generating_infra_hosts', () => hosts) + ), + ] + : []), + ...(ingestTraces + ? [ + withClient( + apmEsClient, + logger.perf('generating_apm_events', () => + instances.flatMap((instance) => instanceSpans(instance)) + ) + ), + ] + : []), + withClient( + logsEsClient, + logger.perf('generating_logs', () => logs) + ), + ]; + }, + }; +}; + +export default scenario; + +/** + * The function will pick an item from the list of items and rotate the item based on the index and maxPickCount. + * + * @param index Current running index of the item. Can be any sequential number. + * @param items List of items to pick from. + * @param maxPickCount How many items to pick from the list. If maxPickCount > items.length, then it will pick all items. + */ +function getRotatedItem(index: number, items: T[], maxPickCount: number) { + const normalizedIndex = Math.floor(index > 1000000 ? index / 1000 : index); // To randomize timestamps + const maxLength = Math.min(maxPickCount, items.length); + return items[normalizedIndex % maxLength]; +} + +/** + * The function will return the (passed) timestamp block based on the milliInterval. + * + * @param timestamp The timestamp to get the block for. + * @param milliInterval The interval in milliseconds. E.g. 30000 for 30 second block. + */ +function getTimestampBlock(timestamp: number, milliInterval: number) { + const remainder = timestamp % milliInterval; + return timestamp - remainder; +} + +/** + * Generate extra fields with sequential keys (0 - n) to simulate extra/custom log fields in a log document. + * + * @param numFields Number of fields to generate. + * @param isDegraded If true, will generate fields with more than 1024 characters, and will use a mix of numeric and + * string values to cause mapping conflicts. If false, will generate first half with numeric values and second half with + * string values. + * @param customFieldPrefix Prefix for the field key. Default is 'field'. + */ +function getExtraFields( + numFields: number, + isDegraded = false, + customFieldPrefix = DEFAULT_SCENARIO_OPTS.customFieldPrefix +) { + const extraFields: Record = {}; + for (let i = 0; i < numFields; i++) { + if (isDegraded) { + extraFields[`${customFieldPrefix}-${i}`] = getRandomSlice( + MORE_THAN_1024_CHARS, + MORE_THAN_1024_CHARS.length + ); + } else { + if (i % 2 === 0) { + extraFields[`${customFieldPrefix}-${i}`] = Math.random() * 1000 * 1000; // Assign half of the fields with numeric values + } else { + extraFields[`${customFieldPrefix}-${i}`] = getRandomSlice(MORE_THAN_1024_CHARS, 200); + } + } + } + return extraFields; +} + +/** + * Slices a string from the start index to a random number until length. + */ +function getRandomSlice(str: string, maxLength: number, startIndex = 0) { + const start = Math.min(str.length, startIndex); + const end = Math.min(str.length, start + Math.floor(Math.random() * maxLength)); + return str.slice(start, end); +} + +function killIfUnknownScenarioOptions(logger: Logger, options: Record) { + const unknownOptions = Object.keys(options).filter( + (key) => !Object.keys(DEFAULT_SCENARIO_OPTS).includes(key) + ); + if (unknownOptions.length > 0) { + logger.error(`Unknown scenario option(s): ${unknownOptions.join(', ')}`); + process.exit(1); + } +} + +const NAMESPACES = ['default', 'space-01', 'space-02', 'space-03']; + +const SERVICE_NAMES = [ + 'frontend-rum', + 'azure-functions', + 'frontend', + 'checkout-service', + 'synth-go', + 'synth-node', + 'synth-dotnet', + 'synth-java', + 'productcatalogservice', + 'synth-rum', + 'auditbeat', + 'synth-node-0', + 'payment-service', + 'opbeans-java-otel', + 'packetbeat', + 'cartservice', + 'web-go-0', + 'aws-lambdas', + 'opbeans-go', + 'synth-service-0', + 'synth-service-1', + 'order-processing-dotnet-1', + 'synth-java-0', + 'arn:aws:lambda:us-west-2:001:function:fn-node-2', + 'opbeans-ruby', + 'synth-android', + 'currencyservice', + 'opbeans-python', + 'synth-ios', + 'shippingservice', + 'adservice', + 'recommendationservice', + 'frauddetectionservice', + 'paymentservice', + 'checkoutservice', + 'emailservice', + 'quoteservice', +]; + +const HOSTS = [ + 'apache-integrations-557cfb8fcb-6kb65', + 'mysql-integrations-6b69cc89b4-fzhvw', + 'docker-integrations-6cf69b8966-mnccd', + 'siem-linux-edge-lite-oblt', + 'gke-edge-lite-oblt-edge-lite-oblt-poo-0d6e31aa-wrzm', + 'gke-edge-lite-oblt-edge-lite-oblt-poo-0d6e31aa-vvnh', + 'gke-edge-lite-oblt-edge-lite-oblt-poo-0d6e31aa-tc5h', + 'gke-edge-lite-oblt-edge-lite-oblt-poo-0d6e31aa-rc92', + 'gke-edge-lite-oblt-edge-lite-oblt-poo-0d6e31aa-mlgl', + 'gke-edge-lite-oblt-edge-lite-oblt-poo-0d6e31aa-l7r5', + 'gke-edge-lite-oblt-edge-lite-oblt-poo-0d6e31aa-js5d', + 'gke-edge-lite-oblt-edge-lite-oblt-poo-0d6e31aa-f4q6', + 'gke-edge-lite-oblt-edge-lite-oblt-poo-0d6e31aa-ddlj', + 'gke-edge-lite-oblt-edge-lite-oblt-poo-0d6e31aa-d18l', + 'gke-edge-lite-oblt-edge-lite-oblt-poo-0d6e31aa-9m5v', + 'gke-edge-lite-oblt-edge-lite-oblt-poo-0d6e31aa-7jvw', + 'gke-edge-lite-oblt-edge-lite-oblt-poo-0d6e31aa-475z', +]; + +const CLUSTERS = [ + { clusterId: generateShortId(), clusterName: 'synth-cluster-1' }, + { clusterId: generateShortId(), clusterName: 'synth-cluster-2' }, + { clusterId: generateShortId(), clusterName: 'synth-cluster-3' }, +]; +const CLOUD_PROVIDERS = ['gcp', 'aws', 'azure']; +const CLOUD_REGION = ['eu-central-1', 'us-east-1', 'area-51']; +const AGENTS = [ + { id: 'go-id', name: 'go' }, + { id: 'rum-js-id', name: 'rum-js' }, + { id: 'nodejs-id', name: 'nodejs' }, + { id: 'opbeans-java-id', name: 'opbeans-java' }, + { id: 'opbeans-node-id', name: 'opbeans-node' }, + { id: 'opbeans-python-id', name: 'opbeans-python' }, + { id: 'opbeans-ruby-id', name: 'opbeans-ruby' }, + { id: 'opbeans-dotnet-id', name: 'opbeans-dotnet' }, + { id: 'synth-agent-id', name: 'synth-agent' }, +]; + +const TRANSACTION_NAMES = ['GET /synth/customers', 'GET /synth/orders', 'GET /synth/articles']; + +const DATASETS = [ + 'apache.access', + 'apache.error', + 'nginx.access', + 'nginx.error', + 'apm.access', + 'apm.error', + 'apm.metrics', + 'mysql.access', + 'mysql.error', + 'kubernetes.audit_logs', + 'kubernetes.container_logs', + 'system.syslog', + 'postgresql.error', + 'postgresql.log', + 'redis.error', + 'redis.log', + 'mongodb.error', + 'mongodb.log', + 'activemq.audit', + 'activemq.log', + 'elasticsearch.audit', + 'elasticsearch.slowlog', + 'akamai.siem', + 'auditd.log', + 'auditd_manager.auditd', + 'cloud_security_posture.findings', + 'docker.container_logs', + 'elastic_agent.apm_server', + 'elastic_agent.filebeat', + 'elastic_agent.heartbeat', + 'fleet_server.output_health', + 'fleet_server.logs', + 'kibana.audit', + 'kibana.log', + 'microsoft_sqlserver.audit', + 'microsoft_sqlserver.log', + 'network_traffic.http', + 'network_traffic.dns', + 'apm.logs', + 'apm.traces', + 'apm.rum', + 'apm.profiling', + 'apm.uptime', + 'apm.synthetics', + 'apm.infra', + 'apm.sourcemap', + 'apm.span', + 'apm.transaction', + 'synth.1', + 'synth.2', + 'synth.3', +]; + +const LOG_LEVELS = ['info', 'error', 'warn', 'debug']; + +const MORE_THAN_1024_CHARS = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?'; 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 67860ded13bdc..f72b31a0f834d 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 @@ -10,7 +10,7 @@ import type { JSXElementConstructor, MutableRefObject } from 'react'; import React from 'react'; import type { EuiDataGridColumnCellActionProps, EuiDataGridRefProps } from '@elastic/eui'; import { EuiButtonEmpty, type EuiDataGridColumnCellAction } from '@elastic/eui'; -import { render, waitFor, act } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import { renderHook } from '@testing-library/react-hooks'; import { makeAction } from '../mocks/helpers'; import type { UseDataGridColumnsCellActionsProps } from './use_data_grid_column_cell_actions'; @@ -74,8 +74,8 @@ describe('useDataGridColumnsCellActions', () => { const { result, waitForNextUpdate } = renderHook(useDataGridColumnsCellActions, { initialProps: useDataGridColumnsCellActionsProps, }); - expect(result.current).toHaveLength(columns.length); - expect(result.current[0]).toHaveLength(1); // loader + + expect(result.current).toHaveLength(0); await waitForNextUpdate(); @@ -83,16 +83,6 @@ describe('useDataGridColumnsCellActions', () => { expect(result.current[0]).toHaveLength(actions.length); }); - it('should render cell actions loading state', async () => { - const { result } = renderHook(useDataGridColumnsCellActions, { - initialProps: useDataGridColumnsCellActionsProps, - }); - await act(async () => { - const cellAction = renderCellAction(result.current[0][0]); - expect(cellAction.getByTestId('dataGridColumnCellAction-loading')).toBeInTheDocument(); - }); - }); - it('should call getCellValue with the proper params', async () => { const { result, waitForNextUpdate } = renderHook(useDataGridColumnsCellActions, { initialProps: useDataGridColumnsCellActionsProps, diff --git a/packages/kbn-cell-actions/src/hooks/use_data_grid_column_cell_actions.tsx b/packages/kbn-cell-actions/src/hooks/use_data_grid_column_cell_actions.tsx index b5b8ea32d9b52..96132a80bbeb2 100644 --- a/packages/kbn-cell-actions/src/hooks/use_data_grid_column_cell_actions.tsx +++ b/packages/kbn-cell-actions/src/hooks/use_data_grid_column_cell_actions.tsx @@ -7,9 +7,9 @@ */ import type { MutableRefObject } from 'react'; -import React, { useCallback, useMemo, useRef } from 'react'; +import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react'; import type { EuiDataGridRefProps } from '@elastic/eui'; -import { EuiLoadingSpinner, type EuiDataGridColumnCellAction } from '@elastic/eui'; +import { type EuiDataGridColumnCellAction } from '@elastic/eui'; import type { FieldSpec } from '@kbn/data-views-plugin/common'; import type { CellAction, @@ -46,10 +46,6 @@ export type UseDataGridColumnsCellActions< P extends UseDataGridColumnsCellActionsProps = UseDataGridColumnsCellActionsProps > = (props: P) => EuiDataGridColumnCellAction[][]; -// static actions array references to prevent React updates -const loadingColumnActions: EuiDataGridColumnCellAction[] = [ - () => , -]; const emptyActions: EuiDataGridColumnCellAction[][] = []; export const useDataGridColumnsCellActions: UseDataGridColumnsCellActions = ({ @@ -60,6 +56,8 @@ export const useDataGridColumnsCellActions: UseDataGridColumnsCellActions = ({ dataGridRef, disabledActionTypes = [], }) => { + const [cellActions, setCellActions] = useState(emptyActions); + const bulkContexts: CellActionCompatibilityContext[] | undefined = useMemo(() => { if (!triggerId || !fields?.length) { return undefined; @@ -75,35 +73,35 @@ export const useDataGridColumnsCellActions: UseDataGridColumnsCellActions = ({ disabledActionTypes, }); - const columnsCellActions = useMemo(() => { - if (loading) { - return fields?.length ? fields.map(() => loadingColumnActions) : emptyActions; - } - if (!triggerId || !columnsActions?.length || !fields?.length) { - return emptyActions; + useEffect(() => { + // no-op + if (loading || !triggerId || !columnsActions?.length || !fields?.length) { + return; } // Check for a temporary inconsistency because `useBulkLoadActions` takes one render loop before setting `loading` to true. // It will eventually update to a consistent state if (columnsActions.length !== fields.length) { - return emptyActions; + return; } - return columnsActions.map((actions, columnIndex) => - actions.map((action) => - createColumnCellAction({ - action, - field: fields[columnIndex], - getCellValue, - metadata, - triggerId, - dataGridRef, - }) + setCellActions( + columnsActions.map((actions, columnIndex) => + actions.map((action) => + createColumnCellAction({ + action, + field: fields[columnIndex], + getCellValue, + metadata, + triggerId, + dataGridRef, + }) + ) ) ); }, [columnsActions, fields, getCellValue, loading, metadata, triggerId, dataGridRef]); - return columnsCellActions; + return cellActions; }; interface CreateColumnCellActionParams diff --git a/packages/kbn-config-schema/README.md b/packages/kbn-config-schema/README.md index 5916f49ef2836..45e46c078baee 100644 --- a/packages/kbn-config-schema/README.md +++ b/packages/kbn-config-schema/README.md @@ -21,6 +21,7 @@ Kibana configuration entries providing developers with a fully typed model of th - [`schema.object()`](#schemaobject) - [`schema.recordOf()`](#schemarecordof) - [`schema.mapOf()`](#schemamapof) + - [`schema.allOf` / `schema.intersection`](#schemaintersection--schemaallof) - [Advanced types](#advanced-types) - [`schema.oneOf()`](#schemaoneof) - [`schema.any()`](#schemaany) @@ -295,6 +296,30 @@ __Notes:__ * You can use a union of literal types as a record's key schema to restrict record to a specific set of keys, e.g. `schema.oneOf([schema.literal('isEnabled'), schema.literal('name')])`. * `schema.mapOf()` also supports a json string as input if it can be safely parsed using `JSON.parse` and if the resulting value is a plain object. +#### `schema.intersection()` / `schema.allOf()` + +Creates an `object` schema being the intersection of the provided `object` schemas. +Note that schema construction will throw an error if some of the intersection schema share the same key(s). + +See the documentation for [schema.object](#schemaobject). + +__Options:__ +* `defaultValue: TObject | Reference | (() => TObject)` - defines a default value, see [Default values](#default-values) section for more details. +* `validate: (value: TObject) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details. +* `unknowns: 'allow' | 'ignore' | 'forbid'` - indicates whether unknown object properties should be allowed, ignored, or forbidden. It's `forbid` by default. + +__Usage:__ +```typescript +const mySchema = schema.intersection([ + schema.object({ + someKey: schema.string(), + }), + schema.object({ + anotherKey: schema.string(), + }) +]); +``` + ### Advanced types #### `schema.oneOf()` diff --git a/packages/kbn-config-schema/index.ts b/packages/kbn-config-schema/index.ts index c6ef8c2ce99dc..8195631606381 100644 --- a/packages/kbn-config-schema/index.ts +++ b/packages/kbn-config-schema/index.ts @@ -23,6 +23,8 @@ import { ConditionalTypeValue, DurationOptions, DurationType, + IntersectionType, + IntersectionTypeOptions, IpOptions, IpType, LiteralType, @@ -34,6 +36,7 @@ import { NumberType, ObjectType, ObjectTypeOptions, + ObjectResultType, Props, NullableProps, RecordOfOptions, @@ -199,6 +202,164 @@ function oneOf>>( return new UnionType(types, options); } +function allOf< + A extends Props, + B extends Props, + C extends Props, + D extends Props, + E extends Props, + F extends Props, + G extends Props, + H extends Props, + I extends Props, + J extends Props, + K extends Props +>( + types: [ + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType + ], + options?: UnionTypeOptions +): Type>; +function allOf< + A extends Props, + B extends Props, + C extends Props, + D extends Props, + E extends Props, + F extends Props, + G extends Props, + H extends Props, + I extends Props, + J extends Props +>( + types: [ + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType + ], + options?: UnionTypeOptions +): Type>; +function allOf< + A extends Props, + B extends Props, + C extends Props, + D extends Props, + E extends Props, + F extends Props, + G extends Props, + H extends Props, + I extends Props +>( + types: [ + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType + ], + options?: UnionTypeOptions +): Type>; +function allOf< + A extends Props, + B extends Props, + C extends Props, + D extends Props, + E extends Props, + F extends Props, + G extends Props, + H extends Props +>( + types: [ + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType + ], + options?: UnionTypeOptions +): Type>; +function allOf< + A extends Props, + B extends Props, + C extends Props, + D extends Props, + E extends Props, + F extends Props, + G extends Props +>( + types: [ + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType, + ObjectType + ], + options?: UnionTypeOptions +): Type>; +function allOf< + A extends Props, + B extends Props, + C extends Props, + D extends Props, + E extends Props, + F extends Props +>( + types: [ObjectType, ObjectType, ObjectType, ObjectType, ObjectType, ObjectType], + options?: UnionTypeOptions +): Type>; +function allOf( + types: [ObjectType, ObjectType, ObjectType, ObjectType, ObjectType], + options?: UnionTypeOptions +): Type>; +function allOf( + types: [ObjectType, ObjectType, ObjectType, ObjectType], + options?: UnionTypeOptions +): Type>; +function allOf( + types: [ObjectType, ObjectType, ObjectType], + options?: UnionTypeOptions +): Type>; +function allOf( + types: [ObjectType, ObjectType], + options?: UnionTypeOptions +): Type>; +function allOf( + types: [ObjectType], + options?: UnionTypeOptions +): Type>; +function allOf>>( + types: RTS, + options?: IntersectionTypeOptions +): Type { + return new IntersectionType(types, options); +} + function contextRef(key: string): ContextReference { return new ContextReference(key); } @@ -225,6 +386,7 @@ function lazy(id: string) { } export const schema = { + allOf, any, arrayOf, boolean, @@ -233,6 +395,7 @@ export const schema = { conditional, contextRef, duration, + intersection: allOf, ip, lazy, literal, diff --git a/packages/kbn-config-schema/src/types/index.ts b/packages/kbn-config-schema/src/types/index.ts index 78eab0957557d..4c92bb6d078dc 100644 --- a/packages/kbn-config-schema/src/types/index.ts +++ b/packages/kbn-config-schema/src/types/index.ts @@ -20,13 +20,21 @@ export type { ConditionalTypeValue } from './conditional_type'; export { ConditionalType } from './conditional_type'; export type { DurationOptions } from './duration_type'; export { DurationType } from './duration_type'; +export type { IntersectionTypeOptions } from './intersection_type'; +export { IntersectionType } from './intersection_type'; export { LiteralType } from './literal_type'; export { MaybeType } from './maybe_type'; export type { MapOfOptions } from './map_type'; export { MapOfType } from './map_type'; export type { NumberOptions } from './number_type'; export { NumberType } from './number_type'; -export type { ObjectTypeOptions, Props, NullableProps, TypeOf } from './object_type'; +export type { + ObjectTypeOptions, + Props, + NullableProps, + TypeOf, + ObjectResultType, +} from './object_type'; export { ObjectType } from './object_type'; export type { RecordOfOptions } from './record_type'; export { RecordOfType } from './record_type'; diff --git a/packages/kbn-config-schema/src/types/intersection_type.test.ts b/packages/kbn-config-schema/src/types/intersection_type.test.ts new file mode 100644 index 0000000000000..cf1262da0550b --- /dev/null +++ b/packages/kbn-config-schema/src/types/intersection_type.test.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { schema, TypeOf } from '../..'; + +describe('schema.allOf', () => { + it('validates all parts of the intersection', () => { + const type = schema.allOf([ + schema.object({ foo: schema.string() }), + schema.object({ bar: schema.string() }), + ]); + + expect(type.validate({ foo: 'hello', bar: 'dolly' })).toEqual({ foo: 'hello', bar: 'dolly' }); + }); + + it('throw error when one part of the intersection is not matched with the correct error message', () => { + const type = schema.allOf([ + schema.object({ foo: schema.string() }), + schema.object({ bar: schema.string() }), + ]); + + expect(() => type.validate({ foo: 'something' })).toThrowErrorMatchingInlineSnapshot( + `"[bar]: expected value of type [string] but got [undefined]"` + ); + }); + + it('supports default value', () => { + const type = schema.allOf([ + schema.object({ foo: schema.string() }), + schema.object({ bar: schema.string({ defaultValue: 'default' }) }), + ]); + + expect(type.validate({ foo: 'hello' })).toEqual({ foo: 'hello', bar: 'default' }); + }); + + it('throw error if multiple schemas define the same key', () => { + expect(() => + schema.allOf([ + schema.object({ foo: schema.string() }), + schema.object({ foo: schema.literal('bar') }), + ]) + ).toThrowErrorMatchingInlineSnapshot(`"Duplicate key found in intersection: 'foo'"`); + }); + + it('has the right type inference', () => { + const resultingSchema = schema.object({ + foo: schema.string(), + bar: schema.string(), + }); + type ResultingType = TypeOf; + + const type = schema.allOf([ + schema.object({ foo: schema.string() }), + schema.object({ bar: schema.string() }), + ]); + + // asserting the type is the expected one + const output: ResultingType = type.validate({ foo: 'hello', bar: 'dolly' }); + // required to make TS happy + expect(output).toEqual({ foo: 'hello', bar: 'dolly' }); + }); +}); diff --git a/packages/kbn-config-schema/src/types/intersection_type.ts b/packages/kbn-config-schema/src/types/intersection_type.ts new file mode 100644 index 0000000000000..766a0786103fc --- /dev/null +++ b/packages/kbn-config-schema/src/types/intersection_type.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ObjectType, Props, ObjectTypeOptions } from './object_type'; + +export type IntersectionTypeOptions = ObjectTypeOptions; + +export class IntersectionType< + RTS extends Array>, + T extends Props +> extends ObjectType { + constructor(types: RTS, options?: IntersectionTypeOptions) { + const props = types.reduce((mergedProps, type) => { + Object.entries(type.getPropSchemas() as Record).forEach(([key, value]) => { + if (mergedProps[key] !== undefined) { + throw new Error(`Duplicate key found in intersection: '${key}'`); + } + mergedProps[key as keyof T] = value; + }); + + return mergedProps; + }, {} as T); + + super(props, options); + } +} diff --git a/packages/kbn-config-schema/src/types/object_type.ts b/packages/kbn-config-schema/src/types/object_type.ts index d5193b5e0fc38..423d6030b30b0 100644 --- a/packages/kbn-config-schema/src/types/object_type.ts +++ b/packages/kbn-config-schema/src/types/object_type.ts @@ -223,6 +223,15 @@ export class ObjectType

extends Type> } } + /** + * Return the schema for this object's underlying properties + * + * @internal should only be used internal for type reflection + */ + public getPropSchemas(): P { + return this.props; + } + validateKey(key: string, value: any) { if (!this.propSchemas[key]) { throw new Error(`${key} is not a valid part of this schema`); diff --git a/packages/kbn-config-schema/src/types/type.ts b/packages/kbn-config-schema/src/types/type.ts index 652eae077d5af..3b8808dba61af 100644 --- a/packages/kbn-config-schema/src/types/type.ts +++ b/packages/kbn-config-schema/src/types/type.ts @@ -125,7 +125,11 @@ export abstract class Type { return this; } - public validate(value: any, context: Record = {}, namespace?: string): V { + /** + * Validates the provided value against this schema. + * If valid, the resulting output will be returned, otherwise an exception will be thrown. + */ + public validate(value: unknown, context: Record = {}, namespace?: string): V { const { value: validatedValue, error } = this.internalSchema.validate(value, { context, presence: 'required', diff --git a/packages/kbn-data-view-utils/index.ts b/packages/kbn-data-view-utils/index.ts index c78869a471cb0..1c881dbdacf79 100644 --- a/packages/kbn-data-view-utils/index.ts +++ b/packages/kbn-data-view-utils/index.ts @@ -7,3 +7,6 @@ */ export * from './src/constants'; + +export { createRegExpPatternFrom } from './src/utils/create_regexp_pattern_from'; +export { testPatternAgainstAllowedList } from './src/utils/test_pattern_against_allowed_list'; diff --git a/packages/kbn-data-view-utils/src/utils/create_regexp_pattern_from.test.ts b/packages/kbn-data-view-utils/src/utils/create_regexp_pattern_from.test.ts new file mode 100644 index 0000000000000..24975576582d6 --- /dev/null +++ b/packages/kbn-data-view-utils/src/utils/create_regexp_pattern_from.test.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createRegExpPatternFrom } from './create_regexp_pattern_from'; + +describe('createRegExpPatternFrom should create a regular expression starting from a string that', () => { + const regExpPattern = createRegExpPatternFrom('logs'); + + it('tests positive for single index patterns starting with the passed base pattern', () => { + expect('logs*').toMatch(regExpPattern); + expect('logs-*').toMatch(regExpPattern); + expect('logs-*-*').toMatch(regExpPattern); + expect('logs-system.syslog-*').toMatch(regExpPattern); + + expect('logss*').not.toMatch(regExpPattern); + expect('logss-*').not.toMatch(regExpPattern); + expect('metrics*').not.toMatch(regExpPattern); + expect('metrics-*').not.toMatch(regExpPattern); + }); + + it('tests positive for single index patterns containing the passed base pattern', () => { + expect('foo-logs*').toMatch(regExpPattern); + expect('foo-logs-*').toMatch(regExpPattern); + expect('foo-logs-*-*').toMatch(regExpPattern); + expect('foo-logs-system.syslog-*').toMatch(regExpPattern); + expect('.ds-kibana_sample_data_logs-2024.06.13-000001').toMatch(regExpPattern); + + expect('foo-logss*').not.toMatch(regExpPattern); + expect('foo-logss-*').not.toMatch(regExpPattern); + expect('foo-metrics*').not.toMatch(regExpPattern); + expect('foo-metrics-*').not.toMatch(regExpPattern); + }); + + it('tests positive for single index patterns with CCS prefixes', () => { + expect('cluster1:logs-*').toMatch(regExpPattern); + expect('cluster1:logs-*-*').toMatch(regExpPattern); + expect('cluster1:logs-system.syslog-*').toMatch(regExpPattern); + expect('cluster1:logs-system.syslog-default').toMatch(regExpPattern); + + expect('cluster1:logss*').not.toMatch(regExpPattern); + expect('cluster1:logss-*').not.toMatch(regExpPattern); + expect('cluster1:metrics*').not.toMatch(regExpPattern); + expect('cluster1:metrics-*').not.toMatch(regExpPattern); + }); + + it('tests positive for multiple index patterns comma-separated if all of them individually match the criteria', () => { + expect('logs-*,cluster1:logs-*').toMatch(regExpPattern); + expect('cluster1:logs-*,cluster2:logs-*').toMatch(regExpPattern); + expect('*:logs-system.syslog-*,*:logs-system.errors-*').toMatch(regExpPattern); + + expect('*:metrics-system.syslog-*,logs-system.errors-*').not.toMatch(regExpPattern); + }); + + it('tests positive for patterns with trailing commas', () => { + expect('logs-*,').toMatch(regExpPattern); + expect('cluster1:logs-*,logs-*,').toMatch(regExpPattern); + }); + + it('tests negative for patterns with spaces and unexpected commas', () => { + expect('cluster1:logs-*,clust,er2:logs-*').not.toMatch(regExpPattern); + expect('cluster1:logs-*, cluster2:logs-*').not.toMatch(regExpPattern); + }); +}); diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/data_views/utils.ts b/packages/kbn-data-view-utils/src/utils/create_regexp_pattern_from.ts similarity index 52% rename from x-pack/plugins/observability_solution/logs_explorer/common/data_views/utils.ts rename to packages/kbn-data-view-utils/src/utils/create_regexp_pattern_from.ts index 4b316ba1d5877..c240f2f893688 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/common/data_views/utils.ts +++ b/packages/kbn-data-view-utils/src/utils/create_regexp_pattern_from.ts @@ -1,15 +1,17 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ -export const buildIndexPatternRegExp = (basePatterns: string[]) => { +export const createRegExpPatternFrom = (basePatterns: string | string[]) => { + const patterns = Array.isArray(basePatterns) ? basePatterns : [basePatterns]; // Create the base patterns union with strict boundaries - const basePatternGroup = `\\b(${basePatterns.join('|')})\\b([^,\\s]+)?`; + const basePatternGroup = `[^,\\s]*(\\b|_)(${patterns.join('|')})(\\b|_)([^,\\s]*)?`; // Apply base patterns union for local and remote clusters - const localAndRemotePatternGroup = `((${basePatternGroup})|([^:,\\s]+:${basePatternGroup}))`; + const localAndRemotePatternGroup = `((${basePatternGroup})|([^:,\\s]*:${basePatternGroup}))`; // Handle trailing comma and multiple pattern concatenation return new RegExp(`^${localAndRemotePatternGroup}(,${localAndRemotePatternGroup})*(,$|$)`, 'i'); }; diff --git a/packages/kbn-data-view-utils/src/utils/test_pattern_against_allowed_list.test.ts b/packages/kbn-data-view-utils/src/utils/test_pattern_against_allowed_list.test.ts new file mode 100644 index 0000000000000..e72038771b036 --- /dev/null +++ b/packages/kbn-data-view-utils/src/utils/test_pattern_against_allowed_list.test.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { testPatternAgainstAllowedList } from './test_pattern_against_allowed_list'; + +describe('testPatternAgainstAllowedList', () => { + const allowedList = ['foo-logs-bar', /^\b(logs)\b([^,\s]*)/i]; + + it('should return true if the passed input matches any string or regexp of the passed list', () => { + expect(testPatternAgainstAllowedList(allowedList)('logs-*')).toBeTruthy(); + expect(testPatternAgainstAllowedList(allowedList)('logs-*-*')).toBeTruthy(); + expect(testPatternAgainstAllowedList(allowedList)('logs-system.syslog-*')).toBeTruthy(); + expect(testPatternAgainstAllowedList(allowedList)('foo-logs-bar')).toBeTruthy(); + + expect(testPatternAgainstAllowedList(allowedList)('logss-*')).toBeFalsy(); + expect(testPatternAgainstAllowedList(allowedList)('metrics*')).toBeFalsy(); + expect(testPatternAgainstAllowedList(allowedList)('metrics-*')).toBeFalsy(); + }); +}); diff --git a/packages/kbn-data-view-utils/src/utils/test_pattern_against_allowed_list.ts b/packages/kbn-data-view-utils/src/utils/test_pattern_against_allowed_list.ts new file mode 100644 index 0000000000000..62cbd4db9d0fe --- /dev/null +++ b/packages/kbn-data-view-utils/src/utils/test_pattern_against_allowed_list.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const testPatternAgainstAllowedList = + (allowedList: Array) => (value: string) => { + for (const allowedItem of allowedList) { + const isMatchingString = typeof allowedItem === 'string' && value === allowedItem; + const isMatchingRegExp = allowedItem instanceof RegExp && allowedItem.test(value); + + if (isMatchingString || isMatchingRegExp) { + return true; + } + } + + // If no match is found in the allowedList, return false + return false; + }; diff --git a/packages/kbn-discover-utils/index.ts b/packages/kbn-discover-utils/index.ts index 7a7fedfb5f1e3..1656a36a91da2 100644 --- a/packages/kbn-discover-utils/index.ts +++ b/packages/kbn-discover-utils/index.ts @@ -10,6 +10,7 @@ export { CONTEXT_DEFAULT_SIZE_SETTING, CONTEXT_STEP_SETTING, CONTEXT_TIE_BREAKER_FIELDS_SETTING, + DEFAULT_ALLOWED_LOGS_BASE_PATTERNS, DEFAULT_COLUMNS_SETTING, DOC_HIDE_TIME_COLUMN_SETTING, DOC_TABLE_LEGACY, @@ -30,6 +31,7 @@ export { IgnoredReason, buildDataTableRecord, buildDataTableRecordList, + createLogsContextService, fieldConstants, formatFieldValue, formatHit, @@ -43,4 +45,6 @@ export { usePager, } from './src'; +export type { LogsContextService } from './src'; + export * from './src/types'; diff --git a/packages/kbn-discover-utils/src/data_types/logs/index.ts b/packages/kbn-discover-utils/src/data_types/logs/index.ts index 21d54fd754a3e..f1147d7aa071b 100644 --- a/packages/kbn-discover-utils/src/data_types/logs/index.ts +++ b/packages/kbn-discover-utils/src/data_types/logs/index.ts @@ -8,3 +8,5 @@ export * from './types'; export * from './utils'; + +export * from './logs_context_service'; diff --git a/packages/kbn-discover-utils/src/data_types/logs/logs_context_service.ts b/packages/kbn-discover-utils/src/data_types/logs/logs_context_service.ts new file mode 100644 index 0000000000000..6eecd1beff491 --- /dev/null +++ b/packages/kbn-discover-utils/src/data_types/logs/logs_context_service.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createRegExpPatternFrom, testPatternAgainstAllowedList } from '@kbn/data-view-utils'; + +export interface LogsContextService { + isLogsIndexPattern(indexPattern: unknown): boolean; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface LogsContextServiceDeps { + // We will probably soon add uiSettings as a dependency + // to consume user configured indices +} + +export const DEFAULT_ALLOWED_LOGS_BASE_PATTERNS = [ + 'log', + 'logs', + 'logstash', + 'auditbeat', + 'filebeat', + 'winlogbeat', +]; + +export const createLogsContextService = (_deps: LogsContextServiceDeps = {}) => { + // This is initially an hard-coded set of well-known base patterns, + // we can extend this allowed list with any setting coming from uiSettings + const ALLOWED_LOGS_DATA_SOURCES = [createRegExpPatternFrom(DEFAULT_ALLOWED_LOGS_BASE_PATTERNS)]; + + const isLogsIndexPattern = (indexPattern: unknown) => { + return ( + typeof indexPattern === 'string' && + testPatternAgainstAllowedList(ALLOWED_LOGS_DATA_SOURCES)(indexPattern) + ); + }; + + return { + isLogsIndexPattern, + }; +}; diff --git a/packages/kbn-discover-utils/src/utils/build_data_record.test.ts b/packages/kbn-discover-utils/src/utils/build_data_record.test.ts index e6046d4f5977f..93b5b623ac13d 100644 --- a/packages/kbn-discover-utils/src/utils/build_data_record.test.ts +++ b/packages/kbn-discover-utils/src/utils/build_data_record.test.ts @@ -22,7 +22,10 @@ describe('Data table record utils', () => { describe('buildDataTableRecordList', () => { test('should return a list of DataTableRecord', () => { - const result = buildDataTableRecordList(esHitsMock, dataViewMock); + const result = buildDataTableRecordList({ + records: esHitsMock, + dataView: dataViewMock, + }); result.forEach((doc) => { expect(doc).toHaveProperty('id'); expect(doc).toHaveProperty('raw'); @@ -32,7 +35,9 @@ describe('Data table record utils', () => { }); test('should support processing each record', () => { - const result = buildDataTableRecordList(esHitsMock, dataViewMock, { + const result = buildDataTableRecordList({ + records: esHitsMock, + dataView: dataViewMock, processRecord: (record) => ({ ...record, id: 'custom-id' }), }); result.forEach((doc) => { diff --git a/packages/kbn-discover-utils/src/utils/build_data_record.ts b/packages/kbn-discover-utils/src/utils/build_data_record.ts index 9769201e94aa4..7c64b71cf5138 100644 --- a/packages/kbn-discover-utils/src/utils/build_data_record.ts +++ b/packages/kbn-discover-utils/src/utils/build_data_record.ts @@ -32,15 +32,19 @@ export function buildDataTableRecord( /** * Helper to build multiple DataTableRecords at once, saved a bit of testing code lines - * @param docs Array of documents returned from Elasticsearch + * @param records Array of documents returned from Elasticsearch * @param dataView this current data view */ -export function buildDataTableRecordList( - docs: EsHitRecord[], - dataView?: DataView, - { processRecord }: { processRecord?: (record: DataTableRecord) => T } = {} -): DataTableRecord[] { - return docs.map((doc) => { +export function buildDataTableRecordList({ + records, + dataView, + processRecord, +}: { + records: EsHitRecord[]; + dataView?: DataView; + processRecord?: (record: DataTableRecord) => T; +}): DataTableRecord[] { + return records.map((doc) => { const record = buildDataTableRecord(doc, dataView); return processRecord ? processRecord(record) : record; }); diff --git a/packages/kbn-discover-utils/tsconfig.json b/packages/kbn-discover-utils/tsconfig.json index 64453f8245afe..bbd35127c43fb 100644 --- a/packages/kbn-discover-utils/tsconfig.json +++ b/packages/kbn-discover-utils/tsconfig.json @@ -18,6 +18,7 @@ "kbn_references": [ "@kbn/data-service", "@kbn/data-views-plugin", + "@kbn/data-view-utils", "@kbn/es-query", "@kbn/field-formats-plugin", "@kbn/field-types", diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index c79988f4ea964..11e29fd14a35f 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -421,6 +421,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D mappingSourceFields: `${ELASTICSEARCH_DOCS}mapping-source-field.html`, mappingSourceFieldsDisable: `${ELASTICSEARCH_DOCS}mapping-source-field.html#disable-source-field`, mappingStore: `${ELASTICSEARCH_DOCS}mapping-store.html`, + mappingSubobjects: `${ELASTICSEARCH_DOCS}subobjects.html`, mappingTermVector: `${ELASTICSEARCH_DOCS}term-vector.html`, mappingTypesRemoval: `${ELASTICSEARCH_DOCS}removal-of-types.html`, migrateIndexAllocationFilters: `${ELASTICSEARCH_DOCS}migrate-index-allocation-filters.html`, @@ -434,6 +435,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D remoteClustersOnPremSetupTrustWithCert: `${ELASTICSEARCH_DOCS}remote-clusters-cert.html`, remoteClustersOnPremSetupTrustWithApiKey: `${ELASTICSEARCH_DOCS}remote-clusters-api-key.html`, remoteClustersCloudSetupTrust: `${ELASTIC_WEBSITE_URL}guide/en/cloud/current/ec-enable-ccs.html`, + rollupMigratingToDownsampling: `${ELASTICSEARCH_DOCS}rollup-migrating-to-downsampling.html`, rrf: `${ELASTICSEARCH_DOCS}rrf.html`, scriptParameters: `${ELASTICSEARCH_DOCS}modules-scripting-using.html#prefer-params`, secureCluster: `${ELASTICSEARCH_DOCS}secure-cluster.html`, @@ -515,6 +517,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D : `${ELASTICSEARCH_DOCS}index-mgmt.html`, kibanaSearchSettings: `${KIBANA_DOCS}advanced-options.html#kibana-search-settings`, discoverSettings: `${KIBANA_DOCS}advanced-options.html#kibana-discover-settings`, + rollupSettings: `${KIBANA_DOCS}advanced-options.html#kibana-rollups-settings`, visualizationSettings: `${KIBANA_DOCS}advanced-options.html#kibana-visualization-settings`, timelionSettings: `${KIBANA_DOCS}advanced-options.html#kibana-timelion-settings`, savedObjectsApiList: `${KIBANA_DOCS}saved-objects-api.html#saved-objects-api`, @@ -841,6 +844,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D datastreamsManualRollover: `${ELASTICSEARCH_DOCS}use-a-data-stream.html#manually-roll-over-a-data-stream`, datastreamsTSDS: `${ELASTICSEARCH_DOCS}tsds.html`, datastreamsTSDSMetrics: `${ELASTICSEARCH_DOCS}tsds.html#time-series-metric`, + datastreamsDownsampling: `${ELASTICSEARCH_DOCS}downsampling.html`, installElasticAgent: `${FLEET_DOCS}install-fleet-managed-elastic-agent.html`, installElasticAgentStandalone: `${FLEET_DOCS}install-standalone-elastic-agent.html`, upgradeElasticAgent: `${FLEET_DOCS}upgrade-elastic-agent.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index 0f6070c4016e3..f7cd6ba482aac 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -534,6 +534,7 @@ export interface DocLinks { datastreamsManualRollover: string; datastreamsTSDS: string; datastreamsTSDSMetrics: string; + datastreamsDownsampling: string; installElasticAgent: string; installElasticAgentStandalone: string; packageSignatures: string; diff --git a/packages/kbn-ebt-tools/src/performance_metric_events/helpers.test.ts b/packages/kbn-ebt-tools/src/performance_metric_events/helpers.test.ts index a6ad3970a6a97..de4271a474961 100644 --- a/packages/kbn-ebt-tools/src/performance_metric_events/helpers.test.ts +++ b/packages/kbn-ebt-tools/src/performance_metric_events/helpers.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { createAnalytics, type AnalyticsClient } from '@kbn/analytics-client'; +import { createAnalytics, type AnalyticsClient } from '@kbn/ebt/client'; import { loggerMock } from '@kbn/logging-mocks'; import { registerPerformanceMetricEventType, reportPerformanceMetricEvent } from './helpers'; import { METRIC_EVENT_SCHEMA } from './schema'; diff --git a/packages/kbn-ebt-tools/src/performance_metric_events/helpers.ts b/packages/kbn-ebt-tools/src/performance_metric_events/helpers.ts index f9aecd2bd7ec9..2763222c60d1f 100644 --- a/packages/kbn-ebt-tools/src/performance_metric_events/helpers.ts +++ b/packages/kbn-ebt-tools/src/performance_metric_events/helpers.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { AnalyticsClient } from '@kbn/analytics-client'; +import type { AnalyticsClient } from '@kbn/ebt/client'; import { type PerformanceMetricEvent, METRIC_EVENT_SCHEMA } from './schema'; const PERFORMANCE_METRIC_EVENT_TYPE = 'performance_metric'; diff --git a/packages/kbn-ebt-tools/src/performance_metric_events/schema.ts b/packages/kbn-ebt-tools/src/performance_metric_events/schema.ts index 15fdb4a1e9b33..b841b9b8d6f1d 100644 --- a/packages/kbn-ebt-tools/src/performance_metric_events/schema.ts +++ b/packages/kbn-ebt-tools/src/performance_metric_events/schema.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { RootSchema } from '@kbn/analytics-client'; +import type { RootSchema } from '@kbn/ebt/client'; /** * Structure of the `metric` event diff --git a/packages/kbn-ebt-tools/tsconfig.json b/packages/kbn-ebt-tools/tsconfig.json index 7dd6d0703f073..881349b3f4d9b 100644 --- a/packages/kbn-ebt-tools/tsconfig.json +++ b/packages/kbn-ebt-tools/tsconfig.json @@ -5,6 +5,9 @@ "types": ["jest", "node"] }, "include": ["**/*.ts", "**/*.tsx"], - "kbn_references": ["@kbn/analytics-client", "@kbn/logging-mocks"], + "kbn_references": [ + "@kbn/logging-mocks", + "@kbn/ebt", + ], "exclude": ["target/**/*"] } diff --git a/packages/kbn-es-errors/index.ts b/packages/kbn-es-errors/index.ts index 0d5b6007e5cc8..b5e18f6206474 100644 --- a/packages/kbn-es-errors/index.ts +++ b/packages/kbn-es-errors/index.ts @@ -7,5 +7,10 @@ */ export type { ElasticsearchErrorDetails } from './src/types'; -export { isUnauthorizedError, isResponseError } from './src/errors'; +export { + isUnauthorizedError, + isResponseError, + isRequestAbortedError, + isMaximumResponseSizeExceededError, +} from './src/errors'; export type { UnauthorizedError } from './src/errors'; diff --git a/packages/kbn-es-errors/src/errors.test.ts b/packages/kbn-es-errors/src/errors.test.ts index 59c4296d2bbb2..6a6384585f5a9 100644 --- a/packages/kbn-es-errors/src/errors.test.ts +++ b/packages/kbn-es-errors/src/errors.test.ts @@ -8,7 +8,12 @@ import { errors } from '@elastic/elasticsearch'; import type { TransportResult } from '@elastic/elasticsearch'; -import { isResponseError, isUnauthorizedError } from './errors'; +import { + isResponseError, + isUnauthorizedError, + isRequestAbortedError, + isMaximumResponseSizeExceededError, +} from './errors'; const createApiResponseError = ({ statusCode = 200, @@ -69,3 +74,36 @@ describe('isUnauthorizedError', () => { expect(isUnauthorizedError(new errors.ConfigurationError('foo'))).toBe(false); }); }); + +describe('isRequestAbortedError', () => { + it('returns `true` when the input is a `RequestAbortedError`', () => { + expect(isRequestAbortedError(new errors.RequestAbortedError('Oh no'))).toBe(true); + }); + it('returns `false` when the input is not a `RequestAbortedError`', () => { + expect( + isRequestAbortedError(new errors.ResponseError(createApiResponseError({ statusCode: 500 }))) + ).toBe(false); + }); +}); + +describe('isMaximumResponseSizeExceededError', () => { + it('returns `true` when the input is a `RequestAbortedError` with the right message', () => { + expect( + isMaximumResponseSizeExceededError( + new errors.RequestAbortedError( + `The content length (9000) is bigger than the maximum allowed buffer (42)` + ) + ) + ).toBe(true); + }); + it('returns `false` when the input is a `RequestAbortedError` without the right message', () => { + expect(isMaximumResponseSizeExceededError(new errors.RequestAbortedError('Oh no'))).toBe(false); + }); + it('returns `false` when the input is not a `RequestAbortedError`', () => { + expect( + isMaximumResponseSizeExceededError( + new errors.ResponseError(createApiResponseError({ statusCode: 500 })) + ) + ).toBe(false); + }); +}); diff --git a/packages/kbn-es-errors/src/errors.ts b/packages/kbn-es-errors/src/errors.ts index d46097eb95a2b..24e314f084b59 100644 --- a/packages/kbn-es-errors/src/errors.ts +++ b/packages/kbn-es-errors/src/errors.ts @@ -31,3 +31,17 @@ export function isResponseError(error: unknown): error is errors.ResponseError { export function isUnauthorizedError(error: unknown): error is UnauthorizedError { return isResponseError(error) && error.statusCode === 401; } + +/** + * Checks if the provided `error` is an {@link errors.RequestAbortedError | elasticsearch request aborted error} + * @public + */ +export function isRequestAbortedError(error: unknown): error is errors.RequestAbortedError { + return error instanceof errors.RequestAbortedError; +} + +export function isMaximumResponseSizeExceededError( + error: unknown +): error is errors.RequestAbortedError { + return isRequestAbortedError(error) && error.message.includes('content length'); +} diff --git a/packages/kbn-esql-ast/index.ts b/packages/kbn-esql-ast/index.ts index 4f93614ffa487..fc129c5c6fac3 100644 --- a/packages/kbn-esql-ast/index.ts +++ b/packages/kbn-esql-ast/index.ts @@ -24,6 +24,7 @@ export type { ESQLLiteral, AstProviderFn, EditorError, + ESQLAstNode, } from './src/types'; // Low level functions to parse grammar @@ -37,3 +38,5 @@ export { getParser, getLexer, ROOT_STATEMENT } from './src/antlr_facade'; export { getAstAndSyntaxErrors } from './src/ast_parser'; export { ESQLErrorListener } from './src/antlr_error_listener'; + +export { Walker, type WalkerOptions, walk } from './src/walker'; diff --git a/packages/kbn-esql-ast/src/__tests__/ast_parser.inlinecast.test.ts b/packages/kbn-esql-ast/src/__tests__/ast_parser.inlinecast.test.ts new file mode 100644 index 0000000000000..3f45f55b549da --- /dev/null +++ b/packages/kbn-esql-ast/src/__tests__/ast_parser.inlinecast.test.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getAstAndSyntaxErrors as parse } from '../ast_parser'; +import { ESQLFunction, ESQLInlineCast, ESQLSingleAstItem } from '../types'; + +describe('Inline cast (::)', () => { + describe('correctly formatted', () => { + it('can be a command argument', () => { + const text = 'FROM kibana_ecommerce_data | EVAL field::string'; + const { ast, errors } = parse(text); + + expect(errors.length).toBe(0); + expect(ast[1].args[0]).toEqual( + expect.objectContaining({ + castType: 'string', + name: 'inlineCast', + type: 'inlineCast', + value: expect.objectContaining({ + name: 'field', + type: 'column', + }), + }) + ); + }); + + it('can be a function argument', () => { + const text = 'FROM kibana_ecommerce_data | EVAL round(field::long)'; + const { ast, errors } = parse(text); + + expect(errors.length).toBe(0); + expect((ast[1].args[0] as ESQLFunction).args[0]).toEqual( + expect.objectContaining({ + castType: 'long', + name: 'inlineCast', + type: 'inlineCast', + value: expect.objectContaining({ + name: 'field', + type: 'column', + }), + }) + ); + }); + + it('can be nested', () => { + const text = 'FROM kibana_ecommerce_data | EVAL field::long::string::datetime'; + const { ast, errors } = parse(text); + + expect(errors.length).toBe(0); + let currentNode = ast[1].args[0]; + let depth = 0; + + while (depth < 3) { + expect((currentNode as ESQLSingleAstItem).type).toBe('inlineCast'); + currentNode = (currentNode as ESQLInlineCast).value; + depth++; + } + + expect((currentNode as ESQLSingleAstItem).name).toBe('field'); + }); + }); +}); diff --git a/packages/kbn-esql-ast/src/__tests__/ast_parser.metrics.test.ts b/packages/kbn-esql-ast/src/__tests__/ast_parser.metrics.test.ts index 43cf738d599aa..ec56b1d015c64 100644 --- a/packages/kbn-esql-ast/src/__tests__/ast_parser.metrics.test.ts +++ b/packages/kbn-esql-ast/src/__tests__/ast_parser.metrics.test.ts @@ -19,7 +19,7 @@ describe('METRICS', () => { { type: 'command', name: 'metrics', - indices: [ + sources: [ { type: 'source', name: 'foo', @@ -30,7 +30,7 @@ describe('METRICS', () => { ]); }); - it('can parse multiple "indices"', () => { + it('can parse multiple "sources"', () => { const text = 'METRICS foo ,\nbar\t,\t\nbaz \n'; const { ast, errors } = parse(text); @@ -39,7 +39,7 @@ describe('METRICS', () => { { type: 'command', name: 'metrics', - indices: [ + sources: [ { type: 'source', name: 'foo', @@ -69,7 +69,7 @@ describe('METRICS', () => { { type: 'command', name: 'metrics', - indices: [ + sources: [ { type: 'source', name: 'foo', @@ -99,7 +99,7 @@ describe('METRICS', () => { { type: 'command', name: 'metrics', - indices: [ + sources: [ { type: 'source', name: 'foo', diff --git a/packages/kbn-esql-ast/src/__tests__/ast_parser.params.test.ts b/packages/kbn-esql-ast/src/__tests__/ast_parser.params.test.ts new file mode 100644 index 0000000000000..f69412a053e02 --- /dev/null +++ b/packages/kbn-esql-ast/src/__tests__/ast_parser.params.test.ts @@ -0,0 +1,176 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getAstAndSyntaxErrors as parse } from '../ast_parser'; +import { Walker } from '../walker'; + +/** + * Un-named parameters are represented by a question mark "?". + */ +describe('un-named parameters', () => { + describe('correctly formatted', () => { + it('can parse a single un-named param', () => { + const query = 'ROW x = ?'; + const { ast, errors } = parse(query); + const params = Walker.params(ast); + + expect(errors.length).toBe(0); + expect(params).toMatchObject([ + { + type: 'literal', + literalType: 'param', + paramType: 'unnamed', + location: { + min: 8, + max: 8, + }, + }, + ]); + + const { min, max } = params[0].location; + + expect(query.slice(min, max + 1)).toBe('?'); + }); + }); +}); + +/** + * Positional parameters are represented by a question mark followed by a number "?1". + */ +describe('positional parameters', () => { + describe('correctly formatted', () => { + it('can parse a single positional param', () => { + const query = 'ROW x = ?1'; + const { ast, errors } = parse(query); + const params = Walker.params(ast); + + expect(errors.length).toBe(0); + expect(params).toMatchObject([ + { + type: 'literal', + literalType: 'param', + paramType: 'positional', + value: 1, + location: { + min: 8, + max: 9, + }, + }, + ]); + + const { min, max } = params[0].location; + + expect(query.slice(min, max + 1)).toBe('?1'); + }); + + it('multiple positional params', () => { + const query = 'ROW x = ?5, x2 = ?5, y = ?6, z = ?7'; + const { ast, errors } = parse(query); + const params = Walker.params(ast); + + expect(errors.length).toBe(0); + expect(params.length).toBe(4); + params.sort((a, b) => a.location.min - b.location.min); + expect(params).toMatchObject([ + { + type: 'literal', + literalType: 'param', + paramType: 'positional', + value: 5, + }, + { + type: 'literal', + literalType: 'param', + paramType: 'positional', + value: 5, + }, + { + type: 'literal', + literalType: 'param', + paramType: 'positional', + value: 6, + }, + { + type: 'literal', + literalType: 'param', + paramType: 'positional', + value: 7, + }, + ]); + }); + }); +}); + +/** + * Named parameters are represented by a question mark followed by a name "?name". + */ +describe('named parameters', () => { + describe('correctly formatted', () => { + it('can parse a single named param', () => { + const query = 'ROW x = ?theName'; + const { ast, errors } = parse(query); + const params = Walker.params(ast); + + expect(errors.length).toBe(0); + expect(params).toMatchObject([ + { + type: 'literal', + literalType: 'param', + paramType: 'named', + value: 'theName', + location: { + min: 8, + max: 15, + }, + }, + ]); + + const { min, max } = params[0].location; + + expect(query.slice(min, max + 1)).toBe('?theName'); + }); + }); + + it('multiple named params', () => { + const query = 'ROW x = ?a, y = ?b, z = ?c'; + const { ast, errors } = parse(query); + const params = Walker.params(ast); + + expect(errors.length).toBe(0); + expect(params.length).toBe(3); + params.sort((a, b) => a.location.min - b.location.min); + expect(params).toMatchObject([ + { + type: 'literal', + literalType: 'param', + paramType: 'named', + value: 'a', + }, + { + type: 'literal', + literalType: 'param', + paramType: 'named', + value: 'b', + }, + { + type: 'literal', + literalType: 'param', + paramType: 'named', + value: 'c', + }, + ]); + }); + + describe('when incorrectly formatted, returns errors', () => { + it('two question marks "?" in a row', () => { + const text = 'ROW x = ??'; + const { errors } = parse(text); + expect(errors.length > 0).toBe(true); + }); + }); +}); diff --git a/packages/kbn-esql-ast/src/antlr/esql_lexer.g4 b/packages/kbn-esql-ast/src/antlr/esql_lexer.g4 index 39cb43b84d356..d7a4b88e6d5c5 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_lexer.g4 +++ b/packages/kbn-esql-ast/src/antlr/esql_lexer.g4 @@ -166,6 +166,11 @@ ASTERISK : '*'; SLASH : '/'; PERCENT : '%'; +NAMED_OR_POSITIONAL_PARAM + : PARAM LETTER UNQUOTED_ID_BODY* + | PARAM DIGIT+ + ; + // Brackets are funny. We can happen upon a CLOSING_BRACKET in two ways - one // way is to start in an explain command which then shifts us to expression // mode. Thus, the two popModes on CLOSING_BRACKET. The other way could as diff --git a/packages/kbn-esql-ast/src/antlr/esql_lexer.interp b/packages/kbn-esql-ast/src/antlr/esql_lexer.interp index df8215d1a0461..911e1371fd129 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_lexer.interp +++ b/packages/kbn-esql-ast/src/antlr/esql_lexer.interp @@ -68,6 +68,7 @@ null '/' '%' null +null ']' null null @@ -193,6 +194,7 @@ MINUS ASTERISK SLASH PERCENT +NAMED_OR_POSITIONAL_PARAM OPENING_BRACKET CLOSING_BRACKET UNQUOTED_IDENTIFIER @@ -331,6 +333,7 @@ MINUS ASTERISK SLASH PERCENT +NAMED_OR_POSITIONAL_PARAM OPENING_BRACKET CLOSING_BRACKET UNQUOTED_IDENTIFIER @@ -464,4 +467,4 @@ METRICS_MODE CLOSING_METRICS_MODE atn: -[4, 0, 123, 1404, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 2, 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, 2, 94, 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, 99, 7, 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, 2, 104, 7, 104, 2, 105, 7, 105, 2, 106, 7, 106, 2, 107, 7, 107, 2, 108, 7, 108, 2, 109, 7, 109, 2, 110, 7, 110, 2, 111, 7, 111, 2, 112, 7, 112, 2, 113, 7, 113, 2, 114, 7, 114, 2, 115, 7, 115, 2, 116, 7, 116, 2, 117, 7, 117, 2, 118, 7, 118, 2, 119, 7, 119, 2, 120, 7, 120, 2, 121, 7, 121, 2, 122, 7, 122, 2, 123, 7, 123, 2, 124, 7, 124, 2, 125, 7, 125, 2, 126, 7, 126, 2, 127, 7, 127, 2, 128, 7, 128, 2, 129, 7, 129, 2, 130, 7, 130, 2, 131, 7, 131, 2, 132, 7, 132, 2, 133, 7, 133, 2, 134, 7, 134, 2, 135, 7, 135, 2, 136, 7, 136, 2, 137, 7, 137, 2, 138, 7, 138, 2, 139, 7, 139, 2, 140, 7, 140, 2, 141, 7, 141, 2, 142, 7, 142, 2, 143, 7, 143, 2, 144, 7, 144, 2, 145, 7, 145, 2, 146, 7, 146, 2, 147, 7, 147, 2, 148, 7, 148, 2, 149, 7, 149, 2, 150, 7, 150, 2, 151, 7, 151, 2, 152, 7, 152, 2, 153, 7, 153, 2, 154, 7, 154, 2, 155, 7, 155, 2, 156, 7, 156, 2, 157, 7, 157, 2, 158, 7, 158, 2, 159, 7, 159, 2, 160, 7, 160, 2, 161, 7, 161, 2, 162, 7, 162, 2, 163, 7, 163, 2, 164, 7, 164, 2, 165, 7, 165, 2, 166, 7, 166, 2, 167, 7, 167, 2, 168, 7, 168, 2, 169, 7, 169, 2, 170, 7, 170, 2, 171, 7, 171, 2, 172, 7, 172, 2, 173, 7, 173, 2, 174, 7, 174, 2, 175, 7, 175, 2, 176, 7, 176, 2, 177, 7, 177, 2, 178, 7, 178, 2, 179, 7, 179, 2, 180, 7, 180, 2, 181, 7, 181, 2, 182, 7, 182, 2, 183, 7, 183, 2, 184, 7, 184, 2, 185, 7, 185, 2, 186, 7, 186, 2, 187, 7, 187, 2, 188, 7, 188, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 4, 20, 565, 8, 20, 11, 20, 12, 20, 566, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 5, 21, 575, 8, 21, 10, 21, 12, 21, 578, 9, 21, 1, 21, 3, 21, 581, 8, 21, 1, 21, 3, 21, 584, 8, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 5, 22, 593, 8, 22, 10, 22, 12, 22, 596, 9, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 4, 23, 604, 8, 23, 11, 23, 12, 23, 605, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 3, 24, 613, 8, 24, 1, 25, 4, 25, 616, 8, 25, 11, 25, 12, 25, 617, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 36, 1, 36, 3, 36, 657, 8, 36, 1, 36, 4, 36, 660, 8, 36, 11, 36, 12, 36, 661, 1, 37, 1, 37, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 3, 39, 671, 8, 39, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 3, 41, 678, 8, 41, 1, 42, 1, 42, 1, 42, 5, 42, 683, 8, 42, 10, 42, 12, 42, 686, 9, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 5, 42, 694, 8, 42, 10, 42, 12, 42, 697, 9, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 3, 42, 704, 8, 42, 1, 42, 3, 42, 707, 8, 42, 3, 42, 709, 8, 42, 1, 43, 4, 43, 712, 8, 43, 11, 43, 12, 43, 713, 1, 44, 4, 44, 717, 8, 44, 11, 44, 12, 44, 718, 1, 44, 1, 44, 5, 44, 723, 8, 44, 10, 44, 12, 44, 726, 9, 44, 1, 44, 1, 44, 4, 44, 730, 8, 44, 11, 44, 12, 44, 731, 1, 44, 4, 44, 735, 8, 44, 11, 44, 12, 44, 736, 1, 44, 1, 44, 5, 44, 741, 8, 44, 10, 44, 12, 44, 744, 9, 44, 3, 44, 746, 8, 44, 1, 44, 1, 44, 1, 44, 1, 44, 4, 44, 752, 8, 44, 11, 44, 12, 44, 753, 1, 44, 1, 44, 3, 44, 758, 8, 44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 72, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 1, 80, 1, 80, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 82, 1, 82, 5, 82, 889, 8, 82, 10, 82, 12, 82, 892, 9, 82, 1, 82, 1, 82, 3, 82, 896, 8, 82, 1, 82, 4, 82, 899, 8, 82, 11, 82, 12, 82, 900, 3, 82, 903, 8, 82, 1, 83, 1, 83, 4, 83, 907, 8, 83, 11, 83, 12, 83, 908, 1, 83, 1, 83, 1, 84, 1, 84, 1, 85, 1, 85, 1, 85, 1, 85, 1, 86, 1, 86, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 1, 87, 1, 88, 1, 88, 1, 88, 1, 88, 1, 88, 1, 89, 1, 89, 1, 89, 1, 89, 1, 90, 1, 90, 1, 90, 1, 90, 1, 91, 1, 91, 1, 91, 1, 91, 1, 92, 1, 92, 1, 92, 1, 92, 1, 93, 1, 93, 1, 93, 1, 93, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 94, 1, 95, 1, 95, 1, 95, 1, 95, 1, 96, 1, 96, 1, 96, 1, 96, 1, 97, 1, 97, 1, 97, 1, 97, 1, 98, 1, 98, 1, 98, 1, 98, 1, 99, 1, 99, 1, 99, 1, 99, 1, 99, 1, 100, 1, 100, 1, 100, 1, 100, 1, 101, 1, 101, 1, 101, 1, 101, 1, 102, 1, 102, 1, 102, 1, 102, 3, 102, 994, 8, 102, 1, 103, 1, 103, 3, 103, 998, 8, 103, 1, 103, 5, 103, 1001, 8, 103, 10, 103, 12, 103, 1004, 9, 103, 1, 103, 1, 103, 3, 103, 1008, 8, 103, 1, 103, 4, 103, 1011, 8, 103, 11, 103, 12, 103, 1012, 3, 103, 1015, 8, 103, 1, 104, 1, 104, 4, 104, 1019, 8, 104, 11, 104, 12, 104, 1020, 1, 105, 1, 105, 1, 105, 1, 105, 1, 106, 1, 106, 1, 106, 1, 106, 1, 107, 1, 107, 1, 107, 1, 107, 1, 108, 1, 108, 1, 108, 1, 108, 1, 108, 1, 109, 1, 109, 1, 109, 1, 109, 1, 110, 1, 110, 1, 110, 1, 110, 1, 111, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 1, 113, 1, 113, 1, 113, 1, 113, 1, 114, 1, 114, 1, 114, 1, 114, 1, 115, 1, 115, 1, 115, 1, 115, 1, 116, 1, 116, 1, 116, 1, 116, 1, 117, 1, 117, 1, 117, 1, 117, 1, 117, 1, 118, 1, 118, 1, 118, 1, 118, 1, 118, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 121, 1, 121, 1, 122, 4, 122, 1096, 8, 122, 11, 122, 12, 122, 1097, 1, 122, 1, 122, 3, 122, 1102, 8, 122, 1, 122, 4, 122, 1105, 8, 122, 11, 122, 12, 122, 1106, 1, 123, 1, 123, 1, 123, 1, 123, 1, 124, 1, 124, 1, 124, 1, 124, 1, 125, 1, 125, 1, 125, 1, 125, 1, 126, 1, 126, 1, 126, 1, 126, 1, 127, 1, 127, 1, 127, 1, 127, 1, 128, 1, 128, 1, 128, 1, 128, 1, 128, 1, 128, 1, 129, 1, 129, 1, 129, 1, 129, 1, 130, 1, 130, 1, 130, 1, 130, 1, 131, 1, 131, 1, 131, 1, 131, 1, 132, 1, 132, 1, 132, 1, 132, 1, 133, 1, 133, 1, 133, 1, 133, 1, 134, 1, 134, 1, 134, 1, 134, 1, 135, 1, 135, 1, 135, 1, 135, 1, 136, 1, 136, 1, 136, 1, 136, 1, 137, 1, 137, 1, 137, 1, 137, 1, 138, 1, 138, 1, 138, 1, 138, 1, 138, 1, 139, 1, 139, 1, 139, 1, 139, 1, 140, 1, 140, 1, 140, 1, 140, 1, 141, 1, 141, 1, 141, 1, 141, 1, 141, 1, 142, 1, 142, 1, 142, 1, 142, 1, 143, 1, 143, 1, 143, 1, 143, 1, 144, 1, 144, 1, 144, 1, 144, 1, 145, 1, 145, 1, 145, 1, 145, 1, 146, 1, 146, 1, 146, 1, 146, 1, 146, 1, 146, 1, 147, 1, 147, 1, 147, 1, 147, 1, 148, 1, 148, 1, 148, 1, 148, 1, 149, 1, 149, 1, 149, 1, 149, 1, 150, 1, 150, 1, 150, 1, 150, 1, 151, 1, 151, 1, 151, 1, 151, 1, 152, 1, 152, 1, 152, 1, 152, 1, 153, 1, 153, 1, 153, 1, 153, 1, 153, 1, 154, 1, 154, 1, 154, 1, 154, 1, 155, 1, 155, 1, 155, 1, 155, 1, 156, 1, 156, 1, 156, 1, 156, 1, 157, 1, 157, 1, 157, 1, 157, 1, 158, 1, 158, 1, 158, 1, 158, 1, 159, 1, 159, 1, 159, 1, 159, 1, 160, 1, 160, 1, 160, 1, 160, 1, 160, 1, 161, 1, 161, 1, 161, 1, 161, 1, 161, 1, 162, 1, 162, 1, 162, 1, 162, 1, 163, 1, 163, 1, 163, 1, 163, 1, 164, 1, 164, 1, 164, 1, 164, 1, 165, 1, 165, 1, 165, 1, 165, 1, 165, 1, 166, 1, 166, 1, 166, 1, 166, 1, 166, 1, 166, 1, 166, 1, 166, 1, 166, 1, 166, 1, 167, 1, 167, 1, 167, 1, 167, 1, 168, 1, 168, 1, 168, 1, 168, 1, 169, 1, 169, 1, 169, 1, 169, 1, 170, 1, 170, 1, 170, 1, 170, 1, 170, 1, 171, 1, 171, 1, 172, 1, 172, 1, 172, 1, 172, 1, 172, 4, 172, 1325, 8, 172, 11, 172, 12, 172, 1326, 1, 173, 1, 173, 1, 173, 1, 173, 1, 174, 1, 174, 1, 174, 1, 174, 1, 175, 1, 175, 1, 175, 1, 175, 1, 176, 1, 176, 1, 176, 1, 176, 1, 176, 1, 177, 1, 177, 1, 177, 1, 177, 1, 177, 1, 177, 1, 178, 1, 178, 1, 178, 1, 178, 1, 179, 1, 179, 1, 179, 1, 179, 1, 180, 1, 180, 1, 180, 1, 180, 1, 181, 1, 181, 1, 181, 1, 181, 1, 181, 1, 181, 1, 182, 1, 182, 1, 182, 1, 182, 1, 183, 1, 183, 1, 183, 1, 183, 1, 184, 1, 184, 1, 184, 1, 184, 1, 185, 1, 185, 1, 185, 1, 185, 1, 185, 1, 185, 1, 186, 1, 186, 1, 186, 1, 186, 1, 186, 1, 186, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 188, 1, 188, 1, 188, 1, 188, 1, 188, 2, 594, 695, 0, 189, 16, 1, 18, 2, 20, 3, 22, 4, 24, 5, 26, 6, 28, 7, 30, 8, 32, 9, 34, 10, 36, 11, 38, 12, 40, 13, 42, 14, 44, 15, 46, 16, 48, 17, 50, 18, 52, 19, 54, 20, 56, 21, 58, 22, 60, 23, 62, 24, 64, 0, 66, 25, 68, 0, 70, 0, 72, 26, 74, 27, 76, 28, 78, 29, 80, 0, 82, 0, 84, 0, 86, 0, 88, 0, 90, 0, 92, 0, 94, 0, 96, 0, 98, 0, 100, 30, 102, 31, 104, 32, 106, 33, 108, 34, 110, 35, 112, 36, 114, 37, 116, 38, 118, 39, 120, 40, 122, 41, 124, 42, 126, 43, 128, 44, 130, 45, 132, 46, 134, 47, 136, 48, 138, 49, 140, 50, 142, 51, 144, 52, 146, 53, 148, 54, 150, 55, 152, 56, 154, 57, 156, 58, 158, 59, 160, 60, 162, 61, 164, 62, 166, 63, 168, 64, 170, 65, 172, 66, 174, 67, 176, 68, 178, 69, 180, 70, 182, 0, 184, 71, 186, 72, 188, 73, 190, 74, 192, 0, 194, 0, 196, 0, 198, 0, 200, 0, 202, 0, 204, 75, 206, 0, 208, 76, 210, 77, 212, 78, 214, 0, 216, 0, 218, 0, 220, 0, 222, 0, 224, 79, 226, 80, 228, 81, 230, 82, 232, 0, 234, 0, 236, 0, 238, 0, 240, 83, 242, 0, 244, 84, 246, 85, 248, 86, 250, 0, 252, 0, 254, 87, 256, 88, 258, 0, 260, 89, 262, 0, 264, 0, 266, 90, 268, 91, 270, 92, 272, 0, 274, 0, 276, 0, 278, 0, 280, 0, 282, 0, 284, 0, 286, 93, 288, 94, 290, 95, 292, 0, 294, 0, 296, 0, 298, 0, 300, 0, 302, 96, 304, 97, 306, 98, 308, 0, 310, 0, 312, 0, 314, 0, 316, 99, 318, 100, 320, 101, 322, 0, 324, 0, 326, 0, 328, 0, 330, 102, 332, 103, 334, 104, 336, 0, 338, 105, 340, 106, 342, 107, 344, 108, 346, 0, 348, 109, 350, 110, 352, 111, 354, 112, 356, 0, 358, 113, 360, 114, 362, 115, 364, 116, 366, 117, 368, 0, 370, 0, 372, 118, 374, 119, 376, 120, 378, 0, 380, 121, 382, 122, 384, 123, 386, 0, 388, 0, 390, 0, 392, 0, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 35, 2, 0, 68, 68, 100, 100, 2, 0, 73, 73, 105, 105, 2, 0, 83, 83, 115, 115, 2, 0, 69, 69, 101, 101, 2, 0, 67, 67, 99, 99, 2, 0, 84, 84, 116, 116, 2, 0, 82, 82, 114, 114, 2, 0, 79, 79, 111, 111, 2, 0, 80, 80, 112, 112, 2, 0, 78, 78, 110, 110, 2, 0, 72, 72, 104, 104, 2, 0, 86, 86, 118, 118, 2, 0, 65, 65, 97, 97, 2, 0, 76, 76, 108, 108, 2, 0, 88, 88, 120, 120, 2, 0, 70, 70, 102, 102, 2, 0, 77, 77, 109, 109, 2, 0, 71, 71, 103, 103, 2, 0, 75, 75, 107, 107, 2, 0, 85, 85, 117, 117, 2, 0, 87, 87, 119, 119, 6, 0, 9, 10, 13, 13, 32, 32, 47, 47, 91, 91, 93, 93, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 10, 0, 9, 10, 13, 13, 32, 32, 44, 44, 47, 47, 61, 61, 91, 91, 93, 93, 96, 96, 124, 124, 2, 0, 42, 42, 47, 47, 1, 0, 48, 57, 2, 0, 65, 90, 97, 122, 8, 0, 34, 34, 78, 78, 82, 82, 84, 84, 92, 92, 110, 110, 114, 114, 116, 116, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 2, 0, 43, 43, 45, 45, 1, 0, 96, 96, 2, 0, 66, 66, 98, 98, 2, 0, 89, 89, 121, 121, 11, 0, 9, 10, 13, 13, 32, 32, 34, 35, 44, 44, 47, 47, 58, 58, 60, 60, 62, 63, 92, 92, 124, 124, 1427, 0, 16, 1, 0, 0, 0, 0, 18, 1, 0, 0, 0, 0, 20, 1, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 24, 1, 0, 0, 0, 0, 26, 1, 0, 0, 0, 0, 28, 1, 0, 0, 0, 0, 30, 1, 0, 0, 0, 0, 32, 1, 0, 0, 0, 0, 34, 1, 0, 0, 0, 0, 36, 1, 0, 0, 0, 0, 38, 1, 0, 0, 0, 0, 40, 1, 0, 0, 0, 0, 42, 1, 0, 0, 0, 0, 44, 1, 0, 0, 0, 0, 46, 1, 0, 0, 0, 0, 48, 1, 0, 0, 0, 0, 50, 1, 0, 0, 0, 0, 52, 1, 0, 0, 0, 0, 54, 1, 0, 0, 0, 0, 56, 1, 0, 0, 0, 0, 58, 1, 0, 0, 0, 0, 60, 1, 0, 0, 0, 0, 62, 1, 0, 0, 0, 0, 66, 1, 0, 0, 0, 1, 68, 1, 0, 0, 0, 1, 70, 1, 0, 0, 0, 1, 72, 1, 0, 0, 0, 1, 74, 1, 0, 0, 0, 1, 76, 1, 0, 0, 0, 2, 78, 1, 0, 0, 0, 2, 100, 1, 0, 0, 0, 2, 102, 1, 0, 0, 0, 2, 104, 1, 0, 0, 0, 2, 106, 1, 0, 0, 0, 2, 108, 1, 0, 0, 0, 2, 110, 1, 0, 0, 0, 2, 112, 1, 0, 0, 0, 2, 114, 1, 0, 0, 0, 2, 116, 1, 0, 0, 0, 2, 118, 1, 0, 0, 0, 2, 120, 1, 0, 0, 0, 2, 122, 1, 0, 0, 0, 2, 124, 1, 0, 0, 0, 2, 126, 1, 0, 0, 0, 2, 128, 1, 0, 0, 0, 2, 130, 1, 0, 0, 0, 2, 132, 1, 0, 0, 0, 2, 134, 1, 0, 0, 0, 2, 136, 1, 0, 0, 0, 2, 138, 1, 0, 0, 0, 2, 140, 1, 0, 0, 0, 2, 142, 1, 0, 0, 0, 2, 144, 1, 0, 0, 0, 2, 146, 1, 0, 0, 0, 2, 148, 1, 0, 0, 0, 2, 150, 1, 0, 0, 0, 2, 152, 1, 0, 0, 0, 2, 154, 1, 0, 0, 0, 2, 156, 1, 0, 0, 0, 2, 158, 1, 0, 0, 0, 2, 160, 1, 0, 0, 0, 2, 162, 1, 0, 0, 0, 2, 164, 1, 0, 0, 0, 2, 166, 1, 0, 0, 0, 2, 168, 1, 0, 0, 0, 2, 170, 1, 0, 0, 0, 2, 172, 1, 0, 0, 0, 2, 174, 1, 0, 0, 0, 2, 176, 1, 0, 0, 0, 2, 178, 1, 0, 0, 0, 2, 180, 1, 0, 0, 0, 2, 184, 1, 0, 0, 0, 2, 186, 1, 0, 0, 0, 2, 188, 1, 0, 0, 0, 2, 190, 1, 0, 0, 0, 3, 192, 1, 0, 0, 0, 3, 194, 1, 0, 0, 0, 3, 196, 1, 0, 0, 0, 3, 198, 1, 0, 0, 0, 3, 200, 1, 0, 0, 0, 3, 202, 1, 0, 0, 0, 3, 204, 1, 0, 0, 0, 3, 206, 1, 0, 0, 0, 3, 208, 1, 0, 0, 0, 3, 210, 1, 0, 0, 0, 3, 212, 1, 0, 0, 0, 4, 214, 1, 0, 0, 0, 4, 216, 1, 0, 0, 0, 4, 218, 1, 0, 0, 0, 4, 224, 1, 0, 0, 0, 4, 226, 1, 0, 0, 0, 4, 228, 1, 0, 0, 0, 4, 230, 1, 0, 0, 0, 5, 232, 1, 0, 0, 0, 5, 234, 1, 0, 0, 0, 5, 236, 1, 0, 0, 0, 5, 238, 1, 0, 0, 0, 5, 240, 1, 0, 0, 0, 5, 242, 1, 0, 0, 0, 5, 244, 1, 0, 0, 0, 5, 246, 1, 0, 0, 0, 5, 248, 1, 0, 0, 0, 6, 250, 1, 0, 0, 0, 6, 252, 1, 0, 0, 0, 6, 254, 1, 0, 0, 0, 6, 256, 1, 0, 0, 0, 6, 260, 1, 0, 0, 0, 6, 262, 1, 0, 0, 0, 6, 264, 1, 0, 0, 0, 6, 266, 1, 0, 0, 0, 6, 268, 1, 0, 0, 0, 6, 270, 1, 0, 0, 0, 7, 272, 1, 0, 0, 0, 7, 274, 1, 0, 0, 0, 7, 276, 1, 0, 0, 0, 7, 278, 1, 0, 0, 0, 7, 280, 1, 0, 0, 0, 7, 282, 1, 0, 0, 0, 7, 284, 1, 0, 0, 0, 7, 286, 1, 0, 0, 0, 7, 288, 1, 0, 0, 0, 7, 290, 1, 0, 0, 0, 8, 292, 1, 0, 0, 0, 8, 294, 1, 0, 0, 0, 8, 296, 1, 0, 0, 0, 8, 298, 1, 0, 0, 0, 8, 300, 1, 0, 0, 0, 8, 302, 1, 0, 0, 0, 8, 304, 1, 0, 0, 0, 8, 306, 1, 0, 0, 0, 9, 308, 1, 0, 0, 0, 9, 310, 1, 0, 0, 0, 9, 312, 1, 0, 0, 0, 9, 314, 1, 0, 0, 0, 9, 316, 1, 0, 0, 0, 9, 318, 1, 0, 0, 0, 9, 320, 1, 0, 0, 0, 10, 322, 1, 0, 0, 0, 10, 324, 1, 0, 0, 0, 10, 326, 1, 0, 0, 0, 10, 328, 1, 0, 0, 0, 10, 330, 1, 0, 0, 0, 10, 332, 1, 0, 0, 0, 10, 334, 1, 0, 0, 0, 11, 336, 1, 0, 0, 0, 11, 338, 1, 0, 0, 0, 11, 340, 1, 0, 0, 0, 11, 342, 1, 0, 0, 0, 11, 344, 1, 0, 0, 0, 12, 346, 1, 0, 0, 0, 12, 348, 1, 0, 0, 0, 12, 350, 1, 0, 0, 0, 12, 352, 1, 0, 0, 0, 12, 354, 1, 0, 0, 0, 13, 356, 1, 0, 0, 0, 13, 358, 1, 0, 0, 0, 13, 360, 1, 0, 0, 0, 13, 362, 1, 0, 0, 0, 13, 364, 1, 0, 0, 0, 13, 366, 1, 0, 0, 0, 14, 368, 1, 0, 0, 0, 14, 370, 1, 0, 0, 0, 14, 372, 1, 0, 0, 0, 14, 374, 1, 0, 0, 0, 14, 376, 1, 0, 0, 0, 15, 378, 1, 0, 0, 0, 15, 380, 1, 0, 0, 0, 15, 382, 1, 0, 0, 0, 15, 384, 1, 0, 0, 0, 15, 386, 1, 0, 0, 0, 15, 388, 1, 0, 0, 0, 15, 390, 1, 0, 0, 0, 15, 392, 1, 0, 0, 0, 16, 394, 1, 0, 0, 0, 18, 404, 1, 0, 0, 0, 20, 411, 1, 0, 0, 0, 22, 420, 1, 0, 0, 0, 24, 427, 1, 0, 0, 0, 26, 437, 1, 0, 0, 0, 28, 444, 1, 0, 0, 0, 30, 451, 1, 0, 0, 0, 32, 465, 1, 0, 0, 0, 34, 472, 1, 0, 0, 0, 36, 480, 1, 0, 0, 0, 38, 489, 1, 0, 0, 0, 40, 496, 1, 0, 0, 0, 42, 506, 1, 0, 0, 0, 44, 518, 1, 0, 0, 0, 46, 527, 1, 0, 0, 0, 48, 533, 1, 0, 0, 0, 50, 540, 1, 0, 0, 0, 52, 547, 1, 0, 0, 0, 54, 555, 1, 0, 0, 0, 56, 564, 1, 0, 0, 0, 58, 570, 1, 0, 0, 0, 60, 587, 1, 0, 0, 0, 62, 603, 1, 0, 0, 0, 64, 612, 1, 0, 0, 0, 66, 615, 1, 0, 0, 0, 68, 619, 1, 0, 0, 0, 70, 624, 1, 0, 0, 0, 72, 629, 1, 0, 0, 0, 74, 633, 1, 0, 0, 0, 76, 637, 1, 0, 0, 0, 78, 641, 1, 0, 0, 0, 80, 645, 1, 0, 0, 0, 82, 647, 1, 0, 0, 0, 84, 649, 1, 0, 0, 0, 86, 652, 1, 0, 0, 0, 88, 654, 1, 0, 0, 0, 90, 663, 1, 0, 0, 0, 92, 665, 1, 0, 0, 0, 94, 670, 1, 0, 0, 0, 96, 672, 1, 0, 0, 0, 98, 677, 1, 0, 0, 0, 100, 708, 1, 0, 0, 0, 102, 711, 1, 0, 0, 0, 104, 757, 1, 0, 0, 0, 106, 759, 1, 0, 0, 0, 108, 762, 1, 0, 0, 0, 110, 766, 1, 0, 0, 0, 112, 770, 1, 0, 0, 0, 114, 772, 1, 0, 0, 0, 116, 775, 1, 0, 0, 0, 118, 777, 1, 0, 0, 0, 120, 782, 1, 0, 0, 0, 122, 784, 1, 0, 0, 0, 124, 790, 1, 0, 0, 0, 126, 796, 1, 0, 0, 0, 128, 801, 1, 0, 0, 0, 130, 803, 1, 0, 0, 0, 132, 806, 1, 0, 0, 0, 134, 809, 1, 0, 0, 0, 136, 814, 1, 0, 0, 0, 138, 818, 1, 0, 0, 0, 140, 823, 1, 0, 0, 0, 142, 829, 1, 0, 0, 0, 144, 832, 1, 0, 0, 0, 146, 834, 1, 0, 0, 0, 148, 840, 1, 0, 0, 0, 150, 842, 1, 0, 0, 0, 152, 847, 1, 0, 0, 0, 154, 850, 1, 0, 0, 0, 156, 853, 1, 0, 0, 0, 158, 856, 1, 0, 0, 0, 160, 858, 1, 0, 0, 0, 162, 861, 1, 0, 0, 0, 164, 863, 1, 0, 0, 0, 166, 866, 1, 0, 0, 0, 168, 868, 1, 0, 0, 0, 170, 870, 1, 0, 0, 0, 172, 872, 1, 0, 0, 0, 174, 874, 1, 0, 0, 0, 176, 876, 1, 0, 0, 0, 178, 881, 1, 0, 0, 0, 180, 902, 1, 0, 0, 0, 182, 904, 1, 0, 0, 0, 184, 912, 1, 0, 0, 0, 186, 914, 1, 0, 0, 0, 188, 918, 1, 0, 0, 0, 190, 922, 1, 0, 0, 0, 192, 926, 1, 0, 0, 0, 194, 931, 1, 0, 0, 0, 196, 935, 1, 0, 0, 0, 198, 939, 1, 0, 0, 0, 200, 943, 1, 0, 0, 0, 202, 947, 1, 0, 0, 0, 204, 951, 1, 0, 0, 0, 206, 960, 1, 0, 0, 0, 208, 964, 1, 0, 0, 0, 210, 968, 1, 0, 0, 0, 212, 972, 1, 0, 0, 0, 214, 976, 1, 0, 0, 0, 216, 981, 1, 0, 0, 0, 218, 985, 1, 0, 0, 0, 220, 993, 1, 0, 0, 0, 222, 1014, 1, 0, 0, 0, 224, 1018, 1, 0, 0, 0, 226, 1022, 1, 0, 0, 0, 228, 1026, 1, 0, 0, 0, 230, 1030, 1, 0, 0, 0, 232, 1034, 1, 0, 0, 0, 234, 1039, 1, 0, 0, 0, 236, 1043, 1, 0, 0, 0, 238, 1047, 1, 0, 0, 0, 240, 1051, 1, 0, 0, 0, 242, 1054, 1, 0, 0, 0, 244, 1058, 1, 0, 0, 0, 246, 1062, 1, 0, 0, 0, 248, 1066, 1, 0, 0, 0, 250, 1070, 1, 0, 0, 0, 252, 1075, 1, 0, 0, 0, 254, 1080, 1, 0, 0, 0, 256, 1085, 1, 0, 0, 0, 258, 1092, 1, 0, 0, 0, 260, 1101, 1, 0, 0, 0, 262, 1108, 1, 0, 0, 0, 264, 1112, 1, 0, 0, 0, 266, 1116, 1, 0, 0, 0, 268, 1120, 1, 0, 0, 0, 270, 1124, 1, 0, 0, 0, 272, 1128, 1, 0, 0, 0, 274, 1134, 1, 0, 0, 0, 276, 1138, 1, 0, 0, 0, 278, 1142, 1, 0, 0, 0, 280, 1146, 1, 0, 0, 0, 282, 1150, 1, 0, 0, 0, 284, 1154, 1, 0, 0, 0, 286, 1158, 1, 0, 0, 0, 288, 1162, 1, 0, 0, 0, 290, 1166, 1, 0, 0, 0, 292, 1170, 1, 0, 0, 0, 294, 1175, 1, 0, 0, 0, 296, 1179, 1, 0, 0, 0, 298, 1183, 1, 0, 0, 0, 300, 1188, 1, 0, 0, 0, 302, 1192, 1, 0, 0, 0, 304, 1196, 1, 0, 0, 0, 306, 1200, 1, 0, 0, 0, 308, 1204, 1, 0, 0, 0, 310, 1210, 1, 0, 0, 0, 312, 1214, 1, 0, 0, 0, 314, 1218, 1, 0, 0, 0, 316, 1222, 1, 0, 0, 0, 318, 1226, 1, 0, 0, 0, 320, 1230, 1, 0, 0, 0, 322, 1234, 1, 0, 0, 0, 324, 1239, 1, 0, 0, 0, 326, 1243, 1, 0, 0, 0, 328, 1247, 1, 0, 0, 0, 330, 1251, 1, 0, 0, 0, 332, 1255, 1, 0, 0, 0, 334, 1259, 1, 0, 0, 0, 336, 1263, 1, 0, 0, 0, 338, 1268, 1, 0, 0, 0, 340, 1273, 1, 0, 0, 0, 342, 1277, 1, 0, 0, 0, 344, 1281, 1, 0, 0, 0, 346, 1285, 1, 0, 0, 0, 348, 1290, 1, 0, 0, 0, 350, 1300, 1, 0, 0, 0, 352, 1304, 1, 0, 0, 0, 354, 1308, 1, 0, 0, 0, 356, 1312, 1, 0, 0, 0, 358, 1317, 1, 0, 0, 0, 360, 1324, 1, 0, 0, 0, 362, 1328, 1, 0, 0, 0, 364, 1332, 1, 0, 0, 0, 366, 1336, 1, 0, 0, 0, 368, 1340, 1, 0, 0, 0, 370, 1345, 1, 0, 0, 0, 372, 1351, 1, 0, 0, 0, 374, 1355, 1, 0, 0, 0, 376, 1359, 1, 0, 0, 0, 378, 1363, 1, 0, 0, 0, 380, 1369, 1, 0, 0, 0, 382, 1373, 1, 0, 0, 0, 384, 1377, 1, 0, 0, 0, 386, 1381, 1, 0, 0, 0, 388, 1387, 1, 0, 0, 0, 390, 1393, 1, 0, 0, 0, 392, 1399, 1, 0, 0, 0, 394, 395, 7, 0, 0, 0, 395, 396, 7, 1, 0, 0, 396, 397, 7, 2, 0, 0, 397, 398, 7, 2, 0, 0, 398, 399, 7, 3, 0, 0, 399, 400, 7, 4, 0, 0, 400, 401, 7, 5, 0, 0, 401, 402, 1, 0, 0, 0, 402, 403, 6, 0, 0, 0, 403, 17, 1, 0, 0, 0, 404, 405, 7, 0, 0, 0, 405, 406, 7, 6, 0, 0, 406, 407, 7, 7, 0, 0, 407, 408, 7, 8, 0, 0, 408, 409, 1, 0, 0, 0, 409, 410, 6, 1, 1, 0, 410, 19, 1, 0, 0, 0, 411, 412, 7, 3, 0, 0, 412, 413, 7, 9, 0, 0, 413, 414, 7, 6, 0, 0, 414, 415, 7, 1, 0, 0, 415, 416, 7, 4, 0, 0, 416, 417, 7, 10, 0, 0, 417, 418, 1, 0, 0, 0, 418, 419, 6, 2, 2, 0, 419, 21, 1, 0, 0, 0, 420, 421, 7, 3, 0, 0, 421, 422, 7, 11, 0, 0, 422, 423, 7, 12, 0, 0, 423, 424, 7, 13, 0, 0, 424, 425, 1, 0, 0, 0, 425, 426, 6, 3, 0, 0, 426, 23, 1, 0, 0, 0, 427, 428, 7, 3, 0, 0, 428, 429, 7, 14, 0, 0, 429, 430, 7, 8, 0, 0, 430, 431, 7, 13, 0, 0, 431, 432, 7, 12, 0, 0, 432, 433, 7, 1, 0, 0, 433, 434, 7, 9, 0, 0, 434, 435, 1, 0, 0, 0, 435, 436, 6, 4, 3, 0, 436, 25, 1, 0, 0, 0, 437, 438, 7, 15, 0, 0, 438, 439, 7, 6, 0, 0, 439, 440, 7, 7, 0, 0, 440, 441, 7, 16, 0, 0, 441, 442, 1, 0, 0, 0, 442, 443, 6, 5, 4, 0, 443, 27, 1, 0, 0, 0, 444, 445, 7, 17, 0, 0, 445, 446, 7, 6, 0, 0, 446, 447, 7, 7, 0, 0, 447, 448, 7, 18, 0, 0, 448, 449, 1, 0, 0, 0, 449, 450, 6, 6, 0, 0, 450, 29, 1, 0, 0, 0, 451, 452, 7, 1, 0, 0, 452, 453, 7, 9, 0, 0, 453, 454, 7, 13, 0, 0, 454, 455, 7, 1, 0, 0, 455, 456, 7, 9, 0, 0, 456, 457, 7, 3, 0, 0, 457, 458, 7, 2, 0, 0, 458, 459, 7, 5, 0, 0, 459, 460, 7, 12, 0, 0, 460, 461, 7, 5, 0, 0, 461, 462, 7, 2, 0, 0, 462, 463, 1, 0, 0, 0, 463, 464, 6, 7, 0, 0, 464, 31, 1, 0, 0, 0, 465, 466, 7, 18, 0, 0, 466, 467, 7, 3, 0, 0, 467, 468, 7, 3, 0, 0, 468, 469, 7, 8, 0, 0, 469, 470, 1, 0, 0, 0, 470, 471, 6, 8, 1, 0, 471, 33, 1, 0, 0, 0, 472, 473, 7, 13, 0, 0, 473, 474, 7, 1, 0, 0, 474, 475, 7, 16, 0, 0, 475, 476, 7, 1, 0, 0, 476, 477, 7, 5, 0, 0, 477, 478, 1, 0, 0, 0, 478, 479, 6, 9, 0, 0, 479, 35, 1, 0, 0, 0, 480, 481, 7, 13, 0, 0, 481, 482, 7, 7, 0, 0, 482, 483, 7, 7, 0, 0, 483, 484, 7, 18, 0, 0, 484, 485, 7, 19, 0, 0, 485, 486, 7, 8, 0, 0, 486, 487, 1, 0, 0, 0, 487, 488, 6, 10, 5, 0, 488, 37, 1, 0, 0, 0, 489, 490, 7, 16, 0, 0, 490, 491, 7, 3, 0, 0, 491, 492, 7, 5, 0, 0, 492, 493, 7, 12, 0, 0, 493, 494, 1, 0, 0, 0, 494, 495, 6, 11, 6, 0, 495, 39, 1, 0, 0, 0, 496, 497, 7, 16, 0, 0, 497, 498, 7, 3, 0, 0, 498, 499, 7, 5, 0, 0, 499, 500, 7, 6, 0, 0, 500, 501, 7, 1, 0, 0, 501, 502, 7, 4, 0, 0, 502, 503, 7, 2, 0, 0, 503, 504, 1, 0, 0, 0, 504, 505, 6, 12, 7, 0, 505, 41, 1, 0, 0, 0, 506, 507, 7, 16, 0, 0, 507, 508, 7, 11, 0, 0, 508, 509, 5, 95, 0, 0, 509, 510, 7, 3, 0, 0, 510, 511, 7, 14, 0, 0, 511, 512, 7, 8, 0, 0, 512, 513, 7, 12, 0, 0, 513, 514, 7, 9, 0, 0, 514, 515, 7, 0, 0, 0, 515, 516, 1, 0, 0, 0, 516, 517, 6, 13, 8, 0, 517, 43, 1, 0, 0, 0, 518, 519, 7, 6, 0, 0, 519, 520, 7, 3, 0, 0, 520, 521, 7, 9, 0, 0, 521, 522, 7, 12, 0, 0, 522, 523, 7, 16, 0, 0, 523, 524, 7, 3, 0, 0, 524, 525, 1, 0, 0, 0, 525, 526, 6, 14, 9, 0, 526, 45, 1, 0, 0, 0, 527, 528, 7, 6, 0, 0, 528, 529, 7, 7, 0, 0, 529, 530, 7, 20, 0, 0, 530, 531, 1, 0, 0, 0, 531, 532, 6, 15, 0, 0, 532, 47, 1, 0, 0, 0, 533, 534, 7, 2, 0, 0, 534, 535, 7, 10, 0, 0, 535, 536, 7, 7, 0, 0, 536, 537, 7, 20, 0, 0, 537, 538, 1, 0, 0, 0, 538, 539, 6, 16, 10, 0, 539, 49, 1, 0, 0, 0, 540, 541, 7, 2, 0, 0, 541, 542, 7, 7, 0, 0, 542, 543, 7, 6, 0, 0, 543, 544, 7, 5, 0, 0, 544, 545, 1, 0, 0, 0, 545, 546, 6, 17, 0, 0, 546, 51, 1, 0, 0, 0, 547, 548, 7, 2, 0, 0, 548, 549, 7, 5, 0, 0, 549, 550, 7, 12, 0, 0, 550, 551, 7, 5, 0, 0, 551, 552, 7, 2, 0, 0, 552, 553, 1, 0, 0, 0, 553, 554, 6, 18, 0, 0, 554, 53, 1, 0, 0, 0, 555, 556, 7, 20, 0, 0, 556, 557, 7, 10, 0, 0, 557, 558, 7, 3, 0, 0, 558, 559, 7, 6, 0, 0, 559, 560, 7, 3, 0, 0, 560, 561, 1, 0, 0, 0, 561, 562, 6, 19, 0, 0, 562, 55, 1, 0, 0, 0, 563, 565, 8, 21, 0, 0, 564, 563, 1, 0, 0, 0, 565, 566, 1, 0, 0, 0, 566, 564, 1, 0, 0, 0, 566, 567, 1, 0, 0, 0, 567, 568, 1, 0, 0, 0, 568, 569, 6, 20, 0, 0, 569, 57, 1, 0, 0, 0, 570, 571, 5, 47, 0, 0, 571, 572, 5, 47, 0, 0, 572, 576, 1, 0, 0, 0, 573, 575, 8, 22, 0, 0, 574, 573, 1, 0, 0, 0, 575, 578, 1, 0, 0, 0, 576, 574, 1, 0, 0, 0, 576, 577, 1, 0, 0, 0, 577, 580, 1, 0, 0, 0, 578, 576, 1, 0, 0, 0, 579, 581, 5, 13, 0, 0, 580, 579, 1, 0, 0, 0, 580, 581, 1, 0, 0, 0, 581, 583, 1, 0, 0, 0, 582, 584, 5, 10, 0, 0, 583, 582, 1, 0, 0, 0, 583, 584, 1, 0, 0, 0, 584, 585, 1, 0, 0, 0, 585, 586, 6, 21, 11, 0, 586, 59, 1, 0, 0, 0, 587, 588, 5, 47, 0, 0, 588, 589, 5, 42, 0, 0, 589, 594, 1, 0, 0, 0, 590, 593, 3, 60, 22, 0, 591, 593, 9, 0, 0, 0, 592, 590, 1, 0, 0, 0, 592, 591, 1, 0, 0, 0, 593, 596, 1, 0, 0, 0, 594, 595, 1, 0, 0, 0, 594, 592, 1, 0, 0, 0, 595, 597, 1, 0, 0, 0, 596, 594, 1, 0, 0, 0, 597, 598, 5, 42, 0, 0, 598, 599, 5, 47, 0, 0, 599, 600, 1, 0, 0, 0, 600, 601, 6, 22, 11, 0, 601, 61, 1, 0, 0, 0, 602, 604, 7, 23, 0, 0, 603, 602, 1, 0, 0, 0, 604, 605, 1, 0, 0, 0, 605, 603, 1, 0, 0, 0, 605, 606, 1, 0, 0, 0, 606, 607, 1, 0, 0, 0, 607, 608, 6, 23, 11, 0, 608, 63, 1, 0, 0, 0, 609, 613, 8, 24, 0, 0, 610, 611, 5, 47, 0, 0, 611, 613, 8, 25, 0, 0, 612, 609, 1, 0, 0, 0, 612, 610, 1, 0, 0, 0, 613, 65, 1, 0, 0, 0, 614, 616, 3, 64, 24, 0, 615, 614, 1, 0, 0, 0, 616, 617, 1, 0, 0, 0, 617, 615, 1, 0, 0, 0, 617, 618, 1, 0, 0, 0, 618, 67, 1, 0, 0, 0, 619, 620, 3, 176, 80, 0, 620, 621, 1, 0, 0, 0, 621, 622, 6, 26, 12, 0, 622, 623, 6, 26, 13, 0, 623, 69, 1, 0, 0, 0, 624, 625, 3, 78, 31, 0, 625, 626, 1, 0, 0, 0, 626, 627, 6, 27, 14, 0, 627, 628, 6, 27, 15, 0, 628, 71, 1, 0, 0, 0, 629, 630, 3, 62, 23, 0, 630, 631, 1, 0, 0, 0, 631, 632, 6, 28, 11, 0, 632, 73, 1, 0, 0, 0, 633, 634, 3, 58, 21, 0, 634, 635, 1, 0, 0, 0, 635, 636, 6, 29, 11, 0, 636, 75, 1, 0, 0, 0, 637, 638, 3, 60, 22, 0, 638, 639, 1, 0, 0, 0, 639, 640, 6, 30, 11, 0, 640, 77, 1, 0, 0, 0, 641, 642, 5, 124, 0, 0, 642, 643, 1, 0, 0, 0, 643, 644, 6, 31, 15, 0, 644, 79, 1, 0, 0, 0, 645, 646, 7, 26, 0, 0, 646, 81, 1, 0, 0, 0, 647, 648, 7, 27, 0, 0, 648, 83, 1, 0, 0, 0, 649, 650, 5, 92, 0, 0, 650, 651, 7, 28, 0, 0, 651, 85, 1, 0, 0, 0, 652, 653, 8, 29, 0, 0, 653, 87, 1, 0, 0, 0, 654, 656, 7, 3, 0, 0, 655, 657, 7, 30, 0, 0, 656, 655, 1, 0, 0, 0, 656, 657, 1, 0, 0, 0, 657, 659, 1, 0, 0, 0, 658, 660, 3, 80, 32, 0, 659, 658, 1, 0, 0, 0, 660, 661, 1, 0, 0, 0, 661, 659, 1, 0, 0, 0, 661, 662, 1, 0, 0, 0, 662, 89, 1, 0, 0, 0, 663, 664, 5, 64, 0, 0, 664, 91, 1, 0, 0, 0, 665, 666, 5, 96, 0, 0, 666, 93, 1, 0, 0, 0, 667, 671, 8, 31, 0, 0, 668, 669, 5, 96, 0, 0, 669, 671, 5, 96, 0, 0, 670, 667, 1, 0, 0, 0, 670, 668, 1, 0, 0, 0, 671, 95, 1, 0, 0, 0, 672, 673, 5, 95, 0, 0, 673, 97, 1, 0, 0, 0, 674, 678, 3, 82, 33, 0, 675, 678, 3, 80, 32, 0, 676, 678, 3, 96, 40, 0, 677, 674, 1, 0, 0, 0, 677, 675, 1, 0, 0, 0, 677, 676, 1, 0, 0, 0, 678, 99, 1, 0, 0, 0, 679, 684, 5, 34, 0, 0, 680, 683, 3, 84, 34, 0, 681, 683, 3, 86, 35, 0, 682, 680, 1, 0, 0, 0, 682, 681, 1, 0, 0, 0, 683, 686, 1, 0, 0, 0, 684, 682, 1, 0, 0, 0, 684, 685, 1, 0, 0, 0, 685, 687, 1, 0, 0, 0, 686, 684, 1, 0, 0, 0, 687, 709, 5, 34, 0, 0, 688, 689, 5, 34, 0, 0, 689, 690, 5, 34, 0, 0, 690, 691, 5, 34, 0, 0, 691, 695, 1, 0, 0, 0, 692, 694, 8, 22, 0, 0, 693, 692, 1, 0, 0, 0, 694, 697, 1, 0, 0, 0, 695, 696, 1, 0, 0, 0, 695, 693, 1, 0, 0, 0, 696, 698, 1, 0, 0, 0, 697, 695, 1, 0, 0, 0, 698, 699, 5, 34, 0, 0, 699, 700, 5, 34, 0, 0, 700, 701, 5, 34, 0, 0, 701, 703, 1, 0, 0, 0, 702, 704, 5, 34, 0, 0, 703, 702, 1, 0, 0, 0, 703, 704, 1, 0, 0, 0, 704, 706, 1, 0, 0, 0, 705, 707, 5, 34, 0, 0, 706, 705, 1, 0, 0, 0, 706, 707, 1, 0, 0, 0, 707, 709, 1, 0, 0, 0, 708, 679, 1, 0, 0, 0, 708, 688, 1, 0, 0, 0, 709, 101, 1, 0, 0, 0, 710, 712, 3, 80, 32, 0, 711, 710, 1, 0, 0, 0, 712, 713, 1, 0, 0, 0, 713, 711, 1, 0, 0, 0, 713, 714, 1, 0, 0, 0, 714, 103, 1, 0, 0, 0, 715, 717, 3, 80, 32, 0, 716, 715, 1, 0, 0, 0, 717, 718, 1, 0, 0, 0, 718, 716, 1, 0, 0, 0, 718, 719, 1, 0, 0, 0, 719, 720, 1, 0, 0, 0, 720, 724, 3, 120, 52, 0, 721, 723, 3, 80, 32, 0, 722, 721, 1, 0, 0, 0, 723, 726, 1, 0, 0, 0, 724, 722, 1, 0, 0, 0, 724, 725, 1, 0, 0, 0, 725, 758, 1, 0, 0, 0, 726, 724, 1, 0, 0, 0, 727, 729, 3, 120, 52, 0, 728, 730, 3, 80, 32, 0, 729, 728, 1, 0, 0, 0, 730, 731, 1, 0, 0, 0, 731, 729, 1, 0, 0, 0, 731, 732, 1, 0, 0, 0, 732, 758, 1, 0, 0, 0, 733, 735, 3, 80, 32, 0, 734, 733, 1, 0, 0, 0, 735, 736, 1, 0, 0, 0, 736, 734, 1, 0, 0, 0, 736, 737, 1, 0, 0, 0, 737, 745, 1, 0, 0, 0, 738, 742, 3, 120, 52, 0, 739, 741, 3, 80, 32, 0, 740, 739, 1, 0, 0, 0, 741, 744, 1, 0, 0, 0, 742, 740, 1, 0, 0, 0, 742, 743, 1, 0, 0, 0, 743, 746, 1, 0, 0, 0, 744, 742, 1, 0, 0, 0, 745, 738, 1, 0, 0, 0, 745, 746, 1, 0, 0, 0, 746, 747, 1, 0, 0, 0, 747, 748, 3, 88, 36, 0, 748, 758, 1, 0, 0, 0, 749, 751, 3, 120, 52, 0, 750, 752, 3, 80, 32, 0, 751, 750, 1, 0, 0, 0, 752, 753, 1, 0, 0, 0, 753, 751, 1, 0, 0, 0, 753, 754, 1, 0, 0, 0, 754, 755, 1, 0, 0, 0, 755, 756, 3, 88, 36, 0, 756, 758, 1, 0, 0, 0, 757, 716, 1, 0, 0, 0, 757, 727, 1, 0, 0, 0, 757, 734, 1, 0, 0, 0, 757, 749, 1, 0, 0, 0, 758, 105, 1, 0, 0, 0, 759, 760, 7, 32, 0, 0, 760, 761, 7, 33, 0, 0, 761, 107, 1, 0, 0, 0, 762, 763, 7, 12, 0, 0, 763, 764, 7, 9, 0, 0, 764, 765, 7, 0, 0, 0, 765, 109, 1, 0, 0, 0, 766, 767, 7, 12, 0, 0, 767, 768, 7, 2, 0, 0, 768, 769, 7, 4, 0, 0, 769, 111, 1, 0, 0, 0, 770, 771, 5, 61, 0, 0, 771, 113, 1, 0, 0, 0, 772, 773, 5, 58, 0, 0, 773, 774, 5, 58, 0, 0, 774, 115, 1, 0, 0, 0, 775, 776, 5, 44, 0, 0, 776, 117, 1, 0, 0, 0, 777, 778, 7, 0, 0, 0, 778, 779, 7, 3, 0, 0, 779, 780, 7, 2, 0, 0, 780, 781, 7, 4, 0, 0, 781, 119, 1, 0, 0, 0, 782, 783, 5, 46, 0, 0, 783, 121, 1, 0, 0, 0, 784, 785, 7, 15, 0, 0, 785, 786, 7, 12, 0, 0, 786, 787, 7, 13, 0, 0, 787, 788, 7, 2, 0, 0, 788, 789, 7, 3, 0, 0, 789, 123, 1, 0, 0, 0, 790, 791, 7, 15, 0, 0, 791, 792, 7, 1, 0, 0, 792, 793, 7, 6, 0, 0, 793, 794, 7, 2, 0, 0, 794, 795, 7, 5, 0, 0, 795, 125, 1, 0, 0, 0, 796, 797, 7, 13, 0, 0, 797, 798, 7, 12, 0, 0, 798, 799, 7, 2, 0, 0, 799, 800, 7, 5, 0, 0, 800, 127, 1, 0, 0, 0, 801, 802, 5, 40, 0, 0, 802, 129, 1, 0, 0, 0, 803, 804, 7, 1, 0, 0, 804, 805, 7, 9, 0, 0, 805, 131, 1, 0, 0, 0, 806, 807, 7, 1, 0, 0, 807, 808, 7, 2, 0, 0, 808, 133, 1, 0, 0, 0, 809, 810, 7, 13, 0, 0, 810, 811, 7, 1, 0, 0, 811, 812, 7, 18, 0, 0, 812, 813, 7, 3, 0, 0, 813, 135, 1, 0, 0, 0, 814, 815, 7, 9, 0, 0, 815, 816, 7, 7, 0, 0, 816, 817, 7, 5, 0, 0, 817, 137, 1, 0, 0, 0, 818, 819, 7, 9, 0, 0, 819, 820, 7, 19, 0, 0, 820, 821, 7, 13, 0, 0, 821, 822, 7, 13, 0, 0, 822, 139, 1, 0, 0, 0, 823, 824, 7, 9, 0, 0, 824, 825, 7, 19, 0, 0, 825, 826, 7, 13, 0, 0, 826, 827, 7, 13, 0, 0, 827, 828, 7, 2, 0, 0, 828, 141, 1, 0, 0, 0, 829, 830, 7, 7, 0, 0, 830, 831, 7, 6, 0, 0, 831, 143, 1, 0, 0, 0, 832, 833, 5, 63, 0, 0, 833, 145, 1, 0, 0, 0, 834, 835, 7, 6, 0, 0, 835, 836, 7, 13, 0, 0, 836, 837, 7, 1, 0, 0, 837, 838, 7, 18, 0, 0, 838, 839, 7, 3, 0, 0, 839, 147, 1, 0, 0, 0, 840, 841, 5, 41, 0, 0, 841, 149, 1, 0, 0, 0, 842, 843, 7, 5, 0, 0, 843, 844, 7, 6, 0, 0, 844, 845, 7, 19, 0, 0, 845, 846, 7, 3, 0, 0, 846, 151, 1, 0, 0, 0, 847, 848, 5, 61, 0, 0, 848, 849, 5, 61, 0, 0, 849, 153, 1, 0, 0, 0, 850, 851, 5, 61, 0, 0, 851, 852, 5, 126, 0, 0, 852, 155, 1, 0, 0, 0, 853, 854, 5, 33, 0, 0, 854, 855, 5, 61, 0, 0, 855, 157, 1, 0, 0, 0, 856, 857, 5, 60, 0, 0, 857, 159, 1, 0, 0, 0, 858, 859, 5, 60, 0, 0, 859, 860, 5, 61, 0, 0, 860, 161, 1, 0, 0, 0, 861, 862, 5, 62, 0, 0, 862, 163, 1, 0, 0, 0, 863, 864, 5, 62, 0, 0, 864, 865, 5, 61, 0, 0, 865, 165, 1, 0, 0, 0, 866, 867, 5, 43, 0, 0, 867, 167, 1, 0, 0, 0, 868, 869, 5, 45, 0, 0, 869, 169, 1, 0, 0, 0, 870, 871, 5, 42, 0, 0, 871, 171, 1, 0, 0, 0, 872, 873, 5, 47, 0, 0, 873, 173, 1, 0, 0, 0, 874, 875, 5, 37, 0, 0, 875, 175, 1, 0, 0, 0, 876, 877, 5, 91, 0, 0, 877, 878, 1, 0, 0, 0, 878, 879, 6, 80, 0, 0, 879, 880, 6, 80, 0, 0, 880, 177, 1, 0, 0, 0, 881, 882, 5, 93, 0, 0, 882, 883, 1, 0, 0, 0, 883, 884, 6, 81, 15, 0, 884, 885, 6, 81, 15, 0, 885, 179, 1, 0, 0, 0, 886, 890, 3, 82, 33, 0, 887, 889, 3, 98, 41, 0, 888, 887, 1, 0, 0, 0, 889, 892, 1, 0, 0, 0, 890, 888, 1, 0, 0, 0, 890, 891, 1, 0, 0, 0, 891, 903, 1, 0, 0, 0, 892, 890, 1, 0, 0, 0, 893, 896, 3, 96, 40, 0, 894, 896, 3, 90, 37, 0, 895, 893, 1, 0, 0, 0, 895, 894, 1, 0, 0, 0, 896, 898, 1, 0, 0, 0, 897, 899, 3, 98, 41, 0, 898, 897, 1, 0, 0, 0, 899, 900, 1, 0, 0, 0, 900, 898, 1, 0, 0, 0, 900, 901, 1, 0, 0, 0, 901, 903, 1, 0, 0, 0, 902, 886, 1, 0, 0, 0, 902, 895, 1, 0, 0, 0, 903, 181, 1, 0, 0, 0, 904, 906, 3, 92, 38, 0, 905, 907, 3, 94, 39, 0, 906, 905, 1, 0, 0, 0, 907, 908, 1, 0, 0, 0, 908, 906, 1, 0, 0, 0, 908, 909, 1, 0, 0, 0, 909, 910, 1, 0, 0, 0, 910, 911, 3, 92, 38, 0, 911, 183, 1, 0, 0, 0, 912, 913, 3, 182, 83, 0, 913, 185, 1, 0, 0, 0, 914, 915, 3, 58, 21, 0, 915, 916, 1, 0, 0, 0, 916, 917, 6, 85, 11, 0, 917, 187, 1, 0, 0, 0, 918, 919, 3, 60, 22, 0, 919, 920, 1, 0, 0, 0, 920, 921, 6, 86, 11, 0, 921, 189, 1, 0, 0, 0, 922, 923, 3, 62, 23, 0, 923, 924, 1, 0, 0, 0, 924, 925, 6, 87, 11, 0, 925, 191, 1, 0, 0, 0, 926, 927, 3, 78, 31, 0, 927, 928, 1, 0, 0, 0, 928, 929, 6, 88, 14, 0, 929, 930, 6, 88, 15, 0, 930, 193, 1, 0, 0, 0, 931, 932, 3, 176, 80, 0, 932, 933, 1, 0, 0, 0, 933, 934, 6, 89, 12, 0, 934, 195, 1, 0, 0, 0, 935, 936, 3, 178, 81, 0, 936, 937, 1, 0, 0, 0, 937, 938, 6, 90, 16, 0, 938, 197, 1, 0, 0, 0, 939, 940, 3, 116, 50, 0, 940, 941, 1, 0, 0, 0, 941, 942, 6, 91, 17, 0, 942, 199, 1, 0, 0, 0, 943, 944, 3, 112, 48, 0, 944, 945, 1, 0, 0, 0, 945, 946, 6, 92, 18, 0, 946, 201, 1, 0, 0, 0, 947, 948, 3, 100, 42, 0, 948, 949, 1, 0, 0, 0, 949, 950, 6, 93, 19, 0, 950, 203, 1, 0, 0, 0, 951, 952, 7, 16, 0, 0, 952, 953, 7, 3, 0, 0, 953, 954, 7, 5, 0, 0, 954, 955, 7, 12, 0, 0, 955, 956, 7, 0, 0, 0, 956, 957, 7, 12, 0, 0, 957, 958, 7, 5, 0, 0, 958, 959, 7, 12, 0, 0, 959, 205, 1, 0, 0, 0, 960, 961, 3, 66, 25, 0, 961, 962, 1, 0, 0, 0, 962, 963, 6, 95, 20, 0, 963, 207, 1, 0, 0, 0, 964, 965, 3, 58, 21, 0, 965, 966, 1, 0, 0, 0, 966, 967, 6, 96, 11, 0, 967, 209, 1, 0, 0, 0, 968, 969, 3, 60, 22, 0, 969, 970, 1, 0, 0, 0, 970, 971, 6, 97, 11, 0, 971, 211, 1, 0, 0, 0, 972, 973, 3, 62, 23, 0, 973, 974, 1, 0, 0, 0, 974, 975, 6, 98, 11, 0, 975, 213, 1, 0, 0, 0, 976, 977, 3, 78, 31, 0, 977, 978, 1, 0, 0, 0, 978, 979, 6, 99, 14, 0, 979, 980, 6, 99, 15, 0, 980, 215, 1, 0, 0, 0, 981, 982, 3, 120, 52, 0, 982, 983, 1, 0, 0, 0, 983, 984, 6, 100, 21, 0, 984, 217, 1, 0, 0, 0, 985, 986, 3, 116, 50, 0, 986, 987, 1, 0, 0, 0, 987, 988, 6, 101, 17, 0, 988, 219, 1, 0, 0, 0, 989, 994, 3, 82, 33, 0, 990, 994, 3, 80, 32, 0, 991, 994, 3, 96, 40, 0, 992, 994, 3, 170, 77, 0, 993, 989, 1, 0, 0, 0, 993, 990, 1, 0, 0, 0, 993, 991, 1, 0, 0, 0, 993, 992, 1, 0, 0, 0, 994, 221, 1, 0, 0, 0, 995, 998, 3, 82, 33, 0, 996, 998, 3, 170, 77, 0, 997, 995, 1, 0, 0, 0, 997, 996, 1, 0, 0, 0, 998, 1002, 1, 0, 0, 0, 999, 1001, 3, 220, 102, 0, 1000, 999, 1, 0, 0, 0, 1001, 1004, 1, 0, 0, 0, 1002, 1000, 1, 0, 0, 0, 1002, 1003, 1, 0, 0, 0, 1003, 1015, 1, 0, 0, 0, 1004, 1002, 1, 0, 0, 0, 1005, 1008, 3, 96, 40, 0, 1006, 1008, 3, 90, 37, 0, 1007, 1005, 1, 0, 0, 0, 1007, 1006, 1, 0, 0, 0, 1008, 1010, 1, 0, 0, 0, 1009, 1011, 3, 220, 102, 0, 1010, 1009, 1, 0, 0, 0, 1011, 1012, 1, 0, 0, 0, 1012, 1010, 1, 0, 0, 0, 1012, 1013, 1, 0, 0, 0, 1013, 1015, 1, 0, 0, 0, 1014, 997, 1, 0, 0, 0, 1014, 1007, 1, 0, 0, 0, 1015, 223, 1, 0, 0, 0, 1016, 1019, 3, 222, 103, 0, 1017, 1019, 3, 182, 83, 0, 1018, 1016, 1, 0, 0, 0, 1018, 1017, 1, 0, 0, 0, 1019, 1020, 1, 0, 0, 0, 1020, 1018, 1, 0, 0, 0, 1020, 1021, 1, 0, 0, 0, 1021, 225, 1, 0, 0, 0, 1022, 1023, 3, 58, 21, 0, 1023, 1024, 1, 0, 0, 0, 1024, 1025, 6, 105, 11, 0, 1025, 227, 1, 0, 0, 0, 1026, 1027, 3, 60, 22, 0, 1027, 1028, 1, 0, 0, 0, 1028, 1029, 6, 106, 11, 0, 1029, 229, 1, 0, 0, 0, 1030, 1031, 3, 62, 23, 0, 1031, 1032, 1, 0, 0, 0, 1032, 1033, 6, 107, 11, 0, 1033, 231, 1, 0, 0, 0, 1034, 1035, 3, 78, 31, 0, 1035, 1036, 1, 0, 0, 0, 1036, 1037, 6, 108, 14, 0, 1037, 1038, 6, 108, 15, 0, 1038, 233, 1, 0, 0, 0, 1039, 1040, 3, 112, 48, 0, 1040, 1041, 1, 0, 0, 0, 1041, 1042, 6, 109, 18, 0, 1042, 235, 1, 0, 0, 0, 1043, 1044, 3, 116, 50, 0, 1044, 1045, 1, 0, 0, 0, 1045, 1046, 6, 110, 17, 0, 1046, 237, 1, 0, 0, 0, 1047, 1048, 3, 120, 52, 0, 1048, 1049, 1, 0, 0, 0, 1049, 1050, 6, 111, 21, 0, 1050, 239, 1, 0, 0, 0, 1051, 1052, 7, 12, 0, 0, 1052, 1053, 7, 2, 0, 0, 1053, 241, 1, 0, 0, 0, 1054, 1055, 3, 224, 104, 0, 1055, 1056, 1, 0, 0, 0, 1056, 1057, 6, 113, 22, 0, 1057, 243, 1, 0, 0, 0, 1058, 1059, 3, 58, 21, 0, 1059, 1060, 1, 0, 0, 0, 1060, 1061, 6, 114, 11, 0, 1061, 245, 1, 0, 0, 0, 1062, 1063, 3, 60, 22, 0, 1063, 1064, 1, 0, 0, 0, 1064, 1065, 6, 115, 11, 0, 1065, 247, 1, 0, 0, 0, 1066, 1067, 3, 62, 23, 0, 1067, 1068, 1, 0, 0, 0, 1068, 1069, 6, 116, 11, 0, 1069, 249, 1, 0, 0, 0, 1070, 1071, 3, 78, 31, 0, 1071, 1072, 1, 0, 0, 0, 1072, 1073, 6, 117, 14, 0, 1073, 1074, 6, 117, 15, 0, 1074, 251, 1, 0, 0, 0, 1075, 1076, 3, 176, 80, 0, 1076, 1077, 1, 0, 0, 0, 1077, 1078, 6, 118, 12, 0, 1078, 1079, 6, 118, 23, 0, 1079, 253, 1, 0, 0, 0, 1080, 1081, 7, 7, 0, 0, 1081, 1082, 7, 9, 0, 0, 1082, 1083, 1, 0, 0, 0, 1083, 1084, 6, 119, 24, 0, 1084, 255, 1, 0, 0, 0, 1085, 1086, 7, 20, 0, 0, 1086, 1087, 7, 1, 0, 0, 1087, 1088, 7, 5, 0, 0, 1088, 1089, 7, 10, 0, 0, 1089, 1090, 1, 0, 0, 0, 1090, 1091, 6, 120, 24, 0, 1091, 257, 1, 0, 0, 0, 1092, 1093, 8, 34, 0, 0, 1093, 259, 1, 0, 0, 0, 1094, 1096, 3, 258, 121, 0, 1095, 1094, 1, 0, 0, 0, 1096, 1097, 1, 0, 0, 0, 1097, 1095, 1, 0, 0, 0, 1097, 1098, 1, 0, 0, 0, 1098, 1099, 1, 0, 0, 0, 1099, 1100, 3, 358, 171, 0, 1100, 1102, 1, 0, 0, 0, 1101, 1095, 1, 0, 0, 0, 1101, 1102, 1, 0, 0, 0, 1102, 1104, 1, 0, 0, 0, 1103, 1105, 3, 258, 121, 0, 1104, 1103, 1, 0, 0, 0, 1105, 1106, 1, 0, 0, 0, 1106, 1104, 1, 0, 0, 0, 1106, 1107, 1, 0, 0, 0, 1107, 261, 1, 0, 0, 0, 1108, 1109, 3, 184, 84, 0, 1109, 1110, 1, 0, 0, 0, 1110, 1111, 6, 123, 25, 0, 1111, 263, 1, 0, 0, 0, 1112, 1113, 3, 260, 122, 0, 1113, 1114, 1, 0, 0, 0, 1114, 1115, 6, 124, 26, 0, 1115, 265, 1, 0, 0, 0, 1116, 1117, 3, 58, 21, 0, 1117, 1118, 1, 0, 0, 0, 1118, 1119, 6, 125, 11, 0, 1119, 267, 1, 0, 0, 0, 1120, 1121, 3, 60, 22, 0, 1121, 1122, 1, 0, 0, 0, 1122, 1123, 6, 126, 11, 0, 1123, 269, 1, 0, 0, 0, 1124, 1125, 3, 62, 23, 0, 1125, 1126, 1, 0, 0, 0, 1126, 1127, 6, 127, 11, 0, 1127, 271, 1, 0, 0, 0, 1128, 1129, 3, 78, 31, 0, 1129, 1130, 1, 0, 0, 0, 1130, 1131, 6, 128, 14, 0, 1131, 1132, 6, 128, 15, 0, 1132, 1133, 6, 128, 15, 0, 1133, 273, 1, 0, 0, 0, 1134, 1135, 3, 112, 48, 0, 1135, 1136, 1, 0, 0, 0, 1136, 1137, 6, 129, 18, 0, 1137, 275, 1, 0, 0, 0, 1138, 1139, 3, 116, 50, 0, 1139, 1140, 1, 0, 0, 0, 1140, 1141, 6, 130, 17, 0, 1141, 277, 1, 0, 0, 0, 1142, 1143, 3, 120, 52, 0, 1143, 1144, 1, 0, 0, 0, 1144, 1145, 6, 131, 21, 0, 1145, 279, 1, 0, 0, 0, 1146, 1147, 3, 256, 120, 0, 1147, 1148, 1, 0, 0, 0, 1148, 1149, 6, 132, 27, 0, 1149, 281, 1, 0, 0, 0, 1150, 1151, 3, 224, 104, 0, 1151, 1152, 1, 0, 0, 0, 1152, 1153, 6, 133, 22, 0, 1153, 283, 1, 0, 0, 0, 1154, 1155, 3, 184, 84, 0, 1155, 1156, 1, 0, 0, 0, 1156, 1157, 6, 134, 25, 0, 1157, 285, 1, 0, 0, 0, 1158, 1159, 3, 58, 21, 0, 1159, 1160, 1, 0, 0, 0, 1160, 1161, 6, 135, 11, 0, 1161, 287, 1, 0, 0, 0, 1162, 1163, 3, 60, 22, 0, 1163, 1164, 1, 0, 0, 0, 1164, 1165, 6, 136, 11, 0, 1165, 289, 1, 0, 0, 0, 1166, 1167, 3, 62, 23, 0, 1167, 1168, 1, 0, 0, 0, 1168, 1169, 6, 137, 11, 0, 1169, 291, 1, 0, 0, 0, 1170, 1171, 3, 78, 31, 0, 1171, 1172, 1, 0, 0, 0, 1172, 1173, 6, 138, 14, 0, 1173, 1174, 6, 138, 15, 0, 1174, 293, 1, 0, 0, 0, 1175, 1176, 3, 116, 50, 0, 1176, 1177, 1, 0, 0, 0, 1177, 1178, 6, 139, 17, 0, 1178, 295, 1, 0, 0, 0, 1179, 1180, 3, 120, 52, 0, 1180, 1181, 1, 0, 0, 0, 1181, 1182, 6, 140, 21, 0, 1182, 297, 1, 0, 0, 0, 1183, 1184, 3, 254, 119, 0, 1184, 1185, 1, 0, 0, 0, 1185, 1186, 6, 141, 28, 0, 1186, 1187, 6, 141, 29, 0, 1187, 299, 1, 0, 0, 0, 1188, 1189, 3, 66, 25, 0, 1189, 1190, 1, 0, 0, 0, 1190, 1191, 6, 142, 20, 0, 1191, 301, 1, 0, 0, 0, 1192, 1193, 3, 58, 21, 0, 1193, 1194, 1, 0, 0, 0, 1194, 1195, 6, 143, 11, 0, 1195, 303, 1, 0, 0, 0, 1196, 1197, 3, 60, 22, 0, 1197, 1198, 1, 0, 0, 0, 1198, 1199, 6, 144, 11, 0, 1199, 305, 1, 0, 0, 0, 1200, 1201, 3, 62, 23, 0, 1201, 1202, 1, 0, 0, 0, 1202, 1203, 6, 145, 11, 0, 1203, 307, 1, 0, 0, 0, 1204, 1205, 3, 78, 31, 0, 1205, 1206, 1, 0, 0, 0, 1206, 1207, 6, 146, 14, 0, 1207, 1208, 6, 146, 15, 0, 1208, 1209, 6, 146, 15, 0, 1209, 309, 1, 0, 0, 0, 1210, 1211, 3, 116, 50, 0, 1211, 1212, 1, 0, 0, 0, 1212, 1213, 6, 147, 17, 0, 1213, 311, 1, 0, 0, 0, 1214, 1215, 3, 120, 52, 0, 1215, 1216, 1, 0, 0, 0, 1216, 1217, 6, 148, 21, 0, 1217, 313, 1, 0, 0, 0, 1218, 1219, 3, 224, 104, 0, 1219, 1220, 1, 0, 0, 0, 1220, 1221, 6, 149, 22, 0, 1221, 315, 1, 0, 0, 0, 1222, 1223, 3, 58, 21, 0, 1223, 1224, 1, 0, 0, 0, 1224, 1225, 6, 150, 11, 0, 1225, 317, 1, 0, 0, 0, 1226, 1227, 3, 60, 22, 0, 1227, 1228, 1, 0, 0, 0, 1228, 1229, 6, 151, 11, 0, 1229, 319, 1, 0, 0, 0, 1230, 1231, 3, 62, 23, 0, 1231, 1232, 1, 0, 0, 0, 1232, 1233, 6, 152, 11, 0, 1233, 321, 1, 0, 0, 0, 1234, 1235, 3, 78, 31, 0, 1235, 1236, 1, 0, 0, 0, 1236, 1237, 6, 153, 14, 0, 1237, 1238, 6, 153, 15, 0, 1238, 323, 1, 0, 0, 0, 1239, 1240, 3, 120, 52, 0, 1240, 1241, 1, 0, 0, 0, 1241, 1242, 6, 154, 21, 0, 1242, 325, 1, 0, 0, 0, 1243, 1244, 3, 184, 84, 0, 1244, 1245, 1, 0, 0, 0, 1245, 1246, 6, 155, 25, 0, 1246, 327, 1, 0, 0, 0, 1247, 1248, 3, 180, 82, 0, 1248, 1249, 1, 0, 0, 0, 1249, 1250, 6, 156, 30, 0, 1250, 329, 1, 0, 0, 0, 1251, 1252, 3, 58, 21, 0, 1252, 1253, 1, 0, 0, 0, 1253, 1254, 6, 157, 11, 0, 1254, 331, 1, 0, 0, 0, 1255, 1256, 3, 60, 22, 0, 1256, 1257, 1, 0, 0, 0, 1257, 1258, 6, 158, 11, 0, 1258, 333, 1, 0, 0, 0, 1259, 1260, 3, 62, 23, 0, 1260, 1261, 1, 0, 0, 0, 1261, 1262, 6, 159, 11, 0, 1262, 335, 1, 0, 0, 0, 1263, 1264, 3, 78, 31, 0, 1264, 1265, 1, 0, 0, 0, 1265, 1266, 6, 160, 14, 0, 1266, 1267, 6, 160, 15, 0, 1267, 337, 1, 0, 0, 0, 1268, 1269, 7, 1, 0, 0, 1269, 1270, 7, 9, 0, 0, 1270, 1271, 7, 15, 0, 0, 1271, 1272, 7, 7, 0, 0, 1272, 339, 1, 0, 0, 0, 1273, 1274, 3, 58, 21, 0, 1274, 1275, 1, 0, 0, 0, 1275, 1276, 6, 162, 11, 0, 1276, 341, 1, 0, 0, 0, 1277, 1278, 3, 60, 22, 0, 1278, 1279, 1, 0, 0, 0, 1279, 1280, 6, 163, 11, 0, 1280, 343, 1, 0, 0, 0, 1281, 1282, 3, 62, 23, 0, 1282, 1283, 1, 0, 0, 0, 1283, 1284, 6, 164, 11, 0, 1284, 345, 1, 0, 0, 0, 1285, 1286, 3, 78, 31, 0, 1286, 1287, 1, 0, 0, 0, 1287, 1288, 6, 165, 14, 0, 1288, 1289, 6, 165, 15, 0, 1289, 347, 1, 0, 0, 0, 1290, 1291, 7, 15, 0, 0, 1291, 1292, 7, 19, 0, 0, 1292, 1293, 7, 9, 0, 0, 1293, 1294, 7, 4, 0, 0, 1294, 1295, 7, 5, 0, 0, 1295, 1296, 7, 1, 0, 0, 1296, 1297, 7, 7, 0, 0, 1297, 1298, 7, 9, 0, 0, 1298, 1299, 7, 2, 0, 0, 1299, 349, 1, 0, 0, 0, 1300, 1301, 3, 58, 21, 0, 1301, 1302, 1, 0, 0, 0, 1302, 1303, 6, 167, 11, 0, 1303, 351, 1, 0, 0, 0, 1304, 1305, 3, 60, 22, 0, 1305, 1306, 1, 0, 0, 0, 1306, 1307, 6, 168, 11, 0, 1307, 353, 1, 0, 0, 0, 1308, 1309, 3, 62, 23, 0, 1309, 1310, 1, 0, 0, 0, 1310, 1311, 6, 169, 11, 0, 1311, 355, 1, 0, 0, 0, 1312, 1313, 3, 178, 81, 0, 1313, 1314, 1, 0, 0, 0, 1314, 1315, 6, 170, 16, 0, 1315, 1316, 6, 170, 15, 0, 1316, 357, 1, 0, 0, 0, 1317, 1318, 5, 58, 0, 0, 1318, 359, 1, 0, 0, 0, 1319, 1325, 3, 90, 37, 0, 1320, 1325, 3, 80, 32, 0, 1321, 1325, 3, 120, 52, 0, 1322, 1325, 3, 82, 33, 0, 1323, 1325, 3, 96, 40, 0, 1324, 1319, 1, 0, 0, 0, 1324, 1320, 1, 0, 0, 0, 1324, 1321, 1, 0, 0, 0, 1324, 1322, 1, 0, 0, 0, 1324, 1323, 1, 0, 0, 0, 1325, 1326, 1, 0, 0, 0, 1326, 1324, 1, 0, 0, 0, 1326, 1327, 1, 0, 0, 0, 1327, 361, 1, 0, 0, 0, 1328, 1329, 3, 58, 21, 0, 1329, 1330, 1, 0, 0, 0, 1330, 1331, 6, 173, 11, 0, 1331, 363, 1, 0, 0, 0, 1332, 1333, 3, 60, 22, 0, 1333, 1334, 1, 0, 0, 0, 1334, 1335, 6, 174, 11, 0, 1335, 365, 1, 0, 0, 0, 1336, 1337, 3, 62, 23, 0, 1337, 1338, 1, 0, 0, 0, 1338, 1339, 6, 175, 11, 0, 1339, 367, 1, 0, 0, 0, 1340, 1341, 3, 78, 31, 0, 1341, 1342, 1, 0, 0, 0, 1342, 1343, 6, 176, 14, 0, 1343, 1344, 6, 176, 15, 0, 1344, 369, 1, 0, 0, 0, 1345, 1346, 3, 66, 25, 0, 1346, 1347, 1, 0, 0, 0, 1347, 1348, 6, 177, 20, 0, 1348, 1349, 6, 177, 15, 0, 1349, 1350, 6, 177, 31, 0, 1350, 371, 1, 0, 0, 0, 1351, 1352, 3, 58, 21, 0, 1352, 1353, 1, 0, 0, 0, 1353, 1354, 6, 178, 11, 0, 1354, 373, 1, 0, 0, 0, 1355, 1356, 3, 60, 22, 0, 1356, 1357, 1, 0, 0, 0, 1357, 1358, 6, 179, 11, 0, 1358, 375, 1, 0, 0, 0, 1359, 1360, 3, 62, 23, 0, 1360, 1361, 1, 0, 0, 0, 1361, 1362, 6, 180, 11, 0, 1362, 377, 1, 0, 0, 0, 1363, 1364, 3, 116, 50, 0, 1364, 1365, 1, 0, 0, 0, 1365, 1366, 6, 181, 17, 0, 1366, 1367, 6, 181, 15, 0, 1367, 1368, 6, 181, 7, 0, 1368, 379, 1, 0, 0, 0, 1369, 1370, 3, 58, 21, 0, 1370, 1371, 1, 0, 0, 0, 1371, 1372, 6, 182, 11, 0, 1372, 381, 1, 0, 0, 0, 1373, 1374, 3, 60, 22, 0, 1374, 1375, 1, 0, 0, 0, 1375, 1376, 6, 183, 11, 0, 1376, 383, 1, 0, 0, 0, 1377, 1378, 3, 62, 23, 0, 1378, 1379, 1, 0, 0, 0, 1379, 1380, 6, 184, 11, 0, 1380, 385, 1, 0, 0, 0, 1381, 1382, 3, 184, 84, 0, 1382, 1383, 1, 0, 0, 0, 1383, 1384, 6, 185, 15, 0, 1384, 1385, 6, 185, 0, 0, 1385, 1386, 6, 185, 25, 0, 1386, 387, 1, 0, 0, 0, 1387, 1388, 3, 180, 82, 0, 1388, 1389, 1, 0, 0, 0, 1389, 1390, 6, 186, 15, 0, 1390, 1391, 6, 186, 0, 0, 1391, 1392, 6, 186, 30, 0, 1392, 389, 1, 0, 0, 0, 1393, 1394, 3, 106, 45, 0, 1394, 1395, 1, 0, 0, 0, 1395, 1396, 6, 187, 15, 0, 1396, 1397, 6, 187, 0, 0, 1397, 1398, 6, 187, 32, 0, 1398, 391, 1, 0, 0, 0, 1399, 1400, 3, 78, 31, 0, 1400, 1401, 1, 0, 0, 0, 1401, 1402, 6, 188, 14, 0, 1402, 1403, 6, 188, 15, 0, 1403, 393, 1, 0, 0, 0, 62, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 566, 576, 580, 583, 592, 594, 605, 612, 617, 656, 661, 670, 677, 682, 684, 695, 703, 706, 708, 713, 718, 724, 731, 736, 742, 745, 753, 757, 890, 895, 900, 902, 908, 993, 997, 1002, 1007, 1012, 1014, 1018, 1020, 1097, 1101, 1106, 1324, 1326, 33, 5, 2, 0, 5, 4, 0, 5, 6, 0, 5, 1, 0, 5, 3, 0, 5, 8, 0, 5, 12, 0, 5, 14, 0, 5, 10, 0, 5, 5, 0, 5, 11, 0, 0, 1, 0, 7, 68, 0, 5, 0, 0, 7, 29, 0, 4, 0, 0, 7, 69, 0, 7, 38, 0, 7, 36, 0, 7, 30, 0, 7, 25, 0, 7, 40, 0, 7, 79, 0, 5, 13, 0, 5, 7, 0, 7, 71, 0, 7, 89, 0, 7, 88, 0, 7, 87, 0, 5, 9, 0, 7, 70, 0, 5, 15, 0, 7, 33, 0] \ No newline at end of file +[4, 0, 124, 1422, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, 73, 7, 73, 2, 74, 7, 74, 2, 75, 7, 75, 2, 76, 7, 76, 2, 77, 7, 77, 2, 78, 7, 78, 2, 79, 7, 79, 2, 80, 7, 80, 2, 81, 7, 81, 2, 82, 7, 82, 2, 83, 7, 83, 2, 84, 7, 84, 2, 85, 7, 85, 2, 86, 7, 86, 2, 87, 7, 87, 2, 88, 7, 88, 2, 89, 7, 89, 2, 90, 7, 90, 2, 91, 7, 91, 2, 92, 7, 92, 2, 93, 7, 93, 2, 94, 7, 94, 2, 95, 7, 95, 2, 96, 7, 96, 2, 97, 7, 97, 2, 98, 7, 98, 2, 99, 7, 99, 2, 100, 7, 100, 2, 101, 7, 101, 2, 102, 7, 102, 2, 103, 7, 103, 2, 104, 7, 104, 2, 105, 7, 105, 2, 106, 7, 106, 2, 107, 7, 107, 2, 108, 7, 108, 2, 109, 7, 109, 2, 110, 7, 110, 2, 111, 7, 111, 2, 112, 7, 112, 2, 113, 7, 113, 2, 114, 7, 114, 2, 115, 7, 115, 2, 116, 7, 116, 2, 117, 7, 117, 2, 118, 7, 118, 2, 119, 7, 119, 2, 120, 7, 120, 2, 121, 7, 121, 2, 122, 7, 122, 2, 123, 7, 123, 2, 124, 7, 124, 2, 125, 7, 125, 2, 126, 7, 126, 2, 127, 7, 127, 2, 128, 7, 128, 2, 129, 7, 129, 2, 130, 7, 130, 2, 131, 7, 131, 2, 132, 7, 132, 2, 133, 7, 133, 2, 134, 7, 134, 2, 135, 7, 135, 2, 136, 7, 136, 2, 137, 7, 137, 2, 138, 7, 138, 2, 139, 7, 139, 2, 140, 7, 140, 2, 141, 7, 141, 2, 142, 7, 142, 2, 143, 7, 143, 2, 144, 7, 144, 2, 145, 7, 145, 2, 146, 7, 146, 2, 147, 7, 147, 2, 148, 7, 148, 2, 149, 7, 149, 2, 150, 7, 150, 2, 151, 7, 151, 2, 152, 7, 152, 2, 153, 7, 153, 2, 154, 7, 154, 2, 155, 7, 155, 2, 156, 7, 156, 2, 157, 7, 157, 2, 158, 7, 158, 2, 159, 7, 159, 2, 160, 7, 160, 2, 161, 7, 161, 2, 162, 7, 162, 2, 163, 7, 163, 2, 164, 7, 164, 2, 165, 7, 165, 2, 166, 7, 166, 2, 167, 7, 167, 2, 168, 7, 168, 2, 169, 7, 169, 2, 170, 7, 170, 2, 171, 7, 171, 2, 172, 7, 172, 2, 173, 7, 173, 2, 174, 7, 174, 2, 175, 7, 175, 2, 176, 7, 176, 2, 177, 7, 177, 2, 178, 7, 178, 2, 179, 7, 179, 2, 180, 7, 180, 2, 181, 7, 181, 2, 182, 7, 182, 2, 183, 7, 183, 2, 184, 7, 184, 2, 185, 7, 185, 2, 186, 7, 186, 2, 187, 7, 187, 2, 188, 7, 188, 2, 189, 7, 189, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 4, 20, 567, 8, 20, 11, 20, 12, 20, 568, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, 21, 5, 21, 577, 8, 21, 10, 21, 12, 21, 580, 9, 21, 1, 21, 3, 21, 583, 8, 21, 1, 21, 3, 21, 586, 8, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 5, 22, 595, 8, 22, 10, 22, 12, 22, 598, 9, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 4, 23, 606, 8, 23, 11, 23, 12, 23, 607, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 3, 24, 615, 8, 24, 1, 25, 4, 25, 618, 8, 25, 11, 25, 12, 25, 619, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 36, 1, 36, 3, 36, 659, 8, 36, 1, 36, 4, 36, 662, 8, 36, 11, 36, 12, 36, 663, 1, 37, 1, 37, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 3, 39, 673, 8, 39, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 3, 41, 680, 8, 41, 1, 42, 1, 42, 1, 42, 5, 42, 685, 8, 42, 10, 42, 12, 42, 688, 9, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 5, 42, 696, 8, 42, 10, 42, 12, 42, 699, 9, 42, 1, 42, 1, 42, 1, 42, 1, 42, 1, 42, 3, 42, 706, 8, 42, 1, 42, 3, 42, 709, 8, 42, 3, 42, 711, 8, 42, 1, 43, 4, 43, 714, 8, 43, 11, 43, 12, 43, 715, 1, 44, 4, 44, 719, 8, 44, 11, 44, 12, 44, 720, 1, 44, 1, 44, 5, 44, 725, 8, 44, 10, 44, 12, 44, 728, 9, 44, 1, 44, 1, 44, 4, 44, 732, 8, 44, 11, 44, 12, 44, 733, 1, 44, 4, 44, 737, 8, 44, 11, 44, 12, 44, 738, 1, 44, 1, 44, 5, 44, 743, 8, 44, 10, 44, 12, 44, 746, 9, 44, 3, 44, 748, 8, 44, 1, 44, 1, 44, 1, 44, 1, 44, 4, 44, 754, 8, 44, 11, 44, 12, 44, 755, 1, 44, 1, 44, 3, 44, 760, 8, 44, 1, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 60, 1, 60, 1, 60, 1, 60, 1, 61, 1, 61, 1, 61, 1, 61, 1, 61, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 62, 1, 63, 1, 63, 1, 63, 1, 64, 1, 64, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 65, 1, 66, 1, 66, 1, 67, 1, 67, 1, 67, 1, 67, 1, 67, 1, 68, 1, 68, 1, 68, 1, 69, 1, 69, 1, 69, 1, 70, 1, 70, 1, 70, 1, 71, 1, 71, 1, 72, 1, 72, 1, 72, 1, 73, 1, 73, 1, 74, 1, 74, 1, 74, 1, 75, 1, 75, 1, 76, 1, 76, 1, 77, 1, 77, 1, 78, 1, 78, 1, 79, 1, 79, 1, 80, 1, 80, 1, 80, 5, 80, 882, 8, 80, 10, 80, 12, 80, 885, 9, 80, 1, 80, 1, 80, 4, 80, 889, 8, 80, 11, 80, 12, 80, 890, 3, 80, 893, 8, 80, 1, 81, 1, 81, 1, 81, 1, 81, 1, 81, 1, 82, 1, 82, 1, 82, 1, 82, 1, 82, 1, 83, 1, 83, 5, 83, 907, 8, 83, 10, 83, 12, 83, 910, 9, 83, 1, 83, 1, 83, 3, 83, 914, 8, 83, 1, 83, 4, 83, 917, 8, 83, 11, 83, 12, 83, 918, 3, 83, 921, 8, 83, 1, 84, 1, 84, 4, 84, 925, 8, 84, 11, 84, 12, 84, 926, 1, 84, 1, 84, 1, 85, 1, 85, 1, 86, 1, 86, 1, 86, 1, 86, 1, 87, 1, 87, 1, 87, 1, 87, 1, 88, 1, 88, 1, 88, 1, 88, 1, 89, 1, 89, 1, 89, 1, 89, 1, 89, 1, 90, 1, 90, 1, 90, 1, 90, 1, 91, 1, 91, 1, 91, 1, 91, 1, 92, 1, 92, 1, 92, 1, 92, 1, 93, 1, 93, 1, 93, 1, 93, 1, 94, 1, 94, 1, 94, 1, 94, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 95, 1, 96, 1, 96, 1, 96, 1, 96, 1, 97, 1, 97, 1, 97, 1, 97, 1, 98, 1, 98, 1, 98, 1, 98, 1, 99, 1, 99, 1, 99, 1, 99, 1, 100, 1, 100, 1, 100, 1, 100, 1, 100, 1, 101, 1, 101, 1, 101, 1, 101, 1, 102, 1, 102, 1, 102, 1, 102, 1, 103, 1, 103, 1, 103, 1, 103, 3, 103, 1012, 8, 103, 1, 104, 1, 104, 3, 104, 1016, 8, 104, 1, 104, 5, 104, 1019, 8, 104, 10, 104, 12, 104, 1022, 9, 104, 1, 104, 1, 104, 3, 104, 1026, 8, 104, 1, 104, 4, 104, 1029, 8, 104, 11, 104, 12, 104, 1030, 3, 104, 1033, 8, 104, 1, 105, 1, 105, 4, 105, 1037, 8, 105, 11, 105, 12, 105, 1038, 1, 106, 1, 106, 1, 106, 1, 106, 1, 107, 1, 107, 1, 107, 1, 107, 1, 108, 1, 108, 1, 108, 1, 108, 1, 109, 1, 109, 1, 109, 1, 109, 1, 109, 1, 110, 1, 110, 1, 110, 1, 110, 1, 111, 1, 111, 1, 111, 1, 111, 1, 112, 1, 112, 1, 112, 1, 112, 1, 113, 1, 113, 1, 113, 1, 114, 1, 114, 1, 114, 1, 114, 1, 115, 1, 115, 1, 115, 1, 115, 1, 116, 1, 116, 1, 116, 1, 116, 1, 117, 1, 117, 1, 117, 1, 117, 1, 118, 1, 118, 1, 118, 1, 118, 1, 118, 1, 119, 1, 119, 1, 119, 1, 119, 1, 119, 1, 120, 1, 120, 1, 120, 1, 120, 1, 120, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 121, 1, 122, 1, 122, 1, 123, 4, 123, 1114, 8, 123, 11, 123, 12, 123, 1115, 1, 123, 1, 123, 3, 123, 1120, 8, 123, 1, 123, 4, 123, 1123, 8, 123, 11, 123, 12, 123, 1124, 1, 124, 1, 124, 1, 124, 1, 124, 1, 125, 1, 125, 1, 125, 1, 125, 1, 126, 1, 126, 1, 126, 1, 126, 1, 127, 1, 127, 1, 127, 1, 127, 1, 128, 1, 128, 1, 128, 1, 128, 1, 129, 1, 129, 1, 129, 1, 129, 1, 129, 1, 129, 1, 130, 1, 130, 1, 130, 1, 130, 1, 131, 1, 131, 1, 131, 1, 131, 1, 132, 1, 132, 1, 132, 1, 132, 1, 133, 1, 133, 1, 133, 1, 133, 1, 134, 1, 134, 1, 134, 1, 134, 1, 135, 1, 135, 1, 135, 1, 135, 1, 136, 1, 136, 1, 136, 1, 136, 1, 137, 1, 137, 1, 137, 1, 137, 1, 138, 1, 138, 1, 138, 1, 138, 1, 139, 1, 139, 1, 139, 1, 139, 1, 139, 1, 140, 1, 140, 1, 140, 1, 140, 1, 141, 1, 141, 1, 141, 1, 141, 1, 142, 1, 142, 1, 142, 1, 142, 1, 142, 1, 143, 1, 143, 1, 143, 1, 143, 1, 144, 1, 144, 1, 144, 1, 144, 1, 145, 1, 145, 1, 145, 1, 145, 1, 146, 1, 146, 1, 146, 1, 146, 1, 147, 1, 147, 1, 147, 1, 147, 1, 147, 1, 147, 1, 148, 1, 148, 1, 148, 1, 148, 1, 149, 1, 149, 1, 149, 1, 149, 1, 150, 1, 150, 1, 150, 1, 150, 1, 151, 1, 151, 1, 151, 1, 151, 1, 152, 1, 152, 1, 152, 1, 152, 1, 153, 1, 153, 1, 153, 1, 153, 1, 154, 1, 154, 1, 154, 1, 154, 1, 154, 1, 155, 1, 155, 1, 155, 1, 155, 1, 156, 1, 156, 1, 156, 1, 156, 1, 157, 1, 157, 1, 157, 1, 157, 1, 158, 1, 158, 1, 158, 1, 158, 1, 159, 1, 159, 1, 159, 1, 159, 1, 160, 1, 160, 1, 160, 1, 160, 1, 161, 1, 161, 1, 161, 1, 161, 1, 161, 1, 162, 1, 162, 1, 162, 1, 162, 1, 162, 1, 163, 1, 163, 1, 163, 1, 163, 1, 164, 1, 164, 1, 164, 1, 164, 1, 165, 1, 165, 1, 165, 1, 165, 1, 166, 1, 166, 1, 166, 1, 166, 1, 166, 1, 167, 1, 167, 1, 167, 1, 167, 1, 167, 1, 167, 1, 167, 1, 167, 1, 167, 1, 167, 1, 168, 1, 168, 1, 168, 1, 168, 1, 169, 1, 169, 1, 169, 1, 169, 1, 170, 1, 170, 1, 170, 1, 170, 1, 171, 1, 171, 1, 171, 1, 171, 1, 171, 1, 172, 1, 172, 1, 173, 1, 173, 1, 173, 1, 173, 1, 173, 4, 173, 1343, 8, 173, 11, 173, 12, 173, 1344, 1, 174, 1, 174, 1, 174, 1, 174, 1, 175, 1, 175, 1, 175, 1, 175, 1, 176, 1, 176, 1, 176, 1, 176, 1, 177, 1, 177, 1, 177, 1, 177, 1, 177, 1, 178, 1, 178, 1, 178, 1, 178, 1, 178, 1, 178, 1, 179, 1, 179, 1, 179, 1, 179, 1, 180, 1, 180, 1, 180, 1, 180, 1, 181, 1, 181, 1, 181, 1, 181, 1, 182, 1, 182, 1, 182, 1, 182, 1, 182, 1, 182, 1, 183, 1, 183, 1, 183, 1, 183, 1, 184, 1, 184, 1, 184, 1, 184, 1, 185, 1, 185, 1, 185, 1, 185, 1, 186, 1, 186, 1, 186, 1, 186, 1, 186, 1, 186, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 187, 1, 188, 1, 188, 1, 188, 1, 188, 1, 188, 1, 188, 1, 189, 1, 189, 1, 189, 1, 189, 1, 189, 2, 596, 697, 0, 190, 16, 1, 18, 2, 20, 3, 22, 4, 24, 5, 26, 6, 28, 7, 30, 8, 32, 9, 34, 10, 36, 11, 38, 12, 40, 13, 42, 14, 44, 15, 46, 16, 48, 17, 50, 18, 52, 19, 54, 20, 56, 21, 58, 22, 60, 23, 62, 24, 64, 0, 66, 25, 68, 0, 70, 0, 72, 26, 74, 27, 76, 28, 78, 29, 80, 0, 82, 0, 84, 0, 86, 0, 88, 0, 90, 0, 92, 0, 94, 0, 96, 0, 98, 0, 100, 30, 102, 31, 104, 32, 106, 33, 108, 34, 110, 35, 112, 36, 114, 37, 116, 38, 118, 39, 120, 40, 122, 41, 124, 42, 126, 43, 128, 44, 130, 45, 132, 46, 134, 47, 136, 48, 138, 49, 140, 50, 142, 51, 144, 52, 146, 53, 148, 54, 150, 55, 152, 56, 154, 57, 156, 58, 158, 59, 160, 60, 162, 61, 164, 62, 166, 63, 168, 64, 170, 65, 172, 66, 174, 67, 176, 68, 178, 69, 180, 70, 182, 71, 184, 0, 186, 72, 188, 73, 190, 74, 192, 75, 194, 0, 196, 0, 198, 0, 200, 0, 202, 0, 204, 0, 206, 76, 208, 0, 210, 77, 212, 78, 214, 79, 216, 0, 218, 0, 220, 0, 222, 0, 224, 0, 226, 80, 228, 81, 230, 82, 232, 83, 234, 0, 236, 0, 238, 0, 240, 0, 242, 84, 244, 0, 246, 85, 248, 86, 250, 87, 252, 0, 254, 0, 256, 88, 258, 89, 260, 0, 262, 90, 264, 0, 266, 0, 268, 91, 270, 92, 272, 93, 274, 0, 276, 0, 278, 0, 280, 0, 282, 0, 284, 0, 286, 0, 288, 94, 290, 95, 292, 96, 294, 0, 296, 0, 298, 0, 300, 0, 302, 0, 304, 97, 306, 98, 308, 99, 310, 0, 312, 0, 314, 0, 316, 0, 318, 100, 320, 101, 322, 102, 324, 0, 326, 0, 328, 0, 330, 0, 332, 103, 334, 104, 336, 105, 338, 0, 340, 106, 342, 107, 344, 108, 346, 109, 348, 0, 350, 110, 352, 111, 354, 112, 356, 113, 358, 0, 360, 114, 362, 115, 364, 116, 366, 117, 368, 118, 370, 0, 372, 0, 374, 119, 376, 120, 378, 121, 380, 0, 382, 122, 384, 123, 386, 124, 388, 0, 390, 0, 392, 0, 394, 0, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 35, 2, 0, 68, 68, 100, 100, 2, 0, 73, 73, 105, 105, 2, 0, 83, 83, 115, 115, 2, 0, 69, 69, 101, 101, 2, 0, 67, 67, 99, 99, 2, 0, 84, 84, 116, 116, 2, 0, 82, 82, 114, 114, 2, 0, 79, 79, 111, 111, 2, 0, 80, 80, 112, 112, 2, 0, 78, 78, 110, 110, 2, 0, 72, 72, 104, 104, 2, 0, 86, 86, 118, 118, 2, 0, 65, 65, 97, 97, 2, 0, 76, 76, 108, 108, 2, 0, 88, 88, 120, 120, 2, 0, 70, 70, 102, 102, 2, 0, 77, 77, 109, 109, 2, 0, 71, 71, 103, 103, 2, 0, 75, 75, 107, 107, 2, 0, 85, 85, 117, 117, 2, 0, 87, 87, 119, 119, 6, 0, 9, 10, 13, 13, 32, 32, 47, 47, 91, 91, 93, 93, 2, 0, 10, 10, 13, 13, 3, 0, 9, 10, 13, 13, 32, 32, 10, 0, 9, 10, 13, 13, 32, 32, 44, 44, 47, 47, 61, 61, 91, 91, 93, 93, 96, 96, 124, 124, 2, 0, 42, 42, 47, 47, 1, 0, 48, 57, 2, 0, 65, 90, 97, 122, 8, 0, 34, 34, 78, 78, 82, 82, 84, 84, 92, 92, 110, 110, 114, 114, 116, 116, 4, 0, 10, 10, 13, 13, 34, 34, 92, 92, 2, 0, 43, 43, 45, 45, 1, 0, 96, 96, 2, 0, 66, 66, 98, 98, 2, 0, 89, 89, 121, 121, 11, 0, 9, 10, 13, 13, 32, 32, 34, 35, 44, 44, 47, 47, 58, 58, 60, 60, 62, 63, 92, 92, 124, 124, 1448, 0, 16, 1, 0, 0, 0, 0, 18, 1, 0, 0, 0, 0, 20, 1, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 24, 1, 0, 0, 0, 0, 26, 1, 0, 0, 0, 0, 28, 1, 0, 0, 0, 0, 30, 1, 0, 0, 0, 0, 32, 1, 0, 0, 0, 0, 34, 1, 0, 0, 0, 0, 36, 1, 0, 0, 0, 0, 38, 1, 0, 0, 0, 0, 40, 1, 0, 0, 0, 0, 42, 1, 0, 0, 0, 0, 44, 1, 0, 0, 0, 0, 46, 1, 0, 0, 0, 0, 48, 1, 0, 0, 0, 0, 50, 1, 0, 0, 0, 0, 52, 1, 0, 0, 0, 0, 54, 1, 0, 0, 0, 0, 56, 1, 0, 0, 0, 0, 58, 1, 0, 0, 0, 0, 60, 1, 0, 0, 0, 0, 62, 1, 0, 0, 0, 0, 66, 1, 0, 0, 0, 1, 68, 1, 0, 0, 0, 1, 70, 1, 0, 0, 0, 1, 72, 1, 0, 0, 0, 1, 74, 1, 0, 0, 0, 1, 76, 1, 0, 0, 0, 2, 78, 1, 0, 0, 0, 2, 100, 1, 0, 0, 0, 2, 102, 1, 0, 0, 0, 2, 104, 1, 0, 0, 0, 2, 106, 1, 0, 0, 0, 2, 108, 1, 0, 0, 0, 2, 110, 1, 0, 0, 0, 2, 112, 1, 0, 0, 0, 2, 114, 1, 0, 0, 0, 2, 116, 1, 0, 0, 0, 2, 118, 1, 0, 0, 0, 2, 120, 1, 0, 0, 0, 2, 122, 1, 0, 0, 0, 2, 124, 1, 0, 0, 0, 2, 126, 1, 0, 0, 0, 2, 128, 1, 0, 0, 0, 2, 130, 1, 0, 0, 0, 2, 132, 1, 0, 0, 0, 2, 134, 1, 0, 0, 0, 2, 136, 1, 0, 0, 0, 2, 138, 1, 0, 0, 0, 2, 140, 1, 0, 0, 0, 2, 142, 1, 0, 0, 0, 2, 144, 1, 0, 0, 0, 2, 146, 1, 0, 0, 0, 2, 148, 1, 0, 0, 0, 2, 150, 1, 0, 0, 0, 2, 152, 1, 0, 0, 0, 2, 154, 1, 0, 0, 0, 2, 156, 1, 0, 0, 0, 2, 158, 1, 0, 0, 0, 2, 160, 1, 0, 0, 0, 2, 162, 1, 0, 0, 0, 2, 164, 1, 0, 0, 0, 2, 166, 1, 0, 0, 0, 2, 168, 1, 0, 0, 0, 2, 170, 1, 0, 0, 0, 2, 172, 1, 0, 0, 0, 2, 174, 1, 0, 0, 0, 2, 176, 1, 0, 0, 0, 2, 178, 1, 0, 0, 0, 2, 180, 1, 0, 0, 0, 2, 182, 1, 0, 0, 0, 2, 186, 1, 0, 0, 0, 2, 188, 1, 0, 0, 0, 2, 190, 1, 0, 0, 0, 2, 192, 1, 0, 0, 0, 3, 194, 1, 0, 0, 0, 3, 196, 1, 0, 0, 0, 3, 198, 1, 0, 0, 0, 3, 200, 1, 0, 0, 0, 3, 202, 1, 0, 0, 0, 3, 204, 1, 0, 0, 0, 3, 206, 1, 0, 0, 0, 3, 208, 1, 0, 0, 0, 3, 210, 1, 0, 0, 0, 3, 212, 1, 0, 0, 0, 3, 214, 1, 0, 0, 0, 4, 216, 1, 0, 0, 0, 4, 218, 1, 0, 0, 0, 4, 220, 1, 0, 0, 0, 4, 226, 1, 0, 0, 0, 4, 228, 1, 0, 0, 0, 4, 230, 1, 0, 0, 0, 4, 232, 1, 0, 0, 0, 5, 234, 1, 0, 0, 0, 5, 236, 1, 0, 0, 0, 5, 238, 1, 0, 0, 0, 5, 240, 1, 0, 0, 0, 5, 242, 1, 0, 0, 0, 5, 244, 1, 0, 0, 0, 5, 246, 1, 0, 0, 0, 5, 248, 1, 0, 0, 0, 5, 250, 1, 0, 0, 0, 6, 252, 1, 0, 0, 0, 6, 254, 1, 0, 0, 0, 6, 256, 1, 0, 0, 0, 6, 258, 1, 0, 0, 0, 6, 262, 1, 0, 0, 0, 6, 264, 1, 0, 0, 0, 6, 266, 1, 0, 0, 0, 6, 268, 1, 0, 0, 0, 6, 270, 1, 0, 0, 0, 6, 272, 1, 0, 0, 0, 7, 274, 1, 0, 0, 0, 7, 276, 1, 0, 0, 0, 7, 278, 1, 0, 0, 0, 7, 280, 1, 0, 0, 0, 7, 282, 1, 0, 0, 0, 7, 284, 1, 0, 0, 0, 7, 286, 1, 0, 0, 0, 7, 288, 1, 0, 0, 0, 7, 290, 1, 0, 0, 0, 7, 292, 1, 0, 0, 0, 8, 294, 1, 0, 0, 0, 8, 296, 1, 0, 0, 0, 8, 298, 1, 0, 0, 0, 8, 300, 1, 0, 0, 0, 8, 302, 1, 0, 0, 0, 8, 304, 1, 0, 0, 0, 8, 306, 1, 0, 0, 0, 8, 308, 1, 0, 0, 0, 9, 310, 1, 0, 0, 0, 9, 312, 1, 0, 0, 0, 9, 314, 1, 0, 0, 0, 9, 316, 1, 0, 0, 0, 9, 318, 1, 0, 0, 0, 9, 320, 1, 0, 0, 0, 9, 322, 1, 0, 0, 0, 10, 324, 1, 0, 0, 0, 10, 326, 1, 0, 0, 0, 10, 328, 1, 0, 0, 0, 10, 330, 1, 0, 0, 0, 10, 332, 1, 0, 0, 0, 10, 334, 1, 0, 0, 0, 10, 336, 1, 0, 0, 0, 11, 338, 1, 0, 0, 0, 11, 340, 1, 0, 0, 0, 11, 342, 1, 0, 0, 0, 11, 344, 1, 0, 0, 0, 11, 346, 1, 0, 0, 0, 12, 348, 1, 0, 0, 0, 12, 350, 1, 0, 0, 0, 12, 352, 1, 0, 0, 0, 12, 354, 1, 0, 0, 0, 12, 356, 1, 0, 0, 0, 13, 358, 1, 0, 0, 0, 13, 360, 1, 0, 0, 0, 13, 362, 1, 0, 0, 0, 13, 364, 1, 0, 0, 0, 13, 366, 1, 0, 0, 0, 13, 368, 1, 0, 0, 0, 14, 370, 1, 0, 0, 0, 14, 372, 1, 0, 0, 0, 14, 374, 1, 0, 0, 0, 14, 376, 1, 0, 0, 0, 14, 378, 1, 0, 0, 0, 15, 380, 1, 0, 0, 0, 15, 382, 1, 0, 0, 0, 15, 384, 1, 0, 0, 0, 15, 386, 1, 0, 0, 0, 15, 388, 1, 0, 0, 0, 15, 390, 1, 0, 0, 0, 15, 392, 1, 0, 0, 0, 15, 394, 1, 0, 0, 0, 16, 396, 1, 0, 0, 0, 18, 406, 1, 0, 0, 0, 20, 413, 1, 0, 0, 0, 22, 422, 1, 0, 0, 0, 24, 429, 1, 0, 0, 0, 26, 439, 1, 0, 0, 0, 28, 446, 1, 0, 0, 0, 30, 453, 1, 0, 0, 0, 32, 467, 1, 0, 0, 0, 34, 474, 1, 0, 0, 0, 36, 482, 1, 0, 0, 0, 38, 491, 1, 0, 0, 0, 40, 498, 1, 0, 0, 0, 42, 508, 1, 0, 0, 0, 44, 520, 1, 0, 0, 0, 46, 529, 1, 0, 0, 0, 48, 535, 1, 0, 0, 0, 50, 542, 1, 0, 0, 0, 52, 549, 1, 0, 0, 0, 54, 557, 1, 0, 0, 0, 56, 566, 1, 0, 0, 0, 58, 572, 1, 0, 0, 0, 60, 589, 1, 0, 0, 0, 62, 605, 1, 0, 0, 0, 64, 614, 1, 0, 0, 0, 66, 617, 1, 0, 0, 0, 68, 621, 1, 0, 0, 0, 70, 626, 1, 0, 0, 0, 72, 631, 1, 0, 0, 0, 74, 635, 1, 0, 0, 0, 76, 639, 1, 0, 0, 0, 78, 643, 1, 0, 0, 0, 80, 647, 1, 0, 0, 0, 82, 649, 1, 0, 0, 0, 84, 651, 1, 0, 0, 0, 86, 654, 1, 0, 0, 0, 88, 656, 1, 0, 0, 0, 90, 665, 1, 0, 0, 0, 92, 667, 1, 0, 0, 0, 94, 672, 1, 0, 0, 0, 96, 674, 1, 0, 0, 0, 98, 679, 1, 0, 0, 0, 100, 710, 1, 0, 0, 0, 102, 713, 1, 0, 0, 0, 104, 759, 1, 0, 0, 0, 106, 761, 1, 0, 0, 0, 108, 764, 1, 0, 0, 0, 110, 768, 1, 0, 0, 0, 112, 772, 1, 0, 0, 0, 114, 774, 1, 0, 0, 0, 116, 777, 1, 0, 0, 0, 118, 779, 1, 0, 0, 0, 120, 784, 1, 0, 0, 0, 122, 786, 1, 0, 0, 0, 124, 792, 1, 0, 0, 0, 126, 798, 1, 0, 0, 0, 128, 803, 1, 0, 0, 0, 130, 805, 1, 0, 0, 0, 132, 808, 1, 0, 0, 0, 134, 811, 1, 0, 0, 0, 136, 816, 1, 0, 0, 0, 138, 820, 1, 0, 0, 0, 140, 825, 1, 0, 0, 0, 142, 831, 1, 0, 0, 0, 144, 834, 1, 0, 0, 0, 146, 836, 1, 0, 0, 0, 148, 842, 1, 0, 0, 0, 150, 844, 1, 0, 0, 0, 152, 849, 1, 0, 0, 0, 154, 852, 1, 0, 0, 0, 156, 855, 1, 0, 0, 0, 158, 858, 1, 0, 0, 0, 160, 860, 1, 0, 0, 0, 162, 863, 1, 0, 0, 0, 164, 865, 1, 0, 0, 0, 166, 868, 1, 0, 0, 0, 168, 870, 1, 0, 0, 0, 170, 872, 1, 0, 0, 0, 172, 874, 1, 0, 0, 0, 174, 876, 1, 0, 0, 0, 176, 892, 1, 0, 0, 0, 178, 894, 1, 0, 0, 0, 180, 899, 1, 0, 0, 0, 182, 920, 1, 0, 0, 0, 184, 922, 1, 0, 0, 0, 186, 930, 1, 0, 0, 0, 188, 932, 1, 0, 0, 0, 190, 936, 1, 0, 0, 0, 192, 940, 1, 0, 0, 0, 194, 944, 1, 0, 0, 0, 196, 949, 1, 0, 0, 0, 198, 953, 1, 0, 0, 0, 200, 957, 1, 0, 0, 0, 202, 961, 1, 0, 0, 0, 204, 965, 1, 0, 0, 0, 206, 969, 1, 0, 0, 0, 208, 978, 1, 0, 0, 0, 210, 982, 1, 0, 0, 0, 212, 986, 1, 0, 0, 0, 214, 990, 1, 0, 0, 0, 216, 994, 1, 0, 0, 0, 218, 999, 1, 0, 0, 0, 220, 1003, 1, 0, 0, 0, 222, 1011, 1, 0, 0, 0, 224, 1032, 1, 0, 0, 0, 226, 1036, 1, 0, 0, 0, 228, 1040, 1, 0, 0, 0, 230, 1044, 1, 0, 0, 0, 232, 1048, 1, 0, 0, 0, 234, 1052, 1, 0, 0, 0, 236, 1057, 1, 0, 0, 0, 238, 1061, 1, 0, 0, 0, 240, 1065, 1, 0, 0, 0, 242, 1069, 1, 0, 0, 0, 244, 1072, 1, 0, 0, 0, 246, 1076, 1, 0, 0, 0, 248, 1080, 1, 0, 0, 0, 250, 1084, 1, 0, 0, 0, 252, 1088, 1, 0, 0, 0, 254, 1093, 1, 0, 0, 0, 256, 1098, 1, 0, 0, 0, 258, 1103, 1, 0, 0, 0, 260, 1110, 1, 0, 0, 0, 262, 1119, 1, 0, 0, 0, 264, 1126, 1, 0, 0, 0, 266, 1130, 1, 0, 0, 0, 268, 1134, 1, 0, 0, 0, 270, 1138, 1, 0, 0, 0, 272, 1142, 1, 0, 0, 0, 274, 1146, 1, 0, 0, 0, 276, 1152, 1, 0, 0, 0, 278, 1156, 1, 0, 0, 0, 280, 1160, 1, 0, 0, 0, 282, 1164, 1, 0, 0, 0, 284, 1168, 1, 0, 0, 0, 286, 1172, 1, 0, 0, 0, 288, 1176, 1, 0, 0, 0, 290, 1180, 1, 0, 0, 0, 292, 1184, 1, 0, 0, 0, 294, 1188, 1, 0, 0, 0, 296, 1193, 1, 0, 0, 0, 298, 1197, 1, 0, 0, 0, 300, 1201, 1, 0, 0, 0, 302, 1206, 1, 0, 0, 0, 304, 1210, 1, 0, 0, 0, 306, 1214, 1, 0, 0, 0, 308, 1218, 1, 0, 0, 0, 310, 1222, 1, 0, 0, 0, 312, 1228, 1, 0, 0, 0, 314, 1232, 1, 0, 0, 0, 316, 1236, 1, 0, 0, 0, 318, 1240, 1, 0, 0, 0, 320, 1244, 1, 0, 0, 0, 322, 1248, 1, 0, 0, 0, 324, 1252, 1, 0, 0, 0, 326, 1257, 1, 0, 0, 0, 328, 1261, 1, 0, 0, 0, 330, 1265, 1, 0, 0, 0, 332, 1269, 1, 0, 0, 0, 334, 1273, 1, 0, 0, 0, 336, 1277, 1, 0, 0, 0, 338, 1281, 1, 0, 0, 0, 340, 1286, 1, 0, 0, 0, 342, 1291, 1, 0, 0, 0, 344, 1295, 1, 0, 0, 0, 346, 1299, 1, 0, 0, 0, 348, 1303, 1, 0, 0, 0, 350, 1308, 1, 0, 0, 0, 352, 1318, 1, 0, 0, 0, 354, 1322, 1, 0, 0, 0, 356, 1326, 1, 0, 0, 0, 358, 1330, 1, 0, 0, 0, 360, 1335, 1, 0, 0, 0, 362, 1342, 1, 0, 0, 0, 364, 1346, 1, 0, 0, 0, 366, 1350, 1, 0, 0, 0, 368, 1354, 1, 0, 0, 0, 370, 1358, 1, 0, 0, 0, 372, 1363, 1, 0, 0, 0, 374, 1369, 1, 0, 0, 0, 376, 1373, 1, 0, 0, 0, 378, 1377, 1, 0, 0, 0, 380, 1381, 1, 0, 0, 0, 382, 1387, 1, 0, 0, 0, 384, 1391, 1, 0, 0, 0, 386, 1395, 1, 0, 0, 0, 388, 1399, 1, 0, 0, 0, 390, 1405, 1, 0, 0, 0, 392, 1411, 1, 0, 0, 0, 394, 1417, 1, 0, 0, 0, 396, 397, 7, 0, 0, 0, 397, 398, 7, 1, 0, 0, 398, 399, 7, 2, 0, 0, 399, 400, 7, 2, 0, 0, 400, 401, 7, 3, 0, 0, 401, 402, 7, 4, 0, 0, 402, 403, 7, 5, 0, 0, 403, 404, 1, 0, 0, 0, 404, 405, 6, 0, 0, 0, 405, 17, 1, 0, 0, 0, 406, 407, 7, 0, 0, 0, 407, 408, 7, 6, 0, 0, 408, 409, 7, 7, 0, 0, 409, 410, 7, 8, 0, 0, 410, 411, 1, 0, 0, 0, 411, 412, 6, 1, 1, 0, 412, 19, 1, 0, 0, 0, 413, 414, 7, 3, 0, 0, 414, 415, 7, 9, 0, 0, 415, 416, 7, 6, 0, 0, 416, 417, 7, 1, 0, 0, 417, 418, 7, 4, 0, 0, 418, 419, 7, 10, 0, 0, 419, 420, 1, 0, 0, 0, 420, 421, 6, 2, 2, 0, 421, 21, 1, 0, 0, 0, 422, 423, 7, 3, 0, 0, 423, 424, 7, 11, 0, 0, 424, 425, 7, 12, 0, 0, 425, 426, 7, 13, 0, 0, 426, 427, 1, 0, 0, 0, 427, 428, 6, 3, 0, 0, 428, 23, 1, 0, 0, 0, 429, 430, 7, 3, 0, 0, 430, 431, 7, 14, 0, 0, 431, 432, 7, 8, 0, 0, 432, 433, 7, 13, 0, 0, 433, 434, 7, 12, 0, 0, 434, 435, 7, 1, 0, 0, 435, 436, 7, 9, 0, 0, 436, 437, 1, 0, 0, 0, 437, 438, 6, 4, 3, 0, 438, 25, 1, 0, 0, 0, 439, 440, 7, 15, 0, 0, 440, 441, 7, 6, 0, 0, 441, 442, 7, 7, 0, 0, 442, 443, 7, 16, 0, 0, 443, 444, 1, 0, 0, 0, 444, 445, 6, 5, 4, 0, 445, 27, 1, 0, 0, 0, 446, 447, 7, 17, 0, 0, 447, 448, 7, 6, 0, 0, 448, 449, 7, 7, 0, 0, 449, 450, 7, 18, 0, 0, 450, 451, 1, 0, 0, 0, 451, 452, 6, 6, 0, 0, 452, 29, 1, 0, 0, 0, 453, 454, 7, 1, 0, 0, 454, 455, 7, 9, 0, 0, 455, 456, 7, 13, 0, 0, 456, 457, 7, 1, 0, 0, 457, 458, 7, 9, 0, 0, 458, 459, 7, 3, 0, 0, 459, 460, 7, 2, 0, 0, 460, 461, 7, 5, 0, 0, 461, 462, 7, 12, 0, 0, 462, 463, 7, 5, 0, 0, 463, 464, 7, 2, 0, 0, 464, 465, 1, 0, 0, 0, 465, 466, 6, 7, 0, 0, 466, 31, 1, 0, 0, 0, 467, 468, 7, 18, 0, 0, 468, 469, 7, 3, 0, 0, 469, 470, 7, 3, 0, 0, 470, 471, 7, 8, 0, 0, 471, 472, 1, 0, 0, 0, 472, 473, 6, 8, 1, 0, 473, 33, 1, 0, 0, 0, 474, 475, 7, 13, 0, 0, 475, 476, 7, 1, 0, 0, 476, 477, 7, 16, 0, 0, 477, 478, 7, 1, 0, 0, 478, 479, 7, 5, 0, 0, 479, 480, 1, 0, 0, 0, 480, 481, 6, 9, 0, 0, 481, 35, 1, 0, 0, 0, 482, 483, 7, 13, 0, 0, 483, 484, 7, 7, 0, 0, 484, 485, 7, 7, 0, 0, 485, 486, 7, 18, 0, 0, 486, 487, 7, 19, 0, 0, 487, 488, 7, 8, 0, 0, 488, 489, 1, 0, 0, 0, 489, 490, 6, 10, 5, 0, 490, 37, 1, 0, 0, 0, 491, 492, 7, 16, 0, 0, 492, 493, 7, 3, 0, 0, 493, 494, 7, 5, 0, 0, 494, 495, 7, 12, 0, 0, 495, 496, 1, 0, 0, 0, 496, 497, 6, 11, 6, 0, 497, 39, 1, 0, 0, 0, 498, 499, 7, 16, 0, 0, 499, 500, 7, 3, 0, 0, 500, 501, 7, 5, 0, 0, 501, 502, 7, 6, 0, 0, 502, 503, 7, 1, 0, 0, 503, 504, 7, 4, 0, 0, 504, 505, 7, 2, 0, 0, 505, 506, 1, 0, 0, 0, 506, 507, 6, 12, 7, 0, 507, 41, 1, 0, 0, 0, 508, 509, 7, 16, 0, 0, 509, 510, 7, 11, 0, 0, 510, 511, 5, 95, 0, 0, 511, 512, 7, 3, 0, 0, 512, 513, 7, 14, 0, 0, 513, 514, 7, 8, 0, 0, 514, 515, 7, 12, 0, 0, 515, 516, 7, 9, 0, 0, 516, 517, 7, 0, 0, 0, 517, 518, 1, 0, 0, 0, 518, 519, 6, 13, 8, 0, 519, 43, 1, 0, 0, 0, 520, 521, 7, 6, 0, 0, 521, 522, 7, 3, 0, 0, 522, 523, 7, 9, 0, 0, 523, 524, 7, 12, 0, 0, 524, 525, 7, 16, 0, 0, 525, 526, 7, 3, 0, 0, 526, 527, 1, 0, 0, 0, 527, 528, 6, 14, 9, 0, 528, 45, 1, 0, 0, 0, 529, 530, 7, 6, 0, 0, 530, 531, 7, 7, 0, 0, 531, 532, 7, 20, 0, 0, 532, 533, 1, 0, 0, 0, 533, 534, 6, 15, 0, 0, 534, 47, 1, 0, 0, 0, 535, 536, 7, 2, 0, 0, 536, 537, 7, 10, 0, 0, 537, 538, 7, 7, 0, 0, 538, 539, 7, 20, 0, 0, 539, 540, 1, 0, 0, 0, 540, 541, 6, 16, 10, 0, 541, 49, 1, 0, 0, 0, 542, 543, 7, 2, 0, 0, 543, 544, 7, 7, 0, 0, 544, 545, 7, 6, 0, 0, 545, 546, 7, 5, 0, 0, 546, 547, 1, 0, 0, 0, 547, 548, 6, 17, 0, 0, 548, 51, 1, 0, 0, 0, 549, 550, 7, 2, 0, 0, 550, 551, 7, 5, 0, 0, 551, 552, 7, 12, 0, 0, 552, 553, 7, 5, 0, 0, 553, 554, 7, 2, 0, 0, 554, 555, 1, 0, 0, 0, 555, 556, 6, 18, 0, 0, 556, 53, 1, 0, 0, 0, 557, 558, 7, 20, 0, 0, 558, 559, 7, 10, 0, 0, 559, 560, 7, 3, 0, 0, 560, 561, 7, 6, 0, 0, 561, 562, 7, 3, 0, 0, 562, 563, 1, 0, 0, 0, 563, 564, 6, 19, 0, 0, 564, 55, 1, 0, 0, 0, 565, 567, 8, 21, 0, 0, 566, 565, 1, 0, 0, 0, 567, 568, 1, 0, 0, 0, 568, 566, 1, 0, 0, 0, 568, 569, 1, 0, 0, 0, 569, 570, 1, 0, 0, 0, 570, 571, 6, 20, 0, 0, 571, 57, 1, 0, 0, 0, 572, 573, 5, 47, 0, 0, 573, 574, 5, 47, 0, 0, 574, 578, 1, 0, 0, 0, 575, 577, 8, 22, 0, 0, 576, 575, 1, 0, 0, 0, 577, 580, 1, 0, 0, 0, 578, 576, 1, 0, 0, 0, 578, 579, 1, 0, 0, 0, 579, 582, 1, 0, 0, 0, 580, 578, 1, 0, 0, 0, 581, 583, 5, 13, 0, 0, 582, 581, 1, 0, 0, 0, 582, 583, 1, 0, 0, 0, 583, 585, 1, 0, 0, 0, 584, 586, 5, 10, 0, 0, 585, 584, 1, 0, 0, 0, 585, 586, 1, 0, 0, 0, 586, 587, 1, 0, 0, 0, 587, 588, 6, 21, 11, 0, 588, 59, 1, 0, 0, 0, 589, 590, 5, 47, 0, 0, 590, 591, 5, 42, 0, 0, 591, 596, 1, 0, 0, 0, 592, 595, 3, 60, 22, 0, 593, 595, 9, 0, 0, 0, 594, 592, 1, 0, 0, 0, 594, 593, 1, 0, 0, 0, 595, 598, 1, 0, 0, 0, 596, 597, 1, 0, 0, 0, 596, 594, 1, 0, 0, 0, 597, 599, 1, 0, 0, 0, 598, 596, 1, 0, 0, 0, 599, 600, 5, 42, 0, 0, 600, 601, 5, 47, 0, 0, 601, 602, 1, 0, 0, 0, 602, 603, 6, 22, 11, 0, 603, 61, 1, 0, 0, 0, 604, 606, 7, 23, 0, 0, 605, 604, 1, 0, 0, 0, 606, 607, 1, 0, 0, 0, 607, 605, 1, 0, 0, 0, 607, 608, 1, 0, 0, 0, 608, 609, 1, 0, 0, 0, 609, 610, 6, 23, 11, 0, 610, 63, 1, 0, 0, 0, 611, 615, 8, 24, 0, 0, 612, 613, 5, 47, 0, 0, 613, 615, 8, 25, 0, 0, 614, 611, 1, 0, 0, 0, 614, 612, 1, 0, 0, 0, 615, 65, 1, 0, 0, 0, 616, 618, 3, 64, 24, 0, 617, 616, 1, 0, 0, 0, 618, 619, 1, 0, 0, 0, 619, 617, 1, 0, 0, 0, 619, 620, 1, 0, 0, 0, 620, 67, 1, 0, 0, 0, 621, 622, 3, 178, 81, 0, 622, 623, 1, 0, 0, 0, 623, 624, 6, 26, 12, 0, 624, 625, 6, 26, 13, 0, 625, 69, 1, 0, 0, 0, 626, 627, 3, 78, 31, 0, 627, 628, 1, 0, 0, 0, 628, 629, 6, 27, 14, 0, 629, 630, 6, 27, 15, 0, 630, 71, 1, 0, 0, 0, 631, 632, 3, 62, 23, 0, 632, 633, 1, 0, 0, 0, 633, 634, 6, 28, 11, 0, 634, 73, 1, 0, 0, 0, 635, 636, 3, 58, 21, 0, 636, 637, 1, 0, 0, 0, 637, 638, 6, 29, 11, 0, 638, 75, 1, 0, 0, 0, 639, 640, 3, 60, 22, 0, 640, 641, 1, 0, 0, 0, 641, 642, 6, 30, 11, 0, 642, 77, 1, 0, 0, 0, 643, 644, 5, 124, 0, 0, 644, 645, 1, 0, 0, 0, 645, 646, 6, 31, 15, 0, 646, 79, 1, 0, 0, 0, 647, 648, 7, 26, 0, 0, 648, 81, 1, 0, 0, 0, 649, 650, 7, 27, 0, 0, 650, 83, 1, 0, 0, 0, 651, 652, 5, 92, 0, 0, 652, 653, 7, 28, 0, 0, 653, 85, 1, 0, 0, 0, 654, 655, 8, 29, 0, 0, 655, 87, 1, 0, 0, 0, 656, 658, 7, 3, 0, 0, 657, 659, 7, 30, 0, 0, 658, 657, 1, 0, 0, 0, 658, 659, 1, 0, 0, 0, 659, 661, 1, 0, 0, 0, 660, 662, 3, 80, 32, 0, 661, 660, 1, 0, 0, 0, 662, 663, 1, 0, 0, 0, 663, 661, 1, 0, 0, 0, 663, 664, 1, 0, 0, 0, 664, 89, 1, 0, 0, 0, 665, 666, 5, 64, 0, 0, 666, 91, 1, 0, 0, 0, 667, 668, 5, 96, 0, 0, 668, 93, 1, 0, 0, 0, 669, 673, 8, 31, 0, 0, 670, 671, 5, 96, 0, 0, 671, 673, 5, 96, 0, 0, 672, 669, 1, 0, 0, 0, 672, 670, 1, 0, 0, 0, 673, 95, 1, 0, 0, 0, 674, 675, 5, 95, 0, 0, 675, 97, 1, 0, 0, 0, 676, 680, 3, 82, 33, 0, 677, 680, 3, 80, 32, 0, 678, 680, 3, 96, 40, 0, 679, 676, 1, 0, 0, 0, 679, 677, 1, 0, 0, 0, 679, 678, 1, 0, 0, 0, 680, 99, 1, 0, 0, 0, 681, 686, 5, 34, 0, 0, 682, 685, 3, 84, 34, 0, 683, 685, 3, 86, 35, 0, 684, 682, 1, 0, 0, 0, 684, 683, 1, 0, 0, 0, 685, 688, 1, 0, 0, 0, 686, 684, 1, 0, 0, 0, 686, 687, 1, 0, 0, 0, 687, 689, 1, 0, 0, 0, 688, 686, 1, 0, 0, 0, 689, 711, 5, 34, 0, 0, 690, 691, 5, 34, 0, 0, 691, 692, 5, 34, 0, 0, 692, 693, 5, 34, 0, 0, 693, 697, 1, 0, 0, 0, 694, 696, 8, 22, 0, 0, 695, 694, 1, 0, 0, 0, 696, 699, 1, 0, 0, 0, 697, 698, 1, 0, 0, 0, 697, 695, 1, 0, 0, 0, 698, 700, 1, 0, 0, 0, 699, 697, 1, 0, 0, 0, 700, 701, 5, 34, 0, 0, 701, 702, 5, 34, 0, 0, 702, 703, 5, 34, 0, 0, 703, 705, 1, 0, 0, 0, 704, 706, 5, 34, 0, 0, 705, 704, 1, 0, 0, 0, 705, 706, 1, 0, 0, 0, 706, 708, 1, 0, 0, 0, 707, 709, 5, 34, 0, 0, 708, 707, 1, 0, 0, 0, 708, 709, 1, 0, 0, 0, 709, 711, 1, 0, 0, 0, 710, 681, 1, 0, 0, 0, 710, 690, 1, 0, 0, 0, 711, 101, 1, 0, 0, 0, 712, 714, 3, 80, 32, 0, 713, 712, 1, 0, 0, 0, 714, 715, 1, 0, 0, 0, 715, 713, 1, 0, 0, 0, 715, 716, 1, 0, 0, 0, 716, 103, 1, 0, 0, 0, 717, 719, 3, 80, 32, 0, 718, 717, 1, 0, 0, 0, 719, 720, 1, 0, 0, 0, 720, 718, 1, 0, 0, 0, 720, 721, 1, 0, 0, 0, 721, 722, 1, 0, 0, 0, 722, 726, 3, 120, 52, 0, 723, 725, 3, 80, 32, 0, 724, 723, 1, 0, 0, 0, 725, 728, 1, 0, 0, 0, 726, 724, 1, 0, 0, 0, 726, 727, 1, 0, 0, 0, 727, 760, 1, 0, 0, 0, 728, 726, 1, 0, 0, 0, 729, 731, 3, 120, 52, 0, 730, 732, 3, 80, 32, 0, 731, 730, 1, 0, 0, 0, 732, 733, 1, 0, 0, 0, 733, 731, 1, 0, 0, 0, 733, 734, 1, 0, 0, 0, 734, 760, 1, 0, 0, 0, 735, 737, 3, 80, 32, 0, 736, 735, 1, 0, 0, 0, 737, 738, 1, 0, 0, 0, 738, 736, 1, 0, 0, 0, 738, 739, 1, 0, 0, 0, 739, 747, 1, 0, 0, 0, 740, 744, 3, 120, 52, 0, 741, 743, 3, 80, 32, 0, 742, 741, 1, 0, 0, 0, 743, 746, 1, 0, 0, 0, 744, 742, 1, 0, 0, 0, 744, 745, 1, 0, 0, 0, 745, 748, 1, 0, 0, 0, 746, 744, 1, 0, 0, 0, 747, 740, 1, 0, 0, 0, 747, 748, 1, 0, 0, 0, 748, 749, 1, 0, 0, 0, 749, 750, 3, 88, 36, 0, 750, 760, 1, 0, 0, 0, 751, 753, 3, 120, 52, 0, 752, 754, 3, 80, 32, 0, 753, 752, 1, 0, 0, 0, 754, 755, 1, 0, 0, 0, 755, 753, 1, 0, 0, 0, 755, 756, 1, 0, 0, 0, 756, 757, 1, 0, 0, 0, 757, 758, 3, 88, 36, 0, 758, 760, 1, 0, 0, 0, 759, 718, 1, 0, 0, 0, 759, 729, 1, 0, 0, 0, 759, 736, 1, 0, 0, 0, 759, 751, 1, 0, 0, 0, 760, 105, 1, 0, 0, 0, 761, 762, 7, 32, 0, 0, 762, 763, 7, 33, 0, 0, 763, 107, 1, 0, 0, 0, 764, 765, 7, 12, 0, 0, 765, 766, 7, 9, 0, 0, 766, 767, 7, 0, 0, 0, 767, 109, 1, 0, 0, 0, 768, 769, 7, 12, 0, 0, 769, 770, 7, 2, 0, 0, 770, 771, 7, 4, 0, 0, 771, 111, 1, 0, 0, 0, 772, 773, 5, 61, 0, 0, 773, 113, 1, 0, 0, 0, 774, 775, 5, 58, 0, 0, 775, 776, 5, 58, 0, 0, 776, 115, 1, 0, 0, 0, 777, 778, 5, 44, 0, 0, 778, 117, 1, 0, 0, 0, 779, 780, 7, 0, 0, 0, 780, 781, 7, 3, 0, 0, 781, 782, 7, 2, 0, 0, 782, 783, 7, 4, 0, 0, 783, 119, 1, 0, 0, 0, 784, 785, 5, 46, 0, 0, 785, 121, 1, 0, 0, 0, 786, 787, 7, 15, 0, 0, 787, 788, 7, 12, 0, 0, 788, 789, 7, 13, 0, 0, 789, 790, 7, 2, 0, 0, 790, 791, 7, 3, 0, 0, 791, 123, 1, 0, 0, 0, 792, 793, 7, 15, 0, 0, 793, 794, 7, 1, 0, 0, 794, 795, 7, 6, 0, 0, 795, 796, 7, 2, 0, 0, 796, 797, 7, 5, 0, 0, 797, 125, 1, 0, 0, 0, 798, 799, 7, 13, 0, 0, 799, 800, 7, 12, 0, 0, 800, 801, 7, 2, 0, 0, 801, 802, 7, 5, 0, 0, 802, 127, 1, 0, 0, 0, 803, 804, 5, 40, 0, 0, 804, 129, 1, 0, 0, 0, 805, 806, 7, 1, 0, 0, 806, 807, 7, 9, 0, 0, 807, 131, 1, 0, 0, 0, 808, 809, 7, 1, 0, 0, 809, 810, 7, 2, 0, 0, 810, 133, 1, 0, 0, 0, 811, 812, 7, 13, 0, 0, 812, 813, 7, 1, 0, 0, 813, 814, 7, 18, 0, 0, 814, 815, 7, 3, 0, 0, 815, 135, 1, 0, 0, 0, 816, 817, 7, 9, 0, 0, 817, 818, 7, 7, 0, 0, 818, 819, 7, 5, 0, 0, 819, 137, 1, 0, 0, 0, 820, 821, 7, 9, 0, 0, 821, 822, 7, 19, 0, 0, 822, 823, 7, 13, 0, 0, 823, 824, 7, 13, 0, 0, 824, 139, 1, 0, 0, 0, 825, 826, 7, 9, 0, 0, 826, 827, 7, 19, 0, 0, 827, 828, 7, 13, 0, 0, 828, 829, 7, 13, 0, 0, 829, 830, 7, 2, 0, 0, 830, 141, 1, 0, 0, 0, 831, 832, 7, 7, 0, 0, 832, 833, 7, 6, 0, 0, 833, 143, 1, 0, 0, 0, 834, 835, 5, 63, 0, 0, 835, 145, 1, 0, 0, 0, 836, 837, 7, 6, 0, 0, 837, 838, 7, 13, 0, 0, 838, 839, 7, 1, 0, 0, 839, 840, 7, 18, 0, 0, 840, 841, 7, 3, 0, 0, 841, 147, 1, 0, 0, 0, 842, 843, 5, 41, 0, 0, 843, 149, 1, 0, 0, 0, 844, 845, 7, 5, 0, 0, 845, 846, 7, 6, 0, 0, 846, 847, 7, 19, 0, 0, 847, 848, 7, 3, 0, 0, 848, 151, 1, 0, 0, 0, 849, 850, 5, 61, 0, 0, 850, 851, 5, 61, 0, 0, 851, 153, 1, 0, 0, 0, 852, 853, 5, 61, 0, 0, 853, 854, 5, 126, 0, 0, 854, 155, 1, 0, 0, 0, 855, 856, 5, 33, 0, 0, 856, 857, 5, 61, 0, 0, 857, 157, 1, 0, 0, 0, 858, 859, 5, 60, 0, 0, 859, 159, 1, 0, 0, 0, 860, 861, 5, 60, 0, 0, 861, 862, 5, 61, 0, 0, 862, 161, 1, 0, 0, 0, 863, 864, 5, 62, 0, 0, 864, 163, 1, 0, 0, 0, 865, 866, 5, 62, 0, 0, 866, 867, 5, 61, 0, 0, 867, 165, 1, 0, 0, 0, 868, 869, 5, 43, 0, 0, 869, 167, 1, 0, 0, 0, 870, 871, 5, 45, 0, 0, 871, 169, 1, 0, 0, 0, 872, 873, 5, 42, 0, 0, 873, 171, 1, 0, 0, 0, 874, 875, 5, 47, 0, 0, 875, 173, 1, 0, 0, 0, 876, 877, 5, 37, 0, 0, 877, 175, 1, 0, 0, 0, 878, 879, 3, 144, 64, 0, 879, 883, 3, 82, 33, 0, 880, 882, 3, 98, 41, 0, 881, 880, 1, 0, 0, 0, 882, 885, 1, 0, 0, 0, 883, 881, 1, 0, 0, 0, 883, 884, 1, 0, 0, 0, 884, 893, 1, 0, 0, 0, 885, 883, 1, 0, 0, 0, 886, 888, 3, 144, 64, 0, 887, 889, 3, 80, 32, 0, 888, 887, 1, 0, 0, 0, 889, 890, 1, 0, 0, 0, 890, 888, 1, 0, 0, 0, 890, 891, 1, 0, 0, 0, 891, 893, 1, 0, 0, 0, 892, 878, 1, 0, 0, 0, 892, 886, 1, 0, 0, 0, 893, 177, 1, 0, 0, 0, 894, 895, 5, 91, 0, 0, 895, 896, 1, 0, 0, 0, 896, 897, 6, 81, 0, 0, 897, 898, 6, 81, 0, 0, 898, 179, 1, 0, 0, 0, 899, 900, 5, 93, 0, 0, 900, 901, 1, 0, 0, 0, 901, 902, 6, 82, 15, 0, 902, 903, 6, 82, 15, 0, 903, 181, 1, 0, 0, 0, 904, 908, 3, 82, 33, 0, 905, 907, 3, 98, 41, 0, 906, 905, 1, 0, 0, 0, 907, 910, 1, 0, 0, 0, 908, 906, 1, 0, 0, 0, 908, 909, 1, 0, 0, 0, 909, 921, 1, 0, 0, 0, 910, 908, 1, 0, 0, 0, 911, 914, 3, 96, 40, 0, 912, 914, 3, 90, 37, 0, 913, 911, 1, 0, 0, 0, 913, 912, 1, 0, 0, 0, 914, 916, 1, 0, 0, 0, 915, 917, 3, 98, 41, 0, 916, 915, 1, 0, 0, 0, 917, 918, 1, 0, 0, 0, 918, 916, 1, 0, 0, 0, 918, 919, 1, 0, 0, 0, 919, 921, 1, 0, 0, 0, 920, 904, 1, 0, 0, 0, 920, 913, 1, 0, 0, 0, 921, 183, 1, 0, 0, 0, 922, 924, 3, 92, 38, 0, 923, 925, 3, 94, 39, 0, 924, 923, 1, 0, 0, 0, 925, 926, 1, 0, 0, 0, 926, 924, 1, 0, 0, 0, 926, 927, 1, 0, 0, 0, 927, 928, 1, 0, 0, 0, 928, 929, 3, 92, 38, 0, 929, 185, 1, 0, 0, 0, 930, 931, 3, 184, 84, 0, 931, 187, 1, 0, 0, 0, 932, 933, 3, 58, 21, 0, 933, 934, 1, 0, 0, 0, 934, 935, 6, 86, 11, 0, 935, 189, 1, 0, 0, 0, 936, 937, 3, 60, 22, 0, 937, 938, 1, 0, 0, 0, 938, 939, 6, 87, 11, 0, 939, 191, 1, 0, 0, 0, 940, 941, 3, 62, 23, 0, 941, 942, 1, 0, 0, 0, 942, 943, 6, 88, 11, 0, 943, 193, 1, 0, 0, 0, 944, 945, 3, 78, 31, 0, 945, 946, 1, 0, 0, 0, 946, 947, 6, 89, 14, 0, 947, 948, 6, 89, 15, 0, 948, 195, 1, 0, 0, 0, 949, 950, 3, 178, 81, 0, 950, 951, 1, 0, 0, 0, 951, 952, 6, 90, 12, 0, 952, 197, 1, 0, 0, 0, 953, 954, 3, 180, 82, 0, 954, 955, 1, 0, 0, 0, 955, 956, 6, 91, 16, 0, 956, 199, 1, 0, 0, 0, 957, 958, 3, 116, 50, 0, 958, 959, 1, 0, 0, 0, 959, 960, 6, 92, 17, 0, 960, 201, 1, 0, 0, 0, 961, 962, 3, 112, 48, 0, 962, 963, 1, 0, 0, 0, 963, 964, 6, 93, 18, 0, 964, 203, 1, 0, 0, 0, 965, 966, 3, 100, 42, 0, 966, 967, 1, 0, 0, 0, 967, 968, 6, 94, 19, 0, 968, 205, 1, 0, 0, 0, 969, 970, 7, 16, 0, 0, 970, 971, 7, 3, 0, 0, 971, 972, 7, 5, 0, 0, 972, 973, 7, 12, 0, 0, 973, 974, 7, 0, 0, 0, 974, 975, 7, 12, 0, 0, 975, 976, 7, 5, 0, 0, 976, 977, 7, 12, 0, 0, 977, 207, 1, 0, 0, 0, 978, 979, 3, 66, 25, 0, 979, 980, 1, 0, 0, 0, 980, 981, 6, 96, 20, 0, 981, 209, 1, 0, 0, 0, 982, 983, 3, 58, 21, 0, 983, 984, 1, 0, 0, 0, 984, 985, 6, 97, 11, 0, 985, 211, 1, 0, 0, 0, 986, 987, 3, 60, 22, 0, 987, 988, 1, 0, 0, 0, 988, 989, 6, 98, 11, 0, 989, 213, 1, 0, 0, 0, 990, 991, 3, 62, 23, 0, 991, 992, 1, 0, 0, 0, 992, 993, 6, 99, 11, 0, 993, 215, 1, 0, 0, 0, 994, 995, 3, 78, 31, 0, 995, 996, 1, 0, 0, 0, 996, 997, 6, 100, 14, 0, 997, 998, 6, 100, 15, 0, 998, 217, 1, 0, 0, 0, 999, 1000, 3, 120, 52, 0, 1000, 1001, 1, 0, 0, 0, 1001, 1002, 6, 101, 21, 0, 1002, 219, 1, 0, 0, 0, 1003, 1004, 3, 116, 50, 0, 1004, 1005, 1, 0, 0, 0, 1005, 1006, 6, 102, 17, 0, 1006, 221, 1, 0, 0, 0, 1007, 1012, 3, 82, 33, 0, 1008, 1012, 3, 80, 32, 0, 1009, 1012, 3, 96, 40, 0, 1010, 1012, 3, 170, 77, 0, 1011, 1007, 1, 0, 0, 0, 1011, 1008, 1, 0, 0, 0, 1011, 1009, 1, 0, 0, 0, 1011, 1010, 1, 0, 0, 0, 1012, 223, 1, 0, 0, 0, 1013, 1016, 3, 82, 33, 0, 1014, 1016, 3, 170, 77, 0, 1015, 1013, 1, 0, 0, 0, 1015, 1014, 1, 0, 0, 0, 1016, 1020, 1, 0, 0, 0, 1017, 1019, 3, 222, 103, 0, 1018, 1017, 1, 0, 0, 0, 1019, 1022, 1, 0, 0, 0, 1020, 1018, 1, 0, 0, 0, 1020, 1021, 1, 0, 0, 0, 1021, 1033, 1, 0, 0, 0, 1022, 1020, 1, 0, 0, 0, 1023, 1026, 3, 96, 40, 0, 1024, 1026, 3, 90, 37, 0, 1025, 1023, 1, 0, 0, 0, 1025, 1024, 1, 0, 0, 0, 1026, 1028, 1, 0, 0, 0, 1027, 1029, 3, 222, 103, 0, 1028, 1027, 1, 0, 0, 0, 1029, 1030, 1, 0, 0, 0, 1030, 1028, 1, 0, 0, 0, 1030, 1031, 1, 0, 0, 0, 1031, 1033, 1, 0, 0, 0, 1032, 1015, 1, 0, 0, 0, 1032, 1025, 1, 0, 0, 0, 1033, 225, 1, 0, 0, 0, 1034, 1037, 3, 224, 104, 0, 1035, 1037, 3, 184, 84, 0, 1036, 1034, 1, 0, 0, 0, 1036, 1035, 1, 0, 0, 0, 1037, 1038, 1, 0, 0, 0, 1038, 1036, 1, 0, 0, 0, 1038, 1039, 1, 0, 0, 0, 1039, 227, 1, 0, 0, 0, 1040, 1041, 3, 58, 21, 0, 1041, 1042, 1, 0, 0, 0, 1042, 1043, 6, 106, 11, 0, 1043, 229, 1, 0, 0, 0, 1044, 1045, 3, 60, 22, 0, 1045, 1046, 1, 0, 0, 0, 1046, 1047, 6, 107, 11, 0, 1047, 231, 1, 0, 0, 0, 1048, 1049, 3, 62, 23, 0, 1049, 1050, 1, 0, 0, 0, 1050, 1051, 6, 108, 11, 0, 1051, 233, 1, 0, 0, 0, 1052, 1053, 3, 78, 31, 0, 1053, 1054, 1, 0, 0, 0, 1054, 1055, 6, 109, 14, 0, 1055, 1056, 6, 109, 15, 0, 1056, 235, 1, 0, 0, 0, 1057, 1058, 3, 112, 48, 0, 1058, 1059, 1, 0, 0, 0, 1059, 1060, 6, 110, 18, 0, 1060, 237, 1, 0, 0, 0, 1061, 1062, 3, 116, 50, 0, 1062, 1063, 1, 0, 0, 0, 1063, 1064, 6, 111, 17, 0, 1064, 239, 1, 0, 0, 0, 1065, 1066, 3, 120, 52, 0, 1066, 1067, 1, 0, 0, 0, 1067, 1068, 6, 112, 21, 0, 1068, 241, 1, 0, 0, 0, 1069, 1070, 7, 12, 0, 0, 1070, 1071, 7, 2, 0, 0, 1071, 243, 1, 0, 0, 0, 1072, 1073, 3, 226, 105, 0, 1073, 1074, 1, 0, 0, 0, 1074, 1075, 6, 114, 22, 0, 1075, 245, 1, 0, 0, 0, 1076, 1077, 3, 58, 21, 0, 1077, 1078, 1, 0, 0, 0, 1078, 1079, 6, 115, 11, 0, 1079, 247, 1, 0, 0, 0, 1080, 1081, 3, 60, 22, 0, 1081, 1082, 1, 0, 0, 0, 1082, 1083, 6, 116, 11, 0, 1083, 249, 1, 0, 0, 0, 1084, 1085, 3, 62, 23, 0, 1085, 1086, 1, 0, 0, 0, 1086, 1087, 6, 117, 11, 0, 1087, 251, 1, 0, 0, 0, 1088, 1089, 3, 78, 31, 0, 1089, 1090, 1, 0, 0, 0, 1090, 1091, 6, 118, 14, 0, 1091, 1092, 6, 118, 15, 0, 1092, 253, 1, 0, 0, 0, 1093, 1094, 3, 178, 81, 0, 1094, 1095, 1, 0, 0, 0, 1095, 1096, 6, 119, 12, 0, 1096, 1097, 6, 119, 23, 0, 1097, 255, 1, 0, 0, 0, 1098, 1099, 7, 7, 0, 0, 1099, 1100, 7, 9, 0, 0, 1100, 1101, 1, 0, 0, 0, 1101, 1102, 6, 120, 24, 0, 1102, 257, 1, 0, 0, 0, 1103, 1104, 7, 20, 0, 0, 1104, 1105, 7, 1, 0, 0, 1105, 1106, 7, 5, 0, 0, 1106, 1107, 7, 10, 0, 0, 1107, 1108, 1, 0, 0, 0, 1108, 1109, 6, 121, 24, 0, 1109, 259, 1, 0, 0, 0, 1110, 1111, 8, 34, 0, 0, 1111, 261, 1, 0, 0, 0, 1112, 1114, 3, 260, 122, 0, 1113, 1112, 1, 0, 0, 0, 1114, 1115, 1, 0, 0, 0, 1115, 1113, 1, 0, 0, 0, 1115, 1116, 1, 0, 0, 0, 1116, 1117, 1, 0, 0, 0, 1117, 1118, 3, 360, 172, 0, 1118, 1120, 1, 0, 0, 0, 1119, 1113, 1, 0, 0, 0, 1119, 1120, 1, 0, 0, 0, 1120, 1122, 1, 0, 0, 0, 1121, 1123, 3, 260, 122, 0, 1122, 1121, 1, 0, 0, 0, 1123, 1124, 1, 0, 0, 0, 1124, 1122, 1, 0, 0, 0, 1124, 1125, 1, 0, 0, 0, 1125, 263, 1, 0, 0, 0, 1126, 1127, 3, 186, 85, 0, 1127, 1128, 1, 0, 0, 0, 1128, 1129, 6, 124, 25, 0, 1129, 265, 1, 0, 0, 0, 1130, 1131, 3, 262, 123, 0, 1131, 1132, 1, 0, 0, 0, 1132, 1133, 6, 125, 26, 0, 1133, 267, 1, 0, 0, 0, 1134, 1135, 3, 58, 21, 0, 1135, 1136, 1, 0, 0, 0, 1136, 1137, 6, 126, 11, 0, 1137, 269, 1, 0, 0, 0, 1138, 1139, 3, 60, 22, 0, 1139, 1140, 1, 0, 0, 0, 1140, 1141, 6, 127, 11, 0, 1141, 271, 1, 0, 0, 0, 1142, 1143, 3, 62, 23, 0, 1143, 1144, 1, 0, 0, 0, 1144, 1145, 6, 128, 11, 0, 1145, 273, 1, 0, 0, 0, 1146, 1147, 3, 78, 31, 0, 1147, 1148, 1, 0, 0, 0, 1148, 1149, 6, 129, 14, 0, 1149, 1150, 6, 129, 15, 0, 1150, 1151, 6, 129, 15, 0, 1151, 275, 1, 0, 0, 0, 1152, 1153, 3, 112, 48, 0, 1153, 1154, 1, 0, 0, 0, 1154, 1155, 6, 130, 18, 0, 1155, 277, 1, 0, 0, 0, 1156, 1157, 3, 116, 50, 0, 1157, 1158, 1, 0, 0, 0, 1158, 1159, 6, 131, 17, 0, 1159, 279, 1, 0, 0, 0, 1160, 1161, 3, 120, 52, 0, 1161, 1162, 1, 0, 0, 0, 1162, 1163, 6, 132, 21, 0, 1163, 281, 1, 0, 0, 0, 1164, 1165, 3, 258, 121, 0, 1165, 1166, 1, 0, 0, 0, 1166, 1167, 6, 133, 27, 0, 1167, 283, 1, 0, 0, 0, 1168, 1169, 3, 226, 105, 0, 1169, 1170, 1, 0, 0, 0, 1170, 1171, 6, 134, 22, 0, 1171, 285, 1, 0, 0, 0, 1172, 1173, 3, 186, 85, 0, 1173, 1174, 1, 0, 0, 0, 1174, 1175, 6, 135, 25, 0, 1175, 287, 1, 0, 0, 0, 1176, 1177, 3, 58, 21, 0, 1177, 1178, 1, 0, 0, 0, 1178, 1179, 6, 136, 11, 0, 1179, 289, 1, 0, 0, 0, 1180, 1181, 3, 60, 22, 0, 1181, 1182, 1, 0, 0, 0, 1182, 1183, 6, 137, 11, 0, 1183, 291, 1, 0, 0, 0, 1184, 1185, 3, 62, 23, 0, 1185, 1186, 1, 0, 0, 0, 1186, 1187, 6, 138, 11, 0, 1187, 293, 1, 0, 0, 0, 1188, 1189, 3, 78, 31, 0, 1189, 1190, 1, 0, 0, 0, 1190, 1191, 6, 139, 14, 0, 1191, 1192, 6, 139, 15, 0, 1192, 295, 1, 0, 0, 0, 1193, 1194, 3, 116, 50, 0, 1194, 1195, 1, 0, 0, 0, 1195, 1196, 6, 140, 17, 0, 1196, 297, 1, 0, 0, 0, 1197, 1198, 3, 120, 52, 0, 1198, 1199, 1, 0, 0, 0, 1199, 1200, 6, 141, 21, 0, 1200, 299, 1, 0, 0, 0, 1201, 1202, 3, 256, 120, 0, 1202, 1203, 1, 0, 0, 0, 1203, 1204, 6, 142, 28, 0, 1204, 1205, 6, 142, 29, 0, 1205, 301, 1, 0, 0, 0, 1206, 1207, 3, 66, 25, 0, 1207, 1208, 1, 0, 0, 0, 1208, 1209, 6, 143, 20, 0, 1209, 303, 1, 0, 0, 0, 1210, 1211, 3, 58, 21, 0, 1211, 1212, 1, 0, 0, 0, 1212, 1213, 6, 144, 11, 0, 1213, 305, 1, 0, 0, 0, 1214, 1215, 3, 60, 22, 0, 1215, 1216, 1, 0, 0, 0, 1216, 1217, 6, 145, 11, 0, 1217, 307, 1, 0, 0, 0, 1218, 1219, 3, 62, 23, 0, 1219, 1220, 1, 0, 0, 0, 1220, 1221, 6, 146, 11, 0, 1221, 309, 1, 0, 0, 0, 1222, 1223, 3, 78, 31, 0, 1223, 1224, 1, 0, 0, 0, 1224, 1225, 6, 147, 14, 0, 1225, 1226, 6, 147, 15, 0, 1226, 1227, 6, 147, 15, 0, 1227, 311, 1, 0, 0, 0, 1228, 1229, 3, 116, 50, 0, 1229, 1230, 1, 0, 0, 0, 1230, 1231, 6, 148, 17, 0, 1231, 313, 1, 0, 0, 0, 1232, 1233, 3, 120, 52, 0, 1233, 1234, 1, 0, 0, 0, 1234, 1235, 6, 149, 21, 0, 1235, 315, 1, 0, 0, 0, 1236, 1237, 3, 226, 105, 0, 1237, 1238, 1, 0, 0, 0, 1238, 1239, 6, 150, 22, 0, 1239, 317, 1, 0, 0, 0, 1240, 1241, 3, 58, 21, 0, 1241, 1242, 1, 0, 0, 0, 1242, 1243, 6, 151, 11, 0, 1243, 319, 1, 0, 0, 0, 1244, 1245, 3, 60, 22, 0, 1245, 1246, 1, 0, 0, 0, 1246, 1247, 6, 152, 11, 0, 1247, 321, 1, 0, 0, 0, 1248, 1249, 3, 62, 23, 0, 1249, 1250, 1, 0, 0, 0, 1250, 1251, 6, 153, 11, 0, 1251, 323, 1, 0, 0, 0, 1252, 1253, 3, 78, 31, 0, 1253, 1254, 1, 0, 0, 0, 1254, 1255, 6, 154, 14, 0, 1255, 1256, 6, 154, 15, 0, 1256, 325, 1, 0, 0, 0, 1257, 1258, 3, 120, 52, 0, 1258, 1259, 1, 0, 0, 0, 1259, 1260, 6, 155, 21, 0, 1260, 327, 1, 0, 0, 0, 1261, 1262, 3, 186, 85, 0, 1262, 1263, 1, 0, 0, 0, 1263, 1264, 6, 156, 25, 0, 1264, 329, 1, 0, 0, 0, 1265, 1266, 3, 182, 83, 0, 1266, 1267, 1, 0, 0, 0, 1267, 1268, 6, 157, 30, 0, 1268, 331, 1, 0, 0, 0, 1269, 1270, 3, 58, 21, 0, 1270, 1271, 1, 0, 0, 0, 1271, 1272, 6, 158, 11, 0, 1272, 333, 1, 0, 0, 0, 1273, 1274, 3, 60, 22, 0, 1274, 1275, 1, 0, 0, 0, 1275, 1276, 6, 159, 11, 0, 1276, 335, 1, 0, 0, 0, 1277, 1278, 3, 62, 23, 0, 1278, 1279, 1, 0, 0, 0, 1279, 1280, 6, 160, 11, 0, 1280, 337, 1, 0, 0, 0, 1281, 1282, 3, 78, 31, 0, 1282, 1283, 1, 0, 0, 0, 1283, 1284, 6, 161, 14, 0, 1284, 1285, 6, 161, 15, 0, 1285, 339, 1, 0, 0, 0, 1286, 1287, 7, 1, 0, 0, 1287, 1288, 7, 9, 0, 0, 1288, 1289, 7, 15, 0, 0, 1289, 1290, 7, 7, 0, 0, 1290, 341, 1, 0, 0, 0, 1291, 1292, 3, 58, 21, 0, 1292, 1293, 1, 0, 0, 0, 1293, 1294, 6, 163, 11, 0, 1294, 343, 1, 0, 0, 0, 1295, 1296, 3, 60, 22, 0, 1296, 1297, 1, 0, 0, 0, 1297, 1298, 6, 164, 11, 0, 1298, 345, 1, 0, 0, 0, 1299, 1300, 3, 62, 23, 0, 1300, 1301, 1, 0, 0, 0, 1301, 1302, 6, 165, 11, 0, 1302, 347, 1, 0, 0, 0, 1303, 1304, 3, 78, 31, 0, 1304, 1305, 1, 0, 0, 0, 1305, 1306, 6, 166, 14, 0, 1306, 1307, 6, 166, 15, 0, 1307, 349, 1, 0, 0, 0, 1308, 1309, 7, 15, 0, 0, 1309, 1310, 7, 19, 0, 0, 1310, 1311, 7, 9, 0, 0, 1311, 1312, 7, 4, 0, 0, 1312, 1313, 7, 5, 0, 0, 1313, 1314, 7, 1, 0, 0, 1314, 1315, 7, 7, 0, 0, 1315, 1316, 7, 9, 0, 0, 1316, 1317, 7, 2, 0, 0, 1317, 351, 1, 0, 0, 0, 1318, 1319, 3, 58, 21, 0, 1319, 1320, 1, 0, 0, 0, 1320, 1321, 6, 168, 11, 0, 1321, 353, 1, 0, 0, 0, 1322, 1323, 3, 60, 22, 0, 1323, 1324, 1, 0, 0, 0, 1324, 1325, 6, 169, 11, 0, 1325, 355, 1, 0, 0, 0, 1326, 1327, 3, 62, 23, 0, 1327, 1328, 1, 0, 0, 0, 1328, 1329, 6, 170, 11, 0, 1329, 357, 1, 0, 0, 0, 1330, 1331, 3, 180, 82, 0, 1331, 1332, 1, 0, 0, 0, 1332, 1333, 6, 171, 16, 0, 1333, 1334, 6, 171, 15, 0, 1334, 359, 1, 0, 0, 0, 1335, 1336, 5, 58, 0, 0, 1336, 361, 1, 0, 0, 0, 1337, 1343, 3, 90, 37, 0, 1338, 1343, 3, 80, 32, 0, 1339, 1343, 3, 120, 52, 0, 1340, 1343, 3, 82, 33, 0, 1341, 1343, 3, 96, 40, 0, 1342, 1337, 1, 0, 0, 0, 1342, 1338, 1, 0, 0, 0, 1342, 1339, 1, 0, 0, 0, 1342, 1340, 1, 0, 0, 0, 1342, 1341, 1, 0, 0, 0, 1343, 1344, 1, 0, 0, 0, 1344, 1342, 1, 0, 0, 0, 1344, 1345, 1, 0, 0, 0, 1345, 363, 1, 0, 0, 0, 1346, 1347, 3, 58, 21, 0, 1347, 1348, 1, 0, 0, 0, 1348, 1349, 6, 174, 11, 0, 1349, 365, 1, 0, 0, 0, 1350, 1351, 3, 60, 22, 0, 1351, 1352, 1, 0, 0, 0, 1352, 1353, 6, 175, 11, 0, 1353, 367, 1, 0, 0, 0, 1354, 1355, 3, 62, 23, 0, 1355, 1356, 1, 0, 0, 0, 1356, 1357, 6, 176, 11, 0, 1357, 369, 1, 0, 0, 0, 1358, 1359, 3, 78, 31, 0, 1359, 1360, 1, 0, 0, 0, 1360, 1361, 6, 177, 14, 0, 1361, 1362, 6, 177, 15, 0, 1362, 371, 1, 0, 0, 0, 1363, 1364, 3, 66, 25, 0, 1364, 1365, 1, 0, 0, 0, 1365, 1366, 6, 178, 20, 0, 1366, 1367, 6, 178, 15, 0, 1367, 1368, 6, 178, 31, 0, 1368, 373, 1, 0, 0, 0, 1369, 1370, 3, 58, 21, 0, 1370, 1371, 1, 0, 0, 0, 1371, 1372, 6, 179, 11, 0, 1372, 375, 1, 0, 0, 0, 1373, 1374, 3, 60, 22, 0, 1374, 1375, 1, 0, 0, 0, 1375, 1376, 6, 180, 11, 0, 1376, 377, 1, 0, 0, 0, 1377, 1378, 3, 62, 23, 0, 1378, 1379, 1, 0, 0, 0, 1379, 1380, 6, 181, 11, 0, 1380, 379, 1, 0, 0, 0, 1381, 1382, 3, 116, 50, 0, 1382, 1383, 1, 0, 0, 0, 1383, 1384, 6, 182, 17, 0, 1384, 1385, 6, 182, 15, 0, 1385, 1386, 6, 182, 7, 0, 1386, 381, 1, 0, 0, 0, 1387, 1388, 3, 58, 21, 0, 1388, 1389, 1, 0, 0, 0, 1389, 1390, 6, 183, 11, 0, 1390, 383, 1, 0, 0, 0, 1391, 1392, 3, 60, 22, 0, 1392, 1393, 1, 0, 0, 0, 1393, 1394, 6, 184, 11, 0, 1394, 385, 1, 0, 0, 0, 1395, 1396, 3, 62, 23, 0, 1396, 1397, 1, 0, 0, 0, 1397, 1398, 6, 185, 11, 0, 1398, 387, 1, 0, 0, 0, 1399, 1400, 3, 186, 85, 0, 1400, 1401, 1, 0, 0, 0, 1401, 1402, 6, 186, 15, 0, 1402, 1403, 6, 186, 0, 0, 1403, 1404, 6, 186, 25, 0, 1404, 389, 1, 0, 0, 0, 1405, 1406, 3, 182, 83, 0, 1406, 1407, 1, 0, 0, 0, 1407, 1408, 6, 187, 15, 0, 1408, 1409, 6, 187, 0, 0, 1409, 1410, 6, 187, 30, 0, 1410, 391, 1, 0, 0, 0, 1411, 1412, 3, 106, 45, 0, 1412, 1413, 1, 0, 0, 0, 1413, 1414, 6, 188, 15, 0, 1414, 1415, 6, 188, 0, 0, 1415, 1416, 6, 188, 32, 0, 1416, 393, 1, 0, 0, 0, 1417, 1418, 3, 78, 31, 0, 1418, 1419, 1, 0, 0, 0, 1419, 1420, 6, 189, 14, 0, 1420, 1421, 6, 189, 15, 0, 1421, 395, 1, 0, 0, 0, 65, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 568, 578, 582, 585, 594, 596, 607, 614, 619, 658, 663, 672, 679, 684, 686, 697, 705, 708, 710, 715, 720, 726, 733, 738, 744, 747, 755, 759, 883, 890, 892, 908, 913, 918, 920, 926, 1011, 1015, 1020, 1025, 1030, 1032, 1036, 1038, 1115, 1119, 1124, 1342, 1344, 33, 5, 2, 0, 5, 4, 0, 5, 6, 0, 5, 1, 0, 5, 3, 0, 5, 8, 0, 5, 12, 0, 5, 14, 0, 5, 10, 0, 5, 5, 0, 5, 11, 0, 0, 1, 0, 7, 69, 0, 5, 0, 0, 7, 29, 0, 4, 0, 0, 7, 70, 0, 7, 38, 0, 7, 36, 0, 7, 30, 0, 7, 25, 0, 7, 40, 0, 7, 80, 0, 5, 13, 0, 5, 7, 0, 7, 72, 0, 7, 90, 0, 7, 89, 0, 7, 88, 0, 5, 9, 0, 7, 71, 0, 5, 15, 0, 7, 33, 0] \ No newline at end of file diff --git a/packages/kbn-esql-ast/src/antlr/esql_lexer.tokens b/packages/kbn-esql-ast/src/antlr/esql_lexer.tokens index 30ce0d5eea55b..04798fc3dca8a 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_lexer.tokens +++ b/packages/kbn-esql-ast/src/antlr/esql_lexer.tokens @@ -65,62 +65,63 @@ MINUS=64 ASTERISK=65 SLASH=66 PERCENT=67 -OPENING_BRACKET=68 -CLOSING_BRACKET=69 -UNQUOTED_IDENTIFIER=70 -QUOTED_IDENTIFIER=71 -EXPR_LINE_COMMENT=72 -EXPR_MULTILINE_COMMENT=73 -EXPR_WS=74 -METADATA=75 -FROM_LINE_COMMENT=76 -FROM_MULTILINE_COMMENT=77 -FROM_WS=78 -ID_PATTERN=79 -PROJECT_LINE_COMMENT=80 -PROJECT_MULTILINE_COMMENT=81 -PROJECT_WS=82 -AS=83 -RENAME_LINE_COMMENT=84 -RENAME_MULTILINE_COMMENT=85 -RENAME_WS=86 -ON=87 -WITH=88 -ENRICH_POLICY_NAME=89 -ENRICH_LINE_COMMENT=90 -ENRICH_MULTILINE_COMMENT=91 -ENRICH_WS=92 -ENRICH_FIELD_LINE_COMMENT=93 -ENRICH_FIELD_MULTILINE_COMMENT=94 -ENRICH_FIELD_WS=95 -LOOKUP_LINE_COMMENT=96 -LOOKUP_MULTILINE_COMMENT=97 -LOOKUP_WS=98 -LOOKUP_FIELD_LINE_COMMENT=99 -LOOKUP_FIELD_MULTILINE_COMMENT=100 -LOOKUP_FIELD_WS=101 -MVEXPAND_LINE_COMMENT=102 -MVEXPAND_MULTILINE_COMMENT=103 -MVEXPAND_WS=104 -INFO=105 -SHOW_LINE_COMMENT=106 -SHOW_MULTILINE_COMMENT=107 -SHOW_WS=108 -FUNCTIONS=109 -META_LINE_COMMENT=110 -META_MULTILINE_COMMENT=111 -META_WS=112 -COLON=113 -SETTING=114 -SETTING_LINE_COMMENT=115 -SETTTING_MULTILINE_COMMENT=116 -SETTING_WS=117 -METRICS_LINE_COMMENT=118 -METRICS_MULTILINE_COMMENT=119 -METRICS_WS=120 -CLOSING_METRICS_LINE_COMMENT=121 -CLOSING_METRICS_MULTILINE_COMMENT=122 -CLOSING_METRICS_WS=123 +NAMED_OR_POSITIONAL_PARAM=68 +OPENING_BRACKET=69 +CLOSING_BRACKET=70 +UNQUOTED_IDENTIFIER=71 +QUOTED_IDENTIFIER=72 +EXPR_LINE_COMMENT=73 +EXPR_MULTILINE_COMMENT=74 +EXPR_WS=75 +METADATA=76 +FROM_LINE_COMMENT=77 +FROM_MULTILINE_COMMENT=78 +FROM_WS=79 +ID_PATTERN=80 +PROJECT_LINE_COMMENT=81 +PROJECT_MULTILINE_COMMENT=82 +PROJECT_WS=83 +AS=84 +RENAME_LINE_COMMENT=85 +RENAME_MULTILINE_COMMENT=86 +RENAME_WS=87 +ON=88 +WITH=89 +ENRICH_POLICY_NAME=90 +ENRICH_LINE_COMMENT=91 +ENRICH_MULTILINE_COMMENT=92 +ENRICH_WS=93 +ENRICH_FIELD_LINE_COMMENT=94 +ENRICH_FIELD_MULTILINE_COMMENT=95 +ENRICH_FIELD_WS=96 +LOOKUP_LINE_COMMENT=97 +LOOKUP_MULTILINE_COMMENT=98 +LOOKUP_WS=99 +LOOKUP_FIELD_LINE_COMMENT=100 +LOOKUP_FIELD_MULTILINE_COMMENT=101 +LOOKUP_FIELD_WS=102 +MVEXPAND_LINE_COMMENT=103 +MVEXPAND_MULTILINE_COMMENT=104 +MVEXPAND_WS=105 +INFO=106 +SHOW_LINE_COMMENT=107 +SHOW_MULTILINE_COMMENT=108 +SHOW_WS=109 +FUNCTIONS=110 +META_LINE_COMMENT=111 +META_MULTILINE_COMMENT=112 +META_WS=113 +COLON=114 +SETTING=115 +SETTING_LINE_COMMENT=116 +SETTTING_MULTILINE_COMMENT=117 +SETTING_WS=118 +METRICS_LINE_COMMENT=119 +METRICS_MULTILINE_COMMENT=120 +METRICS_WS=121 +CLOSING_METRICS_LINE_COMMENT=122 +CLOSING_METRICS_MULTILINE_COMMENT=123 +CLOSING_METRICS_WS=124 'dissect'=1 'drop'=2 'enrich'=3 @@ -177,11 +178,11 @@ CLOSING_METRICS_WS=123 '*'=65 '/'=66 '%'=67 -']'=69 -'metadata'=75 -'as'=83 -'on'=87 -'with'=88 -'info'=105 -'functions'=109 -':'=113 +']'=70 +'metadata'=76 +'as'=84 +'on'=88 +'with'=89 +'info'=106 +'functions'=110 +':'=114 diff --git a/packages/kbn-esql-ast/src/antlr/esql_lexer.ts b/packages/kbn-esql-ast/src/antlr/esql_lexer.ts index 4dd20eceeb217..ff985e02ccf1d 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_lexer.ts +++ b/packages/kbn-esql-ast/src/antlr/esql_lexer.ts @@ -80,62 +80,63 @@ export default class esql_lexer extends Lexer { public static readonly ASTERISK = 65; public static readonly SLASH = 66; public static readonly PERCENT = 67; - public static readonly OPENING_BRACKET = 68; - public static readonly CLOSING_BRACKET = 69; - public static readonly UNQUOTED_IDENTIFIER = 70; - public static readonly QUOTED_IDENTIFIER = 71; - public static readonly EXPR_LINE_COMMENT = 72; - public static readonly EXPR_MULTILINE_COMMENT = 73; - public static readonly EXPR_WS = 74; - public static readonly METADATA = 75; - public static readonly FROM_LINE_COMMENT = 76; - public static readonly FROM_MULTILINE_COMMENT = 77; - public static readonly FROM_WS = 78; - public static readonly ID_PATTERN = 79; - public static readonly PROJECT_LINE_COMMENT = 80; - public static readonly PROJECT_MULTILINE_COMMENT = 81; - public static readonly PROJECT_WS = 82; - public static readonly AS = 83; - public static readonly RENAME_LINE_COMMENT = 84; - public static readonly RENAME_MULTILINE_COMMENT = 85; - public static readonly RENAME_WS = 86; - public static readonly ON = 87; - public static readonly WITH = 88; - public static readonly ENRICH_POLICY_NAME = 89; - public static readonly ENRICH_LINE_COMMENT = 90; - public static readonly ENRICH_MULTILINE_COMMENT = 91; - public static readonly ENRICH_WS = 92; - public static readonly ENRICH_FIELD_LINE_COMMENT = 93; - public static readonly ENRICH_FIELD_MULTILINE_COMMENT = 94; - public static readonly ENRICH_FIELD_WS = 95; - public static readonly LOOKUP_LINE_COMMENT = 96; - public static readonly LOOKUP_MULTILINE_COMMENT = 97; - public static readonly LOOKUP_WS = 98; - public static readonly LOOKUP_FIELD_LINE_COMMENT = 99; - public static readonly LOOKUP_FIELD_MULTILINE_COMMENT = 100; - public static readonly LOOKUP_FIELD_WS = 101; - public static readonly MVEXPAND_LINE_COMMENT = 102; - public static readonly MVEXPAND_MULTILINE_COMMENT = 103; - public static readonly MVEXPAND_WS = 104; - public static readonly INFO = 105; - public static readonly SHOW_LINE_COMMENT = 106; - public static readonly SHOW_MULTILINE_COMMENT = 107; - public static readonly SHOW_WS = 108; - public static readonly FUNCTIONS = 109; - public static readonly META_LINE_COMMENT = 110; - public static readonly META_MULTILINE_COMMENT = 111; - public static readonly META_WS = 112; - public static readonly COLON = 113; - public static readonly SETTING = 114; - public static readonly SETTING_LINE_COMMENT = 115; - public static readonly SETTTING_MULTILINE_COMMENT = 116; - public static readonly SETTING_WS = 117; - public static readonly METRICS_LINE_COMMENT = 118; - public static readonly METRICS_MULTILINE_COMMENT = 119; - public static readonly METRICS_WS = 120; - public static readonly CLOSING_METRICS_LINE_COMMENT = 121; - public static readonly CLOSING_METRICS_MULTILINE_COMMENT = 122; - public static readonly CLOSING_METRICS_WS = 123; + public static readonly NAMED_OR_POSITIONAL_PARAM = 68; + public static readonly OPENING_BRACKET = 69; + public static readonly CLOSING_BRACKET = 70; + public static readonly UNQUOTED_IDENTIFIER = 71; + public static readonly QUOTED_IDENTIFIER = 72; + public static readonly EXPR_LINE_COMMENT = 73; + public static readonly EXPR_MULTILINE_COMMENT = 74; + public static readonly EXPR_WS = 75; + public static readonly METADATA = 76; + public static readonly FROM_LINE_COMMENT = 77; + public static readonly FROM_MULTILINE_COMMENT = 78; + public static readonly FROM_WS = 79; + public static readonly ID_PATTERN = 80; + public static readonly PROJECT_LINE_COMMENT = 81; + public static readonly PROJECT_MULTILINE_COMMENT = 82; + public static readonly PROJECT_WS = 83; + public static readonly AS = 84; + public static readonly RENAME_LINE_COMMENT = 85; + public static readonly RENAME_MULTILINE_COMMENT = 86; + public static readonly RENAME_WS = 87; + public static readonly ON = 88; + public static readonly WITH = 89; + public static readonly ENRICH_POLICY_NAME = 90; + public static readonly ENRICH_LINE_COMMENT = 91; + public static readonly ENRICH_MULTILINE_COMMENT = 92; + public static readonly ENRICH_WS = 93; + public static readonly ENRICH_FIELD_LINE_COMMENT = 94; + public static readonly ENRICH_FIELD_MULTILINE_COMMENT = 95; + public static readonly ENRICH_FIELD_WS = 96; + public static readonly LOOKUP_LINE_COMMENT = 97; + public static readonly LOOKUP_MULTILINE_COMMENT = 98; + public static readonly LOOKUP_WS = 99; + public static readonly LOOKUP_FIELD_LINE_COMMENT = 100; + public static readonly LOOKUP_FIELD_MULTILINE_COMMENT = 101; + public static readonly LOOKUP_FIELD_WS = 102; + public static readonly MVEXPAND_LINE_COMMENT = 103; + public static readonly MVEXPAND_MULTILINE_COMMENT = 104; + public static readonly MVEXPAND_WS = 105; + public static readonly INFO = 106; + public static readonly SHOW_LINE_COMMENT = 107; + public static readonly SHOW_MULTILINE_COMMENT = 108; + public static readonly SHOW_WS = 109; + public static readonly FUNCTIONS = 110; + public static readonly META_LINE_COMMENT = 111; + public static readonly META_MULTILINE_COMMENT = 112; + public static readonly META_WS = 113; + public static readonly COLON = 114; + public static readonly SETTING = 115; + public static readonly SETTING_LINE_COMMENT = 116; + public static readonly SETTTING_MULTILINE_COMMENT = 117; + public static readonly SETTING_WS = 118; + public static readonly METRICS_LINE_COMMENT = 119; + public static readonly METRICS_MULTILINE_COMMENT = 120; + public static readonly METRICS_WS = 121; + public static readonly CLOSING_METRICS_LINE_COMMENT = 122; + public static readonly CLOSING_METRICS_MULTILINE_COMMENT = 123; + public static readonly CLOSING_METRICS_WS = 124; public static readonly EOF = Token.EOF; public static readonly EXPLAIN_MODE = 1; public static readonly EXPRESSION_MODE = 2; @@ -190,10 +191,11 @@ export default class esql_lexer extends Lexer { "'>='", "'+'", "'-'", "'*'", "'/'", "'%'", - null, "']'", null, null, + "']'", null, null, null, - null, "'metadata'", + null, null, + "'metadata'", null, null, null, null, null, null, @@ -253,6 +255,7 @@ export default class esql_lexer extends Lexer { "GTE", "PLUS", "MINUS", "ASTERISK", "SLASH", "PERCENT", + "NAMED_OR_POSITIONAL_PARAM", "OPENING_BRACKET", "CLOSING_BRACKET", "UNQUOTED_IDENTIFIER", @@ -326,29 +329,29 @@ export default class esql_lexer extends Lexer { "ASSIGN", "CAST_OP", "COMMA", "DESC", "DOT", "FALSE", "FIRST", "LAST", "LP", "IN", "IS", "LIKE", "NOT", "NULL", "NULLS", "OR", "PARAM", "RLIKE", "RP", "TRUE", "EQ", "CIEQ", "NEQ", "LT", "LTE", "GT", "GTE", "PLUS", "MINUS", - "ASTERISK", "SLASH", "PERCENT", "OPENING_BRACKET", "CLOSING_BRACKET", - "UNQUOTED_IDENTIFIER", "QUOTED_ID", "QUOTED_IDENTIFIER", "EXPR_LINE_COMMENT", - "EXPR_MULTILINE_COMMENT", "EXPR_WS", "FROM_PIPE", "FROM_OPENING_BRACKET", - "FROM_CLOSING_BRACKET", "FROM_COMMA", "FROM_ASSIGN", "FROM_QUOTED_STRING", - "METADATA", "FROM_INDEX_UNQUOTED_IDENTIFIER", "FROM_LINE_COMMENT", "FROM_MULTILINE_COMMENT", - "FROM_WS", "PROJECT_PIPE", "PROJECT_DOT", "PROJECT_COMMA", "UNQUOTED_ID_BODY_WITH_PATTERN", - "UNQUOTED_ID_PATTERN", "ID_PATTERN", "PROJECT_LINE_COMMENT", "PROJECT_MULTILINE_COMMENT", - "PROJECT_WS", "RENAME_PIPE", "RENAME_ASSIGN", "RENAME_COMMA", "RENAME_DOT", - "AS", "RENAME_ID_PATTERN", "RENAME_LINE_COMMENT", "RENAME_MULTILINE_COMMENT", - "RENAME_WS", "ENRICH_PIPE", "ENRICH_OPENING_BRACKET", "ON", "WITH", "ENRICH_POLICY_NAME_BODY", - "ENRICH_POLICY_NAME", "ENRICH_QUOTED_IDENTIFIER", "ENRICH_MODE_UNQUOTED_VALUE", - "ENRICH_LINE_COMMENT", "ENRICH_MULTILINE_COMMENT", "ENRICH_WS", "ENRICH_FIELD_PIPE", - "ENRICH_FIELD_ASSIGN", "ENRICH_FIELD_COMMA", "ENRICH_FIELD_DOT", "ENRICH_FIELD_WITH", - "ENRICH_FIELD_ID_PATTERN", "ENRICH_FIELD_QUOTED_IDENTIFIER", "ENRICH_FIELD_LINE_COMMENT", - "ENRICH_FIELD_MULTILINE_COMMENT", "ENRICH_FIELD_WS", "LOOKUP_PIPE", "LOOKUP_COMMA", - "LOOKUP_DOT", "LOOKUP_ON", "LOOKUP_INDEX_UNQUOTED_IDENTIFIER", "LOOKUP_LINE_COMMENT", - "LOOKUP_MULTILINE_COMMENT", "LOOKUP_WS", "LOOKUP_FIELD_PIPE", "LOOKUP_FIELD_COMMA", - "LOOKUP_FIELD_DOT", "LOOKUP_FIELD_ID_PATTERN", "LOOKUP_FIELD_LINE_COMMENT", - "LOOKUP_FIELD_MULTILINE_COMMENT", "LOOKUP_FIELD_WS", "MVEXPAND_PIPE", - "MVEXPAND_DOT", "MVEXPAND_QUOTED_IDENTIFIER", "MVEXPAND_UNQUOTED_IDENTIFIER", - "MVEXPAND_LINE_COMMENT", "MVEXPAND_MULTILINE_COMMENT", "MVEXPAND_WS", - "SHOW_PIPE", "INFO", "SHOW_LINE_COMMENT", "SHOW_MULTILINE_COMMENT", "SHOW_WS", - "META_PIPE", "FUNCTIONS", "META_LINE_COMMENT", "META_MULTILINE_COMMENT", + "ASTERISK", "SLASH", "PERCENT", "NAMED_OR_POSITIONAL_PARAM", "OPENING_BRACKET", + "CLOSING_BRACKET", "UNQUOTED_IDENTIFIER", "QUOTED_ID", "QUOTED_IDENTIFIER", + "EXPR_LINE_COMMENT", "EXPR_MULTILINE_COMMENT", "EXPR_WS", "FROM_PIPE", + "FROM_OPENING_BRACKET", "FROM_CLOSING_BRACKET", "FROM_COMMA", "FROM_ASSIGN", + "FROM_QUOTED_STRING", "METADATA", "FROM_INDEX_UNQUOTED_IDENTIFIER", "FROM_LINE_COMMENT", + "FROM_MULTILINE_COMMENT", "FROM_WS", "PROJECT_PIPE", "PROJECT_DOT", "PROJECT_COMMA", + "UNQUOTED_ID_BODY_WITH_PATTERN", "UNQUOTED_ID_PATTERN", "ID_PATTERN", + "PROJECT_LINE_COMMENT", "PROJECT_MULTILINE_COMMENT", "PROJECT_WS", "RENAME_PIPE", + "RENAME_ASSIGN", "RENAME_COMMA", "RENAME_DOT", "AS", "RENAME_ID_PATTERN", + "RENAME_LINE_COMMENT", "RENAME_MULTILINE_COMMENT", "RENAME_WS", "ENRICH_PIPE", + "ENRICH_OPENING_BRACKET", "ON", "WITH", "ENRICH_POLICY_NAME_BODY", "ENRICH_POLICY_NAME", + "ENRICH_QUOTED_IDENTIFIER", "ENRICH_MODE_UNQUOTED_VALUE", "ENRICH_LINE_COMMENT", + "ENRICH_MULTILINE_COMMENT", "ENRICH_WS", "ENRICH_FIELD_PIPE", "ENRICH_FIELD_ASSIGN", + "ENRICH_FIELD_COMMA", "ENRICH_FIELD_DOT", "ENRICH_FIELD_WITH", "ENRICH_FIELD_ID_PATTERN", + "ENRICH_FIELD_QUOTED_IDENTIFIER", "ENRICH_FIELD_LINE_COMMENT", "ENRICH_FIELD_MULTILINE_COMMENT", + "ENRICH_FIELD_WS", "LOOKUP_PIPE", "LOOKUP_COMMA", "LOOKUP_DOT", "LOOKUP_ON", + "LOOKUP_INDEX_UNQUOTED_IDENTIFIER", "LOOKUP_LINE_COMMENT", "LOOKUP_MULTILINE_COMMENT", + "LOOKUP_WS", "LOOKUP_FIELD_PIPE", "LOOKUP_FIELD_COMMA", "LOOKUP_FIELD_DOT", + "LOOKUP_FIELD_ID_PATTERN", "LOOKUP_FIELD_LINE_COMMENT", "LOOKUP_FIELD_MULTILINE_COMMENT", + "LOOKUP_FIELD_WS", "MVEXPAND_PIPE", "MVEXPAND_DOT", "MVEXPAND_QUOTED_IDENTIFIER", + "MVEXPAND_UNQUOTED_IDENTIFIER", "MVEXPAND_LINE_COMMENT", "MVEXPAND_MULTILINE_COMMENT", + "MVEXPAND_WS", "SHOW_PIPE", "INFO", "SHOW_LINE_COMMENT", "SHOW_MULTILINE_COMMENT", + "SHOW_WS", "META_PIPE", "FUNCTIONS", "META_LINE_COMMENT", "META_MULTILINE_COMMENT", "META_WS", "SETTING_CLOSING_BRACKET", "COLON", "SETTING", "SETTING_LINE_COMMENT", "SETTTING_MULTILINE_COMMENT", "SETTING_WS", "METRICS_PIPE", "METRICS_INDEX_UNQUOTED_IDENTIFIER", "METRICS_LINE_COMMENT", "METRICS_MULTILINE_COMMENT", "METRICS_WS", "CLOSING_METRICS_COMMA", @@ -375,7 +378,7 @@ export default class esql_lexer extends Lexer { public get modeNames(): string[] { return esql_lexer.modeNames; } - public static readonly _serializedATN: number[] = [4,0,123,1404,6,-1,6, + public static readonly _serializedATN: number[] = [4,0,124,1422,6,-1,6, -1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1, 2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8, 2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14,2,15,7,15,2,16, @@ -405,456 +408,463 @@ export default class esql_lexer extends Lexer { 2,169,7,169,2,170,7,170,2,171,7,171,2,172,7,172,2,173,7,173,2,174,7,174, 2,175,7,175,2,176,7,176,2,177,7,177,2,178,7,178,2,179,7,179,2,180,7,180, 2,181,7,181,2,182,7,182,2,183,7,183,2,184,7,184,2,185,7,185,2,186,7,186, - 2,187,7,187,2,188,7,188,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,3,1,3,1,3,1,3, - 1,3,1,3,1,3,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,5,1,5,1,5,1,5,1,5, - 1,5,1,5,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7, - 1,7,1,7,1,7,1,7,1,7,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,9,1,9,1,9,1,9,1,9,1,9, - 1,9,1,9,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1,11,1,11,1,11,1,11, - 1,11,1,11,1,11,1,12,1,12,1,12,1,12,1,12,1,12,1,12,1,12,1,12,1,12,1,13,1, - 13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,14,1,14,1,14,1,14, - 1,14,1,14,1,14,1,14,1,14,1,15,1,15,1,15,1,15,1,15,1,15,1,16,1,16,1,16,1, - 16,1,16,1,16,1,16,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,18,1,18,1,18,1,18, - 1,18,1,18,1,18,1,18,1,19,1,19,1,19,1,19,1,19,1,19,1,19,1,19,1,20,4,20,565, - 8,20,11,20,12,20,566,1,20,1,20,1,21,1,21,1,21,1,21,5,21,575,8,21,10,21, - 12,21,578,9,21,1,21,3,21,581,8,21,1,21,3,21,584,8,21,1,21,1,21,1,22,1,22, - 1,22,1,22,1,22,5,22,593,8,22,10,22,12,22,596,9,22,1,22,1,22,1,22,1,22,1, - 22,1,23,4,23,604,8,23,11,23,12,23,605,1,23,1,23,1,24,1,24,1,24,3,24,613, - 8,24,1,25,4,25,616,8,25,11,25,12,25,617,1,26,1,26,1,26,1,26,1,26,1,27,1, - 27,1,27,1,27,1,27,1,28,1,28,1,28,1,28,1,29,1,29,1,29,1,29,1,30,1,30,1,30, - 1,30,1,31,1,31,1,31,1,31,1,32,1,32,1,33,1,33,1,34,1,34,1,34,1,35,1,35,1, - 36,1,36,3,36,657,8,36,1,36,4,36,660,8,36,11,36,12,36,661,1,37,1,37,1,38, - 1,38,1,39,1,39,1,39,3,39,671,8,39,1,40,1,40,1,41,1,41,1,41,3,41,678,8,41, - 1,42,1,42,1,42,5,42,683,8,42,10,42,12,42,686,9,42,1,42,1,42,1,42,1,42,1, - 42,1,42,5,42,694,8,42,10,42,12,42,697,9,42,1,42,1,42,1,42,1,42,1,42,3,42, - 704,8,42,1,42,3,42,707,8,42,3,42,709,8,42,1,43,4,43,712,8,43,11,43,12,43, - 713,1,44,4,44,717,8,44,11,44,12,44,718,1,44,1,44,5,44,723,8,44,10,44,12, - 44,726,9,44,1,44,1,44,4,44,730,8,44,11,44,12,44,731,1,44,4,44,735,8,44, - 11,44,12,44,736,1,44,1,44,5,44,741,8,44,10,44,12,44,744,9,44,3,44,746,8, - 44,1,44,1,44,1,44,1,44,4,44,752,8,44,11,44,12,44,753,1,44,1,44,3,44,758, - 8,44,1,45,1,45,1,45,1,46,1,46,1,46,1,46,1,47,1,47,1,47,1,47,1,48,1,48,1, - 49,1,49,1,49,1,50,1,50,1,51,1,51,1,51,1,51,1,51,1,52,1,52,1,53,1,53,1,53, - 1,53,1,53,1,53,1,54,1,54,1,54,1,54,1,54,1,54,1,55,1,55,1,55,1,55,1,55,1, - 56,1,56,1,57,1,57,1,57,1,58,1,58,1,58,1,59,1,59,1,59,1,59,1,59,1,60,1,60, - 1,60,1,60,1,61,1,61,1,61,1,61,1,61,1,62,1,62,1,62,1,62,1,62,1,62,1,63,1, - 63,1,63,1,64,1,64,1,65,1,65,1,65,1,65,1,65,1,65,1,66,1,66,1,67,1,67,1,67, - 1,67,1,67,1,68,1,68,1,68,1,69,1,69,1,69,1,70,1,70,1,70,1,71,1,71,1,72,1, - 72,1,72,1,73,1,73,1,74,1,74,1,74,1,75,1,75,1,76,1,76,1,77,1,77,1,78,1,78, - 1,79,1,79,1,80,1,80,1,80,1,80,1,80,1,81,1,81,1,81,1,81,1,81,1,82,1,82,5, - 82,889,8,82,10,82,12,82,892,9,82,1,82,1,82,3,82,896,8,82,1,82,4,82,899, - 8,82,11,82,12,82,900,3,82,903,8,82,1,83,1,83,4,83,907,8,83,11,83,12,83, - 908,1,83,1,83,1,84,1,84,1,85,1,85,1,85,1,85,1,86,1,86,1,86,1,86,1,87,1, - 87,1,87,1,87,1,88,1,88,1,88,1,88,1,88,1,89,1,89,1,89,1,89,1,90,1,90,1,90, - 1,90,1,91,1,91,1,91,1,91,1,92,1,92,1,92,1,92,1,93,1,93,1,93,1,93,1,94,1, - 94,1,94,1,94,1,94,1,94,1,94,1,94,1,94,1,95,1,95,1,95,1,95,1,96,1,96,1,96, - 1,96,1,97,1,97,1,97,1,97,1,98,1,98,1,98,1,98,1,99,1,99,1,99,1,99,1,99,1, - 100,1,100,1,100,1,100,1,101,1,101,1,101,1,101,1,102,1,102,1,102,1,102,3, - 102,994,8,102,1,103,1,103,3,103,998,8,103,1,103,5,103,1001,8,103,10,103, - 12,103,1004,9,103,1,103,1,103,3,103,1008,8,103,1,103,4,103,1011,8,103,11, - 103,12,103,1012,3,103,1015,8,103,1,104,1,104,4,104,1019,8,104,11,104,12, - 104,1020,1,105,1,105,1,105,1,105,1,106,1,106,1,106,1,106,1,107,1,107,1, - 107,1,107,1,108,1,108,1,108,1,108,1,108,1,109,1,109,1,109,1,109,1,110,1, - 110,1,110,1,110,1,111,1,111,1,111,1,111,1,112,1,112,1,112,1,113,1,113,1, - 113,1,113,1,114,1,114,1,114,1,114,1,115,1,115,1,115,1,115,1,116,1,116,1, - 116,1,116,1,117,1,117,1,117,1,117,1,117,1,118,1,118,1,118,1,118,1,118,1, - 119,1,119,1,119,1,119,1,119,1,120,1,120,1,120,1,120,1,120,1,120,1,120,1, - 121,1,121,1,122,4,122,1096,8,122,11,122,12,122,1097,1,122,1,122,3,122,1102, - 8,122,1,122,4,122,1105,8,122,11,122,12,122,1106,1,123,1,123,1,123,1,123, - 1,124,1,124,1,124,1,124,1,125,1,125,1,125,1,125,1,126,1,126,1,126,1,126, - 1,127,1,127,1,127,1,127,1,128,1,128,1,128,1,128,1,128,1,128,1,129,1,129, - 1,129,1,129,1,130,1,130,1,130,1,130,1,131,1,131,1,131,1,131,1,132,1,132, - 1,132,1,132,1,133,1,133,1,133,1,133,1,134,1,134,1,134,1,134,1,135,1,135, - 1,135,1,135,1,136,1,136,1,136,1,136,1,137,1,137,1,137,1,137,1,138,1,138, - 1,138,1,138,1,138,1,139,1,139,1,139,1,139,1,140,1,140,1,140,1,140,1,141, - 1,141,1,141,1,141,1,141,1,142,1,142,1,142,1,142,1,143,1,143,1,143,1,143, - 1,144,1,144,1,144,1,144,1,145,1,145,1,145,1,145,1,146,1,146,1,146,1,146, - 1,146,1,146,1,147,1,147,1,147,1,147,1,148,1,148,1,148,1,148,1,149,1,149, - 1,149,1,149,1,150,1,150,1,150,1,150,1,151,1,151,1,151,1,151,1,152,1,152, - 1,152,1,152,1,153,1,153,1,153,1,153,1,153,1,154,1,154,1,154,1,154,1,155, - 1,155,1,155,1,155,1,156,1,156,1,156,1,156,1,157,1,157,1,157,1,157,1,158, - 1,158,1,158,1,158,1,159,1,159,1,159,1,159,1,160,1,160,1,160,1,160,1,160, - 1,161,1,161,1,161,1,161,1,161,1,162,1,162,1,162,1,162,1,163,1,163,1,163, - 1,163,1,164,1,164,1,164,1,164,1,165,1,165,1,165,1,165,1,165,1,166,1,166, - 1,166,1,166,1,166,1,166,1,166,1,166,1,166,1,166,1,167,1,167,1,167,1,167, - 1,168,1,168,1,168,1,168,1,169,1,169,1,169,1,169,1,170,1,170,1,170,1,170, - 1,170,1,171,1,171,1,172,1,172,1,172,1,172,1,172,4,172,1325,8,172,11,172, - 12,172,1326,1,173,1,173,1,173,1,173,1,174,1,174,1,174,1,174,1,175,1,175, - 1,175,1,175,1,176,1,176,1,176,1,176,1,176,1,177,1,177,1,177,1,177,1,177, - 1,177,1,178,1,178,1,178,1,178,1,179,1,179,1,179,1,179,1,180,1,180,1,180, - 1,180,1,181,1,181,1,181,1,181,1,181,1,181,1,182,1,182,1,182,1,182,1,183, - 1,183,1,183,1,183,1,184,1,184,1,184,1,184,1,185,1,185,1,185,1,185,1,185, - 1,185,1,186,1,186,1,186,1,186,1,186,1,186,1,187,1,187,1,187,1,187,1,187, - 1,187,1,188,1,188,1,188,1,188,1,188,2,594,695,0,189,16,1,18,2,20,3,22,4, - 24,5,26,6,28,7,30,8,32,9,34,10,36,11,38,12,40,13,42,14,44,15,46,16,48,17, - 50,18,52,19,54,20,56,21,58,22,60,23,62,24,64,0,66,25,68,0,70,0,72,26,74, - 27,76,28,78,29,80,0,82,0,84,0,86,0,88,0,90,0,92,0,94,0,96,0,98,0,100,30, - 102,31,104,32,106,33,108,34,110,35,112,36,114,37,116,38,118,39,120,40,122, - 41,124,42,126,43,128,44,130,45,132,46,134,47,136,48,138,49,140,50,142,51, - 144,52,146,53,148,54,150,55,152,56,154,57,156,58,158,59,160,60,162,61,164, - 62,166,63,168,64,170,65,172,66,174,67,176,68,178,69,180,70,182,0,184,71, - 186,72,188,73,190,74,192,0,194,0,196,0,198,0,200,0,202,0,204,75,206,0,208, - 76,210,77,212,78,214,0,216,0,218,0,220,0,222,0,224,79,226,80,228,81,230, - 82,232,0,234,0,236,0,238,0,240,83,242,0,244,84,246,85,248,86,250,0,252, - 0,254,87,256,88,258,0,260,89,262,0,264,0,266,90,268,91,270,92,272,0,274, - 0,276,0,278,0,280,0,282,0,284,0,286,93,288,94,290,95,292,0,294,0,296,0, - 298,0,300,0,302,96,304,97,306,98,308,0,310,0,312,0,314,0,316,99,318,100, - 320,101,322,0,324,0,326,0,328,0,330,102,332,103,334,104,336,0,338,105,340, - 106,342,107,344,108,346,0,348,109,350,110,352,111,354,112,356,0,358,113, - 360,114,362,115,364,116,366,117,368,0,370,0,372,118,374,119,376,120,378, - 0,380,121,382,122,384,123,386,0,388,0,390,0,392,0,16,0,1,2,3,4,5,6,7,8, - 9,10,11,12,13,14,15,35,2,0,68,68,100,100,2,0,73,73,105,105,2,0,83,83,115, - 115,2,0,69,69,101,101,2,0,67,67,99,99,2,0,84,84,116,116,2,0,82,82,114,114, - 2,0,79,79,111,111,2,0,80,80,112,112,2,0,78,78,110,110,2,0,72,72,104,104, - 2,0,86,86,118,118,2,0,65,65,97,97,2,0,76,76,108,108,2,0,88,88,120,120,2, - 0,70,70,102,102,2,0,77,77,109,109,2,0,71,71,103,103,2,0,75,75,107,107,2, - 0,85,85,117,117,2,0,87,87,119,119,6,0,9,10,13,13,32,32,47,47,91,91,93,93, - 2,0,10,10,13,13,3,0,9,10,13,13,32,32,10,0,9,10,13,13,32,32,44,44,47,47, - 61,61,91,91,93,93,96,96,124,124,2,0,42,42,47,47,1,0,48,57,2,0,65,90,97, - 122,8,0,34,34,78,78,82,82,84,84,92,92,110,110,114,114,116,116,4,0,10,10, - 13,13,34,34,92,92,2,0,43,43,45,45,1,0,96,96,2,0,66,66,98,98,2,0,89,89,121, - 121,11,0,9,10,13,13,32,32,34,35,44,44,47,47,58,58,60,60,62,63,92,92,124, - 124,1427,0,16,1,0,0,0,0,18,1,0,0,0,0,20,1,0,0,0,0,22,1,0,0,0,0,24,1,0,0, - 0,0,26,1,0,0,0,0,28,1,0,0,0,0,30,1,0,0,0,0,32,1,0,0,0,0,34,1,0,0,0,0,36, - 1,0,0,0,0,38,1,0,0,0,0,40,1,0,0,0,0,42,1,0,0,0,0,44,1,0,0,0,0,46,1,0,0, - 0,0,48,1,0,0,0,0,50,1,0,0,0,0,52,1,0,0,0,0,54,1,0,0,0,0,56,1,0,0,0,0,58, - 1,0,0,0,0,60,1,0,0,0,0,62,1,0,0,0,0,66,1,0,0,0,1,68,1,0,0,0,1,70,1,0,0, - 0,1,72,1,0,0,0,1,74,1,0,0,0,1,76,1,0,0,0,2,78,1,0,0,0,2,100,1,0,0,0,2,102, - 1,0,0,0,2,104,1,0,0,0,2,106,1,0,0,0,2,108,1,0,0,0,2,110,1,0,0,0,2,112,1, - 0,0,0,2,114,1,0,0,0,2,116,1,0,0,0,2,118,1,0,0,0,2,120,1,0,0,0,2,122,1,0, - 0,0,2,124,1,0,0,0,2,126,1,0,0,0,2,128,1,0,0,0,2,130,1,0,0,0,2,132,1,0,0, - 0,2,134,1,0,0,0,2,136,1,0,0,0,2,138,1,0,0,0,2,140,1,0,0,0,2,142,1,0,0,0, - 2,144,1,0,0,0,2,146,1,0,0,0,2,148,1,0,0,0,2,150,1,0,0,0,2,152,1,0,0,0,2, - 154,1,0,0,0,2,156,1,0,0,0,2,158,1,0,0,0,2,160,1,0,0,0,2,162,1,0,0,0,2,164, - 1,0,0,0,2,166,1,0,0,0,2,168,1,0,0,0,2,170,1,0,0,0,2,172,1,0,0,0,2,174,1, - 0,0,0,2,176,1,0,0,0,2,178,1,0,0,0,2,180,1,0,0,0,2,184,1,0,0,0,2,186,1,0, - 0,0,2,188,1,0,0,0,2,190,1,0,0,0,3,192,1,0,0,0,3,194,1,0,0,0,3,196,1,0,0, - 0,3,198,1,0,0,0,3,200,1,0,0,0,3,202,1,0,0,0,3,204,1,0,0,0,3,206,1,0,0,0, - 3,208,1,0,0,0,3,210,1,0,0,0,3,212,1,0,0,0,4,214,1,0,0,0,4,216,1,0,0,0,4, - 218,1,0,0,0,4,224,1,0,0,0,4,226,1,0,0,0,4,228,1,0,0,0,4,230,1,0,0,0,5,232, - 1,0,0,0,5,234,1,0,0,0,5,236,1,0,0,0,5,238,1,0,0,0,5,240,1,0,0,0,5,242,1, - 0,0,0,5,244,1,0,0,0,5,246,1,0,0,0,5,248,1,0,0,0,6,250,1,0,0,0,6,252,1,0, - 0,0,6,254,1,0,0,0,6,256,1,0,0,0,6,260,1,0,0,0,6,262,1,0,0,0,6,264,1,0,0, - 0,6,266,1,0,0,0,6,268,1,0,0,0,6,270,1,0,0,0,7,272,1,0,0,0,7,274,1,0,0,0, - 7,276,1,0,0,0,7,278,1,0,0,0,7,280,1,0,0,0,7,282,1,0,0,0,7,284,1,0,0,0,7, - 286,1,0,0,0,7,288,1,0,0,0,7,290,1,0,0,0,8,292,1,0,0,0,8,294,1,0,0,0,8,296, - 1,0,0,0,8,298,1,0,0,0,8,300,1,0,0,0,8,302,1,0,0,0,8,304,1,0,0,0,8,306,1, - 0,0,0,9,308,1,0,0,0,9,310,1,0,0,0,9,312,1,0,0,0,9,314,1,0,0,0,9,316,1,0, - 0,0,9,318,1,0,0,0,9,320,1,0,0,0,10,322,1,0,0,0,10,324,1,0,0,0,10,326,1, - 0,0,0,10,328,1,0,0,0,10,330,1,0,0,0,10,332,1,0,0,0,10,334,1,0,0,0,11,336, - 1,0,0,0,11,338,1,0,0,0,11,340,1,0,0,0,11,342,1,0,0,0,11,344,1,0,0,0,12, - 346,1,0,0,0,12,348,1,0,0,0,12,350,1,0,0,0,12,352,1,0,0,0,12,354,1,0,0,0, - 13,356,1,0,0,0,13,358,1,0,0,0,13,360,1,0,0,0,13,362,1,0,0,0,13,364,1,0, - 0,0,13,366,1,0,0,0,14,368,1,0,0,0,14,370,1,0,0,0,14,372,1,0,0,0,14,374, - 1,0,0,0,14,376,1,0,0,0,15,378,1,0,0,0,15,380,1,0,0,0,15,382,1,0,0,0,15, - 384,1,0,0,0,15,386,1,0,0,0,15,388,1,0,0,0,15,390,1,0,0,0,15,392,1,0,0,0, - 16,394,1,0,0,0,18,404,1,0,0,0,20,411,1,0,0,0,22,420,1,0,0,0,24,427,1,0, - 0,0,26,437,1,0,0,0,28,444,1,0,0,0,30,451,1,0,0,0,32,465,1,0,0,0,34,472, - 1,0,0,0,36,480,1,0,0,0,38,489,1,0,0,0,40,496,1,0,0,0,42,506,1,0,0,0,44, - 518,1,0,0,0,46,527,1,0,0,0,48,533,1,0,0,0,50,540,1,0,0,0,52,547,1,0,0,0, - 54,555,1,0,0,0,56,564,1,0,0,0,58,570,1,0,0,0,60,587,1,0,0,0,62,603,1,0, - 0,0,64,612,1,0,0,0,66,615,1,0,0,0,68,619,1,0,0,0,70,624,1,0,0,0,72,629, - 1,0,0,0,74,633,1,0,0,0,76,637,1,0,0,0,78,641,1,0,0,0,80,645,1,0,0,0,82, - 647,1,0,0,0,84,649,1,0,0,0,86,652,1,0,0,0,88,654,1,0,0,0,90,663,1,0,0,0, - 92,665,1,0,0,0,94,670,1,0,0,0,96,672,1,0,0,0,98,677,1,0,0,0,100,708,1,0, - 0,0,102,711,1,0,0,0,104,757,1,0,0,0,106,759,1,0,0,0,108,762,1,0,0,0,110, - 766,1,0,0,0,112,770,1,0,0,0,114,772,1,0,0,0,116,775,1,0,0,0,118,777,1,0, - 0,0,120,782,1,0,0,0,122,784,1,0,0,0,124,790,1,0,0,0,126,796,1,0,0,0,128, - 801,1,0,0,0,130,803,1,0,0,0,132,806,1,0,0,0,134,809,1,0,0,0,136,814,1,0, - 0,0,138,818,1,0,0,0,140,823,1,0,0,0,142,829,1,0,0,0,144,832,1,0,0,0,146, - 834,1,0,0,0,148,840,1,0,0,0,150,842,1,0,0,0,152,847,1,0,0,0,154,850,1,0, - 0,0,156,853,1,0,0,0,158,856,1,0,0,0,160,858,1,0,0,0,162,861,1,0,0,0,164, - 863,1,0,0,0,166,866,1,0,0,0,168,868,1,0,0,0,170,870,1,0,0,0,172,872,1,0, - 0,0,174,874,1,0,0,0,176,876,1,0,0,0,178,881,1,0,0,0,180,902,1,0,0,0,182, - 904,1,0,0,0,184,912,1,0,0,0,186,914,1,0,0,0,188,918,1,0,0,0,190,922,1,0, - 0,0,192,926,1,0,0,0,194,931,1,0,0,0,196,935,1,0,0,0,198,939,1,0,0,0,200, - 943,1,0,0,0,202,947,1,0,0,0,204,951,1,0,0,0,206,960,1,0,0,0,208,964,1,0, - 0,0,210,968,1,0,0,0,212,972,1,0,0,0,214,976,1,0,0,0,216,981,1,0,0,0,218, - 985,1,0,0,0,220,993,1,0,0,0,222,1014,1,0,0,0,224,1018,1,0,0,0,226,1022, - 1,0,0,0,228,1026,1,0,0,0,230,1030,1,0,0,0,232,1034,1,0,0,0,234,1039,1,0, - 0,0,236,1043,1,0,0,0,238,1047,1,0,0,0,240,1051,1,0,0,0,242,1054,1,0,0,0, - 244,1058,1,0,0,0,246,1062,1,0,0,0,248,1066,1,0,0,0,250,1070,1,0,0,0,252, - 1075,1,0,0,0,254,1080,1,0,0,0,256,1085,1,0,0,0,258,1092,1,0,0,0,260,1101, - 1,0,0,0,262,1108,1,0,0,0,264,1112,1,0,0,0,266,1116,1,0,0,0,268,1120,1,0, - 0,0,270,1124,1,0,0,0,272,1128,1,0,0,0,274,1134,1,0,0,0,276,1138,1,0,0,0, - 278,1142,1,0,0,0,280,1146,1,0,0,0,282,1150,1,0,0,0,284,1154,1,0,0,0,286, - 1158,1,0,0,0,288,1162,1,0,0,0,290,1166,1,0,0,0,292,1170,1,0,0,0,294,1175, - 1,0,0,0,296,1179,1,0,0,0,298,1183,1,0,0,0,300,1188,1,0,0,0,302,1192,1,0, - 0,0,304,1196,1,0,0,0,306,1200,1,0,0,0,308,1204,1,0,0,0,310,1210,1,0,0,0, - 312,1214,1,0,0,0,314,1218,1,0,0,0,316,1222,1,0,0,0,318,1226,1,0,0,0,320, - 1230,1,0,0,0,322,1234,1,0,0,0,324,1239,1,0,0,0,326,1243,1,0,0,0,328,1247, - 1,0,0,0,330,1251,1,0,0,0,332,1255,1,0,0,0,334,1259,1,0,0,0,336,1263,1,0, - 0,0,338,1268,1,0,0,0,340,1273,1,0,0,0,342,1277,1,0,0,0,344,1281,1,0,0,0, - 346,1285,1,0,0,0,348,1290,1,0,0,0,350,1300,1,0,0,0,352,1304,1,0,0,0,354, - 1308,1,0,0,0,356,1312,1,0,0,0,358,1317,1,0,0,0,360,1324,1,0,0,0,362,1328, - 1,0,0,0,364,1332,1,0,0,0,366,1336,1,0,0,0,368,1340,1,0,0,0,370,1345,1,0, - 0,0,372,1351,1,0,0,0,374,1355,1,0,0,0,376,1359,1,0,0,0,378,1363,1,0,0,0, - 380,1369,1,0,0,0,382,1373,1,0,0,0,384,1377,1,0,0,0,386,1381,1,0,0,0,388, - 1387,1,0,0,0,390,1393,1,0,0,0,392,1399,1,0,0,0,394,395,7,0,0,0,395,396, - 7,1,0,0,396,397,7,2,0,0,397,398,7,2,0,0,398,399,7,3,0,0,399,400,7,4,0,0, - 400,401,7,5,0,0,401,402,1,0,0,0,402,403,6,0,0,0,403,17,1,0,0,0,404,405, - 7,0,0,0,405,406,7,6,0,0,406,407,7,7,0,0,407,408,7,8,0,0,408,409,1,0,0,0, - 409,410,6,1,1,0,410,19,1,0,0,0,411,412,7,3,0,0,412,413,7,9,0,0,413,414, - 7,6,0,0,414,415,7,1,0,0,415,416,7,4,0,0,416,417,7,10,0,0,417,418,1,0,0, - 0,418,419,6,2,2,0,419,21,1,0,0,0,420,421,7,3,0,0,421,422,7,11,0,0,422,423, - 7,12,0,0,423,424,7,13,0,0,424,425,1,0,0,0,425,426,6,3,0,0,426,23,1,0,0, - 0,427,428,7,3,0,0,428,429,7,14,0,0,429,430,7,8,0,0,430,431,7,13,0,0,431, - 432,7,12,0,0,432,433,7,1,0,0,433,434,7,9,0,0,434,435,1,0,0,0,435,436,6, - 4,3,0,436,25,1,0,0,0,437,438,7,15,0,0,438,439,7,6,0,0,439,440,7,7,0,0,440, - 441,7,16,0,0,441,442,1,0,0,0,442,443,6,5,4,0,443,27,1,0,0,0,444,445,7,17, - 0,0,445,446,7,6,0,0,446,447,7,7,0,0,447,448,7,18,0,0,448,449,1,0,0,0,449, - 450,6,6,0,0,450,29,1,0,0,0,451,452,7,1,0,0,452,453,7,9,0,0,453,454,7,13, - 0,0,454,455,7,1,0,0,455,456,7,9,0,0,456,457,7,3,0,0,457,458,7,2,0,0,458, - 459,7,5,0,0,459,460,7,12,0,0,460,461,7,5,0,0,461,462,7,2,0,0,462,463,1, - 0,0,0,463,464,6,7,0,0,464,31,1,0,0,0,465,466,7,18,0,0,466,467,7,3,0,0,467, - 468,7,3,0,0,468,469,7,8,0,0,469,470,1,0,0,0,470,471,6,8,1,0,471,33,1,0, - 0,0,472,473,7,13,0,0,473,474,7,1,0,0,474,475,7,16,0,0,475,476,7,1,0,0,476, - 477,7,5,0,0,477,478,1,0,0,0,478,479,6,9,0,0,479,35,1,0,0,0,480,481,7,13, - 0,0,481,482,7,7,0,0,482,483,7,7,0,0,483,484,7,18,0,0,484,485,7,19,0,0,485, - 486,7,8,0,0,486,487,1,0,0,0,487,488,6,10,5,0,488,37,1,0,0,0,489,490,7,16, - 0,0,490,491,7,3,0,0,491,492,7,5,0,0,492,493,7,12,0,0,493,494,1,0,0,0,494, - 495,6,11,6,0,495,39,1,0,0,0,496,497,7,16,0,0,497,498,7,3,0,0,498,499,7, - 5,0,0,499,500,7,6,0,0,500,501,7,1,0,0,501,502,7,4,0,0,502,503,7,2,0,0,503, - 504,1,0,0,0,504,505,6,12,7,0,505,41,1,0,0,0,506,507,7,16,0,0,507,508,7, - 11,0,0,508,509,5,95,0,0,509,510,7,3,0,0,510,511,7,14,0,0,511,512,7,8,0, - 0,512,513,7,12,0,0,513,514,7,9,0,0,514,515,7,0,0,0,515,516,1,0,0,0,516, - 517,6,13,8,0,517,43,1,0,0,0,518,519,7,6,0,0,519,520,7,3,0,0,520,521,7,9, - 0,0,521,522,7,12,0,0,522,523,7,16,0,0,523,524,7,3,0,0,524,525,1,0,0,0,525, - 526,6,14,9,0,526,45,1,0,0,0,527,528,7,6,0,0,528,529,7,7,0,0,529,530,7,20, - 0,0,530,531,1,0,0,0,531,532,6,15,0,0,532,47,1,0,0,0,533,534,7,2,0,0,534, - 535,7,10,0,0,535,536,7,7,0,0,536,537,7,20,0,0,537,538,1,0,0,0,538,539,6, - 16,10,0,539,49,1,0,0,0,540,541,7,2,0,0,541,542,7,7,0,0,542,543,7,6,0,0, - 543,544,7,5,0,0,544,545,1,0,0,0,545,546,6,17,0,0,546,51,1,0,0,0,547,548, - 7,2,0,0,548,549,7,5,0,0,549,550,7,12,0,0,550,551,7,5,0,0,551,552,7,2,0, - 0,552,553,1,0,0,0,553,554,6,18,0,0,554,53,1,0,0,0,555,556,7,20,0,0,556, - 557,7,10,0,0,557,558,7,3,0,0,558,559,7,6,0,0,559,560,7,3,0,0,560,561,1, - 0,0,0,561,562,6,19,0,0,562,55,1,0,0,0,563,565,8,21,0,0,564,563,1,0,0,0, - 565,566,1,0,0,0,566,564,1,0,0,0,566,567,1,0,0,0,567,568,1,0,0,0,568,569, - 6,20,0,0,569,57,1,0,0,0,570,571,5,47,0,0,571,572,5,47,0,0,572,576,1,0,0, - 0,573,575,8,22,0,0,574,573,1,0,0,0,575,578,1,0,0,0,576,574,1,0,0,0,576, - 577,1,0,0,0,577,580,1,0,0,0,578,576,1,0,0,0,579,581,5,13,0,0,580,579,1, - 0,0,0,580,581,1,0,0,0,581,583,1,0,0,0,582,584,5,10,0,0,583,582,1,0,0,0, - 583,584,1,0,0,0,584,585,1,0,0,0,585,586,6,21,11,0,586,59,1,0,0,0,587,588, - 5,47,0,0,588,589,5,42,0,0,589,594,1,0,0,0,590,593,3,60,22,0,591,593,9,0, - 0,0,592,590,1,0,0,0,592,591,1,0,0,0,593,596,1,0,0,0,594,595,1,0,0,0,594, - 592,1,0,0,0,595,597,1,0,0,0,596,594,1,0,0,0,597,598,5,42,0,0,598,599,5, - 47,0,0,599,600,1,0,0,0,600,601,6,22,11,0,601,61,1,0,0,0,602,604,7,23,0, - 0,603,602,1,0,0,0,604,605,1,0,0,0,605,603,1,0,0,0,605,606,1,0,0,0,606,607, - 1,0,0,0,607,608,6,23,11,0,608,63,1,0,0,0,609,613,8,24,0,0,610,611,5,47, - 0,0,611,613,8,25,0,0,612,609,1,0,0,0,612,610,1,0,0,0,613,65,1,0,0,0,614, - 616,3,64,24,0,615,614,1,0,0,0,616,617,1,0,0,0,617,615,1,0,0,0,617,618,1, - 0,0,0,618,67,1,0,0,0,619,620,3,176,80,0,620,621,1,0,0,0,621,622,6,26,12, - 0,622,623,6,26,13,0,623,69,1,0,0,0,624,625,3,78,31,0,625,626,1,0,0,0,626, - 627,6,27,14,0,627,628,6,27,15,0,628,71,1,0,0,0,629,630,3,62,23,0,630,631, - 1,0,0,0,631,632,6,28,11,0,632,73,1,0,0,0,633,634,3,58,21,0,634,635,1,0, - 0,0,635,636,6,29,11,0,636,75,1,0,0,0,637,638,3,60,22,0,638,639,1,0,0,0, - 639,640,6,30,11,0,640,77,1,0,0,0,641,642,5,124,0,0,642,643,1,0,0,0,643, - 644,6,31,15,0,644,79,1,0,0,0,645,646,7,26,0,0,646,81,1,0,0,0,647,648,7, - 27,0,0,648,83,1,0,0,0,649,650,5,92,0,0,650,651,7,28,0,0,651,85,1,0,0,0, - 652,653,8,29,0,0,653,87,1,0,0,0,654,656,7,3,0,0,655,657,7,30,0,0,656,655, - 1,0,0,0,656,657,1,0,0,0,657,659,1,0,0,0,658,660,3,80,32,0,659,658,1,0,0, - 0,660,661,1,0,0,0,661,659,1,0,0,0,661,662,1,0,0,0,662,89,1,0,0,0,663,664, - 5,64,0,0,664,91,1,0,0,0,665,666,5,96,0,0,666,93,1,0,0,0,667,671,8,31,0, - 0,668,669,5,96,0,0,669,671,5,96,0,0,670,667,1,0,0,0,670,668,1,0,0,0,671, - 95,1,0,0,0,672,673,5,95,0,0,673,97,1,0,0,0,674,678,3,82,33,0,675,678,3, - 80,32,0,676,678,3,96,40,0,677,674,1,0,0,0,677,675,1,0,0,0,677,676,1,0,0, - 0,678,99,1,0,0,0,679,684,5,34,0,0,680,683,3,84,34,0,681,683,3,86,35,0,682, - 680,1,0,0,0,682,681,1,0,0,0,683,686,1,0,0,0,684,682,1,0,0,0,684,685,1,0, - 0,0,685,687,1,0,0,0,686,684,1,0,0,0,687,709,5,34,0,0,688,689,5,34,0,0,689, - 690,5,34,0,0,690,691,5,34,0,0,691,695,1,0,0,0,692,694,8,22,0,0,693,692, - 1,0,0,0,694,697,1,0,0,0,695,696,1,0,0,0,695,693,1,0,0,0,696,698,1,0,0,0, - 697,695,1,0,0,0,698,699,5,34,0,0,699,700,5,34,0,0,700,701,5,34,0,0,701, - 703,1,0,0,0,702,704,5,34,0,0,703,702,1,0,0,0,703,704,1,0,0,0,704,706,1, - 0,0,0,705,707,5,34,0,0,706,705,1,0,0,0,706,707,1,0,0,0,707,709,1,0,0,0, - 708,679,1,0,0,0,708,688,1,0,0,0,709,101,1,0,0,0,710,712,3,80,32,0,711,710, - 1,0,0,0,712,713,1,0,0,0,713,711,1,0,0,0,713,714,1,0,0,0,714,103,1,0,0,0, - 715,717,3,80,32,0,716,715,1,0,0,0,717,718,1,0,0,0,718,716,1,0,0,0,718,719, - 1,0,0,0,719,720,1,0,0,0,720,724,3,120,52,0,721,723,3,80,32,0,722,721,1, - 0,0,0,723,726,1,0,0,0,724,722,1,0,0,0,724,725,1,0,0,0,725,758,1,0,0,0,726, - 724,1,0,0,0,727,729,3,120,52,0,728,730,3,80,32,0,729,728,1,0,0,0,730,731, - 1,0,0,0,731,729,1,0,0,0,731,732,1,0,0,0,732,758,1,0,0,0,733,735,3,80,32, - 0,734,733,1,0,0,0,735,736,1,0,0,0,736,734,1,0,0,0,736,737,1,0,0,0,737,745, - 1,0,0,0,738,742,3,120,52,0,739,741,3,80,32,0,740,739,1,0,0,0,741,744,1, - 0,0,0,742,740,1,0,0,0,742,743,1,0,0,0,743,746,1,0,0,0,744,742,1,0,0,0,745, - 738,1,0,0,0,745,746,1,0,0,0,746,747,1,0,0,0,747,748,3,88,36,0,748,758,1, - 0,0,0,749,751,3,120,52,0,750,752,3,80,32,0,751,750,1,0,0,0,752,753,1,0, - 0,0,753,751,1,0,0,0,753,754,1,0,0,0,754,755,1,0,0,0,755,756,3,88,36,0,756, - 758,1,0,0,0,757,716,1,0,0,0,757,727,1,0,0,0,757,734,1,0,0,0,757,749,1,0, - 0,0,758,105,1,0,0,0,759,760,7,32,0,0,760,761,7,33,0,0,761,107,1,0,0,0,762, - 763,7,12,0,0,763,764,7,9,0,0,764,765,7,0,0,0,765,109,1,0,0,0,766,767,7, - 12,0,0,767,768,7,2,0,0,768,769,7,4,0,0,769,111,1,0,0,0,770,771,5,61,0,0, - 771,113,1,0,0,0,772,773,5,58,0,0,773,774,5,58,0,0,774,115,1,0,0,0,775,776, - 5,44,0,0,776,117,1,0,0,0,777,778,7,0,0,0,778,779,7,3,0,0,779,780,7,2,0, - 0,780,781,7,4,0,0,781,119,1,0,0,0,782,783,5,46,0,0,783,121,1,0,0,0,784, - 785,7,15,0,0,785,786,7,12,0,0,786,787,7,13,0,0,787,788,7,2,0,0,788,789, - 7,3,0,0,789,123,1,0,0,0,790,791,7,15,0,0,791,792,7,1,0,0,792,793,7,6,0, - 0,793,794,7,2,0,0,794,795,7,5,0,0,795,125,1,0,0,0,796,797,7,13,0,0,797, - 798,7,12,0,0,798,799,7,2,0,0,799,800,7,5,0,0,800,127,1,0,0,0,801,802,5, - 40,0,0,802,129,1,0,0,0,803,804,7,1,0,0,804,805,7,9,0,0,805,131,1,0,0,0, - 806,807,7,1,0,0,807,808,7,2,0,0,808,133,1,0,0,0,809,810,7,13,0,0,810,811, - 7,1,0,0,811,812,7,18,0,0,812,813,7,3,0,0,813,135,1,0,0,0,814,815,7,9,0, - 0,815,816,7,7,0,0,816,817,7,5,0,0,817,137,1,0,0,0,818,819,7,9,0,0,819,820, - 7,19,0,0,820,821,7,13,0,0,821,822,7,13,0,0,822,139,1,0,0,0,823,824,7,9, - 0,0,824,825,7,19,0,0,825,826,7,13,0,0,826,827,7,13,0,0,827,828,7,2,0,0, - 828,141,1,0,0,0,829,830,7,7,0,0,830,831,7,6,0,0,831,143,1,0,0,0,832,833, - 5,63,0,0,833,145,1,0,0,0,834,835,7,6,0,0,835,836,7,13,0,0,836,837,7,1,0, - 0,837,838,7,18,0,0,838,839,7,3,0,0,839,147,1,0,0,0,840,841,5,41,0,0,841, - 149,1,0,0,0,842,843,7,5,0,0,843,844,7,6,0,0,844,845,7,19,0,0,845,846,7, - 3,0,0,846,151,1,0,0,0,847,848,5,61,0,0,848,849,5,61,0,0,849,153,1,0,0,0, - 850,851,5,61,0,0,851,852,5,126,0,0,852,155,1,0,0,0,853,854,5,33,0,0,854, - 855,5,61,0,0,855,157,1,0,0,0,856,857,5,60,0,0,857,159,1,0,0,0,858,859,5, - 60,0,0,859,860,5,61,0,0,860,161,1,0,0,0,861,862,5,62,0,0,862,163,1,0,0, - 0,863,864,5,62,0,0,864,865,5,61,0,0,865,165,1,0,0,0,866,867,5,43,0,0,867, - 167,1,0,0,0,868,869,5,45,0,0,869,169,1,0,0,0,870,871,5,42,0,0,871,171,1, - 0,0,0,872,873,5,47,0,0,873,173,1,0,0,0,874,875,5,37,0,0,875,175,1,0,0,0, - 876,877,5,91,0,0,877,878,1,0,0,0,878,879,6,80,0,0,879,880,6,80,0,0,880, - 177,1,0,0,0,881,882,5,93,0,0,882,883,1,0,0,0,883,884,6,81,15,0,884,885, - 6,81,15,0,885,179,1,0,0,0,886,890,3,82,33,0,887,889,3,98,41,0,888,887,1, - 0,0,0,889,892,1,0,0,0,890,888,1,0,0,0,890,891,1,0,0,0,891,903,1,0,0,0,892, - 890,1,0,0,0,893,896,3,96,40,0,894,896,3,90,37,0,895,893,1,0,0,0,895,894, - 1,0,0,0,896,898,1,0,0,0,897,899,3,98,41,0,898,897,1,0,0,0,899,900,1,0,0, - 0,900,898,1,0,0,0,900,901,1,0,0,0,901,903,1,0,0,0,902,886,1,0,0,0,902,895, - 1,0,0,0,903,181,1,0,0,0,904,906,3,92,38,0,905,907,3,94,39,0,906,905,1,0, - 0,0,907,908,1,0,0,0,908,906,1,0,0,0,908,909,1,0,0,0,909,910,1,0,0,0,910, - 911,3,92,38,0,911,183,1,0,0,0,912,913,3,182,83,0,913,185,1,0,0,0,914,915, - 3,58,21,0,915,916,1,0,0,0,916,917,6,85,11,0,917,187,1,0,0,0,918,919,3,60, - 22,0,919,920,1,0,0,0,920,921,6,86,11,0,921,189,1,0,0,0,922,923,3,62,23, - 0,923,924,1,0,0,0,924,925,6,87,11,0,925,191,1,0,0,0,926,927,3,78,31,0,927, - 928,1,0,0,0,928,929,6,88,14,0,929,930,6,88,15,0,930,193,1,0,0,0,931,932, - 3,176,80,0,932,933,1,0,0,0,933,934,6,89,12,0,934,195,1,0,0,0,935,936,3, - 178,81,0,936,937,1,0,0,0,937,938,6,90,16,0,938,197,1,0,0,0,939,940,3,116, - 50,0,940,941,1,0,0,0,941,942,6,91,17,0,942,199,1,0,0,0,943,944,3,112,48, - 0,944,945,1,0,0,0,945,946,6,92,18,0,946,201,1,0,0,0,947,948,3,100,42,0, - 948,949,1,0,0,0,949,950,6,93,19,0,950,203,1,0,0,0,951,952,7,16,0,0,952, - 953,7,3,0,0,953,954,7,5,0,0,954,955,7,12,0,0,955,956,7,0,0,0,956,957,7, - 12,0,0,957,958,7,5,0,0,958,959,7,12,0,0,959,205,1,0,0,0,960,961,3,66,25, - 0,961,962,1,0,0,0,962,963,6,95,20,0,963,207,1,0,0,0,964,965,3,58,21,0,965, - 966,1,0,0,0,966,967,6,96,11,0,967,209,1,0,0,0,968,969,3,60,22,0,969,970, - 1,0,0,0,970,971,6,97,11,0,971,211,1,0,0,0,972,973,3,62,23,0,973,974,1,0, - 0,0,974,975,6,98,11,0,975,213,1,0,0,0,976,977,3,78,31,0,977,978,1,0,0,0, - 978,979,6,99,14,0,979,980,6,99,15,0,980,215,1,0,0,0,981,982,3,120,52,0, - 982,983,1,0,0,0,983,984,6,100,21,0,984,217,1,0,0,0,985,986,3,116,50,0,986, - 987,1,0,0,0,987,988,6,101,17,0,988,219,1,0,0,0,989,994,3,82,33,0,990,994, - 3,80,32,0,991,994,3,96,40,0,992,994,3,170,77,0,993,989,1,0,0,0,993,990, - 1,0,0,0,993,991,1,0,0,0,993,992,1,0,0,0,994,221,1,0,0,0,995,998,3,82,33, - 0,996,998,3,170,77,0,997,995,1,0,0,0,997,996,1,0,0,0,998,1002,1,0,0,0,999, - 1001,3,220,102,0,1000,999,1,0,0,0,1001,1004,1,0,0,0,1002,1000,1,0,0,0,1002, - 1003,1,0,0,0,1003,1015,1,0,0,0,1004,1002,1,0,0,0,1005,1008,3,96,40,0,1006, - 1008,3,90,37,0,1007,1005,1,0,0,0,1007,1006,1,0,0,0,1008,1010,1,0,0,0,1009, - 1011,3,220,102,0,1010,1009,1,0,0,0,1011,1012,1,0,0,0,1012,1010,1,0,0,0, - 1012,1013,1,0,0,0,1013,1015,1,0,0,0,1014,997,1,0,0,0,1014,1007,1,0,0,0, - 1015,223,1,0,0,0,1016,1019,3,222,103,0,1017,1019,3,182,83,0,1018,1016,1, - 0,0,0,1018,1017,1,0,0,0,1019,1020,1,0,0,0,1020,1018,1,0,0,0,1020,1021,1, - 0,0,0,1021,225,1,0,0,0,1022,1023,3,58,21,0,1023,1024,1,0,0,0,1024,1025, - 6,105,11,0,1025,227,1,0,0,0,1026,1027,3,60,22,0,1027,1028,1,0,0,0,1028, - 1029,6,106,11,0,1029,229,1,0,0,0,1030,1031,3,62,23,0,1031,1032,1,0,0,0, - 1032,1033,6,107,11,0,1033,231,1,0,0,0,1034,1035,3,78,31,0,1035,1036,1,0, - 0,0,1036,1037,6,108,14,0,1037,1038,6,108,15,0,1038,233,1,0,0,0,1039,1040, - 3,112,48,0,1040,1041,1,0,0,0,1041,1042,6,109,18,0,1042,235,1,0,0,0,1043, - 1044,3,116,50,0,1044,1045,1,0,0,0,1045,1046,6,110,17,0,1046,237,1,0,0,0, - 1047,1048,3,120,52,0,1048,1049,1,0,0,0,1049,1050,6,111,21,0,1050,239,1, - 0,0,0,1051,1052,7,12,0,0,1052,1053,7,2,0,0,1053,241,1,0,0,0,1054,1055,3, - 224,104,0,1055,1056,1,0,0,0,1056,1057,6,113,22,0,1057,243,1,0,0,0,1058, - 1059,3,58,21,0,1059,1060,1,0,0,0,1060,1061,6,114,11,0,1061,245,1,0,0,0, - 1062,1063,3,60,22,0,1063,1064,1,0,0,0,1064,1065,6,115,11,0,1065,247,1,0, - 0,0,1066,1067,3,62,23,0,1067,1068,1,0,0,0,1068,1069,6,116,11,0,1069,249, - 1,0,0,0,1070,1071,3,78,31,0,1071,1072,1,0,0,0,1072,1073,6,117,14,0,1073, - 1074,6,117,15,0,1074,251,1,0,0,0,1075,1076,3,176,80,0,1076,1077,1,0,0,0, - 1077,1078,6,118,12,0,1078,1079,6,118,23,0,1079,253,1,0,0,0,1080,1081,7, - 7,0,0,1081,1082,7,9,0,0,1082,1083,1,0,0,0,1083,1084,6,119,24,0,1084,255, - 1,0,0,0,1085,1086,7,20,0,0,1086,1087,7,1,0,0,1087,1088,7,5,0,0,1088,1089, - 7,10,0,0,1089,1090,1,0,0,0,1090,1091,6,120,24,0,1091,257,1,0,0,0,1092,1093, - 8,34,0,0,1093,259,1,0,0,0,1094,1096,3,258,121,0,1095,1094,1,0,0,0,1096, - 1097,1,0,0,0,1097,1095,1,0,0,0,1097,1098,1,0,0,0,1098,1099,1,0,0,0,1099, - 1100,3,358,171,0,1100,1102,1,0,0,0,1101,1095,1,0,0,0,1101,1102,1,0,0,0, - 1102,1104,1,0,0,0,1103,1105,3,258,121,0,1104,1103,1,0,0,0,1105,1106,1,0, - 0,0,1106,1104,1,0,0,0,1106,1107,1,0,0,0,1107,261,1,0,0,0,1108,1109,3,184, - 84,0,1109,1110,1,0,0,0,1110,1111,6,123,25,0,1111,263,1,0,0,0,1112,1113, - 3,260,122,0,1113,1114,1,0,0,0,1114,1115,6,124,26,0,1115,265,1,0,0,0,1116, - 1117,3,58,21,0,1117,1118,1,0,0,0,1118,1119,6,125,11,0,1119,267,1,0,0,0, - 1120,1121,3,60,22,0,1121,1122,1,0,0,0,1122,1123,6,126,11,0,1123,269,1,0, - 0,0,1124,1125,3,62,23,0,1125,1126,1,0,0,0,1126,1127,6,127,11,0,1127,271, - 1,0,0,0,1128,1129,3,78,31,0,1129,1130,1,0,0,0,1130,1131,6,128,14,0,1131, - 1132,6,128,15,0,1132,1133,6,128,15,0,1133,273,1,0,0,0,1134,1135,3,112,48, - 0,1135,1136,1,0,0,0,1136,1137,6,129,18,0,1137,275,1,0,0,0,1138,1139,3,116, - 50,0,1139,1140,1,0,0,0,1140,1141,6,130,17,0,1141,277,1,0,0,0,1142,1143, - 3,120,52,0,1143,1144,1,0,0,0,1144,1145,6,131,21,0,1145,279,1,0,0,0,1146, - 1147,3,256,120,0,1147,1148,1,0,0,0,1148,1149,6,132,27,0,1149,281,1,0,0, - 0,1150,1151,3,224,104,0,1151,1152,1,0,0,0,1152,1153,6,133,22,0,1153,283, - 1,0,0,0,1154,1155,3,184,84,0,1155,1156,1,0,0,0,1156,1157,6,134,25,0,1157, - 285,1,0,0,0,1158,1159,3,58,21,0,1159,1160,1,0,0,0,1160,1161,6,135,11,0, - 1161,287,1,0,0,0,1162,1163,3,60,22,0,1163,1164,1,0,0,0,1164,1165,6,136, - 11,0,1165,289,1,0,0,0,1166,1167,3,62,23,0,1167,1168,1,0,0,0,1168,1169,6, - 137,11,0,1169,291,1,0,0,0,1170,1171,3,78,31,0,1171,1172,1,0,0,0,1172,1173, - 6,138,14,0,1173,1174,6,138,15,0,1174,293,1,0,0,0,1175,1176,3,116,50,0,1176, - 1177,1,0,0,0,1177,1178,6,139,17,0,1178,295,1,0,0,0,1179,1180,3,120,52,0, - 1180,1181,1,0,0,0,1181,1182,6,140,21,0,1182,297,1,0,0,0,1183,1184,3,254, - 119,0,1184,1185,1,0,0,0,1185,1186,6,141,28,0,1186,1187,6,141,29,0,1187, - 299,1,0,0,0,1188,1189,3,66,25,0,1189,1190,1,0,0,0,1190,1191,6,142,20,0, - 1191,301,1,0,0,0,1192,1193,3,58,21,0,1193,1194,1,0,0,0,1194,1195,6,143, - 11,0,1195,303,1,0,0,0,1196,1197,3,60,22,0,1197,1198,1,0,0,0,1198,1199,6, - 144,11,0,1199,305,1,0,0,0,1200,1201,3,62,23,0,1201,1202,1,0,0,0,1202,1203, - 6,145,11,0,1203,307,1,0,0,0,1204,1205,3,78,31,0,1205,1206,1,0,0,0,1206, - 1207,6,146,14,0,1207,1208,6,146,15,0,1208,1209,6,146,15,0,1209,309,1,0, - 0,0,1210,1211,3,116,50,0,1211,1212,1,0,0,0,1212,1213,6,147,17,0,1213,311, - 1,0,0,0,1214,1215,3,120,52,0,1215,1216,1,0,0,0,1216,1217,6,148,21,0,1217, - 313,1,0,0,0,1218,1219,3,224,104,0,1219,1220,1,0,0,0,1220,1221,6,149,22, - 0,1221,315,1,0,0,0,1222,1223,3,58,21,0,1223,1224,1,0,0,0,1224,1225,6,150, - 11,0,1225,317,1,0,0,0,1226,1227,3,60,22,0,1227,1228,1,0,0,0,1228,1229,6, - 151,11,0,1229,319,1,0,0,0,1230,1231,3,62,23,0,1231,1232,1,0,0,0,1232,1233, - 6,152,11,0,1233,321,1,0,0,0,1234,1235,3,78,31,0,1235,1236,1,0,0,0,1236, - 1237,6,153,14,0,1237,1238,6,153,15,0,1238,323,1,0,0,0,1239,1240,3,120,52, - 0,1240,1241,1,0,0,0,1241,1242,6,154,21,0,1242,325,1,0,0,0,1243,1244,3,184, - 84,0,1244,1245,1,0,0,0,1245,1246,6,155,25,0,1246,327,1,0,0,0,1247,1248, - 3,180,82,0,1248,1249,1,0,0,0,1249,1250,6,156,30,0,1250,329,1,0,0,0,1251, - 1252,3,58,21,0,1252,1253,1,0,0,0,1253,1254,6,157,11,0,1254,331,1,0,0,0, - 1255,1256,3,60,22,0,1256,1257,1,0,0,0,1257,1258,6,158,11,0,1258,333,1,0, - 0,0,1259,1260,3,62,23,0,1260,1261,1,0,0,0,1261,1262,6,159,11,0,1262,335, - 1,0,0,0,1263,1264,3,78,31,0,1264,1265,1,0,0,0,1265,1266,6,160,14,0,1266, - 1267,6,160,15,0,1267,337,1,0,0,0,1268,1269,7,1,0,0,1269,1270,7,9,0,0,1270, - 1271,7,15,0,0,1271,1272,7,7,0,0,1272,339,1,0,0,0,1273,1274,3,58,21,0,1274, - 1275,1,0,0,0,1275,1276,6,162,11,0,1276,341,1,0,0,0,1277,1278,3,60,22,0, - 1278,1279,1,0,0,0,1279,1280,6,163,11,0,1280,343,1,0,0,0,1281,1282,3,62, - 23,0,1282,1283,1,0,0,0,1283,1284,6,164,11,0,1284,345,1,0,0,0,1285,1286, - 3,78,31,0,1286,1287,1,0,0,0,1287,1288,6,165,14,0,1288,1289,6,165,15,0,1289, - 347,1,0,0,0,1290,1291,7,15,0,0,1291,1292,7,19,0,0,1292,1293,7,9,0,0,1293, - 1294,7,4,0,0,1294,1295,7,5,0,0,1295,1296,7,1,0,0,1296,1297,7,7,0,0,1297, - 1298,7,9,0,0,1298,1299,7,2,0,0,1299,349,1,0,0,0,1300,1301,3,58,21,0,1301, - 1302,1,0,0,0,1302,1303,6,167,11,0,1303,351,1,0,0,0,1304,1305,3,60,22,0, - 1305,1306,1,0,0,0,1306,1307,6,168,11,0,1307,353,1,0,0,0,1308,1309,3,62, - 23,0,1309,1310,1,0,0,0,1310,1311,6,169,11,0,1311,355,1,0,0,0,1312,1313, - 3,178,81,0,1313,1314,1,0,0,0,1314,1315,6,170,16,0,1315,1316,6,170,15,0, - 1316,357,1,0,0,0,1317,1318,5,58,0,0,1318,359,1,0,0,0,1319,1325,3,90,37, - 0,1320,1325,3,80,32,0,1321,1325,3,120,52,0,1322,1325,3,82,33,0,1323,1325, - 3,96,40,0,1324,1319,1,0,0,0,1324,1320,1,0,0,0,1324,1321,1,0,0,0,1324,1322, - 1,0,0,0,1324,1323,1,0,0,0,1325,1326,1,0,0,0,1326,1324,1,0,0,0,1326,1327, - 1,0,0,0,1327,361,1,0,0,0,1328,1329,3,58,21,0,1329,1330,1,0,0,0,1330,1331, - 6,173,11,0,1331,363,1,0,0,0,1332,1333,3,60,22,0,1333,1334,1,0,0,0,1334, - 1335,6,174,11,0,1335,365,1,0,0,0,1336,1337,3,62,23,0,1337,1338,1,0,0,0, - 1338,1339,6,175,11,0,1339,367,1,0,0,0,1340,1341,3,78,31,0,1341,1342,1,0, - 0,0,1342,1343,6,176,14,0,1343,1344,6,176,15,0,1344,369,1,0,0,0,1345,1346, - 3,66,25,0,1346,1347,1,0,0,0,1347,1348,6,177,20,0,1348,1349,6,177,15,0,1349, - 1350,6,177,31,0,1350,371,1,0,0,0,1351,1352,3,58,21,0,1352,1353,1,0,0,0, - 1353,1354,6,178,11,0,1354,373,1,0,0,0,1355,1356,3,60,22,0,1356,1357,1,0, - 0,0,1357,1358,6,179,11,0,1358,375,1,0,0,0,1359,1360,3,62,23,0,1360,1361, - 1,0,0,0,1361,1362,6,180,11,0,1362,377,1,0,0,0,1363,1364,3,116,50,0,1364, - 1365,1,0,0,0,1365,1366,6,181,17,0,1366,1367,6,181,15,0,1367,1368,6,181, - 7,0,1368,379,1,0,0,0,1369,1370,3,58,21,0,1370,1371,1,0,0,0,1371,1372,6, - 182,11,0,1372,381,1,0,0,0,1373,1374,3,60,22,0,1374,1375,1,0,0,0,1375,1376, - 6,183,11,0,1376,383,1,0,0,0,1377,1378,3,62,23,0,1378,1379,1,0,0,0,1379, - 1380,6,184,11,0,1380,385,1,0,0,0,1381,1382,3,184,84,0,1382,1383,1,0,0,0, - 1383,1384,6,185,15,0,1384,1385,6,185,0,0,1385,1386,6,185,25,0,1386,387, - 1,0,0,0,1387,1388,3,180,82,0,1388,1389,1,0,0,0,1389,1390,6,186,15,0,1390, - 1391,6,186,0,0,1391,1392,6,186,30,0,1392,389,1,0,0,0,1393,1394,3,106,45, - 0,1394,1395,1,0,0,0,1395,1396,6,187,15,0,1396,1397,6,187,0,0,1397,1398, - 6,187,32,0,1398,391,1,0,0,0,1399,1400,3,78,31,0,1400,1401,1,0,0,0,1401, - 1402,6,188,14,0,1402,1403,6,188,15,0,1403,393,1,0,0,0,62,0,1,2,3,4,5,6, - 7,8,9,10,11,12,13,14,15,566,576,580,583,592,594,605,612,617,656,661,670, - 677,682,684,695,703,706,708,713,718,724,731,736,742,745,753,757,890,895, - 900,902,908,993,997,1002,1007,1012,1014,1018,1020,1097,1101,1106,1324,1326, - 33,5,2,0,5,4,0,5,6,0,5,1,0,5,3,0,5,8,0,5,12,0,5,14,0,5,10,0,5,5,0,5,11, - 0,0,1,0,7,68,0,5,0,0,7,29,0,4,0,0,7,69,0,7,38,0,7,36,0,7,30,0,7,25,0,7, - 40,0,7,79,0,5,13,0,5,7,0,7,71,0,7,89,0,7,88,0,7,87,0,5,9,0,7,70,0,5,15, - 0,7,33,0]; + 2,187,7,187,2,188,7,188,2,189,7,189,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0, + 1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,3, + 1,3,1,3,1,3,1,3,1,3,1,3,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,5,1,5, + 1,5,1,5,1,5,1,5,1,5,1,6,1,6,1,6,1,6,1,6,1,6,1,6,1,7,1,7,1,7,1,7,1,7,1,7, + 1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,9,1,9,1,9, + 1,9,1,9,1,9,1,9,1,9,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1,10,1,11,1, + 11,1,11,1,11,1,11,1,11,1,11,1,12,1,12,1,12,1,12,1,12,1,12,1,12,1,12,1,12, + 1,12,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,13,1,14,1, + 14,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,15,1,15,1,15,1,15,1,15,1,15,1,16, + 1,16,1,16,1,16,1,16,1,16,1,16,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,18,1, + 18,1,18,1,18,1,18,1,18,1,18,1,18,1,19,1,19,1,19,1,19,1,19,1,19,1,19,1,19, + 1,20,4,20,567,8,20,11,20,12,20,568,1,20,1,20,1,21,1,21,1,21,1,21,5,21,577, + 8,21,10,21,12,21,580,9,21,1,21,3,21,583,8,21,1,21,3,21,586,8,21,1,21,1, + 21,1,22,1,22,1,22,1,22,1,22,5,22,595,8,22,10,22,12,22,598,9,22,1,22,1,22, + 1,22,1,22,1,22,1,23,4,23,606,8,23,11,23,12,23,607,1,23,1,23,1,24,1,24,1, + 24,3,24,615,8,24,1,25,4,25,618,8,25,11,25,12,25,619,1,26,1,26,1,26,1,26, + 1,26,1,27,1,27,1,27,1,27,1,27,1,28,1,28,1,28,1,28,1,29,1,29,1,29,1,29,1, + 30,1,30,1,30,1,30,1,31,1,31,1,31,1,31,1,32,1,32,1,33,1,33,1,34,1,34,1,34, + 1,35,1,35,1,36,1,36,3,36,659,8,36,1,36,4,36,662,8,36,11,36,12,36,663,1, + 37,1,37,1,38,1,38,1,39,1,39,1,39,3,39,673,8,39,1,40,1,40,1,41,1,41,1,41, + 3,41,680,8,41,1,42,1,42,1,42,5,42,685,8,42,10,42,12,42,688,9,42,1,42,1, + 42,1,42,1,42,1,42,1,42,5,42,696,8,42,10,42,12,42,699,9,42,1,42,1,42,1,42, + 1,42,1,42,3,42,706,8,42,1,42,3,42,709,8,42,3,42,711,8,42,1,43,4,43,714, + 8,43,11,43,12,43,715,1,44,4,44,719,8,44,11,44,12,44,720,1,44,1,44,5,44, + 725,8,44,10,44,12,44,728,9,44,1,44,1,44,4,44,732,8,44,11,44,12,44,733,1, + 44,4,44,737,8,44,11,44,12,44,738,1,44,1,44,5,44,743,8,44,10,44,12,44,746, + 9,44,3,44,748,8,44,1,44,1,44,1,44,1,44,4,44,754,8,44,11,44,12,44,755,1, + 44,1,44,3,44,760,8,44,1,45,1,45,1,45,1,46,1,46,1,46,1,46,1,47,1,47,1,47, + 1,47,1,48,1,48,1,49,1,49,1,49,1,50,1,50,1,51,1,51,1,51,1,51,1,51,1,52,1, + 52,1,53,1,53,1,53,1,53,1,53,1,53,1,54,1,54,1,54,1,54,1,54,1,54,1,55,1,55, + 1,55,1,55,1,55,1,56,1,56,1,57,1,57,1,57,1,58,1,58,1,58,1,59,1,59,1,59,1, + 59,1,59,1,60,1,60,1,60,1,60,1,61,1,61,1,61,1,61,1,61,1,62,1,62,1,62,1,62, + 1,62,1,62,1,63,1,63,1,63,1,64,1,64,1,65,1,65,1,65,1,65,1,65,1,65,1,66,1, + 66,1,67,1,67,1,67,1,67,1,67,1,68,1,68,1,68,1,69,1,69,1,69,1,70,1,70,1,70, + 1,71,1,71,1,72,1,72,1,72,1,73,1,73,1,74,1,74,1,74,1,75,1,75,1,76,1,76,1, + 77,1,77,1,78,1,78,1,79,1,79,1,80,1,80,1,80,5,80,882,8,80,10,80,12,80,885, + 9,80,1,80,1,80,4,80,889,8,80,11,80,12,80,890,3,80,893,8,80,1,81,1,81,1, + 81,1,81,1,81,1,82,1,82,1,82,1,82,1,82,1,83,1,83,5,83,907,8,83,10,83,12, + 83,910,9,83,1,83,1,83,3,83,914,8,83,1,83,4,83,917,8,83,11,83,12,83,918, + 3,83,921,8,83,1,84,1,84,4,84,925,8,84,11,84,12,84,926,1,84,1,84,1,85,1, + 85,1,86,1,86,1,86,1,86,1,87,1,87,1,87,1,87,1,88,1,88,1,88,1,88,1,89,1,89, + 1,89,1,89,1,89,1,90,1,90,1,90,1,90,1,91,1,91,1,91,1,91,1,92,1,92,1,92,1, + 92,1,93,1,93,1,93,1,93,1,94,1,94,1,94,1,94,1,95,1,95,1,95,1,95,1,95,1,95, + 1,95,1,95,1,95,1,96,1,96,1,96,1,96,1,97,1,97,1,97,1,97,1,98,1,98,1,98,1, + 98,1,99,1,99,1,99,1,99,1,100,1,100,1,100,1,100,1,100,1,101,1,101,1,101, + 1,101,1,102,1,102,1,102,1,102,1,103,1,103,1,103,1,103,3,103,1012,8,103, + 1,104,1,104,3,104,1016,8,104,1,104,5,104,1019,8,104,10,104,12,104,1022, + 9,104,1,104,1,104,3,104,1026,8,104,1,104,4,104,1029,8,104,11,104,12,104, + 1030,3,104,1033,8,104,1,105,1,105,4,105,1037,8,105,11,105,12,105,1038,1, + 106,1,106,1,106,1,106,1,107,1,107,1,107,1,107,1,108,1,108,1,108,1,108,1, + 109,1,109,1,109,1,109,1,109,1,110,1,110,1,110,1,110,1,111,1,111,1,111,1, + 111,1,112,1,112,1,112,1,112,1,113,1,113,1,113,1,114,1,114,1,114,1,114,1, + 115,1,115,1,115,1,115,1,116,1,116,1,116,1,116,1,117,1,117,1,117,1,117,1, + 118,1,118,1,118,1,118,1,118,1,119,1,119,1,119,1,119,1,119,1,120,1,120,1, + 120,1,120,1,120,1,121,1,121,1,121,1,121,1,121,1,121,1,121,1,122,1,122,1, + 123,4,123,1114,8,123,11,123,12,123,1115,1,123,1,123,3,123,1120,8,123,1, + 123,4,123,1123,8,123,11,123,12,123,1124,1,124,1,124,1,124,1,124,1,125,1, + 125,1,125,1,125,1,126,1,126,1,126,1,126,1,127,1,127,1,127,1,127,1,128,1, + 128,1,128,1,128,1,129,1,129,1,129,1,129,1,129,1,129,1,130,1,130,1,130,1, + 130,1,131,1,131,1,131,1,131,1,132,1,132,1,132,1,132,1,133,1,133,1,133,1, + 133,1,134,1,134,1,134,1,134,1,135,1,135,1,135,1,135,1,136,1,136,1,136,1, + 136,1,137,1,137,1,137,1,137,1,138,1,138,1,138,1,138,1,139,1,139,1,139,1, + 139,1,139,1,140,1,140,1,140,1,140,1,141,1,141,1,141,1,141,1,142,1,142,1, + 142,1,142,1,142,1,143,1,143,1,143,1,143,1,144,1,144,1,144,1,144,1,145,1, + 145,1,145,1,145,1,146,1,146,1,146,1,146,1,147,1,147,1,147,1,147,1,147,1, + 147,1,148,1,148,1,148,1,148,1,149,1,149,1,149,1,149,1,150,1,150,1,150,1, + 150,1,151,1,151,1,151,1,151,1,152,1,152,1,152,1,152,1,153,1,153,1,153,1, + 153,1,154,1,154,1,154,1,154,1,154,1,155,1,155,1,155,1,155,1,156,1,156,1, + 156,1,156,1,157,1,157,1,157,1,157,1,158,1,158,1,158,1,158,1,159,1,159,1, + 159,1,159,1,160,1,160,1,160,1,160,1,161,1,161,1,161,1,161,1,161,1,162,1, + 162,1,162,1,162,1,162,1,163,1,163,1,163,1,163,1,164,1,164,1,164,1,164,1, + 165,1,165,1,165,1,165,1,166,1,166,1,166,1,166,1,166,1,167,1,167,1,167,1, + 167,1,167,1,167,1,167,1,167,1,167,1,167,1,168,1,168,1,168,1,168,1,169,1, + 169,1,169,1,169,1,170,1,170,1,170,1,170,1,171,1,171,1,171,1,171,1,171,1, + 172,1,172,1,173,1,173,1,173,1,173,1,173,4,173,1343,8,173,11,173,12,173, + 1344,1,174,1,174,1,174,1,174,1,175,1,175,1,175,1,175,1,176,1,176,1,176, + 1,176,1,177,1,177,1,177,1,177,1,177,1,178,1,178,1,178,1,178,1,178,1,178, + 1,179,1,179,1,179,1,179,1,180,1,180,1,180,1,180,1,181,1,181,1,181,1,181, + 1,182,1,182,1,182,1,182,1,182,1,182,1,183,1,183,1,183,1,183,1,184,1,184, + 1,184,1,184,1,185,1,185,1,185,1,185,1,186,1,186,1,186,1,186,1,186,1,186, + 1,187,1,187,1,187,1,187,1,187,1,187,1,188,1,188,1,188,1,188,1,188,1,188, + 1,189,1,189,1,189,1,189,1,189,2,596,697,0,190,16,1,18,2,20,3,22,4,24,5, + 26,6,28,7,30,8,32,9,34,10,36,11,38,12,40,13,42,14,44,15,46,16,48,17,50, + 18,52,19,54,20,56,21,58,22,60,23,62,24,64,0,66,25,68,0,70,0,72,26,74,27, + 76,28,78,29,80,0,82,0,84,0,86,0,88,0,90,0,92,0,94,0,96,0,98,0,100,30,102, + 31,104,32,106,33,108,34,110,35,112,36,114,37,116,38,118,39,120,40,122,41, + 124,42,126,43,128,44,130,45,132,46,134,47,136,48,138,49,140,50,142,51,144, + 52,146,53,148,54,150,55,152,56,154,57,156,58,158,59,160,60,162,61,164,62, + 166,63,168,64,170,65,172,66,174,67,176,68,178,69,180,70,182,71,184,0,186, + 72,188,73,190,74,192,75,194,0,196,0,198,0,200,0,202,0,204,0,206,76,208, + 0,210,77,212,78,214,79,216,0,218,0,220,0,222,0,224,0,226,80,228,81,230, + 82,232,83,234,0,236,0,238,0,240,0,242,84,244,0,246,85,248,86,250,87,252, + 0,254,0,256,88,258,89,260,0,262,90,264,0,266,0,268,91,270,92,272,93,274, + 0,276,0,278,0,280,0,282,0,284,0,286,0,288,94,290,95,292,96,294,0,296,0, + 298,0,300,0,302,0,304,97,306,98,308,99,310,0,312,0,314,0,316,0,318,100, + 320,101,322,102,324,0,326,0,328,0,330,0,332,103,334,104,336,105,338,0,340, + 106,342,107,344,108,346,109,348,0,350,110,352,111,354,112,356,113,358,0, + 360,114,362,115,364,116,366,117,368,118,370,0,372,0,374,119,376,120,378, + 121,380,0,382,122,384,123,386,124,388,0,390,0,392,0,394,0,16,0,1,2,3,4, + 5,6,7,8,9,10,11,12,13,14,15,35,2,0,68,68,100,100,2,0,73,73,105,105,2,0, + 83,83,115,115,2,0,69,69,101,101,2,0,67,67,99,99,2,0,84,84,116,116,2,0,82, + 82,114,114,2,0,79,79,111,111,2,0,80,80,112,112,2,0,78,78,110,110,2,0,72, + 72,104,104,2,0,86,86,118,118,2,0,65,65,97,97,2,0,76,76,108,108,2,0,88,88, + 120,120,2,0,70,70,102,102,2,0,77,77,109,109,2,0,71,71,103,103,2,0,75,75, + 107,107,2,0,85,85,117,117,2,0,87,87,119,119,6,0,9,10,13,13,32,32,47,47, + 91,91,93,93,2,0,10,10,13,13,3,0,9,10,13,13,32,32,10,0,9,10,13,13,32,32, + 44,44,47,47,61,61,91,91,93,93,96,96,124,124,2,0,42,42,47,47,1,0,48,57,2, + 0,65,90,97,122,8,0,34,34,78,78,82,82,84,84,92,92,110,110,114,114,116,116, + 4,0,10,10,13,13,34,34,92,92,2,0,43,43,45,45,1,0,96,96,2,0,66,66,98,98,2, + 0,89,89,121,121,11,0,9,10,13,13,32,32,34,35,44,44,47,47,58,58,60,60,62, + 63,92,92,124,124,1448,0,16,1,0,0,0,0,18,1,0,0,0,0,20,1,0,0,0,0,22,1,0,0, + 0,0,24,1,0,0,0,0,26,1,0,0,0,0,28,1,0,0,0,0,30,1,0,0,0,0,32,1,0,0,0,0,34, + 1,0,0,0,0,36,1,0,0,0,0,38,1,0,0,0,0,40,1,0,0,0,0,42,1,0,0,0,0,44,1,0,0, + 0,0,46,1,0,0,0,0,48,1,0,0,0,0,50,1,0,0,0,0,52,1,0,0,0,0,54,1,0,0,0,0,56, + 1,0,0,0,0,58,1,0,0,0,0,60,1,0,0,0,0,62,1,0,0,0,0,66,1,0,0,0,1,68,1,0,0, + 0,1,70,1,0,0,0,1,72,1,0,0,0,1,74,1,0,0,0,1,76,1,0,0,0,2,78,1,0,0,0,2,100, + 1,0,0,0,2,102,1,0,0,0,2,104,1,0,0,0,2,106,1,0,0,0,2,108,1,0,0,0,2,110,1, + 0,0,0,2,112,1,0,0,0,2,114,1,0,0,0,2,116,1,0,0,0,2,118,1,0,0,0,2,120,1,0, + 0,0,2,122,1,0,0,0,2,124,1,0,0,0,2,126,1,0,0,0,2,128,1,0,0,0,2,130,1,0,0, + 0,2,132,1,0,0,0,2,134,1,0,0,0,2,136,1,0,0,0,2,138,1,0,0,0,2,140,1,0,0,0, + 2,142,1,0,0,0,2,144,1,0,0,0,2,146,1,0,0,0,2,148,1,0,0,0,2,150,1,0,0,0,2, + 152,1,0,0,0,2,154,1,0,0,0,2,156,1,0,0,0,2,158,1,0,0,0,2,160,1,0,0,0,2,162, + 1,0,0,0,2,164,1,0,0,0,2,166,1,0,0,0,2,168,1,0,0,0,2,170,1,0,0,0,2,172,1, + 0,0,0,2,174,1,0,0,0,2,176,1,0,0,0,2,178,1,0,0,0,2,180,1,0,0,0,2,182,1,0, + 0,0,2,186,1,0,0,0,2,188,1,0,0,0,2,190,1,0,0,0,2,192,1,0,0,0,3,194,1,0,0, + 0,3,196,1,0,0,0,3,198,1,0,0,0,3,200,1,0,0,0,3,202,1,0,0,0,3,204,1,0,0,0, + 3,206,1,0,0,0,3,208,1,0,0,0,3,210,1,0,0,0,3,212,1,0,0,0,3,214,1,0,0,0,4, + 216,1,0,0,0,4,218,1,0,0,0,4,220,1,0,0,0,4,226,1,0,0,0,4,228,1,0,0,0,4,230, + 1,0,0,0,4,232,1,0,0,0,5,234,1,0,0,0,5,236,1,0,0,0,5,238,1,0,0,0,5,240,1, + 0,0,0,5,242,1,0,0,0,5,244,1,0,0,0,5,246,1,0,0,0,5,248,1,0,0,0,5,250,1,0, + 0,0,6,252,1,0,0,0,6,254,1,0,0,0,6,256,1,0,0,0,6,258,1,0,0,0,6,262,1,0,0, + 0,6,264,1,0,0,0,6,266,1,0,0,0,6,268,1,0,0,0,6,270,1,0,0,0,6,272,1,0,0,0, + 7,274,1,0,0,0,7,276,1,0,0,0,7,278,1,0,0,0,7,280,1,0,0,0,7,282,1,0,0,0,7, + 284,1,0,0,0,7,286,1,0,0,0,7,288,1,0,0,0,7,290,1,0,0,0,7,292,1,0,0,0,8,294, + 1,0,0,0,8,296,1,0,0,0,8,298,1,0,0,0,8,300,1,0,0,0,8,302,1,0,0,0,8,304,1, + 0,0,0,8,306,1,0,0,0,8,308,1,0,0,0,9,310,1,0,0,0,9,312,1,0,0,0,9,314,1,0, + 0,0,9,316,1,0,0,0,9,318,1,0,0,0,9,320,1,0,0,0,9,322,1,0,0,0,10,324,1,0, + 0,0,10,326,1,0,0,0,10,328,1,0,0,0,10,330,1,0,0,0,10,332,1,0,0,0,10,334, + 1,0,0,0,10,336,1,0,0,0,11,338,1,0,0,0,11,340,1,0,0,0,11,342,1,0,0,0,11, + 344,1,0,0,0,11,346,1,0,0,0,12,348,1,0,0,0,12,350,1,0,0,0,12,352,1,0,0,0, + 12,354,1,0,0,0,12,356,1,0,0,0,13,358,1,0,0,0,13,360,1,0,0,0,13,362,1,0, + 0,0,13,364,1,0,0,0,13,366,1,0,0,0,13,368,1,0,0,0,14,370,1,0,0,0,14,372, + 1,0,0,0,14,374,1,0,0,0,14,376,1,0,0,0,14,378,1,0,0,0,15,380,1,0,0,0,15, + 382,1,0,0,0,15,384,1,0,0,0,15,386,1,0,0,0,15,388,1,0,0,0,15,390,1,0,0,0, + 15,392,1,0,0,0,15,394,1,0,0,0,16,396,1,0,0,0,18,406,1,0,0,0,20,413,1,0, + 0,0,22,422,1,0,0,0,24,429,1,0,0,0,26,439,1,0,0,0,28,446,1,0,0,0,30,453, + 1,0,0,0,32,467,1,0,0,0,34,474,1,0,0,0,36,482,1,0,0,0,38,491,1,0,0,0,40, + 498,1,0,0,0,42,508,1,0,0,0,44,520,1,0,0,0,46,529,1,0,0,0,48,535,1,0,0,0, + 50,542,1,0,0,0,52,549,1,0,0,0,54,557,1,0,0,0,56,566,1,0,0,0,58,572,1,0, + 0,0,60,589,1,0,0,0,62,605,1,0,0,0,64,614,1,0,0,0,66,617,1,0,0,0,68,621, + 1,0,0,0,70,626,1,0,0,0,72,631,1,0,0,0,74,635,1,0,0,0,76,639,1,0,0,0,78, + 643,1,0,0,0,80,647,1,0,0,0,82,649,1,0,0,0,84,651,1,0,0,0,86,654,1,0,0,0, + 88,656,1,0,0,0,90,665,1,0,0,0,92,667,1,0,0,0,94,672,1,0,0,0,96,674,1,0, + 0,0,98,679,1,0,0,0,100,710,1,0,0,0,102,713,1,0,0,0,104,759,1,0,0,0,106, + 761,1,0,0,0,108,764,1,0,0,0,110,768,1,0,0,0,112,772,1,0,0,0,114,774,1,0, + 0,0,116,777,1,0,0,0,118,779,1,0,0,0,120,784,1,0,0,0,122,786,1,0,0,0,124, + 792,1,0,0,0,126,798,1,0,0,0,128,803,1,0,0,0,130,805,1,0,0,0,132,808,1,0, + 0,0,134,811,1,0,0,0,136,816,1,0,0,0,138,820,1,0,0,0,140,825,1,0,0,0,142, + 831,1,0,0,0,144,834,1,0,0,0,146,836,1,0,0,0,148,842,1,0,0,0,150,844,1,0, + 0,0,152,849,1,0,0,0,154,852,1,0,0,0,156,855,1,0,0,0,158,858,1,0,0,0,160, + 860,1,0,0,0,162,863,1,0,0,0,164,865,1,0,0,0,166,868,1,0,0,0,168,870,1,0, + 0,0,170,872,1,0,0,0,172,874,1,0,0,0,174,876,1,0,0,0,176,892,1,0,0,0,178, + 894,1,0,0,0,180,899,1,0,0,0,182,920,1,0,0,0,184,922,1,0,0,0,186,930,1,0, + 0,0,188,932,1,0,0,0,190,936,1,0,0,0,192,940,1,0,0,0,194,944,1,0,0,0,196, + 949,1,0,0,0,198,953,1,0,0,0,200,957,1,0,0,0,202,961,1,0,0,0,204,965,1,0, + 0,0,206,969,1,0,0,0,208,978,1,0,0,0,210,982,1,0,0,0,212,986,1,0,0,0,214, + 990,1,0,0,0,216,994,1,0,0,0,218,999,1,0,0,0,220,1003,1,0,0,0,222,1011,1, + 0,0,0,224,1032,1,0,0,0,226,1036,1,0,0,0,228,1040,1,0,0,0,230,1044,1,0,0, + 0,232,1048,1,0,0,0,234,1052,1,0,0,0,236,1057,1,0,0,0,238,1061,1,0,0,0,240, + 1065,1,0,0,0,242,1069,1,0,0,0,244,1072,1,0,0,0,246,1076,1,0,0,0,248,1080, + 1,0,0,0,250,1084,1,0,0,0,252,1088,1,0,0,0,254,1093,1,0,0,0,256,1098,1,0, + 0,0,258,1103,1,0,0,0,260,1110,1,0,0,0,262,1119,1,0,0,0,264,1126,1,0,0,0, + 266,1130,1,0,0,0,268,1134,1,0,0,0,270,1138,1,0,0,0,272,1142,1,0,0,0,274, + 1146,1,0,0,0,276,1152,1,0,0,0,278,1156,1,0,0,0,280,1160,1,0,0,0,282,1164, + 1,0,0,0,284,1168,1,0,0,0,286,1172,1,0,0,0,288,1176,1,0,0,0,290,1180,1,0, + 0,0,292,1184,1,0,0,0,294,1188,1,0,0,0,296,1193,1,0,0,0,298,1197,1,0,0,0, + 300,1201,1,0,0,0,302,1206,1,0,0,0,304,1210,1,0,0,0,306,1214,1,0,0,0,308, + 1218,1,0,0,0,310,1222,1,0,0,0,312,1228,1,0,0,0,314,1232,1,0,0,0,316,1236, + 1,0,0,0,318,1240,1,0,0,0,320,1244,1,0,0,0,322,1248,1,0,0,0,324,1252,1,0, + 0,0,326,1257,1,0,0,0,328,1261,1,0,0,0,330,1265,1,0,0,0,332,1269,1,0,0,0, + 334,1273,1,0,0,0,336,1277,1,0,0,0,338,1281,1,0,0,0,340,1286,1,0,0,0,342, + 1291,1,0,0,0,344,1295,1,0,0,0,346,1299,1,0,0,0,348,1303,1,0,0,0,350,1308, + 1,0,0,0,352,1318,1,0,0,0,354,1322,1,0,0,0,356,1326,1,0,0,0,358,1330,1,0, + 0,0,360,1335,1,0,0,0,362,1342,1,0,0,0,364,1346,1,0,0,0,366,1350,1,0,0,0, + 368,1354,1,0,0,0,370,1358,1,0,0,0,372,1363,1,0,0,0,374,1369,1,0,0,0,376, + 1373,1,0,0,0,378,1377,1,0,0,0,380,1381,1,0,0,0,382,1387,1,0,0,0,384,1391, + 1,0,0,0,386,1395,1,0,0,0,388,1399,1,0,0,0,390,1405,1,0,0,0,392,1411,1,0, + 0,0,394,1417,1,0,0,0,396,397,7,0,0,0,397,398,7,1,0,0,398,399,7,2,0,0,399, + 400,7,2,0,0,400,401,7,3,0,0,401,402,7,4,0,0,402,403,7,5,0,0,403,404,1,0, + 0,0,404,405,6,0,0,0,405,17,1,0,0,0,406,407,7,0,0,0,407,408,7,6,0,0,408, + 409,7,7,0,0,409,410,7,8,0,0,410,411,1,0,0,0,411,412,6,1,1,0,412,19,1,0, + 0,0,413,414,7,3,0,0,414,415,7,9,0,0,415,416,7,6,0,0,416,417,7,1,0,0,417, + 418,7,4,0,0,418,419,7,10,0,0,419,420,1,0,0,0,420,421,6,2,2,0,421,21,1,0, + 0,0,422,423,7,3,0,0,423,424,7,11,0,0,424,425,7,12,0,0,425,426,7,13,0,0, + 426,427,1,0,0,0,427,428,6,3,0,0,428,23,1,0,0,0,429,430,7,3,0,0,430,431, + 7,14,0,0,431,432,7,8,0,0,432,433,7,13,0,0,433,434,7,12,0,0,434,435,7,1, + 0,0,435,436,7,9,0,0,436,437,1,0,0,0,437,438,6,4,3,0,438,25,1,0,0,0,439, + 440,7,15,0,0,440,441,7,6,0,0,441,442,7,7,0,0,442,443,7,16,0,0,443,444,1, + 0,0,0,444,445,6,5,4,0,445,27,1,0,0,0,446,447,7,17,0,0,447,448,7,6,0,0,448, + 449,7,7,0,0,449,450,7,18,0,0,450,451,1,0,0,0,451,452,6,6,0,0,452,29,1,0, + 0,0,453,454,7,1,0,0,454,455,7,9,0,0,455,456,7,13,0,0,456,457,7,1,0,0,457, + 458,7,9,0,0,458,459,7,3,0,0,459,460,7,2,0,0,460,461,7,5,0,0,461,462,7,12, + 0,0,462,463,7,5,0,0,463,464,7,2,0,0,464,465,1,0,0,0,465,466,6,7,0,0,466, + 31,1,0,0,0,467,468,7,18,0,0,468,469,7,3,0,0,469,470,7,3,0,0,470,471,7,8, + 0,0,471,472,1,0,0,0,472,473,6,8,1,0,473,33,1,0,0,0,474,475,7,13,0,0,475, + 476,7,1,0,0,476,477,7,16,0,0,477,478,7,1,0,0,478,479,7,5,0,0,479,480,1, + 0,0,0,480,481,6,9,0,0,481,35,1,0,0,0,482,483,7,13,0,0,483,484,7,7,0,0,484, + 485,7,7,0,0,485,486,7,18,0,0,486,487,7,19,0,0,487,488,7,8,0,0,488,489,1, + 0,0,0,489,490,6,10,5,0,490,37,1,0,0,0,491,492,7,16,0,0,492,493,7,3,0,0, + 493,494,7,5,0,0,494,495,7,12,0,0,495,496,1,0,0,0,496,497,6,11,6,0,497,39, + 1,0,0,0,498,499,7,16,0,0,499,500,7,3,0,0,500,501,7,5,0,0,501,502,7,6,0, + 0,502,503,7,1,0,0,503,504,7,4,0,0,504,505,7,2,0,0,505,506,1,0,0,0,506,507, + 6,12,7,0,507,41,1,0,0,0,508,509,7,16,0,0,509,510,7,11,0,0,510,511,5,95, + 0,0,511,512,7,3,0,0,512,513,7,14,0,0,513,514,7,8,0,0,514,515,7,12,0,0,515, + 516,7,9,0,0,516,517,7,0,0,0,517,518,1,0,0,0,518,519,6,13,8,0,519,43,1,0, + 0,0,520,521,7,6,0,0,521,522,7,3,0,0,522,523,7,9,0,0,523,524,7,12,0,0,524, + 525,7,16,0,0,525,526,7,3,0,0,526,527,1,0,0,0,527,528,6,14,9,0,528,45,1, + 0,0,0,529,530,7,6,0,0,530,531,7,7,0,0,531,532,7,20,0,0,532,533,1,0,0,0, + 533,534,6,15,0,0,534,47,1,0,0,0,535,536,7,2,0,0,536,537,7,10,0,0,537,538, + 7,7,0,0,538,539,7,20,0,0,539,540,1,0,0,0,540,541,6,16,10,0,541,49,1,0,0, + 0,542,543,7,2,0,0,543,544,7,7,0,0,544,545,7,6,0,0,545,546,7,5,0,0,546,547, + 1,0,0,0,547,548,6,17,0,0,548,51,1,0,0,0,549,550,7,2,0,0,550,551,7,5,0,0, + 551,552,7,12,0,0,552,553,7,5,0,0,553,554,7,2,0,0,554,555,1,0,0,0,555,556, + 6,18,0,0,556,53,1,0,0,0,557,558,7,20,0,0,558,559,7,10,0,0,559,560,7,3,0, + 0,560,561,7,6,0,0,561,562,7,3,0,0,562,563,1,0,0,0,563,564,6,19,0,0,564, + 55,1,0,0,0,565,567,8,21,0,0,566,565,1,0,0,0,567,568,1,0,0,0,568,566,1,0, + 0,0,568,569,1,0,0,0,569,570,1,0,0,0,570,571,6,20,0,0,571,57,1,0,0,0,572, + 573,5,47,0,0,573,574,5,47,0,0,574,578,1,0,0,0,575,577,8,22,0,0,576,575, + 1,0,0,0,577,580,1,0,0,0,578,576,1,0,0,0,578,579,1,0,0,0,579,582,1,0,0,0, + 580,578,1,0,0,0,581,583,5,13,0,0,582,581,1,0,0,0,582,583,1,0,0,0,583,585, + 1,0,0,0,584,586,5,10,0,0,585,584,1,0,0,0,585,586,1,0,0,0,586,587,1,0,0, + 0,587,588,6,21,11,0,588,59,1,0,0,0,589,590,5,47,0,0,590,591,5,42,0,0,591, + 596,1,0,0,0,592,595,3,60,22,0,593,595,9,0,0,0,594,592,1,0,0,0,594,593,1, + 0,0,0,595,598,1,0,0,0,596,597,1,0,0,0,596,594,1,0,0,0,597,599,1,0,0,0,598, + 596,1,0,0,0,599,600,5,42,0,0,600,601,5,47,0,0,601,602,1,0,0,0,602,603,6, + 22,11,0,603,61,1,0,0,0,604,606,7,23,0,0,605,604,1,0,0,0,606,607,1,0,0,0, + 607,605,1,0,0,0,607,608,1,0,0,0,608,609,1,0,0,0,609,610,6,23,11,0,610,63, + 1,0,0,0,611,615,8,24,0,0,612,613,5,47,0,0,613,615,8,25,0,0,614,611,1,0, + 0,0,614,612,1,0,0,0,615,65,1,0,0,0,616,618,3,64,24,0,617,616,1,0,0,0,618, + 619,1,0,0,0,619,617,1,0,0,0,619,620,1,0,0,0,620,67,1,0,0,0,621,622,3,178, + 81,0,622,623,1,0,0,0,623,624,6,26,12,0,624,625,6,26,13,0,625,69,1,0,0,0, + 626,627,3,78,31,0,627,628,1,0,0,0,628,629,6,27,14,0,629,630,6,27,15,0,630, + 71,1,0,0,0,631,632,3,62,23,0,632,633,1,0,0,0,633,634,6,28,11,0,634,73,1, + 0,0,0,635,636,3,58,21,0,636,637,1,0,0,0,637,638,6,29,11,0,638,75,1,0,0, + 0,639,640,3,60,22,0,640,641,1,0,0,0,641,642,6,30,11,0,642,77,1,0,0,0,643, + 644,5,124,0,0,644,645,1,0,0,0,645,646,6,31,15,0,646,79,1,0,0,0,647,648, + 7,26,0,0,648,81,1,0,0,0,649,650,7,27,0,0,650,83,1,0,0,0,651,652,5,92,0, + 0,652,653,7,28,0,0,653,85,1,0,0,0,654,655,8,29,0,0,655,87,1,0,0,0,656,658, + 7,3,0,0,657,659,7,30,0,0,658,657,1,0,0,0,658,659,1,0,0,0,659,661,1,0,0, + 0,660,662,3,80,32,0,661,660,1,0,0,0,662,663,1,0,0,0,663,661,1,0,0,0,663, + 664,1,0,0,0,664,89,1,0,0,0,665,666,5,64,0,0,666,91,1,0,0,0,667,668,5,96, + 0,0,668,93,1,0,0,0,669,673,8,31,0,0,670,671,5,96,0,0,671,673,5,96,0,0,672, + 669,1,0,0,0,672,670,1,0,0,0,673,95,1,0,0,0,674,675,5,95,0,0,675,97,1,0, + 0,0,676,680,3,82,33,0,677,680,3,80,32,0,678,680,3,96,40,0,679,676,1,0,0, + 0,679,677,1,0,0,0,679,678,1,0,0,0,680,99,1,0,0,0,681,686,5,34,0,0,682,685, + 3,84,34,0,683,685,3,86,35,0,684,682,1,0,0,0,684,683,1,0,0,0,685,688,1,0, + 0,0,686,684,1,0,0,0,686,687,1,0,0,0,687,689,1,0,0,0,688,686,1,0,0,0,689, + 711,5,34,0,0,690,691,5,34,0,0,691,692,5,34,0,0,692,693,5,34,0,0,693,697, + 1,0,0,0,694,696,8,22,0,0,695,694,1,0,0,0,696,699,1,0,0,0,697,698,1,0,0, + 0,697,695,1,0,0,0,698,700,1,0,0,0,699,697,1,0,0,0,700,701,5,34,0,0,701, + 702,5,34,0,0,702,703,5,34,0,0,703,705,1,0,0,0,704,706,5,34,0,0,705,704, + 1,0,0,0,705,706,1,0,0,0,706,708,1,0,0,0,707,709,5,34,0,0,708,707,1,0,0, + 0,708,709,1,0,0,0,709,711,1,0,0,0,710,681,1,0,0,0,710,690,1,0,0,0,711,101, + 1,0,0,0,712,714,3,80,32,0,713,712,1,0,0,0,714,715,1,0,0,0,715,713,1,0,0, + 0,715,716,1,0,0,0,716,103,1,0,0,0,717,719,3,80,32,0,718,717,1,0,0,0,719, + 720,1,0,0,0,720,718,1,0,0,0,720,721,1,0,0,0,721,722,1,0,0,0,722,726,3,120, + 52,0,723,725,3,80,32,0,724,723,1,0,0,0,725,728,1,0,0,0,726,724,1,0,0,0, + 726,727,1,0,0,0,727,760,1,0,0,0,728,726,1,0,0,0,729,731,3,120,52,0,730, + 732,3,80,32,0,731,730,1,0,0,0,732,733,1,0,0,0,733,731,1,0,0,0,733,734,1, + 0,0,0,734,760,1,0,0,0,735,737,3,80,32,0,736,735,1,0,0,0,737,738,1,0,0,0, + 738,736,1,0,0,0,738,739,1,0,0,0,739,747,1,0,0,0,740,744,3,120,52,0,741, + 743,3,80,32,0,742,741,1,0,0,0,743,746,1,0,0,0,744,742,1,0,0,0,744,745,1, + 0,0,0,745,748,1,0,0,0,746,744,1,0,0,0,747,740,1,0,0,0,747,748,1,0,0,0,748, + 749,1,0,0,0,749,750,3,88,36,0,750,760,1,0,0,0,751,753,3,120,52,0,752,754, + 3,80,32,0,753,752,1,0,0,0,754,755,1,0,0,0,755,753,1,0,0,0,755,756,1,0,0, + 0,756,757,1,0,0,0,757,758,3,88,36,0,758,760,1,0,0,0,759,718,1,0,0,0,759, + 729,1,0,0,0,759,736,1,0,0,0,759,751,1,0,0,0,760,105,1,0,0,0,761,762,7,32, + 0,0,762,763,7,33,0,0,763,107,1,0,0,0,764,765,7,12,0,0,765,766,7,9,0,0,766, + 767,7,0,0,0,767,109,1,0,0,0,768,769,7,12,0,0,769,770,7,2,0,0,770,771,7, + 4,0,0,771,111,1,0,0,0,772,773,5,61,0,0,773,113,1,0,0,0,774,775,5,58,0,0, + 775,776,5,58,0,0,776,115,1,0,0,0,777,778,5,44,0,0,778,117,1,0,0,0,779,780, + 7,0,0,0,780,781,7,3,0,0,781,782,7,2,0,0,782,783,7,4,0,0,783,119,1,0,0,0, + 784,785,5,46,0,0,785,121,1,0,0,0,786,787,7,15,0,0,787,788,7,12,0,0,788, + 789,7,13,0,0,789,790,7,2,0,0,790,791,7,3,0,0,791,123,1,0,0,0,792,793,7, + 15,0,0,793,794,7,1,0,0,794,795,7,6,0,0,795,796,7,2,0,0,796,797,7,5,0,0, + 797,125,1,0,0,0,798,799,7,13,0,0,799,800,7,12,0,0,800,801,7,2,0,0,801,802, + 7,5,0,0,802,127,1,0,0,0,803,804,5,40,0,0,804,129,1,0,0,0,805,806,7,1,0, + 0,806,807,7,9,0,0,807,131,1,0,0,0,808,809,7,1,0,0,809,810,7,2,0,0,810,133, + 1,0,0,0,811,812,7,13,0,0,812,813,7,1,0,0,813,814,7,18,0,0,814,815,7,3,0, + 0,815,135,1,0,0,0,816,817,7,9,0,0,817,818,7,7,0,0,818,819,7,5,0,0,819,137, + 1,0,0,0,820,821,7,9,0,0,821,822,7,19,0,0,822,823,7,13,0,0,823,824,7,13, + 0,0,824,139,1,0,0,0,825,826,7,9,0,0,826,827,7,19,0,0,827,828,7,13,0,0,828, + 829,7,13,0,0,829,830,7,2,0,0,830,141,1,0,0,0,831,832,7,7,0,0,832,833,7, + 6,0,0,833,143,1,0,0,0,834,835,5,63,0,0,835,145,1,0,0,0,836,837,7,6,0,0, + 837,838,7,13,0,0,838,839,7,1,0,0,839,840,7,18,0,0,840,841,7,3,0,0,841,147, + 1,0,0,0,842,843,5,41,0,0,843,149,1,0,0,0,844,845,7,5,0,0,845,846,7,6,0, + 0,846,847,7,19,0,0,847,848,7,3,0,0,848,151,1,0,0,0,849,850,5,61,0,0,850, + 851,5,61,0,0,851,153,1,0,0,0,852,853,5,61,0,0,853,854,5,126,0,0,854,155, + 1,0,0,0,855,856,5,33,0,0,856,857,5,61,0,0,857,157,1,0,0,0,858,859,5,60, + 0,0,859,159,1,0,0,0,860,861,5,60,0,0,861,862,5,61,0,0,862,161,1,0,0,0,863, + 864,5,62,0,0,864,163,1,0,0,0,865,866,5,62,0,0,866,867,5,61,0,0,867,165, + 1,0,0,0,868,869,5,43,0,0,869,167,1,0,0,0,870,871,5,45,0,0,871,169,1,0,0, + 0,872,873,5,42,0,0,873,171,1,0,0,0,874,875,5,47,0,0,875,173,1,0,0,0,876, + 877,5,37,0,0,877,175,1,0,0,0,878,879,3,144,64,0,879,883,3,82,33,0,880,882, + 3,98,41,0,881,880,1,0,0,0,882,885,1,0,0,0,883,881,1,0,0,0,883,884,1,0,0, + 0,884,893,1,0,0,0,885,883,1,0,0,0,886,888,3,144,64,0,887,889,3,80,32,0, + 888,887,1,0,0,0,889,890,1,0,0,0,890,888,1,0,0,0,890,891,1,0,0,0,891,893, + 1,0,0,0,892,878,1,0,0,0,892,886,1,0,0,0,893,177,1,0,0,0,894,895,5,91,0, + 0,895,896,1,0,0,0,896,897,6,81,0,0,897,898,6,81,0,0,898,179,1,0,0,0,899, + 900,5,93,0,0,900,901,1,0,0,0,901,902,6,82,15,0,902,903,6,82,15,0,903,181, + 1,0,0,0,904,908,3,82,33,0,905,907,3,98,41,0,906,905,1,0,0,0,907,910,1,0, + 0,0,908,906,1,0,0,0,908,909,1,0,0,0,909,921,1,0,0,0,910,908,1,0,0,0,911, + 914,3,96,40,0,912,914,3,90,37,0,913,911,1,0,0,0,913,912,1,0,0,0,914,916, + 1,0,0,0,915,917,3,98,41,0,916,915,1,0,0,0,917,918,1,0,0,0,918,916,1,0,0, + 0,918,919,1,0,0,0,919,921,1,0,0,0,920,904,1,0,0,0,920,913,1,0,0,0,921,183, + 1,0,0,0,922,924,3,92,38,0,923,925,3,94,39,0,924,923,1,0,0,0,925,926,1,0, + 0,0,926,924,1,0,0,0,926,927,1,0,0,0,927,928,1,0,0,0,928,929,3,92,38,0,929, + 185,1,0,0,0,930,931,3,184,84,0,931,187,1,0,0,0,932,933,3,58,21,0,933,934, + 1,0,0,0,934,935,6,86,11,0,935,189,1,0,0,0,936,937,3,60,22,0,937,938,1,0, + 0,0,938,939,6,87,11,0,939,191,1,0,0,0,940,941,3,62,23,0,941,942,1,0,0,0, + 942,943,6,88,11,0,943,193,1,0,0,0,944,945,3,78,31,0,945,946,1,0,0,0,946, + 947,6,89,14,0,947,948,6,89,15,0,948,195,1,0,0,0,949,950,3,178,81,0,950, + 951,1,0,0,0,951,952,6,90,12,0,952,197,1,0,0,0,953,954,3,180,82,0,954,955, + 1,0,0,0,955,956,6,91,16,0,956,199,1,0,0,0,957,958,3,116,50,0,958,959,1, + 0,0,0,959,960,6,92,17,0,960,201,1,0,0,0,961,962,3,112,48,0,962,963,1,0, + 0,0,963,964,6,93,18,0,964,203,1,0,0,0,965,966,3,100,42,0,966,967,1,0,0, + 0,967,968,6,94,19,0,968,205,1,0,0,0,969,970,7,16,0,0,970,971,7,3,0,0,971, + 972,7,5,0,0,972,973,7,12,0,0,973,974,7,0,0,0,974,975,7,12,0,0,975,976,7, + 5,0,0,976,977,7,12,0,0,977,207,1,0,0,0,978,979,3,66,25,0,979,980,1,0,0, + 0,980,981,6,96,20,0,981,209,1,0,0,0,982,983,3,58,21,0,983,984,1,0,0,0,984, + 985,6,97,11,0,985,211,1,0,0,0,986,987,3,60,22,0,987,988,1,0,0,0,988,989, + 6,98,11,0,989,213,1,0,0,0,990,991,3,62,23,0,991,992,1,0,0,0,992,993,6,99, + 11,0,993,215,1,0,0,0,994,995,3,78,31,0,995,996,1,0,0,0,996,997,6,100,14, + 0,997,998,6,100,15,0,998,217,1,0,0,0,999,1000,3,120,52,0,1000,1001,1,0, + 0,0,1001,1002,6,101,21,0,1002,219,1,0,0,0,1003,1004,3,116,50,0,1004,1005, + 1,0,0,0,1005,1006,6,102,17,0,1006,221,1,0,0,0,1007,1012,3,82,33,0,1008, + 1012,3,80,32,0,1009,1012,3,96,40,0,1010,1012,3,170,77,0,1011,1007,1,0,0, + 0,1011,1008,1,0,0,0,1011,1009,1,0,0,0,1011,1010,1,0,0,0,1012,223,1,0,0, + 0,1013,1016,3,82,33,0,1014,1016,3,170,77,0,1015,1013,1,0,0,0,1015,1014, + 1,0,0,0,1016,1020,1,0,0,0,1017,1019,3,222,103,0,1018,1017,1,0,0,0,1019, + 1022,1,0,0,0,1020,1018,1,0,0,0,1020,1021,1,0,0,0,1021,1033,1,0,0,0,1022, + 1020,1,0,0,0,1023,1026,3,96,40,0,1024,1026,3,90,37,0,1025,1023,1,0,0,0, + 1025,1024,1,0,0,0,1026,1028,1,0,0,0,1027,1029,3,222,103,0,1028,1027,1,0, + 0,0,1029,1030,1,0,0,0,1030,1028,1,0,0,0,1030,1031,1,0,0,0,1031,1033,1,0, + 0,0,1032,1015,1,0,0,0,1032,1025,1,0,0,0,1033,225,1,0,0,0,1034,1037,3,224, + 104,0,1035,1037,3,184,84,0,1036,1034,1,0,0,0,1036,1035,1,0,0,0,1037,1038, + 1,0,0,0,1038,1036,1,0,0,0,1038,1039,1,0,0,0,1039,227,1,0,0,0,1040,1041, + 3,58,21,0,1041,1042,1,0,0,0,1042,1043,6,106,11,0,1043,229,1,0,0,0,1044, + 1045,3,60,22,0,1045,1046,1,0,0,0,1046,1047,6,107,11,0,1047,231,1,0,0,0, + 1048,1049,3,62,23,0,1049,1050,1,0,0,0,1050,1051,6,108,11,0,1051,233,1,0, + 0,0,1052,1053,3,78,31,0,1053,1054,1,0,0,0,1054,1055,6,109,14,0,1055,1056, + 6,109,15,0,1056,235,1,0,0,0,1057,1058,3,112,48,0,1058,1059,1,0,0,0,1059, + 1060,6,110,18,0,1060,237,1,0,0,0,1061,1062,3,116,50,0,1062,1063,1,0,0,0, + 1063,1064,6,111,17,0,1064,239,1,0,0,0,1065,1066,3,120,52,0,1066,1067,1, + 0,0,0,1067,1068,6,112,21,0,1068,241,1,0,0,0,1069,1070,7,12,0,0,1070,1071, + 7,2,0,0,1071,243,1,0,0,0,1072,1073,3,226,105,0,1073,1074,1,0,0,0,1074,1075, + 6,114,22,0,1075,245,1,0,0,0,1076,1077,3,58,21,0,1077,1078,1,0,0,0,1078, + 1079,6,115,11,0,1079,247,1,0,0,0,1080,1081,3,60,22,0,1081,1082,1,0,0,0, + 1082,1083,6,116,11,0,1083,249,1,0,0,0,1084,1085,3,62,23,0,1085,1086,1,0, + 0,0,1086,1087,6,117,11,0,1087,251,1,0,0,0,1088,1089,3,78,31,0,1089,1090, + 1,0,0,0,1090,1091,6,118,14,0,1091,1092,6,118,15,0,1092,253,1,0,0,0,1093, + 1094,3,178,81,0,1094,1095,1,0,0,0,1095,1096,6,119,12,0,1096,1097,6,119, + 23,0,1097,255,1,0,0,0,1098,1099,7,7,0,0,1099,1100,7,9,0,0,1100,1101,1,0, + 0,0,1101,1102,6,120,24,0,1102,257,1,0,0,0,1103,1104,7,20,0,0,1104,1105, + 7,1,0,0,1105,1106,7,5,0,0,1106,1107,7,10,0,0,1107,1108,1,0,0,0,1108,1109, + 6,121,24,0,1109,259,1,0,0,0,1110,1111,8,34,0,0,1111,261,1,0,0,0,1112,1114, + 3,260,122,0,1113,1112,1,0,0,0,1114,1115,1,0,0,0,1115,1113,1,0,0,0,1115, + 1116,1,0,0,0,1116,1117,1,0,0,0,1117,1118,3,360,172,0,1118,1120,1,0,0,0, + 1119,1113,1,0,0,0,1119,1120,1,0,0,0,1120,1122,1,0,0,0,1121,1123,3,260,122, + 0,1122,1121,1,0,0,0,1123,1124,1,0,0,0,1124,1122,1,0,0,0,1124,1125,1,0,0, + 0,1125,263,1,0,0,0,1126,1127,3,186,85,0,1127,1128,1,0,0,0,1128,1129,6,124, + 25,0,1129,265,1,0,0,0,1130,1131,3,262,123,0,1131,1132,1,0,0,0,1132,1133, + 6,125,26,0,1133,267,1,0,0,0,1134,1135,3,58,21,0,1135,1136,1,0,0,0,1136, + 1137,6,126,11,0,1137,269,1,0,0,0,1138,1139,3,60,22,0,1139,1140,1,0,0,0, + 1140,1141,6,127,11,0,1141,271,1,0,0,0,1142,1143,3,62,23,0,1143,1144,1,0, + 0,0,1144,1145,6,128,11,0,1145,273,1,0,0,0,1146,1147,3,78,31,0,1147,1148, + 1,0,0,0,1148,1149,6,129,14,0,1149,1150,6,129,15,0,1150,1151,6,129,15,0, + 1151,275,1,0,0,0,1152,1153,3,112,48,0,1153,1154,1,0,0,0,1154,1155,6,130, + 18,0,1155,277,1,0,0,0,1156,1157,3,116,50,0,1157,1158,1,0,0,0,1158,1159, + 6,131,17,0,1159,279,1,0,0,0,1160,1161,3,120,52,0,1161,1162,1,0,0,0,1162, + 1163,6,132,21,0,1163,281,1,0,0,0,1164,1165,3,258,121,0,1165,1166,1,0,0, + 0,1166,1167,6,133,27,0,1167,283,1,0,0,0,1168,1169,3,226,105,0,1169,1170, + 1,0,0,0,1170,1171,6,134,22,0,1171,285,1,0,0,0,1172,1173,3,186,85,0,1173, + 1174,1,0,0,0,1174,1175,6,135,25,0,1175,287,1,0,0,0,1176,1177,3,58,21,0, + 1177,1178,1,0,0,0,1178,1179,6,136,11,0,1179,289,1,0,0,0,1180,1181,3,60, + 22,0,1181,1182,1,0,0,0,1182,1183,6,137,11,0,1183,291,1,0,0,0,1184,1185, + 3,62,23,0,1185,1186,1,0,0,0,1186,1187,6,138,11,0,1187,293,1,0,0,0,1188, + 1189,3,78,31,0,1189,1190,1,0,0,0,1190,1191,6,139,14,0,1191,1192,6,139,15, + 0,1192,295,1,0,0,0,1193,1194,3,116,50,0,1194,1195,1,0,0,0,1195,1196,6,140, + 17,0,1196,297,1,0,0,0,1197,1198,3,120,52,0,1198,1199,1,0,0,0,1199,1200, + 6,141,21,0,1200,299,1,0,0,0,1201,1202,3,256,120,0,1202,1203,1,0,0,0,1203, + 1204,6,142,28,0,1204,1205,6,142,29,0,1205,301,1,0,0,0,1206,1207,3,66,25, + 0,1207,1208,1,0,0,0,1208,1209,6,143,20,0,1209,303,1,0,0,0,1210,1211,3,58, + 21,0,1211,1212,1,0,0,0,1212,1213,6,144,11,0,1213,305,1,0,0,0,1214,1215, + 3,60,22,0,1215,1216,1,0,0,0,1216,1217,6,145,11,0,1217,307,1,0,0,0,1218, + 1219,3,62,23,0,1219,1220,1,0,0,0,1220,1221,6,146,11,0,1221,309,1,0,0,0, + 1222,1223,3,78,31,0,1223,1224,1,0,0,0,1224,1225,6,147,14,0,1225,1226,6, + 147,15,0,1226,1227,6,147,15,0,1227,311,1,0,0,0,1228,1229,3,116,50,0,1229, + 1230,1,0,0,0,1230,1231,6,148,17,0,1231,313,1,0,0,0,1232,1233,3,120,52,0, + 1233,1234,1,0,0,0,1234,1235,6,149,21,0,1235,315,1,0,0,0,1236,1237,3,226, + 105,0,1237,1238,1,0,0,0,1238,1239,6,150,22,0,1239,317,1,0,0,0,1240,1241, + 3,58,21,0,1241,1242,1,0,0,0,1242,1243,6,151,11,0,1243,319,1,0,0,0,1244, + 1245,3,60,22,0,1245,1246,1,0,0,0,1246,1247,6,152,11,0,1247,321,1,0,0,0, + 1248,1249,3,62,23,0,1249,1250,1,0,0,0,1250,1251,6,153,11,0,1251,323,1,0, + 0,0,1252,1253,3,78,31,0,1253,1254,1,0,0,0,1254,1255,6,154,14,0,1255,1256, + 6,154,15,0,1256,325,1,0,0,0,1257,1258,3,120,52,0,1258,1259,1,0,0,0,1259, + 1260,6,155,21,0,1260,327,1,0,0,0,1261,1262,3,186,85,0,1262,1263,1,0,0,0, + 1263,1264,6,156,25,0,1264,329,1,0,0,0,1265,1266,3,182,83,0,1266,1267,1, + 0,0,0,1267,1268,6,157,30,0,1268,331,1,0,0,0,1269,1270,3,58,21,0,1270,1271, + 1,0,0,0,1271,1272,6,158,11,0,1272,333,1,0,0,0,1273,1274,3,60,22,0,1274, + 1275,1,0,0,0,1275,1276,6,159,11,0,1276,335,1,0,0,0,1277,1278,3,62,23,0, + 1278,1279,1,0,0,0,1279,1280,6,160,11,0,1280,337,1,0,0,0,1281,1282,3,78, + 31,0,1282,1283,1,0,0,0,1283,1284,6,161,14,0,1284,1285,6,161,15,0,1285,339, + 1,0,0,0,1286,1287,7,1,0,0,1287,1288,7,9,0,0,1288,1289,7,15,0,0,1289,1290, + 7,7,0,0,1290,341,1,0,0,0,1291,1292,3,58,21,0,1292,1293,1,0,0,0,1293,1294, + 6,163,11,0,1294,343,1,0,0,0,1295,1296,3,60,22,0,1296,1297,1,0,0,0,1297, + 1298,6,164,11,0,1298,345,1,0,0,0,1299,1300,3,62,23,0,1300,1301,1,0,0,0, + 1301,1302,6,165,11,0,1302,347,1,0,0,0,1303,1304,3,78,31,0,1304,1305,1,0, + 0,0,1305,1306,6,166,14,0,1306,1307,6,166,15,0,1307,349,1,0,0,0,1308,1309, + 7,15,0,0,1309,1310,7,19,0,0,1310,1311,7,9,0,0,1311,1312,7,4,0,0,1312,1313, + 7,5,0,0,1313,1314,7,1,0,0,1314,1315,7,7,0,0,1315,1316,7,9,0,0,1316,1317, + 7,2,0,0,1317,351,1,0,0,0,1318,1319,3,58,21,0,1319,1320,1,0,0,0,1320,1321, + 6,168,11,0,1321,353,1,0,0,0,1322,1323,3,60,22,0,1323,1324,1,0,0,0,1324, + 1325,6,169,11,0,1325,355,1,0,0,0,1326,1327,3,62,23,0,1327,1328,1,0,0,0, + 1328,1329,6,170,11,0,1329,357,1,0,0,0,1330,1331,3,180,82,0,1331,1332,1, + 0,0,0,1332,1333,6,171,16,0,1333,1334,6,171,15,0,1334,359,1,0,0,0,1335,1336, + 5,58,0,0,1336,361,1,0,0,0,1337,1343,3,90,37,0,1338,1343,3,80,32,0,1339, + 1343,3,120,52,0,1340,1343,3,82,33,0,1341,1343,3,96,40,0,1342,1337,1,0,0, + 0,1342,1338,1,0,0,0,1342,1339,1,0,0,0,1342,1340,1,0,0,0,1342,1341,1,0,0, + 0,1343,1344,1,0,0,0,1344,1342,1,0,0,0,1344,1345,1,0,0,0,1345,363,1,0,0, + 0,1346,1347,3,58,21,0,1347,1348,1,0,0,0,1348,1349,6,174,11,0,1349,365,1, + 0,0,0,1350,1351,3,60,22,0,1351,1352,1,0,0,0,1352,1353,6,175,11,0,1353,367, + 1,0,0,0,1354,1355,3,62,23,0,1355,1356,1,0,0,0,1356,1357,6,176,11,0,1357, + 369,1,0,0,0,1358,1359,3,78,31,0,1359,1360,1,0,0,0,1360,1361,6,177,14,0, + 1361,1362,6,177,15,0,1362,371,1,0,0,0,1363,1364,3,66,25,0,1364,1365,1,0, + 0,0,1365,1366,6,178,20,0,1366,1367,6,178,15,0,1367,1368,6,178,31,0,1368, + 373,1,0,0,0,1369,1370,3,58,21,0,1370,1371,1,0,0,0,1371,1372,6,179,11,0, + 1372,375,1,0,0,0,1373,1374,3,60,22,0,1374,1375,1,0,0,0,1375,1376,6,180, + 11,0,1376,377,1,0,0,0,1377,1378,3,62,23,0,1378,1379,1,0,0,0,1379,1380,6, + 181,11,0,1380,379,1,0,0,0,1381,1382,3,116,50,0,1382,1383,1,0,0,0,1383,1384, + 6,182,17,0,1384,1385,6,182,15,0,1385,1386,6,182,7,0,1386,381,1,0,0,0,1387, + 1388,3,58,21,0,1388,1389,1,0,0,0,1389,1390,6,183,11,0,1390,383,1,0,0,0, + 1391,1392,3,60,22,0,1392,1393,1,0,0,0,1393,1394,6,184,11,0,1394,385,1,0, + 0,0,1395,1396,3,62,23,0,1396,1397,1,0,0,0,1397,1398,6,185,11,0,1398,387, + 1,0,0,0,1399,1400,3,186,85,0,1400,1401,1,0,0,0,1401,1402,6,186,15,0,1402, + 1403,6,186,0,0,1403,1404,6,186,25,0,1404,389,1,0,0,0,1405,1406,3,182,83, + 0,1406,1407,1,0,0,0,1407,1408,6,187,15,0,1408,1409,6,187,0,0,1409,1410, + 6,187,30,0,1410,391,1,0,0,0,1411,1412,3,106,45,0,1412,1413,1,0,0,0,1413, + 1414,6,188,15,0,1414,1415,6,188,0,0,1415,1416,6,188,32,0,1416,393,1,0,0, + 0,1417,1418,3,78,31,0,1418,1419,1,0,0,0,1419,1420,6,189,14,0,1420,1421, + 6,189,15,0,1421,395,1,0,0,0,65,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,568, + 578,582,585,594,596,607,614,619,658,663,672,679,684,686,697,705,708,710, + 715,720,726,733,738,744,747,755,759,883,890,892,908,913,918,920,926,1011, + 1015,1020,1025,1030,1032,1036,1038,1115,1119,1124,1342,1344,33,5,2,0,5, + 4,0,5,6,0,5,1,0,5,3,0,5,8,0,5,12,0,5,14,0,5,10,0,5,5,0,5,11,0,0,1,0,7,69, + 0,5,0,0,7,29,0,4,0,0,7,70,0,7,38,0,7,36,0,7,30,0,7,25,0,7,40,0,7,80,0,5, + 13,0,5,7,0,7,72,0,7,90,0,7,89,0,7,88,0,5,9,0,7,71,0,5,15,0,7,33,0]; private static __ATN: ATN; public static get _ATN(): ATN { diff --git a/packages/kbn-esql-ast/src/antlr/esql_parser.g4 b/packages/kbn-esql-ast/src/antlr/esql_parser.g4 index d4f9de9dccd9e..59c9ee6ca5770 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_parser.g4 +++ b/packages/kbn-esql-ast/src/antlr/esql_parser.g4 @@ -179,13 +179,18 @@ constant | decimalValue #decimalLiteral | integerValue #integerLiteral | booleanValue #booleanLiteral - | PARAM #inputParam + | params #inputParams | string #stringLiteral | OPENING_BRACKET numericValue (COMMA numericValue)* CLOSING_BRACKET #numericArrayLiteral | OPENING_BRACKET booleanValue (COMMA booleanValue)* CLOSING_BRACKET #booleanArrayLiteral | OPENING_BRACKET string (COMMA string)* CLOSING_BRACKET #stringArrayLiteral ; +params + : PARAM #inputParam + | NAMED_OR_POSITIONAL_PARAM #inputNamedOrPositionalParam + ; + limitCommand : LIMIT INTEGER_LITERAL ; diff --git a/packages/kbn-esql-ast/src/antlr/esql_parser.interp b/packages/kbn-esql-ast/src/antlr/esql_parser.interp index 76663717c5624..5900020590110 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_parser.interp +++ b/packages/kbn-esql-ast/src/antlr/esql_parser.interp @@ -68,6 +68,7 @@ null '/' '%' null +null ']' null null @@ -193,6 +194,7 @@ MINUS ASTERISK SLASH PERCENT +NAMED_OR_POSITIONAL_PARAM OPENING_BRACKET CLOSING_BRACKET UNQUOTED_IDENTIFIER @@ -281,6 +283,7 @@ qualifiedNamePatterns identifier identifierPattern constant +params limitCommand sortCommand orderExpression @@ -309,4 +312,4 @@ lookupCommand atn: -[4, 1, 123, 548, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 120, 8, 1, 10, 1, 12, 1, 123, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 131, 8, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 147, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 159, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 166, 8, 5, 10, 5, 12, 5, 169, 9, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 176, 8, 5, 1, 5, 1, 5, 3, 5, 180, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 188, 8, 5, 10, 5, 12, 5, 191, 9, 5, 1, 6, 1, 6, 3, 6, 195, 8, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 202, 8, 6, 1, 6, 1, 6, 1, 6, 3, 6, 207, 8, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 214, 8, 7, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 220, 8, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 5, 8, 228, 8, 8, 10, 8, 12, 8, 231, 9, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 3, 9, 241, 8, 9, 1, 9, 1, 9, 1, 9, 5, 9, 246, 8, 9, 10, 9, 12, 9, 249, 9, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 5, 10, 257, 8, 10, 10, 10, 12, 10, 260, 9, 10, 3, 10, 262, 8, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 274, 8, 13, 10, 13, 12, 13, 277, 9, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 284, 8, 14, 1, 15, 1, 15, 1, 15, 1, 15, 5, 15, 290, 8, 15, 10, 15, 12, 15, 293, 9, 15, 1, 15, 3, 15, 296, 8, 15, 1, 16, 1, 16, 1, 17, 1, 17, 3, 17, 302, 8, 17, 1, 18, 1, 18, 1, 18, 1, 18, 5, 18, 308, 8, 18, 10, 18, 12, 18, 311, 9, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 5, 20, 321, 8, 20, 10, 20, 12, 20, 324, 9, 20, 1, 20, 3, 20, 327, 8, 20, 1, 20, 1, 20, 3, 20, 331, 8, 20, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 3, 22, 338, 8, 22, 1, 22, 1, 22, 3, 22, 342, 8, 22, 1, 23, 1, 23, 1, 23, 1, 23, 3, 23, 348, 8, 23, 1, 24, 1, 24, 1, 24, 5, 24, 353, 8, 24, 10, 24, 12, 24, 356, 9, 24, 1, 25, 1, 25, 1, 25, 5, 25, 361, 8, 25, 10, 25, 12, 25, 364, 9, 25, 1, 26, 1, 26, 1, 26, 5, 26, 369, 8, 26, 10, 26, 12, 26, 372, 9, 26, 1, 27, 1, 27, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 5, 29, 391, 8, 29, 10, 29, 12, 29, 394, 9, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 5, 29, 402, 8, 29, 10, 29, 12, 29, 405, 9, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 5, 29, 413, 8, 29, 10, 29, 12, 29, 416, 9, 29, 1, 29, 1, 29, 3, 29, 420, 8, 29, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 5, 31, 429, 8, 31, 10, 31, 12, 31, 432, 9, 31, 1, 32, 1, 32, 3, 32, 436, 8, 32, 1, 32, 1, 32, 3, 32, 440, 8, 32, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 35, 5, 35, 452, 8, 35, 10, 35, 12, 35, 455, 9, 35, 1, 36, 1, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, 37, 1, 37, 3, 37, 465, 8, 37, 1, 38, 1, 38, 1, 38, 1, 38, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 5, 40, 477, 8, 40, 10, 40, 12, 40, 480, 9, 40, 1, 41, 1, 41, 1, 41, 1, 41, 1, 42, 1, 42, 1, 43, 1, 43, 3, 43, 490, 8, 43, 1, 44, 3, 44, 493, 8, 44, 1, 44, 1, 44, 1, 45, 3, 45, 498, 8, 45, 1, 45, 1, 45, 1, 46, 1, 46, 1, 47, 1, 47, 1, 48, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 52, 3, 52, 523, 8, 52, 1, 52, 1, 52, 1, 52, 1, 52, 5, 52, 529, 8, 52, 10, 52, 12, 52, 532, 9, 52, 3, 52, 534, 8, 52, 1, 53, 1, 53, 1, 53, 3, 53, 539, 8, 53, 1, 53, 1, 53, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 1, 54, 0, 4, 2, 10, 16, 18, 55, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 0, 7, 1, 0, 63, 64, 1, 0, 65, 67, 1, 0, 70, 71, 2, 0, 35, 35, 39, 39, 1, 0, 42, 43, 2, 0, 41, 41, 55, 55, 2, 0, 56, 56, 58, 62, 574, 0, 110, 1, 0, 0, 0, 2, 113, 1, 0, 0, 0, 4, 130, 1, 0, 0, 0, 6, 146, 1, 0, 0, 0, 8, 148, 1, 0, 0, 0, 10, 179, 1, 0, 0, 0, 12, 206, 1, 0, 0, 0, 14, 213, 1, 0, 0, 0, 16, 219, 1, 0, 0, 0, 18, 240, 1, 0, 0, 0, 20, 250, 1, 0, 0, 0, 22, 265, 1, 0, 0, 0, 24, 267, 1, 0, 0, 0, 26, 270, 1, 0, 0, 0, 28, 283, 1, 0, 0, 0, 30, 285, 1, 0, 0, 0, 32, 297, 1, 0, 0, 0, 34, 301, 1, 0, 0, 0, 36, 303, 1, 0, 0, 0, 38, 312, 1, 0, 0, 0, 40, 316, 1, 0, 0, 0, 42, 332, 1, 0, 0, 0, 44, 335, 1, 0, 0, 0, 46, 343, 1, 0, 0, 0, 48, 349, 1, 0, 0, 0, 50, 357, 1, 0, 0, 0, 52, 365, 1, 0, 0, 0, 54, 373, 1, 0, 0, 0, 56, 375, 1, 0, 0, 0, 58, 419, 1, 0, 0, 0, 60, 421, 1, 0, 0, 0, 62, 424, 1, 0, 0, 0, 64, 433, 1, 0, 0, 0, 66, 441, 1, 0, 0, 0, 68, 444, 1, 0, 0, 0, 70, 447, 1, 0, 0, 0, 72, 456, 1, 0, 0, 0, 74, 460, 1, 0, 0, 0, 76, 466, 1, 0, 0, 0, 78, 470, 1, 0, 0, 0, 80, 473, 1, 0, 0, 0, 82, 481, 1, 0, 0, 0, 84, 485, 1, 0, 0, 0, 86, 489, 1, 0, 0, 0, 88, 492, 1, 0, 0, 0, 90, 497, 1, 0, 0, 0, 92, 501, 1, 0, 0, 0, 94, 503, 1, 0, 0, 0, 96, 505, 1, 0, 0, 0, 98, 508, 1, 0, 0, 0, 100, 512, 1, 0, 0, 0, 102, 515, 1, 0, 0, 0, 104, 518, 1, 0, 0, 0, 106, 538, 1, 0, 0, 0, 108, 542, 1, 0, 0, 0, 110, 111, 3, 2, 1, 0, 111, 112, 5, 0, 0, 1, 112, 1, 1, 0, 0, 0, 113, 114, 6, 1, -1, 0, 114, 115, 3, 4, 2, 0, 115, 121, 1, 0, 0, 0, 116, 117, 10, 1, 0, 0, 117, 118, 5, 29, 0, 0, 118, 120, 3, 6, 3, 0, 119, 116, 1, 0, 0, 0, 120, 123, 1, 0, 0, 0, 121, 119, 1, 0, 0, 0, 121, 122, 1, 0, 0, 0, 122, 3, 1, 0, 0, 0, 123, 121, 1, 0, 0, 0, 124, 131, 3, 96, 48, 0, 125, 131, 3, 30, 15, 0, 126, 131, 3, 24, 12, 0, 127, 131, 3, 40, 20, 0, 128, 131, 3, 100, 50, 0, 129, 131, 3, 102, 51, 0, 130, 124, 1, 0, 0, 0, 130, 125, 1, 0, 0, 0, 130, 126, 1, 0, 0, 0, 130, 127, 1, 0, 0, 0, 130, 128, 1, 0, 0, 0, 130, 129, 1, 0, 0, 0, 131, 5, 1, 0, 0, 0, 132, 147, 3, 42, 21, 0, 133, 147, 3, 46, 23, 0, 134, 147, 3, 60, 30, 0, 135, 147, 3, 108, 54, 0, 136, 147, 3, 66, 33, 0, 137, 147, 3, 62, 31, 0, 138, 147, 3, 44, 22, 0, 139, 147, 3, 8, 4, 0, 140, 147, 3, 68, 34, 0, 141, 147, 3, 70, 35, 0, 142, 147, 3, 74, 37, 0, 143, 147, 3, 76, 38, 0, 144, 147, 3, 104, 52, 0, 145, 147, 3, 78, 39, 0, 146, 132, 1, 0, 0, 0, 146, 133, 1, 0, 0, 0, 146, 134, 1, 0, 0, 0, 146, 135, 1, 0, 0, 0, 146, 136, 1, 0, 0, 0, 146, 137, 1, 0, 0, 0, 146, 138, 1, 0, 0, 0, 146, 139, 1, 0, 0, 0, 146, 140, 1, 0, 0, 0, 146, 141, 1, 0, 0, 0, 146, 142, 1, 0, 0, 0, 146, 143, 1, 0, 0, 0, 146, 144, 1, 0, 0, 0, 146, 145, 1, 0, 0, 0, 147, 7, 1, 0, 0, 0, 148, 149, 5, 20, 0, 0, 149, 150, 3, 10, 5, 0, 150, 9, 1, 0, 0, 0, 151, 152, 6, 5, -1, 0, 152, 153, 5, 48, 0, 0, 153, 180, 3, 10, 5, 7, 154, 180, 3, 14, 7, 0, 155, 180, 3, 12, 6, 0, 156, 158, 3, 14, 7, 0, 157, 159, 5, 48, 0, 0, 158, 157, 1, 0, 0, 0, 158, 159, 1, 0, 0, 0, 159, 160, 1, 0, 0, 0, 160, 161, 5, 45, 0, 0, 161, 162, 5, 44, 0, 0, 162, 167, 3, 14, 7, 0, 163, 164, 5, 38, 0, 0, 164, 166, 3, 14, 7, 0, 165, 163, 1, 0, 0, 0, 166, 169, 1, 0, 0, 0, 167, 165, 1, 0, 0, 0, 167, 168, 1, 0, 0, 0, 168, 170, 1, 0, 0, 0, 169, 167, 1, 0, 0, 0, 170, 171, 5, 54, 0, 0, 171, 180, 1, 0, 0, 0, 172, 173, 3, 14, 7, 0, 173, 175, 5, 46, 0, 0, 174, 176, 5, 48, 0, 0, 175, 174, 1, 0, 0, 0, 175, 176, 1, 0, 0, 0, 176, 177, 1, 0, 0, 0, 177, 178, 5, 49, 0, 0, 178, 180, 1, 0, 0, 0, 179, 151, 1, 0, 0, 0, 179, 154, 1, 0, 0, 0, 179, 155, 1, 0, 0, 0, 179, 156, 1, 0, 0, 0, 179, 172, 1, 0, 0, 0, 180, 189, 1, 0, 0, 0, 181, 182, 10, 4, 0, 0, 182, 183, 5, 34, 0, 0, 183, 188, 3, 10, 5, 5, 184, 185, 10, 3, 0, 0, 185, 186, 5, 51, 0, 0, 186, 188, 3, 10, 5, 4, 187, 181, 1, 0, 0, 0, 187, 184, 1, 0, 0, 0, 188, 191, 1, 0, 0, 0, 189, 187, 1, 0, 0, 0, 189, 190, 1, 0, 0, 0, 190, 11, 1, 0, 0, 0, 191, 189, 1, 0, 0, 0, 192, 194, 3, 14, 7, 0, 193, 195, 5, 48, 0, 0, 194, 193, 1, 0, 0, 0, 194, 195, 1, 0, 0, 0, 195, 196, 1, 0, 0, 0, 196, 197, 5, 47, 0, 0, 197, 198, 3, 92, 46, 0, 198, 207, 1, 0, 0, 0, 199, 201, 3, 14, 7, 0, 200, 202, 5, 48, 0, 0, 201, 200, 1, 0, 0, 0, 201, 202, 1, 0, 0, 0, 202, 203, 1, 0, 0, 0, 203, 204, 5, 53, 0, 0, 204, 205, 3, 92, 46, 0, 205, 207, 1, 0, 0, 0, 206, 192, 1, 0, 0, 0, 206, 199, 1, 0, 0, 0, 207, 13, 1, 0, 0, 0, 208, 214, 3, 16, 8, 0, 209, 210, 3, 16, 8, 0, 210, 211, 3, 94, 47, 0, 211, 212, 3, 16, 8, 0, 212, 214, 1, 0, 0, 0, 213, 208, 1, 0, 0, 0, 213, 209, 1, 0, 0, 0, 214, 15, 1, 0, 0, 0, 215, 216, 6, 8, -1, 0, 216, 220, 3, 18, 9, 0, 217, 218, 7, 0, 0, 0, 218, 220, 3, 16, 8, 3, 219, 215, 1, 0, 0, 0, 219, 217, 1, 0, 0, 0, 220, 229, 1, 0, 0, 0, 221, 222, 10, 2, 0, 0, 222, 223, 7, 1, 0, 0, 223, 228, 3, 16, 8, 3, 224, 225, 10, 1, 0, 0, 225, 226, 7, 0, 0, 0, 226, 228, 3, 16, 8, 2, 227, 221, 1, 0, 0, 0, 227, 224, 1, 0, 0, 0, 228, 231, 1, 0, 0, 0, 229, 227, 1, 0, 0, 0, 229, 230, 1, 0, 0, 0, 230, 17, 1, 0, 0, 0, 231, 229, 1, 0, 0, 0, 232, 233, 6, 9, -1, 0, 233, 241, 3, 58, 29, 0, 234, 241, 3, 48, 24, 0, 235, 241, 3, 20, 10, 0, 236, 237, 5, 44, 0, 0, 237, 238, 3, 10, 5, 0, 238, 239, 5, 54, 0, 0, 239, 241, 1, 0, 0, 0, 240, 232, 1, 0, 0, 0, 240, 234, 1, 0, 0, 0, 240, 235, 1, 0, 0, 0, 240, 236, 1, 0, 0, 0, 241, 247, 1, 0, 0, 0, 242, 243, 10, 1, 0, 0, 243, 244, 5, 37, 0, 0, 244, 246, 3, 22, 11, 0, 245, 242, 1, 0, 0, 0, 246, 249, 1, 0, 0, 0, 247, 245, 1, 0, 0, 0, 247, 248, 1, 0, 0, 0, 248, 19, 1, 0, 0, 0, 249, 247, 1, 0, 0, 0, 250, 251, 3, 54, 27, 0, 251, 261, 5, 44, 0, 0, 252, 262, 5, 65, 0, 0, 253, 258, 3, 10, 5, 0, 254, 255, 5, 38, 0, 0, 255, 257, 3, 10, 5, 0, 256, 254, 1, 0, 0, 0, 257, 260, 1, 0, 0, 0, 258, 256, 1, 0, 0, 0, 258, 259, 1, 0, 0, 0, 259, 262, 1, 0, 0, 0, 260, 258, 1, 0, 0, 0, 261, 252, 1, 0, 0, 0, 261, 253, 1, 0, 0, 0, 261, 262, 1, 0, 0, 0, 262, 263, 1, 0, 0, 0, 263, 264, 5, 54, 0, 0, 264, 21, 1, 0, 0, 0, 265, 266, 3, 54, 27, 0, 266, 23, 1, 0, 0, 0, 267, 268, 5, 16, 0, 0, 268, 269, 3, 26, 13, 0, 269, 25, 1, 0, 0, 0, 270, 275, 3, 28, 14, 0, 271, 272, 5, 38, 0, 0, 272, 274, 3, 28, 14, 0, 273, 271, 1, 0, 0, 0, 274, 277, 1, 0, 0, 0, 275, 273, 1, 0, 0, 0, 275, 276, 1, 0, 0, 0, 276, 27, 1, 0, 0, 0, 277, 275, 1, 0, 0, 0, 278, 284, 3, 10, 5, 0, 279, 280, 3, 48, 24, 0, 280, 281, 5, 36, 0, 0, 281, 282, 3, 10, 5, 0, 282, 284, 1, 0, 0, 0, 283, 278, 1, 0, 0, 0, 283, 279, 1, 0, 0, 0, 284, 29, 1, 0, 0, 0, 285, 286, 5, 6, 0, 0, 286, 291, 3, 32, 16, 0, 287, 288, 5, 38, 0, 0, 288, 290, 3, 32, 16, 0, 289, 287, 1, 0, 0, 0, 290, 293, 1, 0, 0, 0, 291, 289, 1, 0, 0, 0, 291, 292, 1, 0, 0, 0, 292, 295, 1, 0, 0, 0, 293, 291, 1, 0, 0, 0, 294, 296, 3, 34, 17, 0, 295, 294, 1, 0, 0, 0, 295, 296, 1, 0, 0, 0, 296, 31, 1, 0, 0, 0, 297, 298, 5, 25, 0, 0, 298, 33, 1, 0, 0, 0, 299, 302, 3, 36, 18, 0, 300, 302, 3, 38, 19, 0, 301, 299, 1, 0, 0, 0, 301, 300, 1, 0, 0, 0, 302, 35, 1, 0, 0, 0, 303, 304, 5, 75, 0, 0, 304, 309, 3, 32, 16, 0, 305, 306, 5, 38, 0, 0, 306, 308, 3, 32, 16, 0, 307, 305, 1, 0, 0, 0, 308, 311, 1, 0, 0, 0, 309, 307, 1, 0, 0, 0, 309, 310, 1, 0, 0, 0, 310, 37, 1, 0, 0, 0, 311, 309, 1, 0, 0, 0, 312, 313, 5, 68, 0, 0, 313, 314, 3, 36, 18, 0, 314, 315, 5, 69, 0, 0, 315, 39, 1, 0, 0, 0, 316, 317, 5, 13, 0, 0, 317, 322, 3, 32, 16, 0, 318, 319, 5, 38, 0, 0, 319, 321, 3, 32, 16, 0, 320, 318, 1, 0, 0, 0, 321, 324, 1, 0, 0, 0, 322, 320, 1, 0, 0, 0, 322, 323, 1, 0, 0, 0, 323, 326, 1, 0, 0, 0, 324, 322, 1, 0, 0, 0, 325, 327, 3, 26, 13, 0, 326, 325, 1, 0, 0, 0, 326, 327, 1, 0, 0, 0, 327, 330, 1, 0, 0, 0, 328, 329, 5, 33, 0, 0, 329, 331, 3, 26, 13, 0, 330, 328, 1, 0, 0, 0, 330, 331, 1, 0, 0, 0, 331, 41, 1, 0, 0, 0, 332, 333, 5, 4, 0, 0, 333, 334, 3, 26, 13, 0, 334, 43, 1, 0, 0, 0, 335, 337, 5, 19, 0, 0, 336, 338, 3, 26, 13, 0, 337, 336, 1, 0, 0, 0, 337, 338, 1, 0, 0, 0, 338, 341, 1, 0, 0, 0, 339, 340, 5, 33, 0, 0, 340, 342, 3, 26, 13, 0, 341, 339, 1, 0, 0, 0, 341, 342, 1, 0, 0, 0, 342, 45, 1, 0, 0, 0, 343, 344, 5, 8, 0, 0, 344, 347, 3, 26, 13, 0, 345, 346, 5, 33, 0, 0, 346, 348, 3, 26, 13, 0, 347, 345, 1, 0, 0, 0, 347, 348, 1, 0, 0, 0, 348, 47, 1, 0, 0, 0, 349, 354, 3, 54, 27, 0, 350, 351, 5, 40, 0, 0, 351, 353, 3, 54, 27, 0, 352, 350, 1, 0, 0, 0, 353, 356, 1, 0, 0, 0, 354, 352, 1, 0, 0, 0, 354, 355, 1, 0, 0, 0, 355, 49, 1, 0, 0, 0, 356, 354, 1, 0, 0, 0, 357, 362, 3, 56, 28, 0, 358, 359, 5, 40, 0, 0, 359, 361, 3, 56, 28, 0, 360, 358, 1, 0, 0, 0, 361, 364, 1, 0, 0, 0, 362, 360, 1, 0, 0, 0, 362, 363, 1, 0, 0, 0, 363, 51, 1, 0, 0, 0, 364, 362, 1, 0, 0, 0, 365, 370, 3, 50, 25, 0, 366, 367, 5, 38, 0, 0, 367, 369, 3, 50, 25, 0, 368, 366, 1, 0, 0, 0, 369, 372, 1, 0, 0, 0, 370, 368, 1, 0, 0, 0, 370, 371, 1, 0, 0, 0, 371, 53, 1, 0, 0, 0, 372, 370, 1, 0, 0, 0, 373, 374, 7, 2, 0, 0, 374, 55, 1, 0, 0, 0, 375, 376, 5, 79, 0, 0, 376, 57, 1, 0, 0, 0, 377, 420, 5, 49, 0, 0, 378, 379, 3, 90, 45, 0, 379, 380, 5, 70, 0, 0, 380, 420, 1, 0, 0, 0, 381, 420, 3, 88, 44, 0, 382, 420, 3, 90, 45, 0, 383, 420, 3, 84, 42, 0, 384, 420, 5, 52, 0, 0, 385, 420, 3, 92, 46, 0, 386, 387, 5, 68, 0, 0, 387, 392, 3, 86, 43, 0, 388, 389, 5, 38, 0, 0, 389, 391, 3, 86, 43, 0, 390, 388, 1, 0, 0, 0, 391, 394, 1, 0, 0, 0, 392, 390, 1, 0, 0, 0, 392, 393, 1, 0, 0, 0, 393, 395, 1, 0, 0, 0, 394, 392, 1, 0, 0, 0, 395, 396, 5, 69, 0, 0, 396, 420, 1, 0, 0, 0, 397, 398, 5, 68, 0, 0, 398, 403, 3, 84, 42, 0, 399, 400, 5, 38, 0, 0, 400, 402, 3, 84, 42, 0, 401, 399, 1, 0, 0, 0, 402, 405, 1, 0, 0, 0, 403, 401, 1, 0, 0, 0, 403, 404, 1, 0, 0, 0, 404, 406, 1, 0, 0, 0, 405, 403, 1, 0, 0, 0, 406, 407, 5, 69, 0, 0, 407, 420, 1, 0, 0, 0, 408, 409, 5, 68, 0, 0, 409, 414, 3, 92, 46, 0, 410, 411, 5, 38, 0, 0, 411, 413, 3, 92, 46, 0, 412, 410, 1, 0, 0, 0, 413, 416, 1, 0, 0, 0, 414, 412, 1, 0, 0, 0, 414, 415, 1, 0, 0, 0, 415, 417, 1, 0, 0, 0, 416, 414, 1, 0, 0, 0, 417, 418, 5, 69, 0, 0, 418, 420, 1, 0, 0, 0, 419, 377, 1, 0, 0, 0, 419, 378, 1, 0, 0, 0, 419, 381, 1, 0, 0, 0, 419, 382, 1, 0, 0, 0, 419, 383, 1, 0, 0, 0, 419, 384, 1, 0, 0, 0, 419, 385, 1, 0, 0, 0, 419, 386, 1, 0, 0, 0, 419, 397, 1, 0, 0, 0, 419, 408, 1, 0, 0, 0, 420, 59, 1, 0, 0, 0, 421, 422, 5, 10, 0, 0, 422, 423, 5, 31, 0, 0, 423, 61, 1, 0, 0, 0, 424, 425, 5, 18, 0, 0, 425, 430, 3, 64, 32, 0, 426, 427, 5, 38, 0, 0, 427, 429, 3, 64, 32, 0, 428, 426, 1, 0, 0, 0, 429, 432, 1, 0, 0, 0, 430, 428, 1, 0, 0, 0, 430, 431, 1, 0, 0, 0, 431, 63, 1, 0, 0, 0, 432, 430, 1, 0, 0, 0, 433, 435, 3, 10, 5, 0, 434, 436, 7, 3, 0, 0, 435, 434, 1, 0, 0, 0, 435, 436, 1, 0, 0, 0, 436, 439, 1, 0, 0, 0, 437, 438, 5, 50, 0, 0, 438, 440, 7, 4, 0, 0, 439, 437, 1, 0, 0, 0, 439, 440, 1, 0, 0, 0, 440, 65, 1, 0, 0, 0, 441, 442, 5, 9, 0, 0, 442, 443, 3, 52, 26, 0, 443, 67, 1, 0, 0, 0, 444, 445, 5, 2, 0, 0, 445, 446, 3, 52, 26, 0, 446, 69, 1, 0, 0, 0, 447, 448, 5, 15, 0, 0, 448, 453, 3, 72, 36, 0, 449, 450, 5, 38, 0, 0, 450, 452, 3, 72, 36, 0, 451, 449, 1, 0, 0, 0, 452, 455, 1, 0, 0, 0, 453, 451, 1, 0, 0, 0, 453, 454, 1, 0, 0, 0, 454, 71, 1, 0, 0, 0, 455, 453, 1, 0, 0, 0, 456, 457, 3, 50, 25, 0, 457, 458, 5, 83, 0, 0, 458, 459, 3, 50, 25, 0, 459, 73, 1, 0, 0, 0, 460, 461, 5, 1, 0, 0, 461, 462, 3, 18, 9, 0, 462, 464, 3, 92, 46, 0, 463, 465, 3, 80, 40, 0, 464, 463, 1, 0, 0, 0, 464, 465, 1, 0, 0, 0, 465, 75, 1, 0, 0, 0, 466, 467, 5, 7, 0, 0, 467, 468, 3, 18, 9, 0, 468, 469, 3, 92, 46, 0, 469, 77, 1, 0, 0, 0, 470, 471, 5, 14, 0, 0, 471, 472, 3, 48, 24, 0, 472, 79, 1, 0, 0, 0, 473, 478, 3, 82, 41, 0, 474, 475, 5, 38, 0, 0, 475, 477, 3, 82, 41, 0, 476, 474, 1, 0, 0, 0, 477, 480, 1, 0, 0, 0, 478, 476, 1, 0, 0, 0, 478, 479, 1, 0, 0, 0, 479, 81, 1, 0, 0, 0, 480, 478, 1, 0, 0, 0, 481, 482, 3, 54, 27, 0, 482, 483, 5, 36, 0, 0, 483, 484, 3, 58, 29, 0, 484, 83, 1, 0, 0, 0, 485, 486, 7, 5, 0, 0, 486, 85, 1, 0, 0, 0, 487, 490, 3, 88, 44, 0, 488, 490, 3, 90, 45, 0, 489, 487, 1, 0, 0, 0, 489, 488, 1, 0, 0, 0, 490, 87, 1, 0, 0, 0, 491, 493, 7, 0, 0, 0, 492, 491, 1, 0, 0, 0, 492, 493, 1, 0, 0, 0, 493, 494, 1, 0, 0, 0, 494, 495, 5, 32, 0, 0, 495, 89, 1, 0, 0, 0, 496, 498, 7, 0, 0, 0, 497, 496, 1, 0, 0, 0, 497, 498, 1, 0, 0, 0, 498, 499, 1, 0, 0, 0, 499, 500, 5, 31, 0, 0, 500, 91, 1, 0, 0, 0, 501, 502, 5, 30, 0, 0, 502, 93, 1, 0, 0, 0, 503, 504, 7, 6, 0, 0, 504, 95, 1, 0, 0, 0, 505, 506, 5, 5, 0, 0, 506, 507, 3, 98, 49, 0, 507, 97, 1, 0, 0, 0, 508, 509, 5, 68, 0, 0, 509, 510, 3, 2, 1, 0, 510, 511, 5, 69, 0, 0, 511, 99, 1, 0, 0, 0, 512, 513, 5, 17, 0, 0, 513, 514, 5, 105, 0, 0, 514, 101, 1, 0, 0, 0, 515, 516, 5, 12, 0, 0, 516, 517, 5, 109, 0, 0, 517, 103, 1, 0, 0, 0, 518, 519, 5, 3, 0, 0, 519, 522, 5, 89, 0, 0, 520, 521, 5, 87, 0, 0, 521, 523, 3, 50, 25, 0, 522, 520, 1, 0, 0, 0, 522, 523, 1, 0, 0, 0, 523, 533, 1, 0, 0, 0, 524, 525, 5, 88, 0, 0, 525, 530, 3, 106, 53, 0, 526, 527, 5, 38, 0, 0, 527, 529, 3, 106, 53, 0, 528, 526, 1, 0, 0, 0, 529, 532, 1, 0, 0, 0, 530, 528, 1, 0, 0, 0, 530, 531, 1, 0, 0, 0, 531, 534, 1, 0, 0, 0, 532, 530, 1, 0, 0, 0, 533, 524, 1, 0, 0, 0, 533, 534, 1, 0, 0, 0, 534, 105, 1, 0, 0, 0, 535, 536, 3, 50, 25, 0, 536, 537, 5, 36, 0, 0, 537, 539, 1, 0, 0, 0, 538, 535, 1, 0, 0, 0, 538, 539, 1, 0, 0, 0, 539, 540, 1, 0, 0, 0, 540, 541, 3, 50, 25, 0, 541, 107, 1, 0, 0, 0, 542, 543, 5, 11, 0, 0, 543, 544, 5, 25, 0, 0, 544, 545, 5, 87, 0, 0, 545, 546, 3, 52, 26, 0, 546, 109, 1, 0, 0, 0, 52, 121, 130, 146, 158, 167, 175, 179, 187, 189, 194, 201, 206, 213, 219, 227, 229, 240, 247, 258, 261, 275, 283, 291, 295, 301, 309, 322, 326, 330, 337, 341, 347, 354, 362, 370, 392, 403, 414, 419, 430, 435, 439, 453, 464, 478, 489, 492, 497, 522, 530, 533, 538] \ No newline at end of file +[4, 1, 124, 554, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 122, 8, 1, 10, 1, 12, 1, 125, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 133, 8, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 3, 149, 8, 3, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 161, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 168, 8, 5, 10, 5, 12, 5, 171, 9, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 178, 8, 5, 1, 5, 1, 5, 3, 5, 182, 8, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 5, 5, 190, 8, 5, 10, 5, 12, 5, 193, 9, 5, 1, 6, 1, 6, 3, 6, 197, 8, 6, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 3, 6, 204, 8, 6, 1, 6, 1, 6, 1, 6, 3, 6, 209, 8, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 216, 8, 7, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 222, 8, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 5, 8, 230, 8, 8, 10, 8, 12, 8, 233, 9, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 3, 9, 243, 8, 9, 1, 9, 1, 9, 1, 9, 5, 9, 248, 8, 9, 10, 9, 12, 9, 251, 9, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, 5, 10, 259, 8, 10, 10, 10, 12, 10, 262, 9, 10, 3, 10, 264, 8, 10, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 5, 13, 276, 8, 13, 10, 13, 12, 13, 279, 9, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 3, 14, 286, 8, 14, 1, 15, 1, 15, 1, 15, 1, 15, 5, 15, 292, 8, 15, 10, 15, 12, 15, 295, 9, 15, 1, 15, 3, 15, 298, 8, 15, 1, 16, 1, 16, 1, 17, 1, 17, 3, 17, 304, 8, 17, 1, 18, 1, 18, 1, 18, 1, 18, 5, 18, 310, 8, 18, 10, 18, 12, 18, 313, 9, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 20, 5, 20, 323, 8, 20, 10, 20, 12, 20, 326, 9, 20, 1, 20, 3, 20, 329, 8, 20, 1, 20, 1, 20, 3, 20, 333, 8, 20, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 3, 22, 340, 8, 22, 1, 22, 1, 22, 3, 22, 344, 8, 22, 1, 23, 1, 23, 1, 23, 1, 23, 3, 23, 350, 8, 23, 1, 24, 1, 24, 1, 24, 5, 24, 355, 8, 24, 10, 24, 12, 24, 358, 9, 24, 1, 25, 1, 25, 1, 25, 5, 25, 363, 8, 25, 10, 25, 12, 25, 366, 9, 25, 1, 26, 1, 26, 1, 26, 5, 26, 371, 8, 26, 10, 26, 12, 26, 374, 9, 26, 1, 27, 1, 27, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 5, 29, 393, 8, 29, 10, 29, 12, 29, 396, 9, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 5, 29, 404, 8, 29, 10, 29, 12, 29, 407, 9, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 5, 29, 415, 8, 29, 10, 29, 12, 29, 418, 9, 29, 1, 29, 1, 29, 3, 29, 422, 8, 29, 1, 30, 1, 30, 3, 30, 426, 8, 30, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 5, 32, 435, 8, 32, 10, 32, 12, 32, 438, 9, 32, 1, 33, 1, 33, 3, 33, 442, 8, 33, 1, 33, 1, 33, 3, 33, 446, 8, 33, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 5, 36, 458, 8, 36, 10, 36, 12, 36, 461, 9, 36, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 38, 1, 38, 3, 38, 471, 8, 38, 1, 39, 1, 39, 1, 39, 1, 39, 1, 40, 1, 40, 1, 40, 1, 41, 1, 41, 1, 41, 5, 41, 483, 8, 41, 10, 41, 12, 41, 486, 9, 41, 1, 42, 1, 42, 1, 42, 1, 42, 1, 43, 1, 43, 1, 44, 1, 44, 3, 44, 496, 8, 44, 1, 45, 3, 45, 499, 8, 45, 1, 45, 1, 45, 1, 46, 3, 46, 504, 8, 46, 1, 46, 1, 46, 1, 47, 1, 47, 1, 48, 1, 48, 1, 49, 1, 49, 1, 49, 1, 50, 1, 50, 1, 50, 1, 50, 1, 51, 1, 51, 1, 51, 1, 52, 1, 52, 1, 52, 1, 53, 1, 53, 1, 53, 1, 53, 3, 53, 529, 8, 53, 1, 53, 1, 53, 1, 53, 1, 53, 5, 53, 535, 8, 53, 10, 53, 12, 53, 538, 9, 53, 3, 53, 540, 8, 53, 1, 54, 1, 54, 1, 54, 3, 54, 545, 8, 54, 1, 54, 1, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 0, 4, 2, 10, 16, 18, 56, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 0, 7, 1, 0, 63, 64, 1, 0, 65, 67, 1, 0, 71, 72, 2, 0, 35, 35, 39, 39, 1, 0, 42, 43, 2, 0, 41, 41, 55, 55, 2, 0, 56, 56, 58, 62, 580, 0, 112, 1, 0, 0, 0, 2, 115, 1, 0, 0, 0, 4, 132, 1, 0, 0, 0, 6, 148, 1, 0, 0, 0, 8, 150, 1, 0, 0, 0, 10, 181, 1, 0, 0, 0, 12, 208, 1, 0, 0, 0, 14, 215, 1, 0, 0, 0, 16, 221, 1, 0, 0, 0, 18, 242, 1, 0, 0, 0, 20, 252, 1, 0, 0, 0, 22, 267, 1, 0, 0, 0, 24, 269, 1, 0, 0, 0, 26, 272, 1, 0, 0, 0, 28, 285, 1, 0, 0, 0, 30, 287, 1, 0, 0, 0, 32, 299, 1, 0, 0, 0, 34, 303, 1, 0, 0, 0, 36, 305, 1, 0, 0, 0, 38, 314, 1, 0, 0, 0, 40, 318, 1, 0, 0, 0, 42, 334, 1, 0, 0, 0, 44, 337, 1, 0, 0, 0, 46, 345, 1, 0, 0, 0, 48, 351, 1, 0, 0, 0, 50, 359, 1, 0, 0, 0, 52, 367, 1, 0, 0, 0, 54, 375, 1, 0, 0, 0, 56, 377, 1, 0, 0, 0, 58, 421, 1, 0, 0, 0, 60, 425, 1, 0, 0, 0, 62, 427, 1, 0, 0, 0, 64, 430, 1, 0, 0, 0, 66, 439, 1, 0, 0, 0, 68, 447, 1, 0, 0, 0, 70, 450, 1, 0, 0, 0, 72, 453, 1, 0, 0, 0, 74, 462, 1, 0, 0, 0, 76, 466, 1, 0, 0, 0, 78, 472, 1, 0, 0, 0, 80, 476, 1, 0, 0, 0, 82, 479, 1, 0, 0, 0, 84, 487, 1, 0, 0, 0, 86, 491, 1, 0, 0, 0, 88, 495, 1, 0, 0, 0, 90, 498, 1, 0, 0, 0, 92, 503, 1, 0, 0, 0, 94, 507, 1, 0, 0, 0, 96, 509, 1, 0, 0, 0, 98, 511, 1, 0, 0, 0, 100, 514, 1, 0, 0, 0, 102, 518, 1, 0, 0, 0, 104, 521, 1, 0, 0, 0, 106, 524, 1, 0, 0, 0, 108, 544, 1, 0, 0, 0, 110, 548, 1, 0, 0, 0, 112, 113, 3, 2, 1, 0, 113, 114, 5, 0, 0, 1, 114, 1, 1, 0, 0, 0, 115, 116, 6, 1, -1, 0, 116, 117, 3, 4, 2, 0, 117, 123, 1, 0, 0, 0, 118, 119, 10, 1, 0, 0, 119, 120, 5, 29, 0, 0, 120, 122, 3, 6, 3, 0, 121, 118, 1, 0, 0, 0, 122, 125, 1, 0, 0, 0, 123, 121, 1, 0, 0, 0, 123, 124, 1, 0, 0, 0, 124, 3, 1, 0, 0, 0, 125, 123, 1, 0, 0, 0, 126, 133, 3, 98, 49, 0, 127, 133, 3, 30, 15, 0, 128, 133, 3, 24, 12, 0, 129, 133, 3, 40, 20, 0, 130, 133, 3, 102, 51, 0, 131, 133, 3, 104, 52, 0, 132, 126, 1, 0, 0, 0, 132, 127, 1, 0, 0, 0, 132, 128, 1, 0, 0, 0, 132, 129, 1, 0, 0, 0, 132, 130, 1, 0, 0, 0, 132, 131, 1, 0, 0, 0, 133, 5, 1, 0, 0, 0, 134, 149, 3, 42, 21, 0, 135, 149, 3, 46, 23, 0, 136, 149, 3, 62, 31, 0, 137, 149, 3, 110, 55, 0, 138, 149, 3, 68, 34, 0, 139, 149, 3, 64, 32, 0, 140, 149, 3, 44, 22, 0, 141, 149, 3, 8, 4, 0, 142, 149, 3, 70, 35, 0, 143, 149, 3, 72, 36, 0, 144, 149, 3, 76, 38, 0, 145, 149, 3, 78, 39, 0, 146, 149, 3, 106, 53, 0, 147, 149, 3, 80, 40, 0, 148, 134, 1, 0, 0, 0, 148, 135, 1, 0, 0, 0, 148, 136, 1, 0, 0, 0, 148, 137, 1, 0, 0, 0, 148, 138, 1, 0, 0, 0, 148, 139, 1, 0, 0, 0, 148, 140, 1, 0, 0, 0, 148, 141, 1, 0, 0, 0, 148, 142, 1, 0, 0, 0, 148, 143, 1, 0, 0, 0, 148, 144, 1, 0, 0, 0, 148, 145, 1, 0, 0, 0, 148, 146, 1, 0, 0, 0, 148, 147, 1, 0, 0, 0, 149, 7, 1, 0, 0, 0, 150, 151, 5, 20, 0, 0, 151, 152, 3, 10, 5, 0, 152, 9, 1, 0, 0, 0, 153, 154, 6, 5, -1, 0, 154, 155, 5, 48, 0, 0, 155, 182, 3, 10, 5, 7, 156, 182, 3, 14, 7, 0, 157, 182, 3, 12, 6, 0, 158, 160, 3, 14, 7, 0, 159, 161, 5, 48, 0, 0, 160, 159, 1, 0, 0, 0, 160, 161, 1, 0, 0, 0, 161, 162, 1, 0, 0, 0, 162, 163, 5, 45, 0, 0, 163, 164, 5, 44, 0, 0, 164, 169, 3, 14, 7, 0, 165, 166, 5, 38, 0, 0, 166, 168, 3, 14, 7, 0, 167, 165, 1, 0, 0, 0, 168, 171, 1, 0, 0, 0, 169, 167, 1, 0, 0, 0, 169, 170, 1, 0, 0, 0, 170, 172, 1, 0, 0, 0, 171, 169, 1, 0, 0, 0, 172, 173, 5, 54, 0, 0, 173, 182, 1, 0, 0, 0, 174, 175, 3, 14, 7, 0, 175, 177, 5, 46, 0, 0, 176, 178, 5, 48, 0, 0, 177, 176, 1, 0, 0, 0, 177, 178, 1, 0, 0, 0, 178, 179, 1, 0, 0, 0, 179, 180, 5, 49, 0, 0, 180, 182, 1, 0, 0, 0, 181, 153, 1, 0, 0, 0, 181, 156, 1, 0, 0, 0, 181, 157, 1, 0, 0, 0, 181, 158, 1, 0, 0, 0, 181, 174, 1, 0, 0, 0, 182, 191, 1, 0, 0, 0, 183, 184, 10, 4, 0, 0, 184, 185, 5, 34, 0, 0, 185, 190, 3, 10, 5, 5, 186, 187, 10, 3, 0, 0, 187, 188, 5, 51, 0, 0, 188, 190, 3, 10, 5, 4, 189, 183, 1, 0, 0, 0, 189, 186, 1, 0, 0, 0, 190, 193, 1, 0, 0, 0, 191, 189, 1, 0, 0, 0, 191, 192, 1, 0, 0, 0, 192, 11, 1, 0, 0, 0, 193, 191, 1, 0, 0, 0, 194, 196, 3, 14, 7, 0, 195, 197, 5, 48, 0, 0, 196, 195, 1, 0, 0, 0, 196, 197, 1, 0, 0, 0, 197, 198, 1, 0, 0, 0, 198, 199, 5, 47, 0, 0, 199, 200, 3, 94, 47, 0, 200, 209, 1, 0, 0, 0, 201, 203, 3, 14, 7, 0, 202, 204, 5, 48, 0, 0, 203, 202, 1, 0, 0, 0, 203, 204, 1, 0, 0, 0, 204, 205, 1, 0, 0, 0, 205, 206, 5, 53, 0, 0, 206, 207, 3, 94, 47, 0, 207, 209, 1, 0, 0, 0, 208, 194, 1, 0, 0, 0, 208, 201, 1, 0, 0, 0, 209, 13, 1, 0, 0, 0, 210, 216, 3, 16, 8, 0, 211, 212, 3, 16, 8, 0, 212, 213, 3, 96, 48, 0, 213, 214, 3, 16, 8, 0, 214, 216, 1, 0, 0, 0, 215, 210, 1, 0, 0, 0, 215, 211, 1, 0, 0, 0, 216, 15, 1, 0, 0, 0, 217, 218, 6, 8, -1, 0, 218, 222, 3, 18, 9, 0, 219, 220, 7, 0, 0, 0, 220, 222, 3, 16, 8, 3, 221, 217, 1, 0, 0, 0, 221, 219, 1, 0, 0, 0, 222, 231, 1, 0, 0, 0, 223, 224, 10, 2, 0, 0, 224, 225, 7, 1, 0, 0, 225, 230, 3, 16, 8, 3, 226, 227, 10, 1, 0, 0, 227, 228, 7, 0, 0, 0, 228, 230, 3, 16, 8, 2, 229, 223, 1, 0, 0, 0, 229, 226, 1, 0, 0, 0, 230, 233, 1, 0, 0, 0, 231, 229, 1, 0, 0, 0, 231, 232, 1, 0, 0, 0, 232, 17, 1, 0, 0, 0, 233, 231, 1, 0, 0, 0, 234, 235, 6, 9, -1, 0, 235, 243, 3, 58, 29, 0, 236, 243, 3, 48, 24, 0, 237, 243, 3, 20, 10, 0, 238, 239, 5, 44, 0, 0, 239, 240, 3, 10, 5, 0, 240, 241, 5, 54, 0, 0, 241, 243, 1, 0, 0, 0, 242, 234, 1, 0, 0, 0, 242, 236, 1, 0, 0, 0, 242, 237, 1, 0, 0, 0, 242, 238, 1, 0, 0, 0, 243, 249, 1, 0, 0, 0, 244, 245, 10, 1, 0, 0, 245, 246, 5, 37, 0, 0, 246, 248, 3, 22, 11, 0, 247, 244, 1, 0, 0, 0, 248, 251, 1, 0, 0, 0, 249, 247, 1, 0, 0, 0, 249, 250, 1, 0, 0, 0, 250, 19, 1, 0, 0, 0, 251, 249, 1, 0, 0, 0, 252, 253, 3, 54, 27, 0, 253, 263, 5, 44, 0, 0, 254, 264, 5, 65, 0, 0, 255, 260, 3, 10, 5, 0, 256, 257, 5, 38, 0, 0, 257, 259, 3, 10, 5, 0, 258, 256, 1, 0, 0, 0, 259, 262, 1, 0, 0, 0, 260, 258, 1, 0, 0, 0, 260, 261, 1, 0, 0, 0, 261, 264, 1, 0, 0, 0, 262, 260, 1, 0, 0, 0, 263, 254, 1, 0, 0, 0, 263, 255, 1, 0, 0, 0, 263, 264, 1, 0, 0, 0, 264, 265, 1, 0, 0, 0, 265, 266, 5, 54, 0, 0, 266, 21, 1, 0, 0, 0, 267, 268, 3, 54, 27, 0, 268, 23, 1, 0, 0, 0, 269, 270, 5, 16, 0, 0, 270, 271, 3, 26, 13, 0, 271, 25, 1, 0, 0, 0, 272, 277, 3, 28, 14, 0, 273, 274, 5, 38, 0, 0, 274, 276, 3, 28, 14, 0, 275, 273, 1, 0, 0, 0, 276, 279, 1, 0, 0, 0, 277, 275, 1, 0, 0, 0, 277, 278, 1, 0, 0, 0, 278, 27, 1, 0, 0, 0, 279, 277, 1, 0, 0, 0, 280, 286, 3, 10, 5, 0, 281, 282, 3, 48, 24, 0, 282, 283, 5, 36, 0, 0, 283, 284, 3, 10, 5, 0, 284, 286, 1, 0, 0, 0, 285, 280, 1, 0, 0, 0, 285, 281, 1, 0, 0, 0, 286, 29, 1, 0, 0, 0, 287, 288, 5, 6, 0, 0, 288, 293, 3, 32, 16, 0, 289, 290, 5, 38, 0, 0, 290, 292, 3, 32, 16, 0, 291, 289, 1, 0, 0, 0, 292, 295, 1, 0, 0, 0, 293, 291, 1, 0, 0, 0, 293, 294, 1, 0, 0, 0, 294, 297, 1, 0, 0, 0, 295, 293, 1, 0, 0, 0, 296, 298, 3, 34, 17, 0, 297, 296, 1, 0, 0, 0, 297, 298, 1, 0, 0, 0, 298, 31, 1, 0, 0, 0, 299, 300, 5, 25, 0, 0, 300, 33, 1, 0, 0, 0, 301, 304, 3, 36, 18, 0, 302, 304, 3, 38, 19, 0, 303, 301, 1, 0, 0, 0, 303, 302, 1, 0, 0, 0, 304, 35, 1, 0, 0, 0, 305, 306, 5, 76, 0, 0, 306, 311, 3, 32, 16, 0, 307, 308, 5, 38, 0, 0, 308, 310, 3, 32, 16, 0, 309, 307, 1, 0, 0, 0, 310, 313, 1, 0, 0, 0, 311, 309, 1, 0, 0, 0, 311, 312, 1, 0, 0, 0, 312, 37, 1, 0, 0, 0, 313, 311, 1, 0, 0, 0, 314, 315, 5, 69, 0, 0, 315, 316, 3, 36, 18, 0, 316, 317, 5, 70, 0, 0, 317, 39, 1, 0, 0, 0, 318, 319, 5, 13, 0, 0, 319, 324, 3, 32, 16, 0, 320, 321, 5, 38, 0, 0, 321, 323, 3, 32, 16, 0, 322, 320, 1, 0, 0, 0, 323, 326, 1, 0, 0, 0, 324, 322, 1, 0, 0, 0, 324, 325, 1, 0, 0, 0, 325, 328, 1, 0, 0, 0, 326, 324, 1, 0, 0, 0, 327, 329, 3, 26, 13, 0, 328, 327, 1, 0, 0, 0, 328, 329, 1, 0, 0, 0, 329, 332, 1, 0, 0, 0, 330, 331, 5, 33, 0, 0, 331, 333, 3, 26, 13, 0, 332, 330, 1, 0, 0, 0, 332, 333, 1, 0, 0, 0, 333, 41, 1, 0, 0, 0, 334, 335, 5, 4, 0, 0, 335, 336, 3, 26, 13, 0, 336, 43, 1, 0, 0, 0, 337, 339, 5, 19, 0, 0, 338, 340, 3, 26, 13, 0, 339, 338, 1, 0, 0, 0, 339, 340, 1, 0, 0, 0, 340, 343, 1, 0, 0, 0, 341, 342, 5, 33, 0, 0, 342, 344, 3, 26, 13, 0, 343, 341, 1, 0, 0, 0, 343, 344, 1, 0, 0, 0, 344, 45, 1, 0, 0, 0, 345, 346, 5, 8, 0, 0, 346, 349, 3, 26, 13, 0, 347, 348, 5, 33, 0, 0, 348, 350, 3, 26, 13, 0, 349, 347, 1, 0, 0, 0, 349, 350, 1, 0, 0, 0, 350, 47, 1, 0, 0, 0, 351, 356, 3, 54, 27, 0, 352, 353, 5, 40, 0, 0, 353, 355, 3, 54, 27, 0, 354, 352, 1, 0, 0, 0, 355, 358, 1, 0, 0, 0, 356, 354, 1, 0, 0, 0, 356, 357, 1, 0, 0, 0, 357, 49, 1, 0, 0, 0, 358, 356, 1, 0, 0, 0, 359, 364, 3, 56, 28, 0, 360, 361, 5, 40, 0, 0, 361, 363, 3, 56, 28, 0, 362, 360, 1, 0, 0, 0, 363, 366, 1, 0, 0, 0, 364, 362, 1, 0, 0, 0, 364, 365, 1, 0, 0, 0, 365, 51, 1, 0, 0, 0, 366, 364, 1, 0, 0, 0, 367, 372, 3, 50, 25, 0, 368, 369, 5, 38, 0, 0, 369, 371, 3, 50, 25, 0, 370, 368, 1, 0, 0, 0, 371, 374, 1, 0, 0, 0, 372, 370, 1, 0, 0, 0, 372, 373, 1, 0, 0, 0, 373, 53, 1, 0, 0, 0, 374, 372, 1, 0, 0, 0, 375, 376, 7, 2, 0, 0, 376, 55, 1, 0, 0, 0, 377, 378, 5, 80, 0, 0, 378, 57, 1, 0, 0, 0, 379, 422, 5, 49, 0, 0, 380, 381, 3, 92, 46, 0, 381, 382, 5, 71, 0, 0, 382, 422, 1, 0, 0, 0, 383, 422, 3, 90, 45, 0, 384, 422, 3, 92, 46, 0, 385, 422, 3, 86, 43, 0, 386, 422, 3, 60, 30, 0, 387, 422, 3, 94, 47, 0, 388, 389, 5, 69, 0, 0, 389, 394, 3, 88, 44, 0, 390, 391, 5, 38, 0, 0, 391, 393, 3, 88, 44, 0, 392, 390, 1, 0, 0, 0, 393, 396, 1, 0, 0, 0, 394, 392, 1, 0, 0, 0, 394, 395, 1, 0, 0, 0, 395, 397, 1, 0, 0, 0, 396, 394, 1, 0, 0, 0, 397, 398, 5, 70, 0, 0, 398, 422, 1, 0, 0, 0, 399, 400, 5, 69, 0, 0, 400, 405, 3, 86, 43, 0, 401, 402, 5, 38, 0, 0, 402, 404, 3, 86, 43, 0, 403, 401, 1, 0, 0, 0, 404, 407, 1, 0, 0, 0, 405, 403, 1, 0, 0, 0, 405, 406, 1, 0, 0, 0, 406, 408, 1, 0, 0, 0, 407, 405, 1, 0, 0, 0, 408, 409, 5, 70, 0, 0, 409, 422, 1, 0, 0, 0, 410, 411, 5, 69, 0, 0, 411, 416, 3, 94, 47, 0, 412, 413, 5, 38, 0, 0, 413, 415, 3, 94, 47, 0, 414, 412, 1, 0, 0, 0, 415, 418, 1, 0, 0, 0, 416, 414, 1, 0, 0, 0, 416, 417, 1, 0, 0, 0, 417, 419, 1, 0, 0, 0, 418, 416, 1, 0, 0, 0, 419, 420, 5, 70, 0, 0, 420, 422, 1, 0, 0, 0, 421, 379, 1, 0, 0, 0, 421, 380, 1, 0, 0, 0, 421, 383, 1, 0, 0, 0, 421, 384, 1, 0, 0, 0, 421, 385, 1, 0, 0, 0, 421, 386, 1, 0, 0, 0, 421, 387, 1, 0, 0, 0, 421, 388, 1, 0, 0, 0, 421, 399, 1, 0, 0, 0, 421, 410, 1, 0, 0, 0, 422, 59, 1, 0, 0, 0, 423, 426, 5, 52, 0, 0, 424, 426, 5, 68, 0, 0, 425, 423, 1, 0, 0, 0, 425, 424, 1, 0, 0, 0, 426, 61, 1, 0, 0, 0, 427, 428, 5, 10, 0, 0, 428, 429, 5, 31, 0, 0, 429, 63, 1, 0, 0, 0, 430, 431, 5, 18, 0, 0, 431, 436, 3, 66, 33, 0, 432, 433, 5, 38, 0, 0, 433, 435, 3, 66, 33, 0, 434, 432, 1, 0, 0, 0, 435, 438, 1, 0, 0, 0, 436, 434, 1, 0, 0, 0, 436, 437, 1, 0, 0, 0, 437, 65, 1, 0, 0, 0, 438, 436, 1, 0, 0, 0, 439, 441, 3, 10, 5, 0, 440, 442, 7, 3, 0, 0, 441, 440, 1, 0, 0, 0, 441, 442, 1, 0, 0, 0, 442, 445, 1, 0, 0, 0, 443, 444, 5, 50, 0, 0, 444, 446, 7, 4, 0, 0, 445, 443, 1, 0, 0, 0, 445, 446, 1, 0, 0, 0, 446, 67, 1, 0, 0, 0, 447, 448, 5, 9, 0, 0, 448, 449, 3, 52, 26, 0, 449, 69, 1, 0, 0, 0, 450, 451, 5, 2, 0, 0, 451, 452, 3, 52, 26, 0, 452, 71, 1, 0, 0, 0, 453, 454, 5, 15, 0, 0, 454, 459, 3, 74, 37, 0, 455, 456, 5, 38, 0, 0, 456, 458, 3, 74, 37, 0, 457, 455, 1, 0, 0, 0, 458, 461, 1, 0, 0, 0, 459, 457, 1, 0, 0, 0, 459, 460, 1, 0, 0, 0, 460, 73, 1, 0, 0, 0, 461, 459, 1, 0, 0, 0, 462, 463, 3, 50, 25, 0, 463, 464, 5, 84, 0, 0, 464, 465, 3, 50, 25, 0, 465, 75, 1, 0, 0, 0, 466, 467, 5, 1, 0, 0, 467, 468, 3, 18, 9, 0, 468, 470, 3, 94, 47, 0, 469, 471, 3, 82, 41, 0, 470, 469, 1, 0, 0, 0, 470, 471, 1, 0, 0, 0, 471, 77, 1, 0, 0, 0, 472, 473, 5, 7, 0, 0, 473, 474, 3, 18, 9, 0, 474, 475, 3, 94, 47, 0, 475, 79, 1, 0, 0, 0, 476, 477, 5, 14, 0, 0, 477, 478, 3, 48, 24, 0, 478, 81, 1, 0, 0, 0, 479, 484, 3, 84, 42, 0, 480, 481, 5, 38, 0, 0, 481, 483, 3, 84, 42, 0, 482, 480, 1, 0, 0, 0, 483, 486, 1, 0, 0, 0, 484, 482, 1, 0, 0, 0, 484, 485, 1, 0, 0, 0, 485, 83, 1, 0, 0, 0, 486, 484, 1, 0, 0, 0, 487, 488, 3, 54, 27, 0, 488, 489, 5, 36, 0, 0, 489, 490, 3, 58, 29, 0, 490, 85, 1, 0, 0, 0, 491, 492, 7, 5, 0, 0, 492, 87, 1, 0, 0, 0, 493, 496, 3, 90, 45, 0, 494, 496, 3, 92, 46, 0, 495, 493, 1, 0, 0, 0, 495, 494, 1, 0, 0, 0, 496, 89, 1, 0, 0, 0, 497, 499, 7, 0, 0, 0, 498, 497, 1, 0, 0, 0, 498, 499, 1, 0, 0, 0, 499, 500, 1, 0, 0, 0, 500, 501, 5, 32, 0, 0, 501, 91, 1, 0, 0, 0, 502, 504, 7, 0, 0, 0, 503, 502, 1, 0, 0, 0, 503, 504, 1, 0, 0, 0, 504, 505, 1, 0, 0, 0, 505, 506, 5, 31, 0, 0, 506, 93, 1, 0, 0, 0, 507, 508, 5, 30, 0, 0, 508, 95, 1, 0, 0, 0, 509, 510, 7, 6, 0, 0, 510, 97, 1, 0, 0, 0, 511, 512, 5, 5, 0, 0, 512, 513, 3, 100, 50, 0, 513, 99, 1, 0, 0, 0, 514, 515, 5, 69, 0, 0, 515, 516, 3, 2, 1, 0, 516, 517, 5, 70, 0, 0, 517, 101, 1, 0, 0, 0, 518, 519, 5, 17, 0, 0, 519, 520, 5, 106, 0, 0, 520, 103, 1, 0, 0, 0, 521, 522, 5, 12, 0, 0, 522, 523, 5, 110, 0, 0, 523, 105, 1, 0, 0, 0, 524, 525, 5, 3, 0, 0, 525, 528, 5, 90, 0, 0, 526, 527, 5, 88, 0, 0, 527, 529, 3, 50, 25, 0, 528, 526, 1, 0, 0, 0, 528, 529, 1, 0, 0, 0, 529, 539, 1, 0, 0, 0, 530, 531, 5, 89, 0, 0, 531, 536, 3, 108, 54, 0, 532, 533, 5, 38, 0, 0, 533, 535, 3, 108, 54, 0, 534, 532, 1, 0, 0, 0, 535, 538, 1, 0, 0, 0, 536, 534, 1, 0, 0, 0, 536, 537, 1, 0, 0, 0, 537, 540, 1, 0, 0, 0, 538, 536, 1, 0, 0, 0, 539, 530, 1, 0, 0, 0, 539, 540, 1, 0, 0, 0, 540, 107, 1, 0, 0, 0, 541, 542, 3, 50, 25, 0, 542, 543, 5, 36, 0, 0, 543, 545, 1, 0, 0, 0, 544, 541, 1, 0, 0, 0, 544, 545, 1, 0, 0, 0, 545, 546, 1, 0, 0, 0, 546, 547, 3, 50, 25, 0, 547, 109, 1, 0, 0, 0, 548, 549, 5, 11, 0, 0, 549, 550, 5, 25, 0, 0, 550, 551, 5, 88, 0, 0, 551, 552, 3, 52, 26, 0, 552, 111, 1, 0, 0, 0, 53, 123, 132, 148, 160, 169, 177, 181, 189, 191, 196, 203, 208, 215, 221, 229, 231, 242, 249, 260, 263, 277, 285, 293, 297, 303, 311, 324, 328, 332, 339, 343, 349, 356, 364, 372, 394, 405, 416, 421, 425, 436, 441, 445, 459, 470, 484, 495, 498, 503, 528, 536, 539, 544] \ No newline at end of file diff --git a/packages/kbn-esql-ast/src/antlr/esql_parser.tokens b/packages/kbn-esql-ast/src/antlr/esql_parser.tokens index 30ce0d5eea55b..04798fc3dca8a 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_parser.tokens +++ b/packages/kbn-esql-ast/src/antlr/esql_parser.tokens @@ -65,62 +65,63 @@ MINUS=64 ASTERISK=65 SLASH=66 PERCENT=67 -OPENING_BRACKET=68 -CLOSING_BRACKET=69 -UNQUOTED_IDENTIFIER=70 -QUOTED_IDENTIFIER=71 -EXPR_LINE_COMMENT=72 -EXPR_MULTILINE_COMMENT=73 -EXPR_WS=74 -METADATA=75 -FROM_LINE_COMMENT=76 -FROM_MULTILINE_COMMENT=77 -FROM_WS=78 -ID_PATTERN=79 -PROJECT_LINE_COMMENT=80 -PROJECT_MULTILINE_COMMENT=81 -PROJECT_WS=82 -AS=83 -RENAME_LINE_COMMENT=84 -RENAME_MULTILINE_COMMENT=85 -RENAME_WS=86 -ON=87 -WITH=88 -ENRICH_POLICY_NAME=89 -ENRICH_LINE_COMMENT=90 -ENRICH_MULTILINE_COMMENT=91 -ENRICH_WS=92 -ENRICH_FIELD_LINE_COMMENT=93 -ENRICH_FIELD_MULTILINE_COMMENT=94 -ENRICH_FIELD_WS=95 -LOOKUP_LINE_COMMENT=96 -LOOKUP_MULTILINE_COMMENT=97 -LOOKUP_WS=98 -LOOKUP_FIELD_LINE_COMMENT=99 -LOOKUP_FIELD_MULTILINE_COMMENT=100 -LOOKUP_FIELD_WS=101 -MVEXPAND_LINE_COMMENT=102 -MVEXPAND_MULTILINE_COMMENT=103 -MVEXPAND_WS=104 -INFO=105 -SHOW_LINE_COMMENT=106 -SHOW_MULTILINE_COMMENT=107 -SHOW_WS=108 -FUNCTIONS=109 -META_LINE_COMMENT=110 -META_MULTILINE_COMMENT=111 -META_WS=112 -COLON=113 -SETTING=114 -SETTING_LINE_COMMENT=115 -SETTTING_MULTILINE_COMMENT=116 -SETTING_WS=117 -METRICS_LINE_COMMENT=118 -METRICS_MULTILINE_COMMENT=119 -METRICS_WS=120 -CLOSING_METRICS_LINE_COMMENT=121 -CLOSING_METRICS_MULTILINE_COMMENT=122 -CLOSING_METRICS_WS=123 +NAMED_OR_POSITIONAL_PARAM=68 +OPENING_BRACKET=69 +CLOSING_BRACKET=70 +UNQUOTED_IDENTIFIER=71 +QUOTED_IDENTIFIER=72 +EXPR_LINE_COMMENT=73 +EXPR_MULTILINE_COMMENT=74 +EXPR_WS=75 +METADATA=76 +FROM_LINE_COMMENT=77 +FROM_MULTILINE_COMMENT=78 +FROM_WS=79 +ID_PATTERN=80 +PROJECT_LINE_COMMENT=81 +PROJECT_MULTILINE_COMMENT=82 +PROJECT_WS=83 +AS=84 +RENAME_LINE_COMMENT=85 +RENAME_MULTILINE_COMMENT=86 +RENAME_WS=87 +ON=88 +WITH=89 +ENRICH_POLICY_NAME=90 +ENRICH_LINE_COMMENT=91 +ENRICH_MULTILINE_COMMENT=92 +ENRICH_WS=93 +ENRICH_FIELD_LINE_COMMENT=94 +ENRICH_FIELD_MULTILINE_COMMENT=95 +ENRICH_FIELD_WS=96 +LOOKUP_LINE_COMMENT=97 +LOOKUP_MULTILINE_COMMENT=98 +LOOKUP_WS=99 +LOOKUP_FIELD_LINE_COMMENT=100 +LOOKUP_FIELD_MULTILINE_COMMENT=101 +LOOKUP_FIELD_WS=102 +MVEXPAND_LINE_COMMENT=103 +MVEXPAND_MULTILINE_COMMENT=104 +MVEXPAND_WS=105 +INFO=106 +SHOW_LINE_COMMENT=107 +SHOW_MULTILINE_COMMENT=108 +SHOW_WS=109 +FUNCTIONS=110 +META_LINE_COMMENT=111 +META_MULTILINE_COMMENT=112 +META_WS=113 +COLON=114 +SETTING=115 +SETTING_LINE_COMMENT=116 +SETTTING_MULTILINE_COMMENT=117 +SETTING_WS=118 +METRICS_LINE_COMMENT=119 +METRICS_MULTILINE_COMMENT=120 +METRICS_WS=121 +CLOSING_METRICS_LINE_COMMENT=122 +CLOSING_METRICS_MULTILINE_COMMENT=123 +CLOSING_METRICS_WS=124 'dissect'=1 'drop'=2 'enrich'=3 @@ -177,11 +178,11 @@ CLOSING_METRICS_WS=123 '*'=65 '/'=66 '%'=67 -']'=69 -'metadata'=75 -'as'=83 -'on'=87 -'with'=88 -'info'=105 -'functions'=109 -':'=113 +']'=70 +'metadata'=76 +'as'=84 +'on'=88 +'with'=89 +'info'=106 +'functions'=110 +':'=114 diff --git a/packages/kbn-esql-ast/src/antlr/esql_parser.ts b/packages/kbn-esql-ast/src/antlr/esql_parser.ts index 7b132ef4a3358..5181349c62c88 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_parser.ts +++ b/packages/kbn-esql-ast/src/antlr/esql_parser.ts @@ -85,62 +85,63 @@ export default class esql_parser extends Parser { public static readonly ASTERISK = 65; public static readonly SLASH = 66; public static readonly PERCENT = 67; - public static readonly OPENING_BRACKET = 68; - public static readonly CLOSING_BRACKET = 69; - public static readonly UNQUOTED_IDENTIFIER = 70; - public static readonly QUOTED_IDENTIFIER = 71; - public static readonly EXPR_LINE_COMMENT = 72; - public static readonly EXPR_MULTILINE_COMMENT = 73; - public static readonly EXPR_WS = 74; - public static readonly METADATA = 75; - public static readonly FROM_LINE_COMMENT = 76; - public static readonly FROM_MULTILINE_COMMENT = 77; - public static readonly FROM_WS = 78; - public static readonly ID_PATTERN = 79; - public static readonly PROJECT_LINE_COMMENT = 80; - public static readonly PROJECT_MULTILINE_COMMENT = 81; - public static readonly PROJECT_WS = 82; - public static readonly AS = 83; - public static readonly RENAME_LINE_COMMENT = 84; - public static readonly RENAME_MULTILINE_COMMENT = 85; - public static readonly RENAME_WS = 86; - public static readonly ON = 87; - public static readonly WITH = 88; - public static readonly ENRICH_POLICY_NAME = 89; - public static readonly ENRICH_LINE_COMMENT = 90; - public static readonly ENRICH_MULTILINE_COMMENT = 91; - public static readonly ENRICH_WS = 92; - public static readonly ENRICH_FIELD_LINE_COMMENT = 93; - public static readonly ENRICH_FIELD_MULTILINE_COMMENT = 94; - public static readonly ENRICH_FIELD_WS = 95; - public static readonly LOOKUP_LINE_COMMENT = 96; - public static readonly LOOKUP_MULTILINE_COMMENT = 97; - public static readonly LOOKUP_WS = 98; - public static readonly LOOKUP_FIELD_LINE_COMMENT = 99; - public static readonly LOOKUP_FIELD_MULTILINE_COMMENT = 100; - public static readonly LOOKUP_FIELD_WS = 101; - public static readonly MVEXPAND_LINE_COMMENT = 102; - public static readonly MVEXPAND_MULTILINE_COMMENT = 103; - public static readonly MVEXPAND_WS = 104; - public static readonly INFO = 105; - public static readonly SHOW_LINE_COMMENT = 106; - public static readonly SHOW_MULTILINE_COMMENT = 107; - public static readonly SHOW_WS = 108; - public static readonly FUNCTIONS = 109; - public static readonly META_LINE_COMMENT = 110; - public static readonly META_MULTILINE_COMMENT = 111; - public static readonly META_WS = 112; - public static readonly COLON = 113; - public static readonly SETTING = 114; - public static readonly SETTING_LINE_COMMENT = 115; - public static readonly SETTTING_MULTILINE_COMMENT = 116; - public static readonly SETTING_WS = 117; - public static readonly METRICS_LINE_COMMENT = 118; - public static readonly METRICS_MULTILINE_COMMENT = 119; - public static readonly METRICS_WS = 120; - public static readonly CLOSING_METRICS_LINE_COMMENT = 121; - public static readonly CLOSING_METRICS_MULTILINE_COMMENT = 122; - public static readonly CLOSING_METRICS_WS = 123; + public static readonly NAMED_OR_POSITIONAL_PARAM = 68; + public static readonly OPENING_BRACKET = 69; + public static readonly CLOSING_BRACKET = 70; + public static readonly UNQUOTED_IDENTIFIER = 71; + public static readonly QUOTED_IDENTIFIER = 72; + public static readonly EXPR_LINE_COMMENT = 73; + public static readonly EXPR_MULTILINE_COMMENT = 74; + public static readonly EXPR_WS = 75; + public static readonly METADATA = 76; + public static readonly FROM_LINE_COMMENT = 77; + public static readonly FROM_MULTILINE_COMMENT = 78; + public static readonly FROM_WS = 79; + public static readonly ID_PATTERN = 80; + public static readonly PROJECT_LINE_COMMENT = 81; + public static readonly PROJECT_MULTILINE_COMMENT = 82; + public static readonly PROJECT_WS = 83; + public static readonly AS = 84; + public static readonly RENAME_LINE_COMMENT = 85; + public static readonly RENAME_MULTILINE_COMMENT = 86; + public static readonly RENAME_WS = 87; + public static readonly ON = 88; + public static readonly WITH = 89; + public static readonly ENRICH_POLICY_NAME = 90; + public static readonly ENRICH_LINE_COMMENT = 91; + public static readonly ENRICH_MULTILINE_COMMENT = 92; + public static readonly ENRICH_WS = 93; + public static readonly ENRICH_FIELD_LINE_COMMENT = 94; + public static readonly ENRICH_FIELD_MULTILINE_COMMENT = 95; + public static readonly ENRICH_FIELD_WS = 96; + public static readonly LOOKUP_LINE_COMMENT = 97; + public static readonly LOOKUP_MULTILINE_COMMENT = 98; + public static readonly LOOKUP_WS = 99; + public static readonly LOOKUP_FIELD_LINE_COMMENT = 100; + public static readonly LOOKUP_FIELD_MULTILINE_COMMENT = 101; + public static readonly LOOKUP_FIELD_WS = 102; + public static readonly MVEXPAND_LINE_COMMENT = 103; + public static readonly MVEXPAND_MULTILINE_COMMENT = 104; + public static readonly MVEXPAND_WS = 105; + public static readonly INFO = 106; + public static readonly SHOW_LINE_COMMENT = 107; + public static readonly SHOW_MULTILINE_COMMENT = 108; + public static readonly SHOW_WS = 109; + public static readonly FUNCTIONS = 110; + public static readonly META_LINE_COMMENT = 111; + public static readonly META_MULTILINE_COMMENT = 112; + public static readonly META_WS = 113; + public static readonly COLON = 114; + public static readonly SETTING = 115; + public static readonly SETTING_LINE_COMMENT = 116; + public static readonly SETTTING_MULTILINE_COMMENT = 117; + public static readonly SETTING_WS = 118; + public static readonly METRICS_LINE_COMMENT = 119; + public static readonly METRICS_MULTILINE_COMMENT = 120; + public static readonly METRICS_WS = 121; + public static readonly CLOSING_METRICS_LINE_COMMENT = 122; + public static readonly CLOSING_METRICS_MULTILINE_COMMENT = 123; + public static readonly CLOSING_METRICS_WS = 124; public static readonly EOF = Token.EOF; public static readonly RULE_singleStatement = 0; public static readonly RULE_query = 1; @@ -172,31 +173,32 @@ export default class esql_parser extends Parser { public static readonly RULE_identifier = 27; public static readonly RULE_identifierPattern = 28; public static readonly RULE_constant = 29; - public static readonly RULE_limitCommand = 30; - public static readonly RULE_sortCommand = 31; - public static readonly RULE_orderExpression = 32; - public static readonly RULE_keepCommand = 33; - public static readonly RULE_dropCommand = 34; - public static readonly RULE_renameCommand = 35; - public static readonly RULE_renameClause = 36; - public static readonly RULE_dissectCommand = 37; - public static readonly RULE_grokCommand = 38; - public static readonly RULE_mvExpandCommand = 39; - public static readonly RULE_commandOptions = 40; - public static readonly RULE_commandOption = 41; - public static readonly RULE_booleanValue = 42; - public static readonly RULE_numericValue = 43; - public static readonly RULE_decimalValue = 44; - public static readonly RULE_integerValue = 45; - public static readonly RULE_string = 46; - public static readonly RULE_comparisonOperator = 47; - public static readonly RULE_explainCommand = 48; - public static readonly RULE_subqueryExpression = 49; - public static readonly RULE_showCommand = 50; - public static readonly RULE_metaCommand = 51; - public static readonly RULE_enrichCommand = 52; - public static readonly RULE_enrichWithClause = 53; - public static readonly RULE_lookupCommand = 54; + public static readonly RULE_params = 30; + public static readonly RULE_limitCommand = 31; + public static readonly RULE_sortCommand = 32; + public static readonly RULE_orderExpression = 33; + public static readonly RULE_keepCommand = 34; + public static readonly RULE_dropCommand = 35; + public static readonly RULE_renameCommand = 36; + public static readonly RULE_renameClause = 37; + public static readonly RULE_dissectCommand = 38; + public static readonly RULE_grokCommand = 39; + public static readonly RULE_mvExpandCommand = 40; + public static readonly RULE_commandOptions = 41; + public static readonly RULE_commandOption = 42; + public static readonly RULE_booleanValue = 43; + public static readonly RULE_numericValue = 44; + public static readonly RULE_decimalValue = 45; + public static readonly RULE_integerValue = 46; + public static readonly RULE_string = 47; + public static readonly RULE_comparisonOperator = 48; + public static readonly RULE_explainCommand = 49; + public static readonly RULE_subqueryExpression = 50; + public static readonly RULE_showCommand = 51; + public static readonly RULE_metaCommand = 52; + public static readonly RULE_enrichCommand = 53; + public static readonly RULE_enrichWithClause = 54; + public static readonly RULE_lookupCommand = 55; public static readonly literalNames: (string | null)[] = [ null, "'dissect'", "'drop'", "'enrich'", "'eval'", "'explain'", @@ -233,10 +235,11 @@ export default class esql_parser extends Parser { "'>='", "'+'", "'-'", "'*'", "'/'", "'%'", - null, "']'", null, null, + "']'", null, null, null, - null, "'metadata'", + null, null, + "'metadata'", null, null, null, null, null, null, @@ -296,6 +299,7 @@ export default class esql_parser extends Parser { "GTE", "PLUS", "MINUS", "ASTERISK", "SLASH", "PERCENT", + "NAMED_OR_POSITIONAL_PARAM", "OPENING_BRACKET", "CLOSING_BRACKET", "UNQUOTED_IDENTIFIER", @@ -356,9 +360,9 @@ export default class esql_parser extends Parser { "field", "fromCommand", "indexIdentifier", "metadata", "metadataOption", "deprecated_metadata", "metricsCommand", "evalCommand", "statsCommand", "inlinestatsCommand", "qualifiedName", "qualifiedNamePattern", "qualifiedNamePatterns", - "identifier", "identifierPattern", "constant", "limitCommand", "sortCommand", - "orderExpression", "keepCommand", "dropCommand", "renameCommand", "renameClause", - "dissectCommand", "grokCommand", "mvExpandCommand", "commandOptions", + "identifier", "identifierPattern", "constant", "params", "limitCommand", + "sortCommand", "orderExpression", "keepCommand", "dropCommand", "renameCommand", + "renameClause", "dissectCommand", "grokCommand", "mvExpandCommand", "commandOptions", "commandOption", "booleanValue", "numericValue", "decimalValue", "integerValue", "string", "comparisonOperator", "explainCommand", "subqueryExpression", "showCommand", "metaCommand", "enrichCommand", "enrichWithClause", "lookupCommand", @@ -384,9 +388,9 @@ export default class esql_parser extends Parser { try { this.enterOuterAlt(localctx, 1); { - this.state = 110; + this.state = 112; this.query(0); - this.state = 111; + this.state = 113; this.match(esql_parser.EOF); } } @@ -428,11 +432,11 @@ export default class esql_parser extends Parser { this._ctx = localctx; _prevctx = localctx; - this.state = 114; + this.state = 116; this.sourceCommand(); } this._ctx.stop = this._input.LT(-1); - this.state = 121; + this.state = 123; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 0, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { @@ -445,18 +449,18 @@ export default class esql_parser extends Parser { { localctx = new CompositeQueryContext(this, new QueryContext(this, _parentctx, _parentState)); this.pushNewRecursionContext(localctx, _startState, esql_parser.RULE_query); - this.state = 116; + this.state = 118; if (!(this.precpred(this._ctx, 1))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 1)"); } - this.state = 117; + this.state = 119; this.match(esql_parser.PIPE); - this.state = 118; + this.state = 120; this.processingCommand(); } } } - this.state = 123; + this.state = 125; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 0, this._ctx); } @@ -481,48 +485,48 @@ export default class esql_parser extends Parser { let localctx: SourceCommandContext = new SourceCommandContext(this, this._ctx, this.state); this.enterRule(localctx, 4, esql_parser.RULE_sourceCommand); try { - this.state = 130; + this.state = 132; this._errHandler.sync(this); switch (this._input.LA(1)) { case 5: this.enterOuterAlt(localctx, 1); { - this.state = 124; + this.state = 126; this.explainCommand(); } break; case 6: this.enterOuterAlt(localctx, 2); { - this.state = 125; + this.state = 127; this.fromCommand(); } break; case 16: this.enterOuterAlt(localctx, 3); { - this.state = 126; + this.state = 128; this.rowCommand(); } break; case 13: this.enterOuterAlt(localctx, 4); { - this.state = 127; + this.state = 129; this.metricsCommand(); } break; case 17: this.enterOuterAlt(localctx, 5); { - this.state = 128; + this.state = 130; this.showCommand(); } break; case 12: this.enterOuterAlt(localctx, 6); { - this.state = 129; + this.state = 131; this.metaCommand(); } break; @@ -549,104 +553,104 @@ export default class esql_parser extends Parser { let localctx: ProcessingCommandContext = new ProcessingCommandContext(this, this._ctx, this.state); this.enterRule(localctx, 6, esql_parser.RULE_processingCommand); try { - this.state = 146; + this.state = 148; this._errHandler.sync(this); switch (this._input.LA(1)) { case 4: this.enterOuterAlt(localctx, 1); { - this.state = 132; + this.state = 134; this.evalCommand(); } break; case 8: this.enterOuterAlt(localctx, 2); { - this.state = 133; + this.state = 135; this.inlinestatsCommand(); } break; case 10: this.enterOuterAlt(localctx, 3); { - this.state = 134; + this.state = 136; this.limitCommand(); } break; case 11: this.enterOuterAlt(localctx, 4); { - this.state = 135; + this.state = 137; this.lookupCommand(); } break; case 9: this.enterOuterAlt(localctx, 5); { - this.state = 136; + this.state = 138; this.keepCommand(); } break; case 18: this.enterOuterAlt(localctx, 6); { - this.state = 137; + this.state = 139; this.sortCommand(); } break; case 19: this.enterOuterAlt(localctx, 7); { - this.state = 138; + this.state = 140; this.statsCommand(); } break; case 20: this.enterOuterAlt(localctx, 8); { - this.state = 139; + this.state = 141; this.whereCommand(); } break; case 2: this.enterOuterAlt(localctx, 9); { - this.state = 140; + this.state = 142; this.dropCommand(); } break; case 15: this.enterOuterAlt(localctx, 10); { - this.state = 141; + this.state = 143; this.renameCommand(); } break; case 1: this.enterOuterAlt(localctx, 11); { - this.state = 142; + this.state = 144; this.dissectCommand(); } break; case 7: this.enterOuterAlt(localctx, 12); { - this.state = 143; + this.state = 145; this.grokCommand(); } break; case 3: this.enterOuterAlt(localctx, 13); { - this.state = 144; + this.state = 146; this.enrichCommand(); } break; case 14: this.enterOuterAlt(localctx, 14); { - this.state = 145; + this.state = 147; this.mvExpandCommand(); } break; @@ -675,9 +679,9 @@ export default class esql_parser extends Parser { try { this.enterOuterAlt(localctx, 1); { - this.state = 148; + this.state = 150; this.match(esql_parser.WHERE); - this.state = 149; + this.state = 151; this.booleanExpression(0); } } @@ -715,7 +719,7 @@ export default class esql_parser extends Parser { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 179; + this.state = 181; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 6, this._ctx) ) { case 1: @@ -724,9 +728,9 @@ export default class esql_parser extends Parser { this._ctx = localctx; _prevctx = localctx; - this.state = 152; + this.state = 154; this.match(esql_parser.NOT); - this.state = 153; + this.state = 155; this.booleanExpression(7); } break; @@ -735,7 +739,7 @@ export default class esql_parser extends Parser { localctx = new BooleanDefaultContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 154; + this.state = 156; this.valueExpression(); } break; @@ -744,7 +748,7 @@ export default class esql_parser extends Parser { localctx = new RegexExpressionContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 155; + this.state = 157; this.regexBooleanExpression(); } break; @@ -753,41 +757,41 @@ export default class esql_parser extends Parser { localctx = new LogicalInContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 156; - this.valueExpression(); this.state = 158; + this.valueExpression(); + this.state = 160; this._errHandler.sync(this); _la = this._input.LA(1); if (_la===48) { { - this.state = 157; + this.state = 159; this.match(esql_parser.NOT); } } - this.state = 160; + this.state = 162; this.match(esql_parser.IN); - this.state = 161; + this.state = 163; this.match(esql_parser.LP); - this.state = 162; + this.state = 164; this.valueExpression(); - this.state = 167; + this.state = 169; this._errHandler.sync(this); _la = this._input.LA(1); while (_la===38) { { { - this.state = 163; + this.state = 165; this.match(esql_parser.COMMA); - this.state = 164; + this.state = 166; this.valueExpression(); } } - this.state = 169; + this.state = 171; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 170; + this.state = 172; this.match(esql_parser.RP); } break; @@ -796,27 +800,27 @@ export default class esql_parser extends Parser { localctx = new IsNullContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 172; + this.state = 174; this.valueExpression(); - this.state = 173; - this.match(esql_parser.IS); this.state = 175; + this.match(esql_parser.IS); + this.state = 177; this._errHandler.sync(this); _la = this._input.LA(1); if (_la===48) { { - this.state = 174; + this.state = 176; this.match(esql_parser.NOT); } } - this.state = 177; + this.state = 179; this.match(esql_parser.NULL); } break; } this._ctx.stop = this._input.LT(-1); - this.state = 189; + this.state = 191; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 8, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { @@ -826,7 +830,7 @@ export default class esql_parser extends Parser { } _prevctx = localctx; { - this.state = 187; + this.state = 189; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 7, this._ctx) ) { case 1: @@ -834,13 +838,13 @@ export default class esql_parser extends Parser { localctx = new LogicalBinaryContext(this, new BooleanExpressionContext(this, _parentctx, _parentState)); (localctx as LogicalBinaryContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, esql_parser.RULE_booleanExpression); - this.state = 181; + this.state = 183; if (!(this.precpred(this._ctx, 4))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 4)"); } - this.state = 182; + this.state = 184; (localctx as LogicalBinaryContext)._operator = this.match(esql_parser.AND); - this.state = 183; + this.state = 185; (localctx as LogicalBinaryContext)._right = this.booleanExpression(5); } break; @@ -849,20 +853,20 @@ export default class esql_parser extends Parser { localctx = new LogicalBinaryContext(this, new BooleanExpressionContext(this, _parentctx, _parentState)); (localctx as LogicalBinaryContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, esql_parser.RULE_booleanExpression); - this.state = 184; + this.state = 186; if (!(this.precpred(this._ctx, 3))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 3)"); } - this.state = 185; + this.state = 187; (localctx as LogicalBinaryContext)._operator = this.match(esql_parser.OR); - this.state = 186; + this.state = 188; (localctx as LogicalBinaryContext)._right = this.booleanExpression(4); } break; } } } - this.state = 191; + this.state = 193; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 8, this._ctx); } @@ -888,48 +892,48 @@ export default class esql_parser extends Parser { this.enterRule(localctx, 12, esql_parser.RULE_regexBooleanExpression); let _la: number; try { - this.state = 206; + this.state = 208; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 11, this._ctx) ) { case 1: this.enterOuterAlt(localctx, 1); { - this.state = 192; - this.valueExpression(); this.state = 194; + this.valueExpression(); + this.state = 196; this._errHandler.sync(this); _la = this._input.LA(1); if (_la===48) { { - this.state = 193; + this.state = 195; this.match(esql_parser.NOT); } } - this.state = 196; + this.state = 198; localctx._kind = this.match(esql_parser.LIKE); - this.state = 197; + this.state = 199; localctx._pattern = this.string_(); } break; case 2: this.enterOuterAlt(localctx, 2); { - this.state = 199; - this.valueExpression(); this.state = 201; + this.valueExpression(); + this.state = 203; this._errHandler.sync(this); _la = this._input.LA(1); if (_la===48) { { - this.state = 200; + this.state = 202; this.match(esql_parser.NOT); } } - this.state = 203; + this.state = 205; localctx._kind = this.match(esql_parser.RLIKE); - this.state = 204; + this.state = 206; localctx._pattern = this.string_(); } break; @@ -954,14 +958,14 @@ export default class esql_parser extends Parser { let localctx: ValueExpressionContext = new ValueExpressionContext(this, this._ctx, this.state); this.enterRule(localctx, 14, esql_parser.RULE_valueExpression); try { - this.state = 213; + this.state = 215; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 12, this._ctx) ) { case 1: localctx = new ValueExpressionDefaultContext(this, localctx); this.enterOuterAlt(localctx, 1); { - this.state = 208; + this.state = 210; this.operatorExpression(0); } break; @@ -969,11 +973,11 @@ export default class esql_parser extends Parser { localctx = new ComparisonContext(this, localctx); this.enterOuterAlt(localctx, 2); { - this.state = 209; + this.state = 211; (localctx as ComparisonContext)._left = this.operatorExpression(0); - this.state = 210; + this.state = 212; this.comparisonOperator(); - this.state = 211; + this.state = 213; (localctx as ComparisonContext)._right = this.operatorExpression(0); } break; @@ -1013,7 +1017,7 @@ export default class esql_parser extends Parser { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 219; + this.state = 221; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 13, this._ctx) ) { case 1: @@ -1022,7 +1026,7 @@ export default class esql_parser extends Parser { this._ctx = localctx; _prevctx = localctx; - this.state = 216; + this.state = 218; this.primaryExpression(0); } break; @@ -1031,7 +1035,7 @@ export default class esql_parser extends Parser { localctx = new ArithmeticUnaryContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 217; + this.state = 219; (localctx as ArithmeticUnaryContext)._operator = this._input.LT(1); _la = this._input.LA(1); if(!(_la===63 || _la===64)) { @@ -1041,13 +1045,13 @@ export default class esql_parser extends Parser { this._errHandler.reportMatch(this); this.consume(); } - this.state = 218; + this.state = 220; this.operatorExpression(3); } break; } this._ctx.stop = this._input.LT(-1); - this.state = 229; + this.state = 231; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 15, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { @@ -1057,7 +1061,7 @@ export default class esql_parser extends Parser { } _prevctx = localctx; { - this.state = 227; + this.state = 229; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 14, this._ctx) ) { case 1: @@ -1065,11 +1069,11 @@ export default class esql_parser extends Parser { localctx = new ArithmeticBinaryContext(this, new OperatorExpressionContext(this, _parentctx, _parentState)); (localctx as ArithmeticBinaryContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, esql_parser.RULE_operatorExpression); - this.state = 221; + this.state = 223; if (!(this.precpred(this._ctx, 2))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 2)"); } - this.state = 222; + this.state = 224; (localctx as ArithmeticBinaryContext)._operator = this._input.LT(1); _la = this._input.LA(1); if(!(((((_la - 65)) & ~0x1F) === 0 && ((1 << (_la - 65)) & 7) !== 0))) { @@ -1079,7 +1083,7 @@ export default class esql_parser extends Parser { this._errHandler.reportMatch(this); this.consume(); } - this.state = 223; + this.state = 225; (localctx as ArithmeticBinaryContext)._right = this.operatorExpression(3); } break; @@ -1088,11 +1092,11 @@ export default class esql_parser extends Parser { localctx = new ArithmeticBinaryContext(this, new OperatorExpressionContext(this, _parentctx, _parentState)); (localctx as ArithmeticBinaryContext)._left = _prevctx; this.pushNewRecursionContext(localctx, _startState, esql_parser.RULE_operatorExpression); - this.state = 224; + this.state = 226; if (!(this.precpred(this._ctx, 1))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 1)"); } - this.state = 225; + this.state = 227; (localctx as ArithmeticBinaryContext)._operator = this._input.LT(1); _la = this._input.LA(1); if(!(_la===63 || _la===64)) { @@ -1102,14 +1106,14 @@ export default class esql_parser extends Parser { this._errHandler.reportMatch(this); this.consume(); } - this.state = 226; + this.state = 228; (localctx as ArithmeticBinaryContext)._right = this.operatorExpression(2); } break; } } } - this.state = 231; + this.state = 233; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 15, this._ctx); } @@ -1148,7 +1152,7 @@ export default class esql_parser extends Parser { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 240; + this.state = 242; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 16, this._ctx) ) { case 1: @@ -1157,7 +1161,7 @@ export default class esql_parser extends Parser { this._ctx = localctx; _prevctx = localctx; - this.state = 233; + this.state = 235; this.constant(); } break; @@ -1166,7 +1170,7 @@ export default class esql_parser extends Parser { localctx = new DereferenceContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 234; + this.state = 236; this.qualifiedName(); } break; @@ -1175,7 +1179,7 @@ export default class esql_parser extends Parser { localctx = new FunctionContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 235; + this.state = 237; this.functionExpression(); } break; @@ -1184,17 +1188,17 @@ export default class esql_parser extends Parser { localctx = new ParenthesizedExpressionContext(this, localctx); this._ctx = localctx; _prevctx = localctx; - this.state = 236; + this.state = 238; this.match(esql_parser.LP); - this.state = 237; + this.state = 239; this.booleanExpression(0); - this.state = 238; + this.state = 240; this.match(esql_parser.RP); } break; } this._ctx.stop = this._input.LT(-1); - this.state = 247; + this.state = 249; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 17, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { @@ -1207,18 +1211,18 @@ export default class esql_parser extends Parser { { localctx = new InlineCastContext(this, new PrimaryExpressionContext(this, _parentctx, _parentState)); this.pushNewRecursionContext(localctx, _startState, esql_parser.RULE_primaryExpression); - this.state = 242; + this.state = 244; if (!(this.precpred(this._ctx, 1))) { throw this.createFailedPredicateException("this.precpred(this._ctx, 1)"); } - this.state = 243; + this.state = 245; this.match(esql_parser.CAST_OP); - this.state = 244; + this.state = 246; this.dataType(); } } } - this.state = 249; + this.state = 251; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 17, this._ctx); } @@ -1246,16 +1250,16 @@ export default class esql_parser extends Parser { try { this.enterOuterAlt(localctx, 1); { - this.state = 250; + this.state = 252; this.identifier(); - this.state = 251; + this.state = 253; this.match(esql_parser.LP); - this.state = 261; + this.state = 263; this._errHandler.sync(this); switch (this._input.LA(1)) { case 65: { - this.state = 252; + this.state = 254; this.match(esql_parser.ASTERISK); } break; @@ -1271,25 +1275,26 @@ export default class esql_parser extends Parser { case 63: case 64: case 68: - case 70: + case 69: case 71: + case 72: { { - this.state = 253; + this.state = 255; this.booleanExpression(0); - this.state = 258; + this.state = 260; this._errHandler.sync(this); _la = this._input.LA(1); while (_la===38) { { { - this.state = 254; + this.state = 256; this.match(esql_parser.COMMA); - this.state = 255; + this.state = 257; this.booleanExpression(0); } } - this.state = 260; + this.state = 262; this._errHandler.sync(this); _la = this._input.LA(1); } @@ -1301,7 +1306,7 @@ export default class esql_parser extends Parser { default: break; } - this.state = 263; + this.state = 265; this.match(esql_parser.RP); } } @@ -1327,7 +1332,7 @@ export default class esql_parser extends Parser { localctx = new ToDataTypeContext(this, localctx); this.enterOuterAlt(localctx, 1); { - this.state = 265; + this.state = 267; this.identifier(); } } @@ -1352,9 +1357,9 @@ export default class esql_parser extends Parser { try { this.enterOuterAlt(localctx, 1); { - this.state = 267; + this.state = 269; this.match(esql_parser.ROW); - this.state = 268; + this.state = 270; this.fields(); } } @@ -1380,23 +1385,23 @@ export default class esql_parser extends Parser { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 270; + this.state = 272; this.field(); - this.state = 275; + this.state = 277; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 20, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 271; + this.state = 273; this.match(esql_parser.COMMA); - this.state = 272; + this.state = 274; this.field(); } } } - this.state = 277; + this.state = 279; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 20, this._ctx); } @@ -1421,24 +1426,24 @@ export default class esql_parser extends Parser { let localctx: FieldContext = new FieldContext(this, this._ctx, this.state); this.enterRule(localctx, 28, esql_parser.RULE_field); try { - this.state = 283; + this.state = 285; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 21, this._ctx) ) { case 1: this.enterOuterAlt(localctx, 1); { - this.state = 278; + this.state = 280; this.booleanExpression(0); } break; case 2: this.enterOuterAlt(localctx, 2); { - this.state = 279; + this.state = 281; this.qualifiedName(); - this.state = 280; + this.state = 282; this.match(esql_parser.ASSIGN); - this.state = 281; + this.state = 283; this.booleanExpression(0); } break; @@ -1466,34 +1471,34 @@ export default class esql_parser extends Parser { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 285; + this.state = 287; this.match(esql_parser.FROM); - this.state = 286; + this.state = 288; this.indexIdentifier(); - this.state = 291; + this.state = 293; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 22, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 287; + this.state = 289; this.match(esql_parser.COMMA); - this.state = 288; + this.state = 290; this.indexIdentifier(); } } } - this.state = 293; + this.state = 295; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 22, this._ctx); } - this.state = 295; + this.state = 297; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 23, this._ctx) ) { case 1: { - this.state = 294; + this.state = 296; this.metadata(); } break; @@ -1521,7 +1526,7 @@ export default class esql_parser extends Parser { try { this.enterOuterAlt(localctx, 1); { - this.state = 297; + this.state = 299; this.match(esql_parser.INDEX_UNQUOTED_IDENTIFIER); } } @@ -1544,20 +1549,20 @@ export default class esql_parser extends Parser { let localctx: MetadataContext = new MetadataContext(this, this._ctx, this.state); this.enterRule(localctx, 34, esql_parser.RULE_metadata); try { - this.state = 301; + this.state = 303; this._errHandler.sync(this); switch (this._input.LA(1)) { - case 75: + case 76: this.enterOuterAlt(localctx, 1); { - this.state = 299; + this.state = 301; this.metadataOption(); } break; - case 68: + case 69: this.enterOuterAlt(localctx, 2); { - this.state = 300; + this.state = 302; this.deprecated_metadata(); } break; @@ -1587,25 +1592,25 @@ export default class esql_parser extends Parser { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 303; + this.state = 305; this.match(esql_parser.METADATA); - this.state = 304; + this.state = 306; this.indexIdentifier(); - this.state = 309; + this.state = 311; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 25, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 305; + this.state = 307; this.match(esql_parser.COMMA); - this.state = 306; + this.state = 308; this.indexIdentifier(); } } } - this.state = 311; + this.state = 313; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 25, this._ctx); } @@ -1632,11 +1637,11 @@ export default class esql_parser extends Parser { try { this.enterOuterAlt(localctx, 1); { - this.state = 312; + this.state = 314; this.match(esql_parser.OPENING_BRACKET); - this.state = 313; + this.state = 315; this.metadataOption(); - this.state = 314; + this.state = 316; this.match(esql_parser.CLOSING_BRACKET); } } @@ -1662,46 +1667,46 @@ export default class esql_parser extends Parser { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 316; + this.state = 318; this.match(esql_parser.METRICS); - this.state = 317; + this.state = 319; this.indexIdentifier(); - this.state = 322; + this.state = 324; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 26, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 318; + this.state = 320; this.match(esql_parser.COMMA); - this.state = 319; + this.state = 321; this.indexIdentifier(); } } } - this.state = 324; + this.state = 326; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 26, this._ctx); } - this.state = 326; + this.state = 328; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 27, this._ctx) ) { case 1: { - this.state = 325; + this.state = 327; localctx._aggregates = this.fields(); } break; } - this.state = 330; + this.state = 332; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 28, this._ctx) ) { case 1: { - this.state = 328; + this.state = 330; this.match(esql_parser.BY); - this.state = 329; + this.state = 331; localctx._grouping = this.fields(); } break; @@ -1729,9 +1734,9 @@ export default class esql_parser extends Parser { try { this.enterOuterAlt(localctx, 1); { - this.state = 332; + this.state = 334; this.match(esql_parser.EVAL); - this.state = 333; + this.state = 335; this.fields(); } } @@ -1756,26 +1761,26 @@ export default class esql_parser extends Parser { try { this.enterOuterAlt(localctx, 1); { - this.state = 335; - this.match(esql_parser.STATS); this.state = 337; + this.match(esql_parser.STATS); + this.state = 339; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 29, this._ctx) ) { case 1: { - this.state = 336; + this.state = 338; localctx._stats = this.fields(); } break; } - this.state = 341; + this.state = 343; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 30, this._ctx) ) { case 1: { - this.state = 339; + this.state = 341; this.match(esql_parser.BY); - this.state = 340; + this.state = 342; localctx._grouping = this.fields(); } break; @@ -1803,18 +1808,18 @@ export default class esql_parser extends Parser { try { this.enterOuterAlt(localctx, 1); { - this.state = 343; + this.state = 345; this.match(esql_parser.INLINESTATS); - this.state = 344; + this.state = 346; localctx._stats = this.fields(); - this.state = 347; + this.state = 349; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 31, this._ctx) ) { case 1: { - this.state = 345; + this.state = 347; this.match(esql_parser.BY); - this.state = 346; + this.state = 348; localctx._grouping = this.fields(); } break; @@ -1843,23 +1848,23 @@ export default class esql_parser extends Parser { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 349; + this.state = 351; this.identifier(); - this.state = 354; + this.state = 356; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 32, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 350; + this.state = 352; this.match(esql_parser.DOT); - this.state = 351; + this.state = 353; this.identifier(); } } } - this.state = 356; + this.state = 358; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 32, this._ctx); } @@ -1887,23 +1892,23 @@ export default class esql_parser extends Parser { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 357; + this.state = 359; this.identifierPattern(); - this.state = 362; + this.state = 364; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 33, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 358; + this.state = 360; this.match(esql_parser.DOT); - this.state = 359; + this.state = 361; this.identifierPattern(); } } } - this.state = 364; + this.state = 366; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 33, this._ctx); } @@ -1931,23 +1936,23 @@ export default class esql_parser extends Parser { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 365; + this.state = 367; this.qualifiedNamePattern(); - this.state = 370; + this.state = 372; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 34, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 366; + this.state = 368; this.match(esql_parser.COMMA); - this.state = 367; + this.state = 369; this.qualifiedNamePattern(); } } } - this.state = 372; + this.state = 374; this._errHandler.sync(this); _alt = this._interp.adaptivePredict(this._input, 34, this._ctx); } @@ -1975,9 +1980,9 @@ export default class esql_parser extends Parser { try { this.enterOuterAlt(localctx, 1); { - this.state = 373; + this.state = 375; _la = this._input.LA(1); - if(!(_la===70 || _la===71)) { + if(!(_la===71 || _la===72)) { this._errHandler.recoverInline(this); } else { @@ -2007,7 +2012,7 @@ export default class esql_parser extends Parser { try { this.enterOuterAlt(localctx, 1); { - this.state = 375; + this.state = 377; this.match(esql_parser.ID_PATTERN); } } @@ -2031,14 +2036,14 @@ export default class esql_parser extends Parser { this.enterRule(localctx, 58, esql_parser.RULE_constant); let _la: number; try { - this.state = 419; + this.state = 421; this._errHandler.sync(this); switch ( this._interp.adaptivePredict(this._input, 38, this._ctx) ) { case 1: localctx = new NullLiteralContext(this, localctx); this.enterOuterAlt(localctx, 1); { - this.state = 377; + this.state = 379; this.match(esql_parser.NULL); } break; @@ -2046,9 +2051,9 @@ export default class esql_parser extends Parser { localctx = new QualifiedIntegerLiteralContext(this, localctx); this.enterOuterAlt(localctx, 2); { - this.state = 378; + this.state = 380; this.integerValue(); - this.state = 379; + this.state = 381; this.match(esql_parser.UNQUOTED_IDENTIFIER); } break; @@ -2056,7 +2061,7 @@ export default class esql_parser extends Parser { localctx = new DecimalLiteralContext(this, localctx); this.enterOuterAlt(localctx, 3); { - this.state = 381; + this.state = 383; this.decimalValue(); } break; @@ -2064,7 +2069,7 @@ export default class esql_parser extends Parser { localctx = new IntegerLiteralContext(this, localctx); this.enterOuterAlt(localctx, 4); { - this.state = 382; + this.state = 384; this.integerValue(); } break; @@ -2072,23 +2077,23 @@ export default class esql_parser extends Parser { localctx = new BooleanLiteralContext(this, localctx); this.enterOuterAlt(localctx, 5); { - this.state = 383; + this.state = 385; this.booleanValue(); } break; case 6: - localctx = new InputParamContext(this, localctx); + localctx = new InputParamsContext(this, localctx); this.enterOuterAlt(localctx, 6); { - this.state = 384; - this.match(esql_parser.PARAM); + this.state = 386; + this.params(); } break; case 7: localctx = new StringLiteralContext(this, localctx); this.enterOuterAlt(localctx, 7); { - this.state = 385; + this.state = 387; this.string_(); } break; @@ -2096,27 +2101,27 @@ export default class esql_parser extends Parser { localctx = new NumericArrayLiteralContext(this, localctx); this.enterOuterAlt(localctx, 8); { - this.state = 386; + this.state = 388; this.match(esql_parser.OPENING_BRACKET); - this.state = 387; + this.state = 389; this.numericValue(); - this.state = 392; + this.state = 394; this._errHandler.sync(this); _la = this._input.LA(1); while (_la===38) { { { - this.state = 388; + this.state = 390; this.match(esql_parser.COMMA); - this.state = 389; + this.state = 391; this.numericValue(); } } - this.state = 394; + this.state = 396; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 395; + this.state = 397; this.match(esql_parser.CLOSING_BRACKET); } break; @@ -2124,27 +2129,27 @@ export default class esql_parser extends Parser { localctx = new BooleanArrayLiteralContext(this, localctx); this.enterOuterAlt(localctx, 9); { - this.state = 397; + this.state = 399; this.match(esql_parser.OPENING_BRACKET); - this.state = 398; + this.state = 400; this.booleanValue(); - this.state = 403; + this.state = 405; this._errHandler.sync(this); _la = this._input.LA(1); while (_la===38) { { { - this.state = 399; + this.state = 401; this.match(esql_parser.COMMA); - this.state = 400; + this.state = 402; this.booleanValue(); } } - this.state = 405; + this.state = 407; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 406; + this.state = 408; this.match(esql_parser.CLOSING_BRACKET); } break; @@ -2152,27 +2157,27 @@ export default class esql_parser extends Parser { localctx = new StringArrayLiteralContext(this, localctx); this.enterOuterAlt(localctx, 10); { - this.state = 408; + this.state = 410; this.match(esql_parser.OPENING_BRACKET); - this.state = 409; + this.state = 411; this.string_(); - this.state = 414; + this.state = 416; this._errHandler.sync(this); _la = this._input.LA(1); while (_la===38) { { { - this.state = 410; + this.state = 412; this.match(esql_parser.COMMA); - this.state = 411; + this.state = 413; this.string_(); } } - this.state = 416; + this.state = 418; this._errHandler.sync(this); _la = this._input.LA(1); } - this.state = 417; + this.state = 419; this.match(esql_parser.CLOSING_BRACKET); } break; @@ -2193,15 +2198,57 @@ export default class esql_parser extends Parser { return localctx; } // @RuleVersion(0) + public params(): ParamsContext { + let localctx: ParamsContext = new ParamsContext(this, this._ctx, this.state); + this.enterRule(localctx, 60, esql_parser.RULE_params); + try { + this.state = 425; + this._errHandler.sync(this); + switch (this._input.LA(1)) { + case 52: + localctx = new InputParamContext(this, localctx); + this.enterOuterAlt(localctx, 1); + { + this.state = 423; + this.match(esql_parser.PARAM); + } + break; + case 68: + localctx = new InputNamedOrPositionalParamContext(this, localctx); + this.enterOuterAlt(localctx, 2); + { + this.state = 424; + this.match(esql_parser.NAMED_OR_POSITIONAL_PARAM); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (re) { + if (re instanceof RecognitionException) { + localctx.exception = re; + this._errHandler.reportError(this, re); + this._errHandler.recover(this, re); + } else { + throw re; + } + } + finally { + this.exitRule(); + } + return localctx; + } + // @RuleVersion(0) public limitCommand(): LimitCommandContext { let localctx: LimitCommandContext = new LimitCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 60, esql_parser.RULE_limitCommand); + this.enterRule(localctx, 62, esql_parser.RULE_limitCommand); try { this.enterOuterAlt(localctx, 1); { - this.state = 421; + this.state = 427; this.match(esql_parser.LIMIT); - this.state = 422; + this.state = 428; this.match(esql_parser.INTEGER_LITERAL); } } @@ -2222,32 +2269,32 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public sortCommand(): SortCommandContext { let localctx: SortCommandContext = new SortCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 62, esql_parser.RULE_sortCommand); + this.enterRule(localctx, 64, esql_parser.RULE_sortCommand); try { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 424; + this.state = 430; this.match(esql_parser.SORT); - this.state = 425; + this.state = 431; this.orderExpression(); - this.state = 430; + this.state = 436; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 39, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 40, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 426; + this.state = 432; this.match(esql_parser.COMMA); - this.state = 427; + this.state = 433; this.orderExpression(); } } } - this.state = 432; + this.state = 438; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 39, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 40, this._ctx); } } } @@ -2268,19 +2315,19 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public orderExpression(): OrderExpressionContext { let localctx: OrderExpressionContext = new OrderExpressionContext(this, this._ctx, this.state); - this.enterRule(localctx, 64, esql_parser.RULE_orderExpression); + this.enterRule(localctx, 66, esql_parser.RULE_orderExpression); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 433; + this.state = 439; this.booleanExpression(0); - this.state = 435; + this.state = 441; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 40, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 41, this._ctx) ) { case 1: { - this.state = 434; + this.state = 440; localctx._ordering = this._input.LT(1); _la = this._input.LA(1); if(!(_la===35 || _la===39)) { @@ -2293,14 +2340,14 @@ export default class esql_parser extends Parser { } break; } - this.state = 439; + this.state = 445; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 41, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 42, this._ctx) ) { case 1: { - this.state = 437; + this.state = 443; this.match(esql_parser.NULLS); - this.state = 438; + this.state = 444; localctx._nullOrdering = this._input.LT(1); _la = this._input.LA(1); if(!(_la===42 || _la===43)) { @@ -2332,13 +2379,13 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public keepCommand(): KeepCommandContext { let localctx: KeepCommandContext = new KeepCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 66, esql_parser.RULE_keepCommand); + this.enterRule(localctx, 68, esql_parser.RULE_keepCommand); try { this.enterOuterAlt(localctx, 1); { - this.state = 441; + this.state = 447; this.match(esql_parser.KEEP); - this.state = 442; + this.state = 448; this.qualifiedNamePatterns(); } } @@ -2359,13 +2406,13 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public dropCommand(): DropCommandContext { let localctx: DropCommandContext = new DropCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 68, esql_parser.RULE_dropCommand); + this.enterRule(localctx, 70, esql_parser.RULE_dropCommand); try { this.enterOuterAlt(localctx, 1); { - this.state = 444; + this.state = 450; this.match(esql_parser.DROP); - this.state = 445; + this.state = 451; this.qualifiedNamePatterns(); } } @@ -2386,32 +2433,32 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public renameCommand(): RenameCommandContext { let localctx: RenameCommandContext = new RenameCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 70, esql_parser.RULE_renameCommand); + this.enterRule(localctx, 72, esql_parser.RULE_renameCommand); try { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 447; + this.state = 453; this.match(esql_parser.RENAME); - this.state = 448; + this.state = 454; this.renameClause(); - this.state = 453; + this.state = 459; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 42, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 43, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 449; + this.state = 455; this.match(esql_parser.COMMA); - this.state = 450; + this.state = 456; this.renameClause(); } } } - this.state = 455; + this.state = 461; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 42, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 43, this._ctx); } } } @@ -2432,15 +2479,15 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public renameClause(): RenameClauseContext { let localctx: RenameClauseContext = new RenameClauseContext(this, this._ctx, this.state); - this.enterRule(localctx, 72, esql_parser.RULE_renameClause); + this.enterRule(localctx, 74, esql_parser.RULE_renameClause); try { this.enterOuterAlt(localctx, 1); { - this.state = 456; + this.state = 462; localctx._oldName = this.qualifiedNamePattern(); - this.state = 457; + this.state = 463; this.match(esql_parser.AS); - this.state = 458; + this.state = 464; localctx._newName = this.qualifiedNamePattern(); } } @@ -2461,22 +2508,22 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public dissectCommand(): DissectCommandContext { let localctx: DissectCommandContext = new DissectCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 74, esql_parser.RULE_dissectCommand); + this.enterRule(localctx, 76, esql_parser.RULE_dissectCommand); try { this.enterOuterAlt(localctx, 1); { - this.state = 460; + this.state = 466; this.match(esql_parser.DISSECT); - this.state = 461; + this.state = 467; this.primaryExpression(0); - this.state = 462; + this.state = 468; this.string_(); - this.state = 464; + this.state = 470; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 43, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 44, this._ctx) ) { case 1: { - this.state = 463; + this.state = 469; this.commandOptions(); } break; @@ -2500,15 +2547,15 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public grokCommand(): GrokCommandContext { let localctx: GrokCommandContext = new GrokCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 76, esql_parser.RULE_grokCommand); + this.enterRule(localctx, 78, esql_parser.RULE_grokCommand); try { this.enterOuterAlt(localctx, 1); { - this.state = 466; + this.state = 472; this.match(esql_parser.GROK); - this.state = 467; + this.state = 473; this.primaryExpression(0); - this.state = 468; + this.state = 474; this.string_(); } } @@ -2529,13 +2576,13 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public mvExpandCommand(): MvExpandCommandContext { let localctx: MvExpandCommandContext = new MvExpandCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 78, esql_parser.RULE_mvExpandCommand); + this.enterRule(localctx, 80, esql_parser.RULE_mvExpandCommand); try { this.enterOuterAlt(localctx, 1); { - this.state = 470; + this.state = 476; this.match(esql_parser.MV_EXPAND); - this.state = 471; + this.state = 477; this.qualifiedName(); } } @@ -2556,30 +2603,30 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public commandOptions(): CommandOptionsContext { let localctx: CommandOptionsContext = new CommandOptionsContext(this, this._ctx, this.state); - this.enterRule(localctx, 80, esql_parser.RULE_commandOptions); + this.enterRule(localctx, 82, esql_parser.RULE_commandOptions); try { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 473; + this.state = 479; this.commandOption(); - this.state = 478; + this.state = 484; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 44, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 45, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 474; + this.state = 480; this.match(esql_parser.COMMA); - this.state = 475; + this.state = 481; this.commandOption(); } } } - this.state = 480; + this.state = 486; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 44, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 45, this._ctx); } } } @@ -2600,15 +2647,15 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public commandOption(): CommandOptionContext { let localctx: CommandOptionContext = new CommandOptionContext(this, this._ctx, this.state); - this.enterRule(localctx, 82, esql_parser.RULE_commandOption); + this.enterRule(localctx, 84, esql_parser.RULE_commandOption); try { this.enterOuterAlt(localctx, 1); { - this.state = 481; + this.state = 487; this.identifier(); - this.state = 482; + this.state = 488; this.match(esql_parser.ASSIGN); - this.state = 483; + this.state = 489; this.constant(); } } @@ -2629,12 +2676,12 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public booleanValue(): BooleanValueContext { let localctx: BooleanValueContext = new BooleanValueContext(this, this._ctx, this.state); - this.enterRule(localctx, 84, esql_parser.RULE_booleanValue); + this.enterRule(localctx, 86, esql_parser.RULE_booleanValue); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 485; + this.state = 491; _la = this._input.LA(1); if(!(_la===41 || _la===55)) { this._errHandler.recoverInline(this); @@ -2662,22 +2709,22 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public numericValue(): NumericValueContext { let localctx: NumericValueContext = new NumericValueContext(this, this._ctx, this.state); - this.enterRule(localctx, 86, esql_parser.RULE_numericValue); + this.enterRule(localctx, 88, esql_parser.RULE_numericValue); try { - this.state = 489; + this.state = 495; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 45, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 46, this._ctx) ) { case 1: this.enterOuterAlt(localctx, 1); { - this.state = 487; + this.state = 493; this.decimalValue(); } break; case 2: this.enterOuterAlt(localctx, 2); { - this.state = 488; + this.state = 494; this.integerValue(); } break; @@ -2700,17 +2747,17 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public decimalValue(): DecimalValueContext { let localctx: DecimalValueContext = new DecimalValueContext(this, this._ctx, this.state); - this.enterRule(localctx, 88, esql_parser.RULE_decimalValue); + this.enterRule(localctx, 90, esql_parser.RULE_decimalValue); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 492; + this.state = 498; this._errHandler.sync(this); _la = this._input.LA(1); if (_la===63 || _la===64) { { - this.state = 491; + this.state = 497; _la = this._input.LA(1); if(!(_la===63 || _la===64)) { this._errHandler.recoverInline(this); @@ -2722,7 +2769,7 @@ export default class esql_parser extends Parser { } } - this.state = 494; + this.state = 500; this.match(esql_parser.DECIMAL_LITERAL); } } @@ -2743,17 +2790,17 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public integerValue(): IntegerValueContext { let localctx: IntegerValueContext = new IntegerValueContext(this, this._ctx, this.state); - this.enterRule(localctx, 90, esql_parser.RULE_integerValue); + this.enterRule(localctx, 92, esql_parser.RULE_integerValue); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 497; + this.state = 503; this._errHandler.sync(this); _la = this._input.LA(1); if (_la===63 || _la===64) { { - this.state = 496; + this.state = 502; _la = this._input.LA(1); if(!(_la===63 || _la===64)) { this._errHandler.recoverInline(this); @@ -2765,7 +2812,7 @@ export default class esql_parser extends Parser { } } - this.state = 499; + this.state = 505; this.match(esql_parser.INTEGER_LITERAL); } } @@ -2786,11 +2833,11 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public string_(): StringContext { let localctx: StringContext = new StringContext(this, this._ctx, this.state); - this.enterRule(localctx, 92, esql_parser.RULE_string); + this.enterRule(localctx, 94, esql_parser.RULE_string); try { this.enterOuterAlt(localctx, 1); { - this.state = 501; + this.state = 507; this.match(esql_parser.QUOTED_STRING); } } @@ -2811,12 +2858,12 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public comparisonOperator(): ComparisonOperatorContext { let localctx: ComparisonOperatorContext = new ComparisonOperatorContext(this, this._ctx, this.state); - this.enterRule(localctx, 94, esql_parser.RULE_comparisonOperator); + this.enterRule(localctx, 96, esql_parser.RULE_comparisonOperator); let _la: number; try { this.enterOuterAlt(localctx, 1); { - this.state = 503; + this.state = 509; _la = this._input.LA(1); if(!(((((_la - 56)) & ~0x1F) === 0 && ((1 << (_la - 56)) & 125) !== 0))) { this._errHandler.recoverInline(this); @@ -2844,13 +2891,13 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public explainCommand(): ExplainCommandContext { let localctx: ExplainCommandContext = new ExplainCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 96, esql_parser.RULE_explainCommand); + this.enterRule(localctx, 98, esql_parser.RULE_explainCommand); try { this.enterOuterAlt(localctx, 1); { - this.state = 505; + this.state = 511; this.match(esql_parser.EXPLAIN); - this.state = 506; + this.state = 512; this.subqueryExpression(); } } @@ -2871,15 +2918,15 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public subqueryExpression(): SubqueryExpressionContext { let localctx: SubqueryExpressionContext = new SubqueryExpressionContext(this, this._ctx, this.state); - this.enterRule(localctx, 98, esql_parser.RULE_subqueryExpression); + this.enterRule(localctx, 100, esql_parser.RULE_subqueryExpression); try { this.enterOuterAlt(localctx, 1); { - this.state = 508; + this.state = 514; this.match(esql_parser.OPENING_BRACKET); - this.state = 509; + this.state = 515; this.query(0); - this.state = 510; + this.state = 516; this.match(esql_parser.CLOSING_BRACKET); } } @@ -2900,14 +2947,14 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public showCommand(): ShowCommandContext { let localctx: ShowCommandContext = new ShowCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 100, esql_parser.RULE_showCommand); + this.enterRule(localctx, 102, esql_parser.RULE_showCommand); try { localctx = new ShowInfoContext(this, localctx); this.enterOuterAlt(localctx, 1); { - this.state = 512; + this.state = 518; this.match(esql_parser.SHOW); - this.state = 513; + this.state = 519; this.match(esql_parser.INFO); } } @@ -2928,14 +2975,14 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public metaCommand(): MetaCommandContext { let localctx: MetaCommandContext = new MetaCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 102, esql_parser.RULE_metaCommand); + this.enterRule(localctx, 104, esql_parser.RULE_metaCommand); try { localctx = new MetaFunctionsContext(this, localctx); this.enterOuterAlt(localctx, 1); { - this.state = 515; + this.state = 521; this.match(esql_parser.META); - this.state = 516; + this.state = 522; this.match(esql_parser.FUNCTIONS); } } @@ -2956,53 +3003,53 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public enrichCommand(): EnrichCommandContext { let localctx: EnrichCommandContext = new EnrichCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 104, esql_parser.RULE_enrichCommand); + this.enterRule(localctx, 106, esql_parser.RULE_enrichCommand); try { let _alt: number; this.enterOuterAlt(localctx, 1); { - this.state = 518; + this.state = 524; this.match(esql_parser.ENRICH); - this.state = 519; + this.state = 525; localctx._policyName = this.match(esql_parser.ENRICH_POLICY_NAME); - this.state = 522; + this.state = 528; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 48, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 49, this._ctx) ) { case 1: { - this.state = 520; + this.state = 526; this.match(esql_parser.ON); - this.state = 521; + this.state = 527; localctx._matchField = this.qualifiedNamePattern(); } break; } - this.state = 533; + this.state = 539; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 50, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 51, this._ctx) ) { case 1: { - this.state = 524; + this.state = 530; this.match(esql_parser.WITH); - this.state = 525; + this.state = 531; this.enrichWithClause(); - this.state = 530; + this.state = 536; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 49, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 50, this._ctx); while (_alt !== 2 && _alt !== ATN.INVALID_ALT_NUMBER) { if (_alt === 1) { { { - this.state = 526; + this.state = 532; this.match(esql_parser.COMMA); - this.state = 527; + this.state = 533; this.enrichWithClause(); } } } - this.state = 532; + this.state = 538; this._errHandler.sync(this); - _alt = this._interp.adaptivePredict(this._input, 49, this._ctx); + _alt = this._interp.adaptivePredict(this._input, 50, this._ctx); } } break; @@ -3026,23 +3073,23 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public enrichWithClause(): EnrichWithClauseContext { let localctx: EnrichWithClauseContext = new EnrichWithClauseContext(this, this._ctx, this.state); - this.enterRule(localctx, 106, esql_parser.RULE_enrichWithClause); + this.enterRule(localctx, 108, esql_parser.RULE_enrichWithClause); try { this.enterOuterAlt(localctx, 1); { - this.state = 538; + this.state = 544; this._errHandler.sync(this); - switch ( this._interp.adaptivePredict(this._input, 51, this._ctx) ) { + switch ( this._interp.adaptivePredict(this._input, 52, this._ctx) ) { case 1: { - this.state = 535; + this.state = 541; localctx._newName = this.qualifiedNamePattern(); - this.state = 536; + this.state = 542; this.match(esql_parser.ASSIGN); } break; } - this.state = 540; + this.state = 546; localctx._enrichField = this.qualifiedNamePattern(); } } @@ -3063,17 +3110,17 @@ export default class esql_parser extends Parser { // @RuleVersion(0) public lookupCommand(): LookupCommandContext { let localctx: LookupCommandContext = new LookupCommandContext(this, this._ctx, this.state); - this.enterRule(localctx, 108, esql_parser.RULE_lookupCommand); + this.enterRule(localctx, 110, esql_parser.RULE_lookupCommand); try { this.enterOuterAlt(localctx, 1); { - this.state = 542; + this.state = 548; this.match(esql_parser.LOOKUP); - this.state = 543; + this.state = 549; localctx._tableName = this.match(esql_parser.INDEX_UNQUOTED_IDENTIFIER); - this.state = 544; + this.state = 550; this.match(esql_parser.ON); - this.state = 545; + this.state = 551; localctx._matchFields = this.qualifiedNamePatterns(); } } @@ -3138,7 +3185,7 @@ export default class esql_parser extends Parser { return true; } - public static readonly _serializedATN: number[] = [4,1,123,548,2,0,7,0, + public static readonly _serializedATN: number[] = [4,1,124,554,2,0,7,0, 2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9, 2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,2, 17,7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24, @@ -3146,177 +3193,179 @@ export default class esql_parser extends Parser { 31,2,32,7,32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,2,37,7,37,2,38,7,38, 2,39,7,39,2,40,7,40,2,41,7,41,2,42,7,42,2,43,7,43,2,44,7,44,2,45,7,45,2, 46,7,46,2,47,7,47,2,48,7,48,2,49,7,49,2,50,7,50,2,51,7,51,2,52,7,52,2,53, - 7,53,2,54,7,54,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,5,1,120,8,1,10,1,12, - 1,123,9,1,1,2,1,2,1,2,1,2,1,2,1,2,3,2,131,8,2,1,3,1,3,1,3,1,3,1,3,1,3,1, - 3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,3,3,147,8,3,1,4,1,4,1,4,1,5,1,5,1,5,1,5,1, - 5,1,5,1,5,3,5,159,8,5,1,5,1,5,1,5,1,5,1,5,5,5,166,8,5,10,5,12,5,169,9,5, - 1,5,1,5,1,5,1,5,1,5,3,5,176,8,5,1,5,1,5,3,5,180,8,5,1,5,1,5,1,5,1,5,1,5, - 1,5,5,5,188,8,5,10,5,12,5,191,9,5,1,6,1,6,3,6,195,8,6,1,6,1,6,1,6,1,6,1, - 6,3,6,202,8,6,1,6,1,6,1,6,3,6,207,8,6,1,7,1,7,1,7,1,7,1,7,3,7,214,8,7,1, - 8,1,8,1,8,1,8,3,8,220,8,8,1,8,1,8,1,8,1,8,1,8,1,8,5,8,228,8,8,10,8,12,8, - 231,9,8,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,3,9,241,8,9,1,9,1,9,1,9,5,9,246, - 8,9,10,9,12,9,249,9,9,1,10,1,10,1,10,1,10,1,10,1,10,5,10,257,8,10,10,10, - 12,10,260,9,10,3,10,262,8,10,1,10,1,10,1,11,1,11,1,12,1,12,1,12,1,13,1, - 13,1,13,5,13,274,8,13,10,13,12,13,277,9,13,1,14,1,14,1,14,1,14,1,14,3,14, - 284,8,14,1,15,1,15,1,15,1,15,5,15,290,8,15,10,15,12,15,293,9,15,1,15,3, - 15,296,8,15,1,16,1,16,1,17,1,17,3,17,302,8,17,1,18,1,18,1,18,1,18,5,18, - 308,8,18,10,18,12,18,311,9,18,1,19,1,19,1,19,1,19,1,20,1,20,1,20,1,20,5, - 20,321,8,20,10,20,12,20,324,9,20,1,20,3,20,327,8,20,1,20,1,20,3,20,331, - 8,20,1,21,1,21,1,21,1,22,1,22,3,22,338,8,22,1,22,1,22,3,22,342,8,22,1,23, - 1,23,1,23,1,23,3,23,348,8,23,1,24,1,24,1,24,5,24,353,8,24,10,24,12,24,356, - 9,24,1,25,1,25,1,25,5,25,361,8,25,10,25,12,25,364,9,25,1,26,1,26,1,26,5, - 26,369,8,26,10,26,12,26,372,9,26,1,27,1,27,1,28,1,28,1,29,1,29,1,29,1,29, - 1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,5,29,391,8,29,10,29,12,29, - 394,9,29,1,29,1,29,1,29,1,29,1,29,1,29,5,29,402,8,29,10,29,12,29,405,9, - 29,1,29,1,29,1,29,1,29,1,29,1,29,5,29,413,8,29,10,29,12,29,416,9,29,1,29, - 1,29,3,29,420,8,29,1,30,1,30,1,30,1,31,1,31,1,31,1,31,5,31,429,8,31,10, - 31,12,31,432,9,31,1,32,1,32,3,32,436,8,32,1,32,1,32,3,32,440,8,32,1,33, - 1,33,1,33,1,34,1,34,1,34,1,35,1,35,1,35,1,35,5,35,452,8,35,10,35,12,35, - 455,9,35,1,36,1,36,1,36,1,36,1,37,1,37,1,37,1,37,3,37,465,8,37,1,38,1,38, - 1,38,1,38,1,39,1,39,1,39,1,40,1,40,1,40,5,40,477,8,40,10,40,12,40,480,9, - 40,1,41,1,41,1,41,1,41,1,42,1,42,1,43,1,43,3,43,490,8,43,1,44,3,44,493, - 8,44,1,44,1,44,1,45,3,45,498,8,45,1,45,1,45,1,46,1,46,1,47,1,47,1,48,1, - 48,1,48,1,49,1,49,1,49,1,49,1,50,1,50,1,50,1,51,1,51,1,51,1,52,1,52,1,52, - 1,52,3,52,523,8,52,1,52,1,52,1,52,1,52,5,52,529,8,52,10,52,12,52,532,9, - 52,3,52,534,8,52,1,53,1,53,1,53,3,53,539,8,53,1,53,1,53,1,54,1,54,1,54, - 1,54,1,54,1,54,0,4,2,10,16,18,55,0,2,4,6,8,10,12,14,16,18,20,22,24,26,28, - 30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76, - 78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,0,7,1,0,63,64,1,0, - 65,67,1,0,70,71,2,0,35,35,39,39,1,0,42,43,2,0,41,41,55,55,2,0,56,56,58, - 62,574,0,110,1,0,0,0,2,113,1,0,0,0,4,130,1,0,0,0,6,146,1,0,0,0,8,148,1, - 0,0,0,10,179,1,0,0,0,12,206,1,0,0,0,14,213,1,0,0,0,16,219,1,0,0,0,18,240, - 1,0,0,0,20,250,1,0,0,0,22,265,1,0,0,0,24,267,1,0,0,0,26,270,1,0,0,0,28, - 283,1,0,0,0,30,285,1,0,0,0,32,297,1,0,0,0,34,301,1,0,0,0,36,303,1,0,0,0, - 38,312,1,0,0,0,40,316,1,0,0,0,42,332,1,0,0,0,44,335,1,0,0,0,46,343,1,0, - 0,0,48,349,1,0,0,0,50,357,1,0,0,0,52,365,1,0,0,0,54,373,1,0,0,0,56,375, - 1,0,0,0,58,419,1,0,0,0,60,421,1,0,0,0,62,424,1,0,0,0,64,433,1,0,0,0,66, - 441,1,0,0,0,68,444,1,0,0,0,70,447,1,0,0,0,72,456,1,0,0,0,74,460,1,0,0,0, - 76,466,1,0,0,0,78,470,1,0,0,0,80,473,1,0,0,0,82,481,1,0,0,0,84,485,1,0, - 0,0,86,489,1,0,0,0,88,492,1,0,0,0,90,497,1,0,0,0,92,501,1,0,0,0,94,503, - 1,0,0,0,96,505,1,0,0,0,98,508,1,0,0,0,100,512,1,0,0,0,102,515,1,0,0,0,104, - 518,1,0,0,0,106,538,1,0,0,0,108,542,1,0,0,0,110,111,3,2,1,0,111,112,5,0, - 0,1,112,1,1,0,0,0,113,114,6,1,-1,0,114,115,3,4,2,0,115,121,1,0,0,0,116, - 117,10,1,0,0,117,118,5,29,0,0,118,120,3,6,3,0,119,116,1,0,0,0,120,123,1, - 0,0,0,121,119,1,0,0,0,121,122,1,0,0,0,122,3,1,0,0,0,123,121,1,0,0,0,124, - 131,3,96,48,0,125,131,3,30,15,0,126,131,3,24,12,0,127,131,3,40,20,0,128, - 131,3,100,50,0,129,131,3,102,51,0,130,124,1,0,0,0,130,125,1,0,0,0,130,126, - 1,0,0,0,130,127,1,0,0,0,130,128,1,0,0,0,130,129,1,0,0,0,131,5,1,0,0,0,132, - 147,3,42,21,0,133,147,3,46,23,0,134,147,3,60,30,0,135,147,3,108,54,0,136, - 147,3,66,33,0,137,147,3,62,31,0,138,147,3,44,22,0,139,147,3,8,4,0,140,147, - 3,68,34,0,141,147,3,70,35,0,142,147,3,74,37,0,143,147,3,76,38,0,144,147, - 3,104,52,0,145,147,3,78,39,0,146,132,1,0,0,0,146,133,1,0,0,0,146,134,1, - 0,0,0,146,135,1,0,0,0,146,136,1,0,0,0,146,137,1,0,0,0,146,138,1,0,0,0,146, - 139,1,0,0,0,146,140,1,0,0,0,146,141,1,0,0,0,146,142,1,0,0,0,146,143,1,0, - 0,0,146,144,1,0,0,0,146,145,1,0,0,0,147,7,1,0,0,0,148,149,5,20,0,0,149, - 150,3,10,5,0,150,9,1,0,0,0,151,152,6,5,-1,0,152,153,5,48,0,0,153,180,3, - 10,5,7,154,180,3,14,7,0,155,180,3,12,6,0,156,158,3,14,7,0,157,159,5,48, - 0,0,158,157,1,0,0,0,158,159,1,0,0,0,159,160,1,0,0,0,160,161,5,45,0,0,161, - 162,5,44,0,0,162,167,3,14,7,0,163,164,5,38,0,0,164,166,3,14,7,0,165,163, - 1,0,0,0,166,169,1,0,0,0,167,165,1,0,0,0,167,168,1,0,0,0,168,170,1,0,0,0, - 169,167,1,0,0,0,170,171,5,54,0,0,171,180,1,0,0,0,172,173,3,14,7,0,173,175, - 5,46,0,0,174,176,5,48,0,0,175,174,1,0,0,0,175,176,1,0,0,0,176,177,1,0,0, - 0,177,178,5,49,0,0,178,180,1,0,0,0,179,151,1,0,0,0,179,154,1,0,0,0,179, - 155,1,0,0,0,179,156,1,0,0,0,179,172,1,0,0,0,180,189,1,0,0,0,181,182,10, - 4,0,0,182,183,5,34,0,0,183,188,3,10,5,5,184,185,10,3,0,0,185,186,5,51,0, - 0,186,188,3,10,5,4,187,181,1,0,0,0,187,184,1,0,0,0,188,191,1,0,0,0,189, - 187,1,0,0,0,189,190,1,0,0,0,190,11,1,0,0,0,191,189,1,0,0,0,192,194,3,14, - 7,0,193,195,5,48,0,0,194,193,1,0,0,0,194,195,1,0,0,0,195,196,1,0,0,0,196, - 197,5,47,0,0,197,198,3,92,46,0,198,207,1,0,0,0,199,201,3,14,7,0,200,202, - 5,48,0,0,201,200,1,0,0,0,201,202,1,0,0,0,202,203,1,0,0,0,203,204,5,53,0, - 0,204,205,3,92,46,0,205,207,1,0,0,0,206,192,1,0,0,0,206,199,1,0,0,0,207, - 13,1,0,0,0,208,214,3,16,8,0,209,210,3,16,8,0,210,211,3,94,47,0,211,212, - 3,16,8,0,212,214,1,0,0,0,213,208,1,0,0,0,213,209,1,0,0,0,214,15,1,0,0,0, - 215,216,6,8,-1,0,216,220,3,18,9,0,217,218,7,0,0,0,218,220,3,16,8,3,219, - 215,1,0,0,0,219,217,1,0,0,0,220,229,1,0,0,0,221,222,10,2,0,0,222,223,7, - 1,0,0,223,228,3,16,8,3,224,225,10,1,0,0,225,226,7,0,0,0,226,228,3,16,8, - 2,227,221,1,0,0,0,227,224,1,0,0,0,228,231,1,0,0,0,229,227,1,0,0,0,229,230, - 1,0,0,0,230,17,1,0,0,0,231,229,1,0,0,0,232,233,6,9,-1,0,233,241,3,58,29, - 0,234,241,3,48,24,0,235,241,3,20,10,0,236,237,5,44,0,0,237,238,3,10,5,0, - 238,239,5,54,0,0,239,241,1,0,0,0,240,232,1,0,0,0,240,234,1,0,0,0,240,235, - 1,0,0,0,240,236,1,0,0,0,241,247,1,0,0,0,242,243,10,1,0,0,243,244,5,37,0, - 0,244,246,3,22,11,0,245,242,1,0,0,0,246,249,1,0,0,0,247,245,1,0,0,0,247, - 248,1,0,0,0,248,19,1,0,0,0,249,247,1,0,0,0,250,251,3,54,27,0,251,261,5, - 44,0,0,252,262,5,65,0,0,253,258,3,10,5,0,254,255,5,38,0,0,255,257,3,10, - 5,0,256,254,1,0,0,0,257,260,1,0,0,0,258,256,1,0,0,0,258,259,1,0,0,0,259, - 262,1,0,0,0,260,258,1,0,0,0,261,252,1,0,0,0,261,253,1,0,0,0,261,262,1,0, - 0,0,262,263,1,0,0,0,263,264,5,54,0,0,264,21,1,0,0,0,265,266,3,54,27,0,266, - 23,1,0,0,0,267,268,5,16,0,0,268,269,3,26,13,0,269,25,1,0,0,0,270,275,3, - 28,14,0,271,272,5,38,0,0,272,274,3,28,14,0,273,271,1,0,0,0,274,277,1,0, - 0,0,275,273,1,0,0,0,275,276,1,0,0,0,276,27,1,0,0,0,277,275,1,0,0,0,278, - 284,3,10,5,0,279,280,3,48,24,0,280,281,5,36,0,0,281,282,3,10,5,0,282,284, - 1,0,0,0,283,278,1,0,0,0,283,279,1,0,0,0,284,29,1,0,0,0,285,286,5,6,0,0, - 286,291,3,32,16,0,287,288,5,38,0,0,288,290,3,32,16,0,289,287,1,0,0,0,290, - 293,1,0,0,0,291,289,1,0,0,0,291,292,1,0,0,0,292,295,1,0,0,0,293,291,1,0, - 0,0,294,296,3,34,17,0,295,294,1,0,0,0,295,296,1,0,0,0,296,31,1,0,0,0,297, - 298,5,25,0,0,298,33,1,0,0,0,299,302,3,36,18,0,300,302,3,38,19,0,301,299, - 1,0,0,0,301,300,1,0,0,0,302,35,1,0,0,0,303,304,5,75,0,0,304,309,3,32,16, - 0,305,306,5,38,0,0,306,308,3,32,16,0,307,305,1,0,0,0,308,311,1,0,0,0,309, - 307,1,0,0,0,309,310,1,0,0,0,310,37,1,0,0,0,311,309,1,0,0,0,312,313,5,68, - 0,0,313,314,3,36,18,0,314,315,5,69,0,0,315,39,1,0,0,0,316,317,5,13,0,0, - 317,322,3,32,16,0,318,319,5,38,0,0,319,321,3,32,16,0,320,318,1,0,0,0,321, - 324,1,0,0,0,322,320,1,0,0,0,322,323,1,0,0,0,323,326,1,0,0,0,324,322,1,0, - 0,0,325,327,3,26,13,0,326,325,1,0,0,0,326,327,1,0,0,0,327,330,1,0,0,0,328, - 329,5,33,0,0,329,331,3,26,13,0,330,328,1,0,0,0,330,331,1,0,0,0,331,41,1, - 0,0,0,332,333,5,4,0,0,333,334,3,26,13,0,334,43,1,0,0,0,335,337,5,19,0,0, - 336,338,3,26,13,0,337,336,1,0,0,0,337,338,1,0,0,0,338,341,1,0,0,0,339,340, - 5,33,0,0,340,342,3,26,13,0,341,339,1,0,0,0,341,342,1,0,0,0,342,45,1,0,0, - 0,343,344,5,8,0,0,344,347,3,26,13,0,345,346,5,33,0,0,346,348,3,26,13,0, - 347,345,1,0,0,0,347,348,1,0,0,0,348,47,1,0,0,0,349,354,3,54,27,0,350,351, - 5,40,0,0,351,353,3,54,27,0,352,350,1,0,0,0,353,356,1,0,0,0,354,352,1,0, - 0,0,354,355,1,0,0,0,355,49,1,0,0,0,356,354,1,0,0,0,357,362,3,56,28,0,358, - 359,5,40,0,0,359,361,3,56,28,0,360,358,1,0,0,0,361,364,1,0,0,0,362,360, - 1,0,0,0,362,363,1,0,0,0,363,51,1,0,0,0,364,362,1,0,0,0,365,370,3,50,25, - 0,366,367,5,38,0,0,367,369,3,50,25,0,368,366,1,0,0,0,369,372,1,0,0,0,370, - 368,1,0,0,0,370,371,1,0,0,0,371,53,1,0,0,0,372,370,1,0,0,0,373,374,7,2, - 0,0,374,55,1,0,0,0,375,376,5,79,0,0,376,57,1,0,0,0,377,420,5,49,0,0,378, - 379,3,90,45,0,379,380,5,70,0,0,380,420,1,0,0,0,381,420,3,88,44,0,382,420, - 3,90,45,0,383,420,3,84,42,0,384,420,5,52,0,0,385,420,3,92,46,0,386,387, - 5,68,0,0,387,392,3,86,43,0,388,389,5,38,0,0,389,391,3,86,43,0,390,388,1, - 0,0,0,391,394,1,0,0,0,392,390,1,0,0,0,392,393,1,0,0,0,393,395,1,0,0,0,394, - 392,1,0,0,0,395,396,5,69,0,0,396,420,1,0,0,0,397,398,5,68,0,0,398,403,3, - 84,42,0,399,400,5,38,0,0,400,402,3,84,42,0,401,399,1,0,0,0,402,405,1,0, - 0,0,403,401,1,0,0,0,403,404,1,0,0,0,404,406,1,0,0,0,405,403,1,0,0,0,406, - 407,5,69,0,0,407,420,1,0,0,0,408,409,5,68,0,0,409,414,3,92,46,0,410,411, - 5,38,0,0,411,413,3,92,46,0,412,410,1,0,0,0,413,416,1,0,0,0,414,412,1,0, - 0,0,414,415,1,0,0,0,415,417,1,0,0,0,416,414,1,0,0,0,417,418,5,69,0,0,418, - 420,1,0,0,0,419,377,1,0,0,0,419,378,1,0,0,0,419,381,1,0,0,0,419,382,1,0, - 0,0,419,383,1,0,0,0,419,384,1,0,0,0,419,385,1,0,0,0,419,386,1,0,0,0,419, - 397,1,0,0,0,419,408,1,0,0,0,420,59,1,0,0,0,421,422,5,10,0,0,422,423,5,31, - 0,0,423,61,1,0,0,0,424,425,5,18,0,0,425,430,3,64,32,0,426,427,5,38,0,0, - 427,429,3,64,32,0,428,426,1,0,0,0,429,432,1,0,0,0,430,428,1,0,0,0,430,431, - 1,0,0,0,431,63,1,0,0,0,432,430,1,0,0,0,433,435,3,10,5,0,434,436,7,3,0,0, - 435,434,1,0,0,0,435,436,1,0,0,0,436,439,1,0,0,0,437,438,5,50,0,0,438,440, - 7,4,0,0,439,437,1,0,0,0,439,440,1,0,0,0,440,65,1,0,0,0,441,442,5,9,0,0, - 442,443,3,52,26,0,443,67,1,0,0,0,444,445,5,2,0,0,445,446,3,52,26,0,446, - 69,1,0,0,0,447,448,5,15,0,0,448,453,3,72,36,0,449,450,5,38,0,0,450,452, - 3,72,36,0,451,449,1,0,0,0,452,455,1,0,0,0,453,451,1,0,0,0,453,454,1,0,0, - 0,454,71,1,0,0,0,455,453,1,0,0,0,456,457,3,50,25,0,457,458,5,83,0,0,458, - 459,3,50,25,0,459,73,1,0,0,0,460,461,5,1,0,0,461,462,3,18,9,0,462,464,3, - 92,46,0,463,465,3,80,40,0,464,463,1,0,0,0,464,465,1,0,0,0,465,75,1,0,0, - 0,466,467,5,7,0,0,467,468,3,18,9,0,468,469,3,92,46,0,469,77,1,0,0,0,470, - 471,5,14,0,0,471,472,3,48,24,0,472,79,1,0,0,0,473,478,3,82,41,0,474,475, - 5,38,0,0,475,477,3,82,41,0,476,474,1,0,0,0,477,480,1,0,0,0,478,476,1,0, - 0,0,478,479,1,0,0,0,479,81,1,0,0,0,480,478,1,0,0,0,481,482,3,54,27,0,482, - 483,5,36,0,0,483,484,3,58,29,0,484,83,1,0,0,0,485,486,7,5,0,0,486,85,1, - 0,0,0,487,490,3,88,44,0,488,490,3,90,45,0,489,487,1,0,0,0,489,488,1,0,0, - 0,490,87,1,0,0,0,491,493,7,0,0,0,492,491,1,0,0,0,492,493,1,0,0,0,493,494, - 1,0,0,0,494,495,5,32,0,0,495,89,1,0,0,0,496,498,7,0,0,0,497,496,1,0,0,0, - 497,498,1,0,0,0,498,499,1,0,0,0,499,500,5,31,0,0,500,91,1,0,0,0,501,502, - 5,30,0,0,502,93,1,0,0,0,503,504,7,6,0,0,504,95,1,0,0,0,505,506,5,5,0,0, - 506,507,3,98,49,0,507,97,1,0,0,0,508,509,5,68,0,0,509,510,3,2,1,0,510,511, - 5,69,0,0,511,99,1,0,0,0,512,513,5,17,0,0,513,514,5,105,0,0,514,101,1,0, - 0,0,515,516,5,12,0,0,516,517,5,109,0,0,517,103,1,0,0,0,518,519,5,3,0,0, - 519,522,5,89,0,0,520,521,5,87,0,0,521,523,3,50,25,0,522,520,1,0,0,0,522, - 523,1,0,0,0,523,533,1,0,0,0,524,525,5,88,0,0,525,530,3,106,53,0,526,527, - 5,38,0,0,527,529,3,106,53,0,528,526,1,0,0,0,529,532,1,0,0,0,530,528,1,0, - 0,0,530,531,1,0,0,0,531,534,1,0,0,0,532,530,1,0,0,0,533,524,1,0,0,0,533, - 534,1,0,0,0,534,105,1,0,0,0,535,536,3,50,25,0,536,537,5,36,0,0,537,539, - 1,0,0,0,538,535,1,0,0,0,538,539,1,0,0,0,539,540,1,0,0,0,540,541,3,50,25, - 0,541,107,1,0,0,0,542,543,5,11,0,0,543,544,5,25,0,0,544,545,5,87,0,0,545, - 546,3,52,26,0,546,109,1,0,0,0,52,121,130,146,158,167,175,179,187,189,194, - 201,206,213,219,227,229,240,247,258,261,275,283,291,295,301,309,322,326, - 330,337,341,347,354,362,370,392,403,414,419,430,435,439,453,464,478,489, - 492,497,522,530,533,538]; + 7,53,2,54,7,54,2,55,7,55,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,5,1,122,8, + 1,10,1,12,1,125,9,1,1,2,1,2,1,2,1,2,1,2,1,2,3,2,133,8,2,1,3,1,3,1,3,1,3, + 1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,1,3,3,3,149,8,3,1,4,1,4,1,4,1,5,1,5, + 1,5,1,5,1,5,1,5,1,5,3,5,161,8,5,1,5,1,5,1,5,1,5,1,5,5,5,168,8,5,10,5,12, + 5,171,9,5,1,5,1,5,1,5,1,5,1,5,3,5,178,8,5,1,5,1,5,3,5,182,8,5,1,5,1,5,1, + 5,1,5,1,5,1,5,5,5,190,8,5,10,5,12,5,193,9,5,1,6,1,6,3,6,197,8,6,1,6,1,6, + 1,6,1,6,1,6,3,6,204,8,6,1,6,1,6,1,6,3,6,209,8,6,1,7,1,7,1,7,1,7,1,7,3,7, + 216,8,7,1,8,1,8,1,8,1,8,3,8,222,8,8,1,8,1,8,1,8,1,8,1,8,1,8,5,8,230,8,8, + 10,8,12,8,233,9,8,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,3,9,243,8,9,1,9,1,9,1, + 9,5,9,248,8,9,10,9,12,9,251,9,9,1,10,1,10,1,10,1,10,1,10,1,10,5,10,259, + 8,10,10,10,12,10,262,9,10,3,10,264,8,10,1,10,1,10,1,11,1,11,1,12,1,12,1, + 12,1,13,1,13,1,13,5,13,276,8,13,10,13,12,13,279,9,13,1,14,1,14,1,14,1,14, + 1,14,3,14,286,8,14,1,15,1,15,1,15,1,15,5,15,292,8,15,10,15,12,15,295,9, + 15,1,15,3,15,298,8,15,1,16,1,16,1,17,1,17,3,17,304,8,17,1,18,1,18,1,18, + 1,18,5,18,310,8,18,10,18,12,18,313,9,18,1,19,1,19,1,19,1,19,1,20,1,20,1, + 20,1,20,5,20,323,8,20,10,20,12,20,326,9,20,1,20,3,20,329,8,20,1,20,1,20, + 3,20,333,8,20,1,21,1,21,1,21,1,22,1,22,3,22,340,8,22,1,22,1,22,3,22,344, + 8,22,1,23,1,23,1,23,1,23,3,23,350,8,23,1,24,1,24,1,24,5,24,355,8,24,10, + 24,12,24,358,9,24,1,25,1,25,1,25,5,25,363,8,25,10,25,12,25,366,9,25,1,26, + 1,26,1,26,5,26,371,8,26,10,26,12,26,374,9,26,1,27,1,27,1,28,1,28,1,29,1, + 29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,1,29,5,29,393,8,29, + 10,29,12,29,396,9,29,1,29,1,29,1,29,1,29,1,29,1,29,5,29,404,8,29,10,29, + 12,29,407,9,29,1,29,1,29,1,29,1,29,1,29,1,29,5,29,415,8,29,10,29,12,29, + 418,9,29,1,29,1,29,3,29,422,8,29,1,30,1,30,3,30,426,8,30,1,31,1,31,1,31, + 1,32,1,32,1,32,1,32,5,32,435,8,32,10,32,12,32,438,9,32,1,33,1,33,3,33,442, + 8,33,1,33,1,33,3,33,446,8,33,1,34,1,34,1,34,1,35,1,35,1,35,1,36,1,36,1, + 36,1,36,5,36,458,8,36,10,36,12,36,461,9,36,1,37,1,37,1,37,1,37,1,38,1,38, + 1,38,1,38,3,38,471,8,38,1,39,1,39,1,39,1,39,1,40,1,40,1,40,1,41,1,41,1, + 41,5,41,483,8,41,10,41,12,41,486,9,41,1,42,1,42,1,42,1,42,1,43,1,43,1,44, + 1,44,3,44,496,8,44,1,45,3,45,499,8,45,1,45,1,45,1,46,3,46,504,8,46,1,46, + 1,46,1,47,1,47,1,48,1,48,1,49,1,49,1,49,1,50,1,50,1,50,1,50,1,51,1,51,1, + 51,1,52,1,52,1,52,1,53,1,53,1,53,1,53,3,53,529,8,53,1,53,1,53,1,53,1,53, + 5,53,535,8,53,10,53,12,53,538,9,53,3,53,540,8,53,1,54,1,54,1,54,3,54,545, + 8,54,1,54,1,54,1,55,1,55,1,55,1,55,1,55,1,55,0,4,2,10,16,18,56,0,2,4,6, + 8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54, + 56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102, + 104,106,108,110,0,7,1,0,63,64,1,0,65,67,1,0,71,72,2,0,35,35,39,39,1,0,42, + 43,2,0,41,41,55,55,2,0,56,56,58,62,580,0,112,1,0,0,0,2,115,1,0,0,0,4,132, + 1,0,0,0,6,148,1,0,0,0,8,150,1,0,0,0,10,181,1,0,0,0,12,208,1,0,0,0,14,215, + 1,0,0,0,16,221,1,0,0,0,18,242,1,0,0,0,20,252,1,0,0,0,22,267,1,0,0,0,24, + 269,1,0,0,0,26,272,1,0,0,0,28,285,1,0,0,0,30,287,1,0,0,0,32,299,1,0,0,0, + 34,303,1,0,0,0,36,305,1,0,0,0,38,314,1,0,0,0,40,318,1,0,0,0,42,334,1,0, + 0,0,44,337,1,0,0,0,46,345,1,0,0,0,48,351,1,0,0,0,50,359,1,0,0,0,52,367, + 1,0,0,0,54,375,1,0,0,0,56,377,1,0,0,0,58,421,1,0,0,0,60,425,1,0,0,0,62, + 427,1,0,0,0,64,430,1,0,0,0,66,439,1,0,0,0,68,447,1,0,0,0,70,450,1,0,0,0, + 72,453,1,0,0,0,74,462,1,0,0,0,76,466,1,0,0,0,78,472,1,0,0,0,80,476,1,0, + 0,0,82,479,1,0,0,0,84,487,1,0,0,0,86,491,1,0,0,0,88,495,1,0,0,0,90,498, + 1,0,0,0,92,503,1,0,0,0,94,507,1,0,0,0,96,509,1,0,0,0,98,511,1,0,0,0,100, + 514,1,0,0,0,102,518,1,0,0,0,104,521,1,0,0,0,106,524,1,0,0,0,108,544,1,0, + 0,0,110,548,1,0,0,0,112,113,3,2,1,0,113,114,5,0,0,1,114,1,1,0,0,0,115,116, + 6,1,-1,0,116,117,3,4,2,0,117,123,1,0,0,0,118,119,10,1,0,0,119,120,5,29, + 0,0,120,122,3,6,3,0,121,118,1,0,0,0,122,125,1,0,0,0,123,121,1,0,0,0,123, + 124,1,0,0,0,124,3,1,0,0,0,125,123,1,0,0,0,126,133,3,98,49,0,127,133,3,30, + 15,0,128,133,3,24,12,0,129,133,3,40,20,0,130,133,3,102,51,0,131,133,3,104, + 52,0,132,126,1,0,0,0,132,127,1,0,0,0,132,128,1,0,0,0,132,129,1,0,0,0,132, + 130,1,0,0,0,132,131,1,0,0,0,133,5,1,0,0,0,134,149,3,42,21,0,135,149,3,46, + 23,0,136,149,3,62,31,0,137,149,3,110,55,0,138,149,3,68,34,0,139,149,3,64, + 32,0,140,149,3,44,22,0,141,149,3,8,4,0,142,149,3,70,35,0,143,149,3,72,36, + 0,144,149,3,76,38,0,145,149,3,78,39,0,146,149,3,106,53,0,147,149,3,80,40, + 0,148,134,1,0,0,0,148,135,1,0,0,0,148,136,1,0,0,0,148,137,1,0,0,0,148,138, + 1,0,0,0,148,139,1,0,0,0,148,140,1,0,0,0,148,141,1,0,0,0,148,142,1,0,0,0, + 148,143,1,0,0,0,148,144,1,0,0,0,148,145,1,0,0,0,148,146,1,0,0,0,148,147, + 1,0,0,0,149,7,1,0,0,0,150,151,5,20,0,0,151,152,3,10,5,0,152,9,1,0,0,0,153, + 154,6,5,-1,0,154,155,5,48,0,0,155,182,3,10,5,7,156,182,3,14,7,0,157,182, + 3,12,6,0,158,160,3,14,7,0,159,161,5,48,0,0,160,159,1,0,0,0,160,161,1,0, + 0,0,161,162,1,0,0,0,162,163,5,45,0,0,163,164,5,44,0,0,164,169,3,14,7,0, + 165,166,5,38,0,0,166,168,3,14,7,0,167,165,1,0,0,0,168,171,1,0,0,0,169,167, + 1,0,0,0,169,170,1,0,0,0,170,172,1,0,0,0,171,169,1,0,0,0,172,173,5,54,0, + 0,173,182,1,0,0,0,174,175,3,14,7,0,175,177,5,46,0,0,176,178,5,48,0,0,177, + 176,1,0,0,0,177,178,1,0,0,0,178,179,1,0,0,0,179,180,5,49,0,0,180,182,1, + 0,0,0,181,153,1,0,0,0,181,156,1,0,0,0,181,157,1,0,0,0,181,158,1,0,0,0,181, + 174,1,0,0,0,182,191,1,0,0,0,183,184,10,4,0,0,184,185,5,34,0,0,185,190,3, + 10,5,5,186,187,10,3,0,0,187,188,5,51,0,0,188,190,3,10,5,4,189,183,1,0,0, + 0,189,186,1,0,0,0,190,193,1,0,0,0,191,189,1,0,0,0,191,192,1,0,0,0,192,11, + 1,0,0,0,193,191,1,0,0,0,194,196,3,14,7,0,195,197,5,48,0,0,196,195,1,0,0, + 0,196,197,1,0,0,0,197,198,1,0,0,0,198,199,5,47,0,0,199,200,3,94,47,0,200, + 209,1,0,0,0,201,203,3,14,7,0,202,204,5,48,0,0,203,202,1,0,0,0,203,204,1, + 0,0,0,204,205,1,0,0,0,205,206,5,53,0,0,206,207,3,94,47,0,207,209,1,0,0, + 0,208,194,1,0,0,0,208,201,1,0,0,0,209,13,1,0,0,0,210,216,3,16,8,0,211,212, + 3,16,8,0,212,213,3,96,48,0,213,214,3,16,8,0,214,216,1,0,0,0,215,210,1,0, + 0,0,215,211,1,0,0,0,216,15,1,0,0,0,217,218,6,8,-1,0,218,222,3,18,9,0,219, + 220,7,0,0,0,220,222,3,16,8,3,221,217,1,0,0,0,221,219,1,0,0,0,222,231,1, + 0,0,0,223,224,10,2,0,0,224,225,7,1,0,0,225,230,3,16,8,3,226,227,10,1,0, + 0,227,228,7,0,0,0,228,230,3,16,8,2,229,223,1,0,0,0,229,226,1,0,0,0,230, + 233,1,0,0,0,231,229,1,0,0,0,231,232,1,0,0,0,232,17,1,0,0,0,233,231,1,0, + 0,0,234,235,6,9,-1,0,235,243,3,58,29,0,236,243,3,48,24,0,237,243,3,20,10, + 0,238,239,5,44,0,0,239,240,3,10,5,0,240,241,5,54,0,0,241,243,1,0,0,0,242, + 234,1,0,0,0,242,236,1,0,0,0,242,237,1,0,0,0,242,238,1,0,0,0,243,249,1,0, + 0,0,244,245,10,1,0,0,245,246,5,37,0,0,246,248,3,22,11,0,247,244,1,0,0,0, + 248,251,1,0,0,0,249,247,1,0,0,0,249,250,1,0,0,0,250,19,1,0,0,0,251,249, + 1,0,0,0,252,253,3,54,27,0,253,263,5,44,0,0,254,264,5,65,0,0,255,260,3,10, + 5,0,256,257,5,38,0,0,257,259,3,10,5,0,258,256,1,0,0,0,259,262,1,0,0,0,260, + 258,1,0,0,0,260,261,1,0,0,0,261,264,1,0,0,0,262,260,1,0,0,0,263,254,1,0, + 0,0,263,255,1,0,0,0,263,264,1,0,0,0,264,265,1,0,0,0,265,266,5,54,0,0,266, + 21,1,0,0,0,267,268,3,54,27,0,268,23,1,0,0,0,269,270,5,16,0,0,270,271,3, + 26,13,0,271,25,1,0,0,0,272,277,3,28,14,0,273,274,5,38,0,0,274,276,3,28, + 14,0,275,273,1,0,0,0,276,279,1,0,0,0,277,275,1,0,0,0,277,278,1,0,0,0,278, + 27,1,0,0,0,279,277,1,0,0,0,280,286,3,10,5,0,281,282,3,48,24,0,282,283,5, + 36,0,0,283,284,3,10,5,0,284,286,1,0,0,0,285,280,1,0,0,0,285,281,1,0,0,0, + 286,29,1,0,0,0,287,288,5,6,0,0,288,293,3,32,16,0,289,290,5,38,0,0,290,292, + 3,32,16,0,291,289,1,0,0,0,292,295,1,0,0,0,293,291,1,0,0,0,293,294,1,0,0, + 0,294,297,1,0,0,0,295,293,1,0,0,0,296,298,3,34,17,0,297,296,1,0,0,0,297, + 298,1,0,0,0,298,31,1,0,0,0,299,300,5,25,0,0,300,33,1,0,0,0,301,304,3,36, + 18,0,302,304,3,38,19,0,303,301,1,0,0,0,303,302,1,0,0,0,304,35,1,0,0,0,305, + 306,5,76,0,0,306,311,3,32,16,0,307,308,5,38,0,0,308,310,3,32,16,0,309,307, + 1,0,0,0,310,313,1,0,0,0,311,309,1,0,0,0,311,312,1,0,0,0,312,37,1,0,0,0, + 313,311,1,0,0,0,314,315,5,69,0,0,315,316,3,36,18,0,316,317,5,70,0,0,317, + 39,1,0,0,0,318,319,5,13,0,0,319,324,3,32,16,0,320,321,5,38,0,0,321,323, + 3,32,16,0,322,320,1,0,0,0,323,326,1,0,0,0,324,322,1,0,0,0,324,325,1,0,0, + 0,325,328,1,0,0,0,326,324,1,0,0,0,327,329,3,26,13,0,328,327,1,0,0,0,328, + 329,1,0,0,0,329,332,1,0,0,0,330,331,5,33,0,0,331,333,3,26,13,0,332,330, + 1,0,0,0,332,333,1,0,0,0,333,41,1,0,0,0,334,335,5,4,0,0,335,336,3,26,13, + 0,336,43,1,0,0,0,337,339,5,19,0,0,338,340,3,26,13,0,339,338,1,0,0,0,339, + 340,1,0,0,0,340,343,1,0,0,0,341,342,5,33,0,0,342,344,3,26,13,0,343,341, + 1,0,0,0,343,344,1,0,0,0,344,45,1,0,0,0,345,346,5,8,0,0,346,349,3,26,13, + 0,347,348,5,33,0,0,348,350,3,26,13,0,349,347,1,0,0,0,349,350,1,0,0,0,350, + 47,1,0,0,0,351,356,3,54,27,0,352,353,5,40,0,0,353,355,3,54,27,0,354,352, + 1,0,0,0,355,358,1,0,0,0,356,354,1,0,0,0,356,357,1,0,0,0,357,49,1,0,0,0, + 358,356,1,0,0,0,359,364,3,56,28,0,360,361,5,40,0,0,361,363,3,56,28,0,362, + 360,1,0,0,0,363,366,1,0,0,0,364,362,1,0,0,0,364,365,1,0,0,0,365,51,1,0, + 0,0,366,364,1,0,0,0,367,372,3,50,25,0,368,369,5,38,0,0,369,371,3,50,25, + 0,370,368,1,0,0,0,371,374,1,0,0,0,372,370,1,0,0,0,372,373,1,0,0,0,373,53, + 1,0,0,0,374,372,1,0,0,0,375,376,7,2,0,0,376,55,1,0,0,0,377,378,5,80,0,0, + 378,57,1,0,0,0,379,422,5,49,0,0,380,381,3,92,46,0,381,382,5,71,0,0,382, + 422,1,0,0,0,383,422,3,90,45,0,384,422,3,92,46,0,385,422,3,86,43,0,386,422, + 3,60,30,0,387,422,3,94,47,0,388,389,5,69,0,0,389,394,3,88,44,0,390,391, + 5,38,0,0,391,393,3,88,44,0,392,390,1,0,0,0,393,396,1,0,0,0,394,392,1,0, + 0,0,394,395,1,0,0,0,395,397,1,0,0,0,396,394,1,0,0,0,397,398,5,70,0,0,398, + 422,1,0,0,0,399,400,5,69,0,0,400,405,3,86,43,0,401,402,5,38,0,0,402,404, + 3,86,43,0,403,401,1,0,0,0,404,407,1,0,0,0,405,403,1,0,0,0,405,406,1,0,0, + 0,406,408,1,0,0,0,407,405,1,0,0,0,408,409,5,70,0,0,409,422,1,0,0,0,410, + 411,5,69,0,0,411,416,3,94,47,0,412,413,5,38,0,0,413,415,3,94,47,0,414,412, + 1,0,0,0,415,418,1,0,0,0,416,414,1,0,0,0,416,417,1,0,0,0,417,419,1,0,0,0, + 418,416,1,0,0,0,419,420,5,70,0,0,420,422,1,0,0,0,421,379,1,0,0,0,421,380, + 1,0,0,0,421,383,1,0,0,0,421,384,1,0,0,0,421,385,1,0,0,0,421,386,1,0,0,0, + 421,387,1,0,0,0,421,388,1,0,0,0,421,399,1,0,0,0,421,410,1,0,0,0,422,59, + 1,0,0,0,423,426,5,52,0,0,424,426,5,68,0,0,425,423,1,0,0,0,425,424,1,0,0, + 0,426,61,1,0,0,0,427,428,5,10,0,0,428,429,5,31,0,0,429,63,1,0,0,0,430,431, + 5,18,0,0,431,436,3,66,33,0,432,433,5,38,0,0,433,435,3,66,33,0,434,432,1, + 0,0,0,435,438,1,0,0,0,436,434,1,0,0,0,436,437,1,0,0,0,437,65,1,0,0,0,438, + 436,1,0,0,0,439,441,3,10,5,0,440,442,7,3,0,0,441,440,1,0,0,0,441,442,1, + 0,0,0,442,445,1,0,0,0,443,444,5,50,0,0,444,446,7,4,0,0,445,443,1,0,0,0, + 445,446,1,0,0,0,446,67,1,0,0,0,447,448,5,9,0,0,448,449,3,52,26,0,449,69, + 1,0,0,0,450,451,5,2,0,0,451,452,3,52,26,0,452,71,1,0,0,0,453,454,5,15,0, + 0,454,459,3,74,37,0,455,456,5,38,0,0,456,458,3,74,37,0,457,455,1,0,0,0, + 458,461,1,0,0,0,459,457,1,0,0,0,459,460,1,0,0,0,460,73,1,0,0,0,461,459, + 1,0,0,0,462,463,3,50,25,0,463,464,5,84,0,0,464,465,3,50,25,0,465,75,1,0, + 0,0,466,467,5,1,0,0,467,468,3,18,9,0,468,470,3,94,47,0,469,471,3,82,41, + 0,470,469,1,0,0,0,470,471,1,0,0,0,471,77,1,0,0,0,472,473,5,7,0,0,473,474, + 3,18,9,0,474,475,3,94,47,0,475,79,1,0,0,0,476,477,5,14,0,0,477,478,3,48, + 24,0,478,81,1,0,0,0,479,484,3,84,42,0,480,481,5,38,0,0,481,483,3,84,42, + 0,482,480,1,0,0,0,483,486,1,0,0,0,484,482,1,0,0,0,484,485,1,0,0,0,485,83, + 1,0,0,0,486,484,1,0,0,0,487,488,3,54,27,0,488,489,5,36,0,0,489,490,3,58, + 29,0,490,85,1,0,0,0,491,492,7,5,0,0,492,87,1,0,0,0,493,496,3,90,45,0,494, + 496,3,92,46,0,495,493,1,0,0,0,495,494,1,0,0,0,496,89,1,0,0,0,497,499,7, + 0,0,0,498,497,1,0,0,0,498,499,1,0,0,0,499,500,1,0,0,0,500,501,5,32,0,0, + 501,91,1,0,0,0,502,504,7,0,0,0,503,502,1,0,0,0,503,504,1,0,0,0,504,505, + 1,0,0,0,505,506,5,31,0,0,506,93,1,0,0,0,507,508,5,30,0,0,508,95,1,0,0,0, + 509,510,7,6,0,0,510,97,1,0,0,0,511,512,5,5,0,0,512,513,3,100,50,0,513,99, + 1,0,0,0,514,515,5,69,0,0,515,516,3,2,1,0,516,517,5,70,0,0,517,101,1,0,0, + 0,518,519,5,17,0,0,519,520,5,106,0,0,520,103,1,0,0,0,521,522,5,12,0,0,522, + 523,5,110,0,0,523,105,1,0,0,0,524,525,5,3,0,0,525,528,5,90,0,0,526,527, + 5,88,0,0,527,529,3,50,25,0,528,526,1,0,0,0,528,529,1,0,0,0,529,539,1,0, + 0,0,530,531,5,89,0,0,531,536,3,108,54,0,532,533,5,38,0,0,533,535,3,108, + 54,0,534,532,1,0,0,0,535,538,1,0,0,0,536,534,1,0,0,0,536,537,1,0,0,0,537, + 540,1,0,0,0,538,536,1,0,0,0,539,530,1,0,0,0,539,540,1,0,0,0,540,107,1,0, + 0,0,541,542,3,50,25,0,542,543,5,36,0,0,543,545,1,0,0,0,544,541,1,0,0,0, + 544,545,1,0,0,0,545,546,1,0,0,0,546,547,3,50,25,0,547,109,1,0,0,0,548,549, + 5,11,0,0,549,550,5,25,0,0,550,551,5,88,0,0,551,552,3,52,26,0,552,111,1, + 0,0,0,53,123,132,148,160,169,177,181,189,191,196,203,208,215,221,229,231, + 242,249,260,263,277,285,293,297,303,311,324,328,332,339,343,349,356,364, + 372,394,405,416,421,425,436,441,445,459,470,484,495,498,503,528,536,539, + 544]; private static __ATN: ATN; public static get _ATN(): ATN { @@ -4848,22 +4897,22 @@ export class NumericArrayLiteralContext extends ConstantContext { } } } -export class InputParamContext extends ConstantContext { +export class InputParamsContext extends ConstantContext { constructor(parser: esql_parser, ctx: ConstantContext) { super(parser, ctx.parentCtx, ctx.invokingState); super.copyFrom(ctx); } - public PARAM(): TerminalNode { - return this.getToken(esql_parser.PARAM, 0); + public params(): ParamsContext { + return this.getTypedRuleContext(ParamsContext, 0) as ParamsContext; } public enterRule(listener: esql_parserListener): void { - if(listener.enterInputParam) { - listener.enterInputParam(this); + if(listener.enterInputParams) { + listener.enterInputParams(this); } } public exitRule(listener: esql_parserListener): void { - if(listener.exitInputParam) { - listener.exitInputParam(this); + if(listener.exitInputParams) { + listener.exitInputParams(this); } } } @@ -4907,6 +4956,58 @@ export class BooleanLiteralContext extends ConstantContext { } +export class ParamsContext extends ParserRuleContext { + constructor(parser?: esql_parser, parent?: ParserRuleContext, invokingState?: number) { + super(parent, invokingState); + this.parser = parser; + } + public get ruleIndex(): number { + return esql_parser.RULE_params; + } + public copyFrom(ctx: ParamsContext): void { + super.copyFrom(ctx); + } +} +export class InputNamedOrPositionalParamContext extends ParamsContext { + constructor(parser: esql_parser, ctx: ParamsContext) { + super(parser, ctx.parentCtx, ctx.invokingState); + super.copyFrom(ctx); + } + public NAMED_OR_POSITIONAL_PARAM(): TerminalNode { + return this.getToken(esql_parser.NAMED_OR_POSITIONAL_PARAM, 0); + } + public enterRule(listener: esql_parserListener): void { + if(listener.enterInputNamedOrPositionalParam) { + listener.enterInputNamedOrPositionalParam(this); + } + } + public exitRule(listener: esql_parserListener): void { + if(listener.exitInputNamedOrPositionalParam) { + listener.exitInputNamedOrPositionalParam(this); + } + } +} +export class InputParamContext extends ParamsContext { + constructor(parser: esql_parser, ctx: ParamsContext) { + super(parser, ctx.parentCtx, ctx.invokingState); + super.copyFrom(ctx); + } + public PARAM(): TerminalNode { + return this.getToken(esql_parser.PARAM, 0); + } + public enterRule(listener: esql_parserListener): void { + if(listener.enterInputParam) { + listener.enterInputParam(this); + } + } + public exitRule(listener: esql_parserListener): void { + if(listener.exitInputParam) { + listener.exitInputParam(this); + } + } +} + + export class LimitCommandContext extends ParserRuleContext { constructor(parser?: esql_parser, parent?: ParserRuleContext, invokingState?: number) { super(parent, invokingState); diff --git a/packages/kbn-esql-ast/src/antlr/esql_parser_listener.ts b/packages/kbn-esql-ast/src/antlr/esql_parser_listener.ts index 3b98e746de9b3..eee7a421a1593 100644 --- a/packages/kbn-esql-ast/src/antlr/esql_parser_listener.ts +++ b/packages/kbn-esql-ast/src/antlr/esql_parser_listener.ts @@ -51,11 +51,13 @@ import { QualifiedIntegerLiteralContext } from "./esql_parser"; import { DecimalLiteralContext } from "./esql_parser"; import { IntegerLiteralContext } from "./esql_parser"; import { BooleanLiteralContext } from "./esql_parser"; -import { InputParamContext } from "./esql_parser"; +import { InputParamsContext } from "./esql_parser"; import { StringLiteralContext } from "./esql_parser"; import { NumericArrayLiteralContext } from "./esql_parser"; import { BooleanArrayLiteralContext } from "./esql_parser"; import { StringArrayLiteralContext } from "./esql_parser"; +import { InputParamContext } from "./esql_parser"; +import { InputNamedOrPositionalParamContext } from "./esql_parser"; import { LimitCommandContext } from "./esql_parser"; import { SortCommandContext } from "./esql_parser"; import { OrderExpressionContext } from "./esql_parser"; @@ -607,17 +609,17 @@ export default class esql_parserListener extends ParseTreeListener { */ exitBooleanLiteral?: (ctx: BooleanLiteralContext) => void; /** - * Enter a parse tree produced by the `inputParam` + * Enter a parse tree produced by the `inputParams` * labeled alternative in `esql_parser.constant`. * @param ctx the parse tree */ - enterInputParam?: (ctx: InputParamContext) => void; + enterInputParams?: (ctx: InputParamsContext) => void; /** - * Exit a parse tree produced by the `inputParam` + * Exit a parse tree produced by the `inputParams` * labeled alternative in `esql_parser.constant`. * @param ctx the parse tree */ - exitInputParam?: (ctx: InputParamContext) => void; + exitInputParams?: (ctx: InputParamsContext) => void; /** * Enter a parse tree produced by the `stringLiteral` * labeled alternative in `esql_parser.constant`. @@ -666,6 +668,30 @@ export default class esql_parserListener extends ParseTreeListener { * @param ctx the parse tree */ exitStringArrayLiteral?: (ctx: StringArrayLiteralContext) => void; + /** + * Enter a parse tree produced by the `inputParam` + * labeled alternative in `esql_parser.params`. + * @param ctx the parse tree + */ + enterInputParam?: (ctx: InputParamContext) => void; + /** + * Exit a parse tree produced by the `inputParam` + * labeled alternative in `esql_parser.params`. + * @param ctx the parse tree + */ + exitInputParam?: (ctx: InputParamContext) => void; + /** + * Enter a parse tree produced by the `inputNamedOrPositionalParam` + * labeled alternative in `esql_parser.params`. + * @param ctx the parse tree + */ + enterInputNamedOrPositionalParam?: (ctx: InputNamedOrPositionalParamContext) => void; + /** + * Exit a parse tree produced by the `inputNamedOrPositionalParam` + * labeled alternative in `esql_parser.params`. + * @param ctx the parse tree + */ + exitInputNamedOrPositionalParam?: (ctx: InputNamedOrPositionalParamContext) => void; /** * Enter a parse tree produced by `esql_parser.limitCommand`. * @param ctx the parse tree diff --git a/packages/kbn-esql-ast/src/ast_factory.ts b/packages/kbn-esql-ast/src/ast_factory.ts index 476b3364f2744..9d76ff3a7cd4d 100644 --- a/packages/kbn-esql-ast/src/ast_factory.ts +++ b/packages/kbn-esql-ast/src/ast_factory.ts @@ -44,7 +44,7 @@ import { import { getPosition } from './ast_position_utils'; import { collectAllSourceIdentifiers, - collectAllFieldsStatements, + collectAllFields, visitByOption, collectAllColumnIdentifiers, visitRenameClauses, @@ -120,7 +120,7 @@ export class AstListener implements ESQLParserListener { exitRowCommand(ctx: RowCommandContext) { const command = createCommand('row', ctx); this.ast.push(command); - command.args.push(...collectAllFieldsStatements(ctx.fields())); + command.args.push(...collectAllFields(ctx.fields())); } /** @@ -153,20 +153,20 @@ export class AstListener implements ESQLParserListener { ...createAstBaseItem('metrics', ctx), type: 'command', args: [], - indices: ctx + sources: ctx .getTypedRuleContexts(IndexIdentifierContext) .map((sourceCtx) => createSource(sourceCtx)), }; this.ast.push(node); - const aggregates = collectAllFieldsStatements(ctx.fields(0)); - const grouping = collectAllFieldsStatements(ctx.fields(1)); + const aggregates = collectAllFields(ctx.fields(0)); + const grouping = collectAllFields(ctx.fields(1)); if (aggregates && aggregates.length) { node.aggregates = aggregates; } if (grouping && grouping.length) { node.grouping = grouping; } - node.args.push(...node.indices, ...aggregates, ...grouping); + node.args.push(...node.sources, ...aggregates, ...grouping); } /** @@ -176,7 +176,7 @@ export class AstListener implements ESQLParserListener { exitEvalCommand(ctx: EvalCommandContext) { const commandAst = createCommand('eval', ctx); this.ast.push(commandAst); - commandAst.args.push(...collectAllFieldsStatements(ctx.fields())); + commandAst.args.push(...collectAllFields(ctx.fields())); } /** @@ -189,7 +189,7 @@ export class AstListener implements ESQLParserListener { // STATS expression is optional if (ctx._stats) { - command.args.push(...collectAllFieldsStatements(ctx.fields(0))); + command.args.push(...collectAllFields(ctx.fields(0))); } if (ctx._grouping) { command.args.push(...visitByOption(ctx, ctx._stats ? ctx.fields(1) : ctx.fields(0))); diff --git a/packages/kbn-esql-ast/src/ast_helpers.ts b/packages/kbn-esql-ast/src/ast_helpers.ts index ae97040ecaaf1..76e130f233807 100644 --- a/packages/kbn-esql-ast/src/ast_helpers.ts +++ b/packages/kbn-esql-ast/src/ast_helpers.ts @@ -14,6 +14,7 @@ import { type Token, type ParserRuleContext, type TerminalNode } from 'antlr4'; import type { ArithmeticUnaryContext, DecimalValueContext, + InlineCastContext, IntegerValueContext, QualifiedIntegerLiteralContext, } from './antlr/esql_parser'; @@ -32,6 +33,8 @@ import type { ESQLCommandOption, ESQLAstItem, ESQLCommandMode, + ESQLInlineCast, + ESQLUnknownItem, } from './types'; export function nonNullable(v: T): v is NonNullable { @@ -61,6 +64,17 @@ export function createCommand(name: string, ctx: ParserRuleContext): ESQLCommand }; } +export function createInlineCast(ctx: InlineCastContext): Omit { + return { + type: 'inlineCast', + name: 'inlineCast', + text: ctx.getText(), + castType: ctx.dataType().getText(), + location: getPosition(ctx.start, ctx.stop), + incomplete: Boolean(ctx.exception), + }; +} + export function createList(ctx: ParserRuleContext, values: ESQLLiteral[]): ESQLList { return { type: 'list', @@ -120,11 +134,20 @@ export function textExistsAndIsValid(text: string | undefined): text is string { export function createLiteral( type: ESQLLiteral['literalType'], - node: TerminalNode | undefined -): ESQLLiteral | undefined { + node: TerminalNode | null +): ESQLLiteral { if (!node) { - return; + return { + type: 'literal', + name: 'unknown', + text: 'unknown', + value: 'unknown', + literalType: type, + location: { min: 0, max: 0 }, + incomplete: false, + } as ESQLLiteral; } + const text = node.getText(); const partialLiteral: Omit = { @@ -140,6 +163,8 @@ export function createLiteral( literalType: type, value: Number(text), }; + } else if (type === 'param') { + throw new Error('Should never happen'); } return { ...partialLiteral, @@ -347,3 +372,13 @@ export function createOption(name: string, ctx: ParserRuleContext): ESQLCommandO ), }; } + +export function createUnknownItem(ctx: ParserRuleContext): ESQLUnknownItem { + return { + type: 'unknown', + name: 'unknown', + text: ctx.getText(), + location: getPosition(ctx.start, ctx.stop), + incomplete: Boolean(ctx.exception), + }; +} diff --git a/packages/kbn-esql-ast/src/ast_walker.ts b/packages/kbn-esql-ast/src/ast_walker.ts index 064b1b7d3126e..e93c411541feb 100644 --- a/packages/kbn-esql-ast/src/ast_walker.ts +++ b/packages/kbn-esql-ast/src/ast_walker.ts @@ -15,6 +15,7 @@ import { BooleanDefaultContext, type BooleanExpressionContext, BooleanLiteralContext, + InputParamsContext, BooleanValueContext, type CommandOptionsContext, ComparisonContext, @@ -57,6 +58,9 @@ import { type ValueExpressionContext, ValueExpressionDefaultContext, IndexIdentifierContext, + InlineCastContext, + InputNamedOrPositionalParamContext, + InputParamContext, } from './antlr/esql_parser'; import { createSource, @@ -76,6 +80,8 @@ import { createPolicy, createSetting, textExistsAndIsValid, + createInlineCast, + createUnknownItem, } from './ast_helpers'; import { getPosition } from './ast_position_utils'; import type { @@ -84,6 +90,11 @@ import type { ESQLFunction, ESQLCommandOption, ESQLAstItem, + ESQLAstField, + ESQLInlineCast, + ESQLUnnamedParamLiteral, + ESQLPositionalParamLiteral, + ESQLNamedParamLiteral, } from './types'; export function collectAllSourceIdentifiers(ctx: FromCommandContext): ESQLAstItem[] { @@ -292,7 +303,7 @@ function getBooleanValue(ctx: BooleanLiteralContext | BooleanValueContext) { return createLiteral('boolean', booleanTerminalNode!); } -function getConstant(ctx: ConstantContext | undefined): ESQLAstItem | undefined { +function getConstant(ctx: ConstantContext): ESQLAstItem { if (ctx instanceof NullLiteralContext) { return createLiteral('null', ctx.NULL()); } @@ -334,6 +345,59 @@ function getConstant(ctx: ConstantContext | undefined): ESQLAstItem | undefined } return createList(ctx, values); } + if (ctx instanceof InputParamsContext && ctx.children) { + const values: ESQLLiteral[] = []; + + for (const child of ctx.children) { + if (child instanceof InputParamContext) { + const literal: ESQLUnnamedParamLiteral = { + type: 'literal', + literalType: 'param', + paramType: 'unnamed', + text: ctx.getText(), + name: '', + location: getPosition(ctx.start, ctx.stop), + incomplete: Boolean(ctx.exception), + }; + values.push(literal); + } else if (child instanceof InputNamedOrPositionalParamContext) { + const text = child.getText(); + const value = text.slice(1); + const valueAsNumber = Number(value); + const isPositional = String(valueAsNumber) === value; + + if (isPositional) { + const literal: ESQLPositionalParamLiteral = { + type: 'literal', + literalType: 'param', + paramType: 'positional', + value: valueAsNumber, + text, + name: '', + location: getPosition(ctx.start, ctx.stop), + incomplete: Boolean(ctx.exception), + }; + values.push(literal); + } else { + const literal: ESQLNamedParamLiteral = { + type: 'literal', + literalType: 'param', + paramType: 'named', + value, + text, + name: '', + location: getPosition(ctx.start, ctx.stop), + incomplete: Boolean(ctx.exception), + }; + values.push(literal); + } + } + } + + return values; + } + + return createUnknownItem(ctx); } export function visitRenameClauses(clausesCtx: RenameClauseContext[]): ESQLAstItem[] { @@ -355,9 +419,7 @@ export function visitRenameClauses(clausesCtx: RenameClauseContext[]): ESQLAstIt .filter(nonNullable); } -export function visitPrimaryExpression( - ctx: PrimaryExpressionContext -): ESQLAstItem | ESQLAstItem[] | undefined { +export function visitPrimaryExpression(ctx: PrimaryExpressionContext): ESQLAstItem | ESQLAstItem[] { if (ctx instanceof ConstantDefaultContext) { return getConstant(ctx.constant()); } @@ -385,6 +447,18 @@ export function visitPrimaryExpression( } return fn; } + if (ctx instanceof InlineCastContext) { + return collectInlineCast(ctx); + } + return createUnknownItem(ctx); +} + +function collectInlineCast(ctx: InlineCastContext): ESQLInlineCast { + const primaryExpression = visitPrimaryExpression(ctx.primaryExpression()); + return { + ...createInlineCast(ctx), + value: primaryExpression, + }; } export function collectLogicalExpression(ctx: BooleanExpressionContext) { @@ -474,14 +548,14 @@ export function visitField(ctx: FieldContext) { return collectBooleanExpression(ctx.booleanExpression()); } -export function collectAllFieldsStatements(ctx: FieldsContext | undefined): ESQLAstItem[] { - const ast: ESQLAstItem[] = []; +export function collectAllFields(ctx: FieldsContext | undefined): ESQLAstField[] { + const ast: ESQLAstField[] = []; if (!ctx) { return ast; } try { for (const field of ctx.field_list()) { - ast.push(...visitField(field)); + ast.push(...(visitField(field) as ESQLAstField[])); } } catch (e) { // do nothing @@ -494,7 +568,7 @@ export function visitByOption(ctx: StatsCommandContext, expr: FieldsContext | un return []; } const option = createOption(ctx.BY()!.getText().toLowerCase(), ctx); - option.args.push(...collectAllFieldsStatements(expr)); + option.args.push(...collectAllFields(expr)); return [option]; } @@ -512,7 +586,7 @@ export function visitOrderExpression(ctx: OrderExpressionContext[]) { } if (orderCtx.NULLS()) { expression.push(createLiteral('string', orderCtx.NULLS()!)!); - if (orderCtx._nullOrdering) { + if (orderCtx._nullOrdering && orderCtx._nullOrdering.text !== '') { const innerTerminalNode = orderCtx.getToken(esql_parser.FIRST, 0) || orderCtx.getToken(esql_parser.LAST, 0); const literal = createLiteral('string', innerTerminalNode); diff --git a/packages/kbn-esql-ast/src/types.ts b/packages/kbn-esql-ast/src/types.ts index 927ab619abfed..5c6b1d2fdbb8f 100644 --- a/packages/kbn-esql-ast/src/types.ts +++ b/packages/kbn-esql-ast/src/types.ts @@ -10,6 +10,8 @@ export type ESQLAst = ESQLAstCommand[]; export type ESQLAstCommand = ESQLCommand | ESQLAstMetricsCommand; +export type ESQLAstNode = ESQLAstCommand | ESQLAstItem; + export type ESQLSingleAstItem = | ESQLFunction | ESQLCommandOption @@ -18,7 +20,11 @@ export type ESQLSingleAstItem = | ESQLTimeInterval | ESQLList | ESQLLiteral - | ESQLCommandMode; + | ESQLCommandMode + | ESQLInlineCast + | ESQLUnknownItem; + +export type ESQLAstField = ESQLFunction | ESQLColumn; export type ESQLAstItem = ESQLSingleAstItem | ESQLAstItem[]; @@ -40,9 +46,9 @@ export interface ESQLCommand extends ESQLAstBaseItem { } export interface ESQLAstMetricsCommand extends ESQLCommand<'metrics'> { - indices: ESQLSource[]; - aggregates?: ESQLAstItem[]; - grouping?: ESQLAstItem[]; + sources: ESQLSource[]; + aggregates?: ESQLAstField[]; + grouping?: ESQLAstField[]; } export interface ESQLCommandOption extends ESQLAstBaseItem { @@ -59,6 +65,27 @@ export interface ESQLFunction extends ESQLAstBaseItem { args: ESQLAstItem[]; } +export interface ESQLInlineCast extends ESQLAstBaseItem { + type: 'inlineCast'; + value: ValueType; + castType: string; +} + +/** + * This node represents something the AST generator + * didn't recognize in the ANTLR parse tree. + * + * It can show up if the AST generator code is out of sync + * with the ANTLR grammar or if there is some idiosyncrasy + * or bug in the parse tree. + * + * These nodes can be ignored for the purpose of validation + * and autocomplete, but they may be helpful in detecting bugs. + */ +export interface ESQLUnknownItem extends ESQLAstBaseItem { + type: 'unknown'; +} + export interface ESQLTimeInterval extends ESQLAstBaseItem { type: 'timeInterval'; unit: string; @@ -84,7 +111,8 @@ export type ESQLLiteral = | ESQLNumberLiteral | ESQLBooleanLiteral | ESQLNullLiteral - | ESQLStringLiteral; + | ESQLStringLiteral + | ESQLParamLiteral; // @internal export interface ESQLNumberLiteral extends ESQLAstBaseItem { @@ -114,6 +142,39 @@ export interface ESQLStringLiteral extends ESQLAstBaseItem { value: string; } +// @internal +export interface ESQLParamLiteral extends ESQLAstBaseItem { + type: 'literal'; + literalType: 'param'; + paramType: ParamType; + value?: string | number; +} + +/** + * *Unnamed* parameter is not named, just a question mark "?". + * + * @internal + */ +export type ESQLUnnamedParamLiteral = ESQLParamLiteral<'unnamed'>; + +/** + * *Named* parameter is a question mark followed by a name "?name". + * + * @internal + */ +export interface ESQLNamedParamLiteral extends ESQLParamLiteral<'named'> { + value: string; +} + +/** + * *Positional* parameter is a question mark followed by a number "?1". + * + * @internal + */ +export interface ESQLPositionalParamLiteral extends ESQLParamLiteral<'positional'> { + value: number; +} + export interface ESQLMessage { type: 'error' | 'warning'; text: string; diff --git a/packages/kbn-esql-ast/src/walker/index.ts b/packages/kbn-esql-ast/src/walker/index.ts new file mode 100644 index 0000000000000..5dea00bed0f8a --- /dev/null +++ b/packages/kbn-esql-ast/src/walker/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { Walker, type WalkerOptions, walk } from './walker'; diff --git a/packages/kbn-esql-ast/src/walker/walker.test.ts b/packages/kbn-esql-ast/src/walker/walker.test.ts new file mode 100644 index 0000000000000..11c8a141080d5 --- /dev/null +++ b/packages/kbn-esql-ast/src/walker/walker.test.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ESQLColumn, ESQLLiteral, getAstAndSyntaxErrors } from '../..'; +import { walk, Walker } from './walker'; + +test('can walk all functions', () => { + const { ast } = getAstAndSyntaxErrors('METRICS index a(b(c(foo)))'); + const functions: string[] = []; + + walk(ast, { + visitFunction: (fn) => functions.push(fn.name), + }); + + expect(functions.sort()).toStrictEqual(['a', 'b', 'c']); +}); + +test('can walk "columns"', () => { + const query = 'ROW x = 1'; + const { ast } = getAstAndSyntaxErrors(query); + const columns: ESQLColumn[] = []; + + walk(ast, { + visitColumn: (node) => columns.push(node), + }); + + expect(columns).toMatchObject([ + { + type: 'column', + name: 'x', + }, + ]); +}); + +test('can walk literals', () => { + const query = 'ROW x = 1'; + const { ast } = getAstAndSyntaxErrors(query); + const columns: ESQLLiteral[] = []; + + walk(ast, { + visitLiteral: (node) => columns.push(node), + }); + + expect(columns).toMatchObject([ + { + type: 'literal', + name: '1', + }, + ]); +}); + +test('can collect all params', () => { + const query = 'ROW x = ?'; + const { ast } = getAstAndSyntaxErrors(query); + const params = Walker.params(ast); + + expect(params).toMatchObject([ + { + type: 'literal', + literalType: 'param', + paramType: 'unnamed', + }, + ]); +}); diff --git a/packages/kbn-esql-ast/src/walker/walker.ts b/packages/kbn-esql-ast/src/walker/walker.ts new file mode 100644 index 0000000000000..8ebf6911599d4 --- /dev/null +++ b/packages/kbn-esql-ast/src/walker/walker.ts @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { + ESQLAstCommand, + ESQLAstItem, + ESQLAstNode, + ESQLColumn, + ESQLFunction, + ESQLLiteral, + ESQLParamLiteral, + ESQLSingleAstItem, +} from '../types'; + +export interface WalkerOptions { + visitSingleAstItem?: (node: ESQLSingleAstItem) => void; + visitFunction?: (node: ESQLFunction) => void; + visitColumn?: (node: ESQLColumn) => void; + visitLiteral?: (node: ESQLLiteral) => void; +} + +export class Walker { + /** + * Walks the AST and calls the appropriate visitor functions. + */ + public static readonly walk = ( + node: ESQLAstNode | ESQLAstNode[], + options: WalkerOptions + ): Walker => { + const walker = new Walker(options); + walker.walk(node); + return walker; + }; + + /** + * Walks the AST and extracts all parameter literals. + * + * @param node AST node to extract parameters from. + */ + public static readonly params = (node: ESQLAstNode | ESQLAstNode[]): ESQLParamLiteral[] => { + const params: ESQLParamLiteral[] = []; + Walker.walk(node, { + visitLiteral: (param) => { + if (param.literalType === 'param') { + params.push(param); + } + }, + }); + return params; + }; + + constructor(protected readonly options: WalkerOptions) {} + + public walk(node: undefined | ESQLAstNode | ESQLAstNode[]): void { + if (!node) return; + + if (node instanceof Array) { + for (const item of node) this.walk(item); + return; + } + + switch (node.type) { + case 'command': { + this.walkCommand(node as ESQLAstCommand); + break; + } + default: { + this.walkAstItem(node as ESQLAstItem); + break; + } + } + } + + public walkCommand(node: ESQLAstCommand): void { + switch (node.name) { + default: { + this.walk(node.args); + break; + } + } + } + + public walkAstItem(node: ESQLAstItem): void { + if (node instanceof Array) { + const list = node as ESQLAstItem[]; + for (const item of list) this.walkAstItem(item); + } else { + const item = node as ESQLSingleAstItem; + this.walkSingleAstItem(item); + } + } + + public walkSingleAstItem(node: ESQLSingleAstItem): void { + const { options } = this; + options.visitSingleAstItem?.(node); + switch (node.type) { + case 'function': { + this.walkFunction(node as ESQLFunction); + break; + } + case 'column': { + options.visitColumn?.(node); + break; + } + case 'literal': { + options.visitLiteral?.(node); + break; + } + } + } + + public walkFunction(node: ESQLFunction): void { + this.options.visitFunction?.(node); + const args = node.args; + const length = args.length; + for (let i = 0; i < length; i++) { + const arg = args[i]; + this.walkAstItem(arg); + } + } +} + +export const walk = Walker.walk; diff --git a/packages/kbn-esql-utils/src/utils/get_esql_adhoc_dataview.ts b/packages/kbn-esql-utils/src/utils/get_esql_adhoc_dataview.ts index e1e599946ef59..b05dfbe5f5788 100644 --- a/packages/kbn-esql-utils/src/utils/get_esql_adhoc_dataview.ts +++ b/packages/kbn-esql-utils/src/utils/get_esql_adhoc_dataview.ts @@ -31,11 +31,19 @@ export async function getESQLAdHocDataview( indexPattern: string, dataViewsService: DataViewsPublicPluginStart ) { - return await dataViewsService.create({ + const dataView = await dataViewsService.create({ title: indexPattern, type: ESQL_TYPE, id: await sha256(`esql-${indexPattern}`), }); + + // If the indexPattern is empty string means that the user used either the ROW or META FUNCTIONS / SHOW INFO commands + // we don't want to add the @timestamp field in this case https://github.com/elastic/kibana/issues/163417 + if (indexPattern && dataView?.fields?.getByName?.('@timestamp')?.type === 'date') { + dataView.timeFieldName = '@timestamp'; + } + + return dataView; } /** diff --git a/packages/kbn-esql-utils/src/utils/query_parsing_helpers.test.ts b/packages/kbn-esql-utils/src/utils/query_parsing_helpers.test.ts index 3cb3c3171942d..b1bafc2ce4359 100644 --- a/packages/kbn-esql-utils/src/utils/query_parsing_helpers.test.ts +++ b/packages/kbn-esql-utils/src/utils/query_parsing_helpers.test.ts @@ -53,6 +53,17 @@ describe('esql query helpers', () => { const idxPattern13 = getIndexPatternFromESQLQuery('ROW a = 1, b = "two", c = null'); expect(idxPattern13).toBe(''); + + const idxPattern14 = getIndexPatternFromESQLQuery('METRICS tsdb'); + expect(idxPattern14).toBe('tsdb'); + + const idxPattern15 = getIndexPatternFromESQLQuery('METRICS tsdb max(cpu) BY host'); + expect(idxPattern15).toBe('tsdb'); + + const idxPattern16 = getIndexPatternFromESQLQuery( + 'METRICS pods load=avg(cpu), writes=max(rate(indexing_requests)) BY pod | SORT pod' + ); + expect(idxPattern16).toBe('pods'); }); }); @@ -116,5 +127,13 @@ describe('esql query helpers', () => { // | stats var0 = avg(bytes) by geo.dest`) ).toBeFalsy(); }); + + it('should return false for metrics with no aggregation', () => { + expect(hasTransformationalCommand('metrics a')).toBeFalsy(); + }); + + it('should return true for metrics with aggregations', () => { + expect(hasTransformationalCommand('metrics a var = avg(b)')).toBeTruthy(); + }); }); }); diff --git a/packages/kbn-esql-utils/src/utils/query_parsing_helpers.ts b/packages/kbn-esql-utils/src/utils/query_parsing_helpers.ts index 994551930cc5a..79a3bbdd900d9 100644 --- a/packages/kbn-esql-utils/src/utils/query_parsing_helpers.ts +++ b/packages/kbn-esql-utils/src/utils/query_parsing_helpers.ts @@ -12,17 +12,30 @@ const DEFAULT_ESQL_LIMIT = 500; // retrieves the index pattern from the aggregate query for ES|QL using ast parsing export function getIndexPatternFromESQLQuery(esql?: string) { const { ast } = getAstAndSyntaxErrors(esql); - const fromCommand = ast.find(({ name }) => name === 'from'); - const args = (fromCommand?.args ?? []) as ESQLSource[]; + const sourceCommand = ast.find(({ name }) => ['from', 'metrics'].includes(name)); + const args = (sourceCommand?.args ?? []) as ESQLSource[]; const indices = args.filter((arg) => arg.sourceType === 'index'); return indices?.map((index) => index.text).join(','); } -// For ES|QL we consider the following commands as transformational commands +// For ES|QL we consider stats and keep transformational command +// The metrics command too but only if it aggregates export function hasTransformationalCommand(esql?: string) { - const transformationalCommands = ['stats', 'keep', 'metrics']; + const transformationalCommands = ['stats', 'keep']; const { ast } = getAstAndSyntaxErrors(esql); - return transformationalCommands.some((command) => ast.find(({ name }) => name === command)); + const hasAtLeastOneTransformationalCommand = transformationalCommands.some((command) => + ast.find(({ name }) => name === command) + ); + if (hasAtLeastOneTransformationalCommand) { + return true; + } + const metricsCommand = ast.find(({ name }) => name === 'metrics'); + + if (metricsCommand && 'aggregates' in metricsCommand) { + return true; + } + + return false; } export function getLimitFromESQLQuery(esql: string): number { diff --git a/packages/kbn-esql-validation-autocomplete/README.md b/packages/kbn-esql-validation-autocomplete/README.md index fb82a67b9171f..b5a4fc4d71c61 100644 --- a/packages/kbn-esql-validation-autocomplete/README.md +++ b/packages/kbn-esql-validation-autocomplete/README.md @@ -39,7 +39,7 @@ const myCallbacks = { const { errors, warnings } = await validateQuery("from index | stats 1 + avg(myColumn)", getAstAndSyntaxErrors, undefined, myCallbacks); ``` -If not all callbacks are available it is possible to gracefully degradate the validation experience with the `ignoreOnMissingCallbacks` option: +If not all callbacks are available it is possible to gracefully degrade the validation experience with the `ignoreOnMissingCallbacks` option: ```js import { getAstAndSyntaxErrors } from '@kbn/esql-ast'; @@ -61,7 +61,7 @@ const { errors, warnings } = await validateQuery( #### Autocomplete -This is the complete logic for the ES|QL autocomplete language, it is completely indepedent from the actual editor (i.e. Monaco) and the suggestions reported need to be wrapped against the specific editor shape. +This is the complete logic for the ES|QL autocomplete language, it is completely independent from the actual editor (i.e. Monaco) and the suggestions reported need to be wrapped against the specific editor shape. ```js import { getAstAndSyntaxErrors } from '@kbn/esql-ast'; @@ -207,13 +207,13 @@ The autocomplete/suggest task takes a query as input together with the current c Note that autocomplete works most of the time with incomplete/invalid queries, so some logic to manipulate the query into something valid (see the `EDITOR_MARKER` or the `countBracketsUnclosed` functions for more). Once the AST is produced there's a `getAstContext` function that finds the cursor position node (and its parent command), together with some hint like the type of current context: `expression`, `function`, `newCommand`, `option`. -The most complex case is the `expression` as it can cover a moltitude of cases. The function is highly commented in order to identify the specific cases, but there's probably some obscure area still to comment/clarify. +The most complex case is the `expression` as it can cover a multitude of cases. The function is highly commented in order to identify the specific cases, but there's probably some obscure area still to comment/clarify. -### Adding new commands/options/functions/erc... +### Adding new commands/options/functions/etc... To update the definitions: -1. open either approriate definition file within the `definitions` folder and add a new entry to the relative array +1. open either appropriate definition file within the `definitions` folder and add a new entry to the relative array 2. if you are adding a function, run `yarn maketests` to add a set of fundamental validation tests for the new definition. If any of the suggested tests are wrong, feel free to correct them by hand. If it seems like a general problem, open an issue with the details so that we can update the generator code. 3. write new tests for validation and autocomplete diff --git a/packages/kbn-esql-validation-autocomplete/index.ts b/packages/kbn-esql-validation-autocomplete/index.ts index d4c4420ffeca4..31bd8c16b76fb 100644 --- a/packages/kbn-esql-validation-autocomplete/index.ts +++ b/packages/kbn-esql-validation-autocomplete/index.ts @@ -49,11 +49,10 @@ export { getCommandDefinition, getAllCommands, getCommandOption, - getColumnHit, - columnExists, + lookupColumn, shouldBeQuotedText, printFunctionSignature, - isEqualType, + checkFunctionArgMatchesDefinition as isEqualType, isSourceItem, isSettingItem, isFunctionItem, diff --git a/packages/analytics/shippers/elastic_v3/common/jest.config.js b/packages/kbn-esql-validation-autocomplete/jest.integration.config.js similarity index 68% rename from packages/analytics/shippers/elastic_v3/common/jest.config.js rename to packages/kbn-esql-validation-autocomplete/jest.integration.config.js index 1336211347fb7..25671e51f3563 100644 --- a/packages/analytics/shippers/elastic_v3/common/jest.config.js +++ b/packages/kbn-esql-validation-autocomplete/jest.integration.config.js @@ -7,7 +7,9 @@ */ module.exports = { - preset: '@kbn/test/jest_node', - rootDir: '../../../../../', - roots: ['/packages/analytics/shippers/elastic_v3/common'], + preset: '@kbn/test/jest_integration_node', + rootDir: '../..', + roots: ['/packages/kbn-esql-validation-autocomplete'], + openHandlesTimeout: 0, + forceExit: true, }; diff --git a/packages/kbn-esql-validation-autocomplete/package.json b/packages/kbn-esql-validation-autocomplete/package.json index 51d2e86a26fd9..38470d679ff32 100644 --- a/packages/kbn-esql-validation-autocomplete/package.json +++ b/packages/kbn-esql-validation-autocomplete/package.json @@ -8,9 +8,8 @@ "make:tests": "ts-node --transpileOnly ./scripts/generate_function_validation_tests.ts", "postmake:tests": "yarn run lint:fix", "make:defs": "ts-node --transpileOnly ./scripts/generate_function_definitions.ts", - "postmake:defs": "yarn run lint:fix && yarn run i18n:fix", + "postmake:defs": "yarn run lint:fix", "lint:fix": "cd ../.. && node ./scripts/eslint --fix ./packages/kbn-esql-validation-autocomplete/src/**/*.ts", - "i18n:fix": "cd ../.. && node ./scripts/i18n_check.js --fix", "test:validation": "cd ../.. && yarn test:jest ./packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts" } } diff --git a/packages/kbn-esql-validation-autocomplete/scripts/generate_function_definitions.ts b/packages/kbn-esql-validation-autocomplete/scripts/generate_function_definitions.ts index 15333c62300fa..6806b11e49807 100644 --- a/packages/kbn-esql-validation-autocomplete/scripts/generate_function_definitions.ts +++ b/packages/kbn-esql-validation-autocomplete/scripts/generate_function_definitions.ts @@ -12,6 +12,7 @@ import { join } from 'path'; import _ from 'lodash'; import type { RecursivePartial } from '@kbn/utility-types'; import { FunctionDefinition } from '../src/definitions/types'; +import { esqlToKibanaType } from '../src/shared/esql_to_kibana_type'; const aliasTable: Record = { to_version: ['to_ver'], @@ -25,7 +26,7 @@ const aliasTable: Record = { const aliases = new Set(Object.values(aliasTable).flat()); const evalSupportedCommandsAndOptions = { - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], }; @@ -56,36 +57,6 @@ const extraFunctions: FunctionDefinition[] = [ }, ]; -const elasticsearchToKibanaType = (elasticsearchType: string) => { - if ( - [ - 'double', - 'unsigned_long', - 'long', - 'integer', - 'counter_integer', - 'counter_long', - 'counter_double', - ].includes(elasticsearchType) - ) { - return 'number'; - } - - if (['text', 'keyword'].includes(elasticsearchType)) { - return 'string'; - } - - if (['datetime', 'time_duration'].includes(elasticsearchType)) { - return 'date'; - } - - if (elasticsearchType === 'date_period') { - return 'time_literal'; // TODO - consider aligning with Elasticsearch - } - - return elasticsearchType; -}; - const validateLogFunctions = `(fnDef: ESQLFunction) => { const messages = []; // do not really care here about the base and field @@ -237,10 +208,10 @@ function getFunctionDefinition(ESFunctionDefinition: Record): Funct ...signature, params: signature.params.map((param: any) => ({ ...param, - type: elasticsearchToKibanaType(param.type), + type: esqlToKibanaType(param.type), description: undefined, })), - returnType: elasticsearchToKibanaType(signature.returnType), + returnType: esqlToKibanaType(signature.returnType), variadic: undefined, // we don't support variadic property minParams: signature.variadic ? signature.params.filter((param: any) => !param.optional).length @@ -317,8 +288,24 @@ function printGeneratedFunctionsFile(functionDefinitions: FunctionDefinition[]) }`; }; - const fileHeader = `// NOTE: This file is generated by the generate_function_definitions.ts script -// Do not edit it manually + const fileHeader = `/** + * __AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.__ + * + * @note This file is generated by the \`generate_function_definitions.ts\` + * script. Do not edit it manually. + * + * + * + * + * + * + * + * + * + * + * + * + */ import type { ESQLFunction } from '@kbn/esql-ast'; import { i18n } from '@kbn/i18n'; diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts index efff60c382d34..828511762a5f4 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts @@ -16,11 +16,21 @@ import { getUnitDuration, TRIGGER_SUGGESTION_COMMAND } from './factories'; import { camelCase, partition } from 'lodash'; import { getAstAndSyntaxErrors } from '@kbn/esql-ast'; import { groupingFunctionDefinitions } from '../definitions/grouping'; -import { FunctionArgSignature } from '../definitions/types'; +import { FunctionParameter } from '../definitions/types'; import { getParamAtPosition } from './helper'; import { nonNullable } from '../shared/helpers'; import { METADATA_FIELDS } from '../shared/constants'; +interface Integration { + name: string; + hidden: boolean; + title?: string; + dataStreams: Array<{ + name: string; + title?: string; + }>; +} + const triggerCharacters = [',', '(', '=', ' ']; const fields: Array<{ name: string; type: string; suggestedAs?: string }> = [ @@ -42,13 +52,10 @@ const fields: Array<{ name: string; type: string; suggestedAs?: string }> = [ { name: 'kubernetes.something.something', type: 'number' }, ]; -const indexes = ( - [] as Array<{ name: string; hidden: boolean; suggestedAs: string | undefined }> -).concat( +const indexes = ([] as Array<{ name: string; hidden: boolean; suggestedAs?: string }>).concat( ['a', 'index', 'otherIndex', '.secretIndex', 'my-index'].map((name) => ({ name, hidden: name.startsWith('.'), - suggestedAs: undefined, })), ['my-index[quoted]', 'my-index$', 'my_index{}'].map((name) => ({ name, @@ -56,6 +63,18 @@ const indexes = ( suggestedAs: `\`${name}\``, })) ); + +const integrations: Integration[] = ['nginx', 'k8s'].map((name) => ({ + name, + hidden: false, + title: `integration-${name}`, + dataStreams: [ + { + name: `${name}-1`, + title: `integration-${name}-1`, + }, + ], +})); const policies = [ { name: 'policy', @@ -347,9 +366,8 @@ describe('autocomplete', () => { }); describe('from', () => { - const suggestedIndexes = indexes - .filter(({ hidden }) => !hidden) - .map(({ name, suggestedAs }) => suggestedAs || name); + const suggestedIndexes = indexes.filter(({ hidden }) => !hidden).map(({ name }) => name); + // Monaco will filter further down here testSuggestions( 'f', @@ -372,6 +390,16 @@ describe('autocomplete', () => { METADATA_FIELDS.filter((field) => field !== '_index'), ' ' ); + + // with integrations support + const dataSources = indexes.concat(integrations); + const suggestedDataSources = dataSources + .filter(({ hidden }) => !hidden) + .map(({ name }) => name); + + testSuggestions('from ', suggestedDataSources, '', [undefined, dataSources, undefined]); + testSuggestions('from a,', suggestedDataSources, '', [undefined, dataSources, undefined]); + testSuggestions('from *,', suggestedDataSources, '', [undefined, dataSources, undefined]); }); describe('show', () => { @@ -1154,7 +1182,7 @@ describe('autocomplete', () => { (p) => p.constantOnly || /_literal/.test(p.type) ); - const getTypesFromParamDefs = (paramDefs: FunctionArgSignature[]) => + const getTypesFromParamDefs = (paramDefs: FunctionParameter[]) => Array.from(new Set(paramDefs.map((p) => p.type))); const suggestedConstants = param.literalSuggestions || param.literalOptions; diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts index e753255624383..727335fe6d961 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.ts @@ -18,8 +18,7 @@ import type { import { partition } from 'lodash'; import type { EditorContext, SuggestionRawDefinition } from './types'; import { - columnExists, - getColumnHit, + lookupColumn, getCommandDefinition, getCommandOption, getFunctionDefinition, @@ -42,6 +41,7 @@ import { getAllFunctions, isSingleItem, nonNullable, + getColumnExists, } from '../shared/helpers'; import { collectVariables, excludeVariablesFromCurrentCommand } from '../shared/variables'; import type { ESQLPolicy, ESQLRealField, ESQLVariable, ReferenceMaps } from '../validation/types'; @@ -84,11 +84,19 @@ import { getFunctionsToIgnoreForStats, getParamAtPosition, getQueryForFields, + getSourcesFromCommands, isAggFunctionUsedAlready, } from './helper'; -import { FunctionArgSignature } from '../definitions/types'; +import { FunctionParameter } from '../definitions/types'; type GetSourceFn = () => Promise; +type GetDataSourceFn = (sourceName: string) => Promise< + | { + name: string; + dataStreams?: Array<{ name: string; title?: string }>; + } + | undefined +>; type GetFieldsByTypeFn = ( type: string | string[], ignored?: string[] @@ -203,6 +211,7 @@ export async function suggest( resourceRetriever ); const getSources = getSourcesRetriever(resourceRetriever); + const getDatastreamsForIntegration = getDatastreamsForIntegrationRetriever(resourceRetriever); const { getPolicies, getPolicyMetadata } = getPolicyRetriever(resourceRetriever); if (astContext.type === 'newCommand') { @@ -223,6 +232,7 @@ export async function suggest( ast, astContext, getSources, + getDatastreamsForIntegration, getFieldsByType, getFieldsMap, getPolicies, @@ -303,7 +313,21 @@ function getSourcesRetriever(resourceRetriever?: ESQLCallbacks) { return async () => { const list = (await helper()) || []; // hide indexes that start with . - return buildSourcesDefinitions(list.filter(({ hidden }) => !hidden).map(({ name }) => name)); + return buildSourcesDefinitions( + list + .filter(({ hidden }) => !hidden) + .map(({ name, dataStreams, title }) => { + return { name, isIntegration: Boolean(dataStreams && dataStreams.length), title }; + }) + ); + }; +} + +function getDatastreamsForIntegrationRetriever(resourceRetriever?: ESQLCallbacks) { + const helper = getSourcesHelper(resourceRetriever); + return async (sourceName: string) => { + const list = (await helper()) || []; + return list.find(({ name }) => name === sourceName); }; } @@ -321,7 +345,9 @@ function workoutBuiltinOptions( references: Pick ): { skipAssign: boolean } { // skip assign operator if it's a function or an existing field to avoid promoting shadowing - return { skipAssign: Boolean(!isColumnItem(nodeArg) || getColumnHit(nodeArg.name, references)) }; + return { + skipAssign: Boolean(!isColumnItem(nodeArg) || lookupColumn(nodeArg, references)), + }; } function areCurrentArgsValid( @@ -388,7 +414,7 @@ function extractFinalTypeFromArg( return arg.literalType; } if (isColumnItem(arg)) { - const hit = getColumnHit(arg.name, references); + const hit = lookupColumn(arg, references); if (hit) { return hit.type; } @@ -475,6 +501,7 @@ async function getExpressionSuggestionsByType( node: ESQLSingleAstItem | undefined; }, getSources: GetSourceFn, + getDatastreamsForIntegration: GetDataSourceFn, getFieldsByType: GetFieldsByTypeFn, getFieldsMap: GetFieldsMapFn, getPolicies: GetPoliciesFn, @@ -829,9 +856,21 @@ async function getExpressionSuggestionsByType( const policies = await getPolicies(); suggestions.push(...(policies.length ? policies : [buildNoPoliciesAvailableDefinition()])); } else { - // FROM - // @TODO: filter down the suggestions here based on other existing sources defined - suggestions.push(...(await getSources())); + const index = getSourcesFromCommands(commands, 'index'); + // This is going to be empty for simple indices, and not empty for integrations + if (index && index.text) { + const source = index.text.replace(EDITOR_MARKER, ''); + const dataSource = await getDatastreamsForIntegration(source); + const newDefinitions = buildSourcesDefinitions( + dataSource?.dataStreams?.map(({ name }) => ({ name, isIntegration: false })) || [] + ); + suggestions.push(...newDefinitions); + } else { + // FROM + // @TODO: filter down the suggestions here based on other existing sources defined + const sourcesDefinitions = await getSources(); + suggestions.push(...sourcesDefinitions); + } } } } @@ -1129,10 +1168,10 @@ async function getFunctionArgsSuggestions( const isUnknownColumn = arg && isColumnItem(arg) && - !columnExists(arg, { + !getColumnExists(arg, { fields: fieldsMap, variables: variablesExcludingCurrentCommandOnes, - }).hit; + }); if (noArgDefined || isUnknownColumn) { // ... | EVAL fn( ) // ... | EVAL fn( field, ) @@ -1206,7 +1245,7 @@ async function getFunctionArgsSuggestions( (paramDef) => paramDef.constantOnly || /_literal$/.test(paramDef.type) ); - const getTypesFromParamDefs = (paramDefs: FunctionArgSignature[]) => { + const getTypesFromParamDefs = (paramDefs: FunctionParameter[]) => { return Array.from(new Set(paramDefs.map(({ type }) => type))); }; @@ -1480,7 +1519,10 @@ async function getOptionArgsSuggestions( } if (command.name === 'dissect') { - if (option.args.length < 1 && optionDef) { + if ( + option.args.filter((arg) => !(isSingleItem(arg) && arg.type === 'unknown')).length < 1 && + optionDef + ) { suggestions.push(colonCompleteItem, semiColonCompleteItem); } } diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts index 4b2e74729db78..34c28b4b50d09 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/factories.ts @@ -143,14 +143,22 @@ export const buildVariablesDefinitions = (variables: string[]): SuggestionRawDef sortText: 'D', })); -export const buildSourcesDefinitions = (sources: string[]): SuggestionRawDefinition[] => - sources.map((label) => ({ - label, - text: getSafeInsertText(label, { dashSupported: true }), - kind: 'Reference', - detail: i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.sourceDefinition', { - defaultMessage: `Index`, - }), +export const buildSourcesDefinitions = ( + sources: Array<{ name: string; isIntegration: boolean; title?: string }> +): SuggestionRawDefinition[] => + sources.map(({ name, isIntegration, title }) => ({ + label: title ?? name, + text: name, + isSnippet: isIntegration, + ...(isIntegration && { command: TRIGGER_SUGGESTION_COMMAND }), + kind: isIntegration ? 'Class' : 'Issue', + detail: isIntegration + ? i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.integrationDefinition', { + defaultMessage: `Integration`, + }) + : i18n.translate('kbn-esql-validation-autocomplete.esql.autocomplete.sourceDefinition', { + defaultMessage: `Index`, + }), sortText: 'A', })); diff --git a/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts b/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts index 7bba3a5ab15ec..fa6e364c78ca9 100644 --- a/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts +++ b/packages/kbn-esql-validation-autocomplete/src/autocomplete/helper.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { ESQLAstItem, ESQLCommand, ESQLFunction } from '@kbn/esql-ast'; +import type { ESQLAstItem, ESQLCommand, ESQLFunction, ESQLSource } from '@kbn/esql-ast'; import { FunctionDefinition } from '../definitions/types'; import { getFunctionDefinition, isAssignment, isFunctionItem } from '../shared/helpers'; @@ -63,3 +63,11 @@ export function getQueryForFields(queryString: string, commands: ESQLCommand[]) ? '' : queryString; } + +export function getSourcesFromCommands(commands: ESQLCommand[], sourceType: 'index' | 'policy') { + const fromCommand = commands.find(({ name }) => name === 'from'); + const args = (fromCommand?.args ?? []) as ESQLSource[]; + const sources = args.filter((arg) => arg.sourceType === sourceType); + + return sources.length === 1 ? sources[0] : undefined; +} diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts index 2d20b871e251b..08323d121a88a 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/aggs.ts @@ -28,7 +28,7 @@ function createNumericAggDefinition({ name, type: 'agg', description, - supportedCommands: ['stats'], + supportedCommands: ['stats', 'metrics'], signatures: [ { params: [ @@ -98,7 +98,7 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [ defaultMessage: 'Returns the maximum value in a field.', }), type: 'agg', - supportedCommands: ['stats'], + supportedCommands: ['stats', 'metrics'], signatures: [ { params: [{ name: 'column', type: 'number', noNestingFunctions: true }], @@ -117,7 +117,7 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [ defaultMessage: 'Returns the minimum value in a field.', }), type: 'agg', - supportedCommands: ['stats'], + supportedCommands: ['stats', 'metrics'], signatures: [ { params: [{ name: 'column', type: 'number', noNestingFunctions: true }], @@ -138,7 +138,7 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [ description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.countDoc', { defaultMessage: 'Returns the count of the values in a field.', }), - supportedCommands: ['stats'], + supportedCommands: ['stats', 'metrics'], signatures: [ { params: [ @@ -164,7 +164,7 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [ defaultMessage: 'Returns the count of distinct values in a field.', } ), - supportedCommands: ['stats'], + supportedCommands: ['stats', 'metrics'], signatures: [ { params: [ @@ -188,7 +188,7 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [ defaultMessage: 'Returns the count of distinct values in a field.', } ), - supportedCommands: ['stats'], + supportedCommands: ['stats', 'metrics'], signatures: [ { params: [{ name: 'column', type: 'cartesian_point', noNestingFunctions: true }], @@ -212,7 +212,7 @@ export const statsAggregationFunctionDefinitions: FunctionDefinition[] = [ description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.values', { defaultMessage: 'Returns all values in a group as an array.', }), - supportedCommands: ['stats'], + supportedCommands: ['stats', 'metrics'], signatures: [ { params: [{ name: 'expression', type: 'any', noNestingFunctions: true }], diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts index b430e6e7665df..a32cba3d191c3 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts @@ -22,7 +22,7 @@ function createMathDefinition( type: 'builtin', name, description, - supportedCommands: ['eval', 'where', 'row', 'stats', 'sort'], + supportedCommands: ['eval', 'where', 'row', 'stats', 'metrics', 'sort'], supportedOptions: ['by'], signatures: types.map((type) => { if (Array.isArray(type)) { @@ -507,7 +507,7 @@ const otherDefinitions: FunctionDefinition[] = [ description: i18n.translate('kbn-esql-validation-autocomplete.esql.definition.assignDoc', { defaultMessage: 'Assign (=)', }), - supportedCommands: ['eval', 'stats', 'row', 'dissect', 'where', 'enrich'], + supportedCommands: ['eval', 'stats', 'metrics', 'row', 'dissect', 'where', 'enrich'], supportedOptions: ['by', 'with'], signatures: [ { diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/commands.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/commands.ts index 414c0fa6a8a9a..2485b32837a5b 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/commands.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/commands.ts @@ -88,6 +88,36 @@ export const commandDefinitions: CommandDefinition[] = [ params: [{ name: 'functions', type: 'function' }], }, }, + { + name: 'metrics', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.metricsDoc', { + defaultMessage: + 'A metrics-specific source command, use this command to load data from TSDB indices. ' + + 'Similar to STATS command on can calculate aggregate statistics, such as average, count, and sum, over the incoming search results set. ' + + 'When used without a BY clause, only one row is returned, which is the aggregation over the entire incoming search results set. ' + + 'When you use a BY clause, one row is returned for each distinct value in the field specified in the BY clause. ' + + 'The command returns only the fields in the aggregation, and you can use a wide range of statistical functions with the stats command. ' + + 'When you perform more than one aggregation, separate each aggregation with a comma.', + }), + examples: [ + 'metrics index', + 'metrics index, index2', + 'metrics index avg = avg(a)', + 'metrics index sum(b) by b', + 'metrics index, index2 sum(b) by b % 2', + 'metrics [ [ by ]]', + 'metrics src1, src2 agg1, agg2 by field1, field2', + ], + options: [], + modes: [], + signature: { + multipleParams: true, + params: [ + { name: 'index', type: 'source', wildcards: true }, + { name: 'expression', type: 'function', optional: true }, + ], + }, + }, { name: 'stats', description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.statsDoc', { diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/functions.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/functions.ts index 77f4195f15a82..718174ae09f41 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/functions.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/functions.ts @@ -34,7 +34,7 @@ const absDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -63,7 +63,7 @@ const acosDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=.9\n| EVAL acos=ACOS(a)'], @@ -90,7 +90,7 @@ const asinDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=.9\n| EVAL asin=ASIN(a)'], @@ -117,7 +117,7 @@ const atanDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=12.9\n| EVAL atan=ATAN(a)'], @@ -149,7 +149,7 @@ const atan2Definition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW y=12.9, x=.6\n| EVAL atan2=ATAN2(y, x)'], @@ -176,7 +176,7 @@ const cbrtDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW d = 1000.0\n| EVAL c = cbrt(d)'], @@ -202,7 +202,7 @@ const ceilDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=1.8\n| EVAL a=CEIL(a)'], @@ -235,7 +235,7 @@ const cidrMatchDefinition: FunctionDefinition = { minParams: 2, }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -479,7 +479,7 @@ const concatDefinition: FunctionDefinition = { minParams: 2, }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -507,7 +507,7 @@ const cosDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=1.8 \n| EVAL cos=COS(a)'], @@ -533,7 +533,7 @@ const coshDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=1.8 \n| EVAL cosh=COSH(a)'], @@ -631,7 +631,7 @@ const dateDiffDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -664,7 +664,7 @@ const dateExtractDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -698,7 +698,7 @@ const dateFormatDefinition: FunctionDefinition = { returnType: 'string', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -732,7 +732,7 @@ const dateParseDefinition: FunctionDefinition = { returnType: 'date', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW date_string = "2022-05-06"\n| EVAL date = DATE_PARSE("yyyy-MM-dd", date_string)'], @@ -778,7 +778,7 @@ const dateTruncDefinition: FunctionDefinition = { returnType: 'date', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -802,7 +802,7 @@ const eDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW E()'], @@ -834,7 +834,7 @@ const endsWithDefinition: FunctionDefinition = { returnType: 'boolean', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['FROM employees\n| KEEP last_name\n| EVAL ln_E = ENDS_WITH(last_name, "d")'], @@ -860,7 +860,7 @@ const floorDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=1.8\n| EVAL a=FLOOR(a)'], @@ -886,7 +886,7 @@ const fromBase64Definition: FunctionDefinition = { returnType: 'string', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['row a = "ZWxhc3RpYw==" \n| eval d = from_base64(a)'], @@ -1016,7 +1016,7 @@ const greatestDefinition: FunctionDefinition = { minParams: 1, }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a = 10, b = 20\n| EVAL g = GREATEST(a, b)'], @@ -1184,7 +1184,7 @@ const leastDefinition: FunctionDefinition = { minParams: 1, }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a = 10, b = 20\n| EVAL l = LEAST(a, b)'], @@ -1216,7 +1216,7 @@ const leftDefinition: FunctionDefinition = { returnType: 'string', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -1244,7 +1244,7 @@ const lengthDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['FROM employees\n| KEEP first_name, last_name\n| EVAL fn_length = LENGTH(first_name)'], @@ -1296,7 +1296,7 @@ const locateDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['row a = "hello"\n| eval a_ll = locate(a, "ll")'], @@ -1338,14 +1338,14 @@ const logDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: (fnDef: ESQLFunction) => { const messages = []; // do not really care here about the base and field // just need to check both values are not negative for (const arg of fnDef.args) { - if (isLiteralItem(arg) && arg.value < 0) { + if (isLiteralItem(arg) && Number(arg.value) < 0) { messages.push({ type: 'warning' as const, code: 'logOfNegativeValue', @@ -1391,14 +1391,14 @@ const log10Definition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: (fnDef: ESQLFunction) => { const messages = []; // do not really care here about the base and field // just need to check both values are not negative for (const arg of fnDef.args) { - if (isLiteralItem(arg) && arg.value < 0) { + if (isLiteralItem(arg) && Number(arg.value) < 0) { messages.push({ type: 'warning' as const, code: 'logOfNegativeValue', @@ -1440,7 +1440,7 @@ const ltrimDefinition: FunctionDefinition = { returnType: 'string', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -1635,7 +1635,7 @@ const mvAvgDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=[3, 5, 1, 6]\n| EVAL avg_a = MV_AVG(a)'], @@ -1667,7 +1667,7 @@ const mvConcatDefinition: FunctionDefinition = { returnType: 'string', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -1787,7 +1787,7 @@ const mvCountDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=["foo", "zoo", "bar"]\n| EVAL count_a = MV_COUNT(a)'], @@ -1903,7 +1903,7 @@ const mvDedupeDefinition: FunctionDefinition = { returnType: 'version', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=["foo", "foo", "bar", "foo"]\n| EVAL dedupe_a = MV_DEDUPE(a)'], @@ -2020,7 +2020,7 @@ const mvFirstDefinition: FunctionDefinition = { returnType: 'version', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a="foo;bar;baz"\n| EVAL first_a = MV_FIRST(SPLIT(a, ";"))'], @@ -2137,7 +2137,7 @@ const mvLastDefinition: FunctionDefinition = { returnType: 'version', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a="foo;bar;baz"\n| EVAL last_a = MV_LAST(SPLIT(a, ";"))'], @@ -2214,7 +2214,7 @@ const mvMaxDefinition: FunctionDefinition = { returnType: 'version', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -2244,7 +2244,7 @@ const mvMedianDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -2324,7 +2324,7 @@ const mvMinDefinition: FunctionDefinition = { returnType: 'version', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -2544,7 +2544,7 @@ const mvSliceDefinition: FunctionDefinition = { returnType: 'version', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -2659,7 +2659,7 @@ const mvSortDefinition: FunctionDefinition = { returnType: 'version', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a = [4, 2, -3, 2]\n| EVAL sa = mv_sort(a), sd = mv_sort(a, "DESC")'], @@ -2686,7 +2686,7 @@ const mvSumDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=[3, 5, 6]\n| EVAL sum_a = MV_SUM(a)'], @@ -2738,7 +2738,7 @@ const mvZipDefinition: FunctionDefinition = { returnType: 'string', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -2760,7 +2760,7 @@ const nowDefinition: FunctionDefinition = { returnType: 'date', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW current_date = NOW()', 'FROM sample_data\n| WHERE @timestamp > NOW() - 1 hour'], @@ -2780,7 +2780,7 @@ const piDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW PI()'], @@ -2811,7 +2811,7 @@ const powDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -2883,7 +2883,7 @@ const replaceDefinition: FunctionDefinition = { returnType: 'string', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW str = "Hello World"\n| EVAL str = REPLACE(str, "World", "Universe")\n| KEEP str'], @@ -2915,7 +2915,7 @@ const rightDefinition: FunctionDefinition = { returnType: 'string', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -2959,7 +2959,7 @@ const roundDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -2987,7 +2987,7 @@ const rtrimDefinition: FunctionDefinition = { returnType: 'string', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -3016,7 +3016,7 @@ const signumDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW d = 100.0\n| EVAL s = SIGNUM(d)'], @@ -3042,7 +3042,7 @@ const sinDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=1.8 \n| EVAL sin=SIN(a)'], @@ -3068,7 +3068,7 @@ const sinhDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=1.8 \n| EVAL sinh=SINH(a)'], @@ -3099,7 +3099,7 @@ const splitDefinition: FunctionDefinition = { returnType: 'string', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW words="foo;bar;baz;qux;quux;corge"\n| EVAL word = SPLIT(words, ";")'], @@ -3126,7 +3126,7 @@ const sqrtDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW d = 100.0\n| EVAL s = SQRT(d)'], @@ -3263,7 +3263,7 @@ const stContainsDefinition: FunctionDefinition = { returnType: 'boolean', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -3402,7 +3402,7 @@ const stDisjointDefinition: FunctionDefinition = { returnType: 'boolean', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -3541,7 +3541,7 @@ const stIntersectsDefinition: FunctionDefinition = { returnType: 'boolean', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -3680,7 +3680,7 @@ const stWithinDefinition: FunctionDefinition = { returnType: 'boolean', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -3719,7 +3719,7 @@ const stXDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -3758,7 +3758,7 @@ const stYDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -3792,7 +3792,7 @@ const startsWithDefinition: FunctionDefinition = { returnType: 'boolean', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['FROM employees\n| KEEP last_name\n| EVAL ln_S = STARTS_WITH(last_name, "B")'], @@ -3829,7 +3829,7 @@ const substringDefinition: FunctionDefinition = { returnType: 'string', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -3859,7 +3859,7 @@ const tanDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=1.8 \n| EVAL tan=TAN(a)'], @@ -3885,7 +3885,7 @@ const tanhDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=1.8 \n| EVAL tanh=TANH(a)'], @@ -3905,7 +3905,7 @@ const tauDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW TAU()'], @@ -3931,7 +3931,7 @@ const toBase64Definition: FunctionDefinition = { returnType: 'string', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['row a = "elastic" \n| eval e = to_base64(a)'], @@ -3978,7 +3978,7 @@ const toBooleanDefinition: FunctionDefinition = { returnType: 'boolean', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW str = ["true", "TRuE", "false", "", "yes", "1"]\n| EVAL bool = TO_BOOLEAN(str)'], @@ -4018,7 +4018,7 @@ const toCartesianpointDefinition: FunctionDefinition = { returnType: 'cartesian_point', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -4070,7 +4070,7 @@ const toCartesianshapeDefinition: FunctionDefinition = { returnType: 'cartesian_shape', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -4119,7 +4119,7 @@ const toDatetimeDefinition: FunctionDefinition = { returnType: 'date', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -4148,7 +4148,7 @@ const toDegreesDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW rad = [1.57, 3.14, 4.71]\n| EVAL deg = TO_DEGREES(rad)'], @@ -4205,7 +4205,7 @@ const toDoubleDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -4244,7 +4244,7 @@ const toGeopointDefinition: FunctionDefinition = { returnType: 'geo_point', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW wkt = "POINT(42.97109630194 14.7552534413725)"\n| EVAL pt = TO_GEOPOINT(wkt)'], @@ -4291,7 +4291,7 @@ const toGeoshapeDefinition: FunctionDefinition = { returnType: 'geo_shape', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -4350,7 +4350,7 @@ const toIntegerDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW long = [5013792, 2147483647, 501379200000]\n| EVAL int = TO_INTEGER(long)'], @@ -4386,7 +4386,7 @@ const toIpDefinition: FunctionDefinition = { returnType: 'ip', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -4445,7 +4445,7 @@ const toLongDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -4473,7 +4473,7 @@ const toLowerDefinition: FunctionDefinition = { returnType: 'string', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW message = "Some Text"\n| EVAL message_lower = TO_LOWER(message)'], @@ -4499,7 +4499,7 @@ const toRadiansDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW deg = [90.0, 180.0, 270.0]\n| EVAL rad = TO_RADIANS(deg)'], @@ -4615,7 +4615,7 @@ const toStringDefinition: FunctionDefinition = { returnType: 'string', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW a=10\n| EVAL j = TO_STRING(a)', 'ROW a=[10, 9, 8]\n| EVAL j = TO_STRING(a)'], @@ -4675,7 +4675,7 @@ const toUnsignedLongDefinition: FunctionDefinition = { returnType: 'number', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -4703,7 +4703,7 @@ const toUpperDefinition: FunctionDefinition = { returnType: 'string', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW message = "Some Text"\n| EVAL message_upper = TO_UPPER(message)'], @@ -4739,7 +4739,7 @@ const toVersionDefinition: FunctionDefinition = { returnType: 'version', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: ['ROW v = TO_VERSION("1.2.3")'], @@ -4765,7 +4765,7 @@ const trimDefinition: FunctionDefinition = { returnType: 'string', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ @@ -4798,7 +4798,7 @@ const caseDefinition: FunctionDefinition = { returnType: 'any', }, ], - supportedCommands: ['stats', 'eval', 'where', 'row', 'sort'], + supportedCommands: ['stats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, examples: [ diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/options.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/options.ts index 8e30a43db2c8f..cd81ae3096e54 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/options.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/options.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import type { ESQLCommandOption, ESQLMessage } from '@kbn/esql-ast'; -import { isLiteralItem, isColumnItem } from '../shared/helpers'; +import { isLiteralItem, isColumnItem, isInlineCastItem } from '../shared/helpers'; import { getMessageFromId } from '../validation/errors'; import type { CommandOptionsDefinition } from './types'; @@ -130,11 +130,12 @@ export const appendSeparatorOption: CommandOptionsDefinition = { !Array.isArray(firstArg) && (!isLiteralItem(firstArg) || firstArg.literalType !== 'string') ) { - const value = 'value' in firstArg ? firstArg.value : firstArg.name; + const value = + 'value' in firstArg && !isInlineCastItem(firstArg) ? firstArg.value : firstArg.name; messages.push( getMessageFromId({ messageId: 'wrongDissectOptionArgumentType', - values: { value }, + values: { value: value ?? '' }, locations: firstArg.location, }) ); diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/types.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/types.ts index dbfbf08cca08a..05adb6eed5836 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/types.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/types.ts @@ -158,4 +158,4 @@ export type SignatureType = | CommandOptionsDefinition['signature']; export type SignatureArgType = SignatureType['params'][number]; -export type FunctionArgSignature = FunctionDefinition['signatures'][number]['params'][number]; +export type FunctionParameter = FunctionDefinition['signatures'][number]['params'][number]; diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/esql_to_kibana_type.ts b/packages/kbn-esql-validation-autocomplete/src/shared/esql_to_kibana_type.ts new file mode 100644 index 0000000000000..f13052288f29f --- /dev/null +++ b/packages/kbn-esql-validation-autocomplete/src/shared/esql_to_kibana_type.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +const ESQL_NUMBER_TYPES = [ + 'double', + 'unsigned_long', + 'long', + 'integer', + 'int', + 'counter_integer', + 'counter_long', + 'counter_double', +]; + +const ESQL_TEXT_TYPES = ['text', 'keyword', 'string']; + +export const esqlToKibanaType = (elasticsearchType: string) => { + if (ESQL_NUMBER_TYPES.includes(elasticsearchType)) { + return 'number'; + } + + if (ESQL_TEXT_TYPES.includes(elasticsearchType)) { + return 'string'; + } + + if (['datetime', 'time_duration'].includes(elasticsearchType)) { + return 'date'; + } + + if (elasticsearchType === 'bool') { + return 'boolean'; + } + + if (elasticsearchType === 'date_period') { + return 'time_literal'; // TODO - consider aligning with Elasticsearch + } + + return elasticsearchType; +}; diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts index 348a9b45e5d12..b7ef6e0ba9e88 100644 --- a/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts +++ b/packages/kbn-esql-validation-autocomplete/src/shared/helpers.ts @@ -17,6 +17,7 @@ import type { ESQLSource, ESQLTimeInterval, } from '@kbn/esql-ast'; +import { ESQLInlineCast } from '@kbn/esql-ast/src/types'; import { statsAggregationFunctionDefinitions } from '../definitions/aggs'; import { builtinFunctions } from '../definitions/builtin'; import { commandDefinitions } from '../definitions/commands'; @@ -35,14 +36,14 @@ import { import type { CommandDefinition, CommandOptionsDefinition, - FunctionArgSignature, + FunctionParameter, FunctionDefinition, FunctionParameterType, FunctionReturnType, - SignatureArgType, } from '../definitions/types'; import type { ESQLRealField, ESQLVariable, ReferenceMaps } from '../validation/types'; import { removeMarkerArgFromArgsList } from './context'; +import { esqlToKibanaType } from './esql_to_kibana_type'; import type { ReasonTypes } from './types'; export function nonNullable(v: T): v is NonNullable { @@ -76,6 +77,10 @@ export function isLiteralItem(arg: ESQLAstItem): arg is ESQLLiteral { return isSingleItem(arg) && arg.type === 'literal'; } +export function isInlineCastItem(arg: ESQLAstItem): arg is ESQLInlineCast { + return isSingleItem(arg) && arg.type === 'inlineCast'; +} + export function isTimeIntervalItem(arg: ESQLAstItem): arg is ESQLTimeInterval { return isSingleItem(arg) && arg.type === 'timeInterval'; } @@ -235,11 +240,14 @@ function compareLiteralType(argType: string, item: ESQLLiteral) { return ['string', 'date'].includes(argType); } -export function getColumnHit( - columnName: string, - { fields, variables }: Pick, - position?: number +/** + * This function returns the variable or field matching a column + */ +export function lookupColumn( + column: ESQLColumn, + { fields, variables }: Pick ): ESQLRealField | ESQLVariable | undefined { + const columnName = getQuotedColumnName(column); return fields.get(columnName) || variables.get(columnName)?.[0]; } @@ -350,7 +358,7 @@ export function getAllArrayTypes( types.push(subArg.literalType); } if (subArg.type === 'column') { - const hit = getColumnHit(subArg.name, references); + const hit = lookupColumn(subArg, references); types.push(hit?.type || 'unsupported'); } if (subArg.type === 'timeInterval') { @@ -377,7 +385,7 @@ export function inKnownTimeInterval(item: ESQLTimeInterval): boolean { * * TODO - Consider merging with isEqualType to create a unified arg validation function */ -export function isValidLiteralOption(arg: ESQLLiteral, argDef: FunctionArgSignature) { +export function isValidLiteralOption(arg: ESQLLiteral, argDef: FunctionParameter) { return ( arg.literalType === 'string' && argDef.literalOptions && @@ -388,17 +396,16 @@ export function isValidLiteralOption(arg: ESQLLiteral, argDef: FunctionArgSignat } /** - * Checks if an AST argument is of the correct type + * Checks if an AST function argument is of the correct type * given the definition. */ -export function isEqualType( +export function checkFunctionArgMatchesDefinition( arg: ESQLSingleAstItem, - argDef: SignatureArgType, + parameterDefinition: FunctionParameter, references: ReferenceMaps, - parentCommand?: string, - nameHit?: string + parentCommand?: string ) { - const argType = 'innerType' in argDef && argDef.innerType ? argDef.innerType : argDef.type; + const argType = parameterDefinition.type; if (argType === 'any') { return true; } @@ -417,11 +424,7 @@ export function isEqualType( return argType === 'time_literal' && inKnownTimeInterval(arg); } if (arg.type === 'column') { - if (argType === 'column') { - // anything goes, so avoid any effort here - return true; - } - const hit = getColumnHit(nameHit ?? arg.name, references); + const hit = lookupColumn(arg, references); const validHit = hit; if (!validHit) { return false; @@ -430,6 +433,10 @@ export function isEqualType( // if final type is of type any make it pass for now return wrappedTypes.some((ct) => ['any', 'null'].includes(ct) || argType === ct); } + if (arg.type === 'inlineCast') { + // TODO - remove with https://github.com/elastic/kibana/issues/174710 + return argType === esqlToKibanaType(arg.castType); + } } function fuzzySearch(fuzzyName: string, resources: IterableIterator) { @@ -499,25 +506,48 @@ export function hasCCSSource(name: string) { return name.includes(':'); } -export function columnExists( +/** + * This will return the name without any quotes. + * + * E.g. "`bytes`" will become "bytes" + * + * @param column + * @returns + */ +export const getUnquotedColumnName = (column: ESQLColumn) => column.name; + +/** + * This returns the name with any quotes that were present. + * + * E.g. "`bytes`" will be "`bytes`" + * + * @param column + * @returns + */ +export const getQuotedColumnName = (column: ESQLColumn) => + column.quoted ? column.text : column.name; + +/** + * TODO - consider calling lookupColumn under the hood of this function. Seems like they should really do the same thing. + */ +export function getColumnExists( column: ESQLColumn, { fields, variables }: Pick ) { - if (fields.has(column.name) || variables.has(column.name)) { - return { hit: true, nameHit: column.name }; - } - if (column.quoted) { - const originalName = column.text; - if (variables.has(originalName)) { - return { hit: true, nameHit: originalName }; + const namesToCheck = [getUnquotedColumnName(column), getQuotedColumnName(column)]; + + for (const name of namesToCheck) { + if (fields.has(name) || variables.has(name)) { + return true; + } + + // TODO — I don't see this fuzzy searching in lookupColumn... should it be there? + if (Boolean(fuzzySearch(name, fields.keys()) || fuzzySearch(name, variables.keys()))) { + return true; } } - if ( - Boolean(fuzzySearch(column.name, fields.keys()) || fuzzySearch(column.name, variables.keys())) - ) { - return { hit: true, nameHit: column.name }; - } - return { hit: false }; + + return false; } export function sourceExists(index: string, sources: Set) { @@ -541,3 +571,6 @@ export function shouldBeQuotedText( ) { return dashSupported ? /[^a-zA-Z\d_\.@-]/.test(text) : /[^a-zA-Z\d_\.@]/.test(text); } + +export const isAggFunction = (arg: ESQLFunction): boolean => + getFunctionDefinition(arg.name)?.type === 'agg'; diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/types.ts b/packages/kbn-esql-validation-autocomplete/src/shared/types.ts index 758218c52062f..d2ce2e4b104df 100644 --- a/packages/kbn-esql-validation-autocomplete/src/shared/types.ts +++ b/packages/kbn-esql-validation-autocomplete/src/shared/types.ts @@ -11,7 +11,15 @@ type CallbackFn = (ctx?: Options) => Result[] | P /** @public **/ export interface ESQLCallbacks { - getSources?: CallbackFn<{}, { name: string; hidden: boolean }>; + getSources?: CallbackFn< + {}, + { + name: string; + hidden: boolean; + title?: string; + dataStreams?: Array<{ name: string; title?: string }>; + } + >; getFieldsFor?: CallbackFn<{ query: string }, { name: string; type: string }>; getPolicies?: CallbackFn< {}, diff --git a/packages/kbn-esql-validation-autocomplete/src/shared/variables.ts b/packages/kbn-esql-validation-autocomplete/src/shared/variables.ts index 9debf8a67083f..22c38cd286e19 100644 --- a/packages/kbn-esql-validation-autocomplete/src/shared/variables.ts +++ b/packages/kbn-esql-validation-autocomplete/src/shared/variables.ts @@ -134,6 +134,21 @@ function addVariableFromExpression( } } +export const collectVariablesFromList = ( + list: ESQLAstItem[], + fields: Map, + queryString: string, + variables: Map +) => { + for (const arg of list) { + if (isAssignment(arg)) { + addVariableFromAssignment(arg, variables, fields); + } else if (isExpression(arg)) { + addVariableFromExpression(arg, queryString, variables); + } + } +}; + export function collectVariables( commands: ESQLCommand[], fields: Map, @@ -141,28 +156,14 @@ export function collectVariables( ): Map { const variables = new Map(); for (const command of commands) { - if (['row', 'eval', 'stats'].includes(command.name)) { - for (const arg of command.args) { - if (isAssignment(arg)) { - addVariableFromAssignment(arg, variables, fields); - } - if (isExpression(arg)) { - addVariableFromExpression(arg, queryString, variables); - } - } + if (['row', 'eval', 'stats', 'metrics'].includes(command.name)) { + collectVariablesFromList(command.args, fields, queryString, variables); if (command.name === 'stats') { const commandOptionsWithAssignment = command.args.filter( (arg) => isOptionItem(arg) && arg.name === 'by' ) as ESQLCommandOption[]; for (const commandOption of commandOptionsWithAssignment) { - for (const optArg of commandOption.args) { - if (isAssignment(optArg)) { - addVariableFromAssignment(optArg, variables, fields); - } - if (isExpression(optArg)) { - addVariableFromExpression(optArg, queryString, variables); - } - } + collectVariablesFromList(commandOption.args, fields, queryString, variables); } } } diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/callbacks.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/callbacks.test.ts new file mode 100644 index 0000000000000..96338afde56a3 --- /dev/null +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/callbacks.test.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { setup } from './helpers'; + +describe('FROM', () => { + test('does not load fields when validating only a single FROM, SHOW, ROW command', async () => { + const { validate, callbacks } = await setup(); + + await validate('FROM kib'); + await validate('FROM kibana_ecommerce METADATA _i'); + await validate('FROM kibana_ecommerce METADATA _id | '); + await validate('SHOW'); + await validate('ROW \t'); + + expect(callbacks.getFieldsFor.mock.calls.length).toBe(0); + }); + + test('loads fields with FROM source when commands after pipe present', async () => { + const { validate, callbacks } = await setup(); + + await validate('FROM kibana_ecommerce METADATA _id | eval'); + + expect(callbacks.getFieldsFor.mock.calls.length).toBe(1); + }); +}); diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/helpers.ts new file mode 100644 index 0000000000000..9d28f88115b42 --- /dev/null +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/helpers.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EditorError, ESQLMessage, getAstAndSyntaxErrors } from '@kbn/esql-ast'; +import { ESQLCallbacks } from '../../shared/types'; +import { getCallbackMocks } from '../../__tests__/helpers'; +import { ValidationOptions } from '../types'; +import { validateQuery } from '../validation'; + +/** Validation test API factory, can be called at the start of each unit test. */ +export type Setup = typeof setup; + +/** + * Sets up an API for ES|QL query validation testing. + * + * @returns API for testing validation logic. + */ +export const setup = async () => { + const callbacks = getCallbackMocks(); + + const validate = async ( + query: string, + opts: ValidationOptions = {}, + cb: ESQLCallbacks = callbacks + ) => { + return await validateQuery(query, getAstAndSyntaxErrors, opts, cb); + }; + + const assertErrors = (errors: unknown[], expectedErrors: string[]) => { + const errorMessages: string[] = []; + for (const error of errors) { + if (error && typeof error === 'object') { + const message = + typeof (error as ESQLMessage).text === 'string' + ? (error as ESQLMessage).text + : typeof (error as EditorError).message === 'string' + ? (error as EditorError).message + : String(error); + errorMessages.push(message); + } else { + errorMessages.push(String(error)); + } + } + expect(errorMessages.sort()).toStrictEqual(expectedErrors.sort()); + }; + + const expectErrors = async ( + query: string, + expectedErrors: string[], + expectedWarnings?: string[], + opts: ValidationOptions = {}, + cb: ESQLCallbacks = callbacks + ) => { + const { errors, warnings } = await validateQuery(query, getAstAndSyntaxErrors, opts, cb); + assertErrors(errors, expectedErrors); + if (expectedWarnings) { + assertErrors(warnings, expectedWarnings); + } + }; + + return { + callbacks, + validate, + expectErrors, + }; +}; diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.from.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.from.ts new file mode 100644 index 0000000000000..74301ced77e63 --- /dev/null +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.from.ts @@ -0,0 +1,194 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { METADATA_FIELDS } from '../../../shared/constants'; +import * as helpers from '../helpers'; + +export const validationFromCommandTestSuite = (setup: helpers.Setup) => { + describe('validation', () => { + describe('command', () => { + describe('FROM [ METADATA ]', () => { + test('errors on invalid command start', async () => { + const { expectErrors } = await setup(); + + await expectErrors('f', [ + "SyntaxError: mismatched input 'f' expecting {'explain', 'from', 'meta', 'metrics', 'row', 'show'}", + ]); + await expectErrors('from ', [ + "SyntaxError: missing INDEX_UNQUOTED_IDENTIFIER at ''", + ]); + }); + + describe('... ...', () => { + test('no errors on correct indices usage', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from index', []); + await expectErrors('FROM index', []); + await expectErrors('FrOm index', []); + await expectErrors('from index, other_index', []); + await expectErrors('from index, other_index,.secret_index', []); + await expectErrors('from .secret_index', []); + await expectErrors('from .secret_index', []); + await expectErrors('from .secret_index', []); + await expectErrors('from ind*, other*', []); + await expectErrors('from index*', []); + await expectErrors('FROM *a_i*dex*', []); + await expectErrors('FROM in*ex*', []); + await expectErrors('FROM *n*ex', []); + await expectErrors('FROM *n*ex*', []); + await expectErrors('FROM i*d*x*', []); + await expectErrors('FROM i*d*x', []); + await expectErrors('FROM i***x*', []); + await expectErrors('FROM i****', []); + await expectErrors('FROM i**', []); + await expectErrors('fRoM index**', []); + await expectErrors('fRoM *ex', []); + await expectErrors('fRoM *ex*', []); + await expectErrors('fRoM in*ex', []); + await expectErrors('fRoM ind*ex', []); + await expectErrors('fRoM *,-.*', []); + await expectErrors('fRoM remote-*:indexes*', []); + await expectErrors('fRoM remote-*:indexes', []); + await expectErrors('fRoM remote-ccs:indexes', []); + await expectErrors('fRoM a_index, remote-ccs:indexes', []); + await expectErrors('fRoM .secret_index', []); + await expectErrors('from my-index', []); + }); + + test('errors on trailing comma', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from index,', [ + "SyntaxError: missing INDEX_UNQUOTED_IDENTIFIER at ''", + ]); + await expectErrors(`FROM index\n, \tother_index\t,\n \t `, [ + "SyntaxError: missing INDEX_UNQUOTED_IDENTIFIER at ''", + ]); + + await expectErrors(`from assignment = 1`, [ + "SyntaxError: mismatched input '=' expecting ", + 'Unknown index [assignment]', + ]); + }); + + test('errors on invalid syntax', async () => { + const { expectErrors } = await setup(); + + await expectErrors('FROM `index`', [ + "SyntaxError: token recognition error at: '`'", + "SyntaxError: token recognition error at: '`'", + ]); + await expectErrors(`from assignment = 1`, [ + "SyntaxError: mismatched input '=' expecting ", + 'Unknown index [assignment]', + ]); + }); + + test('errors on unknown index', async () => { + const { expectErrors } = await setup(); + + await expectErrors(`FROM index, missingIndex`, ['Unknown index [missingIndex]']); + await expectErrors(`from average()`, ['Unknown index [average()]']); + await expectErrors(`fRom custom_function()`, ['Unknown index [custom_function()]']); + await expectErrors(`FROM indexes*`, ['Unknown index [indexes*]']); + await expectErrors('from numberField', ['Unknown index [numberField]']); + await expectErrors('FROM policy', ['Unknown index [policy]']); + }); + }); + + describe('... METADATA ', () => { + test('no errors on correct METADATA ... usage', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from index metadata _id', []); + await expectErrors('from index metadata _id, \t\n _index\n ', []); + }); + + test('errors when wrapped in brackets', async () => { + const { expectErrors } = await setup(); + + await expectErrors(`from index (metadata _id)`, [ + "SyntaxError: mismatched input '(metadata' expecting ", + ]); + }); + + for (const isWrapped of [true, false]) { + function setWrapping(option: string) { + return isWrapped ? `[${option}]` : option; + } + + function addBracketsWarning() { + return isWrapped + ? ["Square brackets '[]' need to be removed from FROM METADATA declaration"] + : []; + } + + describe(`wrapped = ${isWrapped}`, () => { + test('no errors on correct usage, waning on square brackets', async () => { + const { expectErrors } = await setup(); + + await expectErrors(`from index ${setWrapping('METADATA _id')}`, []); + await expectErrors( + `from index ${setWrapping('METADATA _id')}`, + [], + addBracketsWarning() + ); + await expectErrors( + `from index ${setWrapping('metadata _id')}`, + [], + addBracketsWarning() + ); + await expectErrors( + `from index ${setWrapping('METADATA _id, _source')}`, + [], + addBracketsWarning() + ); + await expectErrors( + `from remote-ccs:indexes ${setWrapping('METADATA _id')}`, + [], + addBracketsWarning() + ); + await expectErrors( + `from *:indexes ${setWrapping('METADATA _id')}`, + [], + addBracketsWarning() + ); + }); + + test('validates fields', async () => { + const { expectErrors } = await setup(); + + await expectErrors( + `from index ${setWrapping('METADATA _id, _source2')}`, + [ + `Metadata field [_source2] is not available. Available metadata fields are: [${METADATA_FIELDS.join( + ', ' + )}]`, + ], + addBracketsWarning() + ); + await expectErrors( + `from index ${setWrapping('metadata _id, _source')} ${setWrapping( + 'METADATA _id2' + )}`, + [ + isWrapped + ? "SyntaxError: mismatched input '[' expecting " + : "SyntaxError: mismatched input 'METADATA' expecting ", + ], + addBracketsWarning() + ); + }); + }); + } + }); + }); + }); + }); +}; diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.metrics.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.metrics.ts new file mode 100644 index 0000000000000..5d1ddb0865ea5 --- /dev/null +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.metrics.ts @@ -0,0 +1,293 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as helpers from '../helpers'; + +export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => { + describe('validation', () => { + describe('command', () => { + describe('METRICS [ [ BY ]]', () => { + test('errors on invalid command start', async () => { + const { expectErrors } = await setup(); + + await expectErrors('m', [ + "SyntaxError: mismatched input 'm' expecting {'explain', 'from', 'meta', 'metrics', 'row', 'show'}", + ]); + await expectErrors('metrics ', [ + "SyntaxError: missing INDEX_UNQUOTED_IDENTIFIER at ''", + ]); + }); + + describe('... ...', () => { + test('no errors on correct indices usage', async () => { + const { expectErrors } = await setup(); + + await expectErrors('metrics index', []); + await expectErrors('metrics index, other_index', []); + await expectErrors('metrics index, other_index,.secret_index', []); + await expectErrors('metrics .secret_index', []); + await expectErrors('METRICS .secret_index', []); + await expectErrors('mEtRiCs .secret_index', []); + await expectErrors('metrics ind*, other*', []); + await expectErrors('metrics index*', []); + await expectErrors('metrics *a_i*dex*', []); + await expectErrors('metrics in*ex*', []); + await expectErrors('metrics *n*ex', []); + await expectErrors('metrics *n*ex*', []); + await expectErrors('metrics i*d*x*', []); + await expectErrors('metrics i*d*x', []); + await expectErrors('metrics i***x*', []); + await expectErrors('metrics i****', []); + await expectErrors('metrics i**', []); + await expectErrors('metrics index**', []); + await expectErrors('metrics *ex', []); + await expectErrors('metrics *ex*', []); + await expectErrors('metrics in*ex', []); + await expectErrors('metrics ind*ex', []); + await expectErrors('metrics *,-.*', []); + await expectErrors('metrics remote-*:indexes*', []); + await expectErrors('metrics remote-*:indexes', []); + await expectErrors('metrics remote-ccs:indexes', []); + await expectErrors('metrics a_index, remote-ccs:indexes', []); + await expectErrors('metrics .secret_index', []); + }); + + test('errors on trailing comma', async () => { + const { expectErrors } = await setup(); + + await expectErrors('metrics index,', [ + "SyntaxError: missing INDEX_UNQUOTED_IDENTIFIER at ''", + ]); + await expectErrors(`metrics index\n, \tother_index\t,\n \t `, [ + "SyntaxError: missing INDEX_UNQUOTED_IDENTIFIER at ''", + ]); + }); + + test('errors on invalid syntax', async () => { + const { expectErrors } = await setup(); + + await expectErrors(`metrics index = 1`, [ + "SyntaxError: token recognition error at: '='", + "SyntaxError: token recognition error at: '1'", + ]); + await expectErrors('metrics `index`', [ + "SyntaxError: token recognition error at: '`'", + "SyntaxError: token recognition error at: '`'", + ]); + }); + + test('errors on unknown index', async () => { + const { expectErrors } = await setup(); + + await expectErrors(`METRICS index, missingIndex`, ['Unknown index [missingIndex]']); + await expectErrors(`METRICS average()`, ['Unknown index [average()]']); + await expectErrors(`metrics custom_function()`, ['Unknown index [custom_function()]']); + await expectErrors(`metrics indexes*`, ['Unknown index [indexes*]']); + await expectErrors('metrics numberField', ['Unknown index [numberField]']); + await expectErrors('metrics policy', ['Unknown index [policy]']); + }); + }); + + describe('... ...', () => { + test('no errors on correct usage', async () => { + const { expectErrors } = await setup(); + + await expectErrors('METRICS a_index count()', []); + await expectErrors('metrics a_index avg(numberField) by 1', []); + await expectErrors('metrics a_index count(`numberField`)', []); + await expectErrors('metrics a_index count(*)', []); + await expectErrors('metrics index var0 = count(*)', []); + await expectErrors('metrics a_index var0 = count()', []); + await expectErrors('metrics a_index var0 = avg(numberField), count(*)', []); + await expectErrors(`metrics a_index sum(case(false, 0, 1))`, []); + await expectErrors(`metrics a_index var0 = sum( case(false, 0, 1))`, []); + await expectErrors('metrics a_index count(stringField == "a" or null)', []); + await expectErrors('metrics other_index max(numberField) by stringField', []); + }); + + test('syntax errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors('metrics a_index numberField=', [ + expect.any(String), + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + ]); + await expectErrors('metrics a_index numberField=5 by ', [ + expect.any(String), + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + ]); + }); + + test('errors on unknown function', async () => { + const { expectErrors } = await setup(); + + await expectErrors('metrics a_index var0 = avg(fn(number)), count(*)', [ + 'Unknown function [fn]', + ]); + }); + + test('errors when no aggregation function specified', async () => { + const { expectErrors } = await setup(); + + await expectErrors('metrics a_index numberField + 1', [ + 'At least one aggregation function required in [METRICS], found [numberField+1]', + ]); + await expectErrors('metrics a_index a = numberField + 1', [ + 'At least one aggregation function required in [METRICS], found [a=numberField+1]', + ]); + await expectErrors('metrics a_index a = numberField + 1, stringField', [ + 'At least one aggregation function required in [METRICS], found [a=numberField+1]', + 'Expected an aggregate function or group but got [stringField] of type [FieldAttribute]', + ]); + await expectErrors('metrics a_index numberField + 1 by ipField', [ + 'At least one aggregation function required in [METRICS], found [numberField+1]', + ]); + }); + + test('errors on agg and non-agg mix', async () => { + const { expectErrors } = await setup(); + + await expectErrors('METRICS a_index sum( numberField ) + abs( numberField ) ', [ + 'Cannot combine aggregation and non-aggregation values in [METRICS], found [sum(numberField)+abs(numberField)]', + ]); + await expectErrors('METRICS a_index abs( numberField + sum( numberField )) ', [ + 'Cannot combine aggregation and non-aggregation values in [METRICS], found [abs(numberField+sum(numberField))]', + ]); + }); + + test('errors when aggregation functions are nested', async () => { + const { expectErrors } = await setup(); + + // avg() inside avg() + await expectErrors('METRICS a_index avg(to_long(avg(2)))', [ + 'The aggregation function [avg] cannot be used as an argument in another aggregation function', + ]); + }); + + test('errors when input is not an aggregate function', async () => { + const { expectErrors } = await setup(); + + await expectErrors('metrics a_index numberField ', [ + 'Expected an aggregate function or group but got [numberField] of type [FieldAttribute]', + ]); + }); + + test('sub-command can reference aggregated field', async () => { + const { expectErrors } = await setup(); + + for (const subCommand of ['keep', 'drop', 'eval']) { + await expectErrors( + 'metrics a_index count(`numberField`) | ' + + subCommand + + ' `count(``numberField``)` ', + [] + ); + } + }); + + test('semantic function validation errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors('metrics a_index count(round(*))', [ + 'Using wildcards (*) in round is not allowed', + ]); + await expectErrors('metrics a_index count(count(*))', [ + `Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [number]`, + ]); + }); + }); + + describe('... BY ', () => { + test('no errors on correct usage', async () => { + const { expectErrors } = await setup(); + + await expectErrors( + 'metrics a_index avg(numberField), percentile(numberField, 50) by ipField', + [] + ); + await expectErrors( + 'metrics a_index avg(numberField), percentile(numberField, 50) BY ipField', + [] + ); + await expectErrors( + 'metrics a_index avg(numberField), percentile(numberField, 50) + 1 by ipField', + [] + ); + await expectErrors('metrics a_index avg(numberField) by stringField | limit 100', []); + for (const op of ['+', '-', '*', '/', '%']) { + await expectErrors( + `metrics a_index avg(numberField) ${op} percentile(numberField, 50) BY ipField`, + [] + ); + } + }); + + test('syntax does not allow clause without ', async () => { + const { expectErrors } = await setup(); + + await expectErrors('metrics a_index BY stringField', [ + 'Expected an aggregate function or group but got [BY] of type [FieldAttribute]', + "SyntaxError: extraneous input 'stringField' expecting ", + ]); + }); + + test('syntax errors in ', async () => { + const { expectErrors } = await setup(); + + await expectErrors('metrics a_index count(* + 1) BY ipField', [ + "SyntaxError: no viable alternative at input 'count(* +'", + ]); + await expectErrors('metrics a_index \n count(* + round(numberField)) BY ipField', [ + "SyntaxError: no viable alternative at input 'count(* +'", + ]); + }); + + test('semantic errors in ', async () => { + const { expectErrors } = await setup(); + + await expectErrors('metrics a_index count(round(*)) BY ipField', [ + 'Using wildcards (*) in round is not allowed', + ]); + await expectErrors('metrics a_index count(count(*)) BY ipField', [ + `Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [number]`, + ]); + }); + + test('errors on unknown field', async () => { + const { expectErrors } = await setup(); + + await expectErrors('metrics a_index avg(numberField) by wrongField', [ + 'Unknown column [wrongField]', + ]); + await expectErrors('metrics a_index avg(numberField) by wrongField + 1', [ + 'Unknown column [wrongField]', + ]); + await expectErrors('metrics a_index avg(numberField) by var0 = wrongField + 1', [ + 'Unknown column [wrongField]', + ]); + }); + + test('various errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors('METRICS a_index avg(numberField) by percentile(numberField)', [ + 'METRICS BY does not support function percentile', + ]); + await expectErrors( + 'METRICS a_index avg(numberField) by stringField, percentile(numberField) by ipField', + [ + "SyntaxError: mismatched input 'by' expecting ", + 'METRICS BY does not support function percentile', + ] + ); + }); + }); + }); + }); + }); +}; diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.stats.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.stats.ts new file mode 100644 index 0000000000000..5a98d362dc002 --- /dev/null +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/test_suites/validation.command.stats.ts @@ -0,0 +1,365 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as helpers from '../helpers'; + +export const validationStatsCommandTestSuite = (setup: helpers.Setup) => { + describe('validation', () => { + describe('command', () => { + describe('STATS [ BY ]', () => { + test('no errors on correct usage', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | stats by stringField', []); + await expectErrors( + `FROM index + | EVAL numberField * 3.281 + | STATS avg_numberField = AVG(\`numberField * 3.281\`)`, + [] + ); + await expectErrors( + `FROM index | STATS AVG(numberField) by round(numberField) + 1 | EVAL \`round(numberField) + 1\` / 2`, + [] + ); + }); + + test('errors on invalid command start', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | stats ', [ + 'At least one aggregation or grouping expression required in [STATS]', + ]); + }); + + describe('... ...', () => { + test('no errors on correct usage', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | stats avg(numberField) by 1', []); + await expectErrors('from a_index | stats count(`numberField`)', []); + await expectErrors('from a_index | stats count(*)', []); + await expectErrors('from a_index | stats count()', []); + await expectErrors('from a_index | stats var0 = count(*)', []); + await expectErrors('from a_index | stats var0 = count()', []); + await expectErrors('from a_index | stats var0 = avg(numberField), count(*)', []); + await expectErrors(`from a_index | stats sum(case(false, 0, 1))`, []); + await expectErrors(`from a_index | stats var0 = sum( case(false, 0, 1))`, []); + + // "or" must accept "null" + await expectErrors('from a_index | stats count(stringField == "a" or null)', []); + }); + + test('sub-command can reference aggregated field', async () => { + const { expectErrors } = await setup(); + + for (const subCommand of ['keep', 'drop', 'eval']) { + await expectErrors( + 'from a_index | stats count(`numberField`) | ' + + subCommand + + ' `count(``numberField``)` ', + [] + ); + } + }); + + test('errors on agg and non-agg mix', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | STATS sum( numberField ) + abs( numberField ) ', [ + 'Cannot combine aggregation and non-aggregation values in [STATS], found [sum(numberField)+abs(numberField)]', + ]); + await expectErrors('from a_index | STATS abs( numberField + sum( numberField )) ', [ + 'Cannot combine aggregation and non-aggregation values in [STATS], found [abs(numberField+sum(numberField))]', + ]); + }); + + test('errors on each aggregation field, which does not contain at least one agg function', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | stats numberField + 1', [ + 'At least one aggregation function required in [STATS], found [numberField+1]', + ]); + await expectErrors('from a_index | stats numberField + 1, stringField', [ + 'At least one aggregation function required in [STATS], found [numberField+1]', + 'Expected an aggregate function or group but got [stringField] of type [FieldAttribute]', + ]); + await expectErrors('from a_index | stats numberField + 1, numberField + 2, count()', [ + 'At least one aggregation function required in [STATS], found [numberField+1]', + 'At least one aggregation function required in [STATS], found [numberField+2]', + ]); + await expectErrors( + 'from a_index | stats numberField + 1, numberField + count(), count()', + ['At least one aggregation function required in [STATS], found [numberField+1]'] + ); + await expectErrors('from a_index | stats 5 + numberField + 1', [ + 'At least one aggregation function required in [STATS], found [5+numberField+1]', + ]); + await expectErrors('from a_index | stats numberField + 1 by ipField', [ + 'At least one aggregation function required in [STATS], found [numberField+1]', + ]); + }); + + test('errors when input is not an aggregate function', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | stats numberField ', [ + 'Expected an aggregate function or group but got [numberField] of type [FieldAttribute]', + ]); + }); + + test('various errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | stats numberField=', [ + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + ]); + await expectErrors('from a_index | stats numberField=5 by ', [ + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + ]); + await expectErrors('from a_index | stats avg(numberField) by wrongField', [ + 'Unknown column [wrongField]', + ]); + await expectErrors('from a_index | stats avg(numberField) by wrongField + 1', [ + 'Unknown column [wrongField]', + ]); + await expectErrors('from a_index | stats avg(numberField) by var0 = wrongField + 1', [ + 'Unknown column [wrongField]', + ]); + await expectErrors('from a_index | stats var0 = avg(fn(number)), count(*)', [ + 'Unknown function [fn]', + ]); + }); + + test('semantic errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | stats count(round(*))', [ + 'Using wildcards (*) in round is not allowed', + ]); + await expectErrors('from a_index | stats count(count(*))', [ + `Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [number]`, + ]); + }); + }); + + describe('... BY ', () => { + test('no errors on correct usage', async () => { + const { expectErrors } = await setup(); + + await expectErrors( + 'from a_index | stats avg(numberField), percentile(numberField, 50) by ipField', + [] + ); + await expectErrors( + 'from a_index | stats avg(numberField), percentile(numberField, 50) BY ipField', + [] + ); + await expectErrors( + 'from a_index | stats avg(numberField), percentile(numberField, 50) + 1 by ipField', + [] + ); + for (const op of ['+', '-', '*', '/', '%']) { + await expectErrors( + `from a_index | stats avg(numberField) ${op} percentile(numberField, 50) BY ipField`, + [] + ); + } + }); + + test('cannot specify without ', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | stats by ', [ + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + ]); + }); + + test('syntax errors in ', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | stats count(* + 1) BY ipField', [ + "SyntaxError: no viable alternative at input 'count(* +'", + ]); + await expectErrors('from a_index | stats count(* + round(numberField)) BY ipField', [ + "SyntaxError: no viable alternative at input 'count(* +'", + ]); + }); + + test('semantic errors in ', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | stats count(round(*)) BY ipField', [ + 'Using wildcards (*) in round is not allowed', + ]); + await expectErrors('from a_index | stats count(count(*)) BY ipField', [ + `Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [number]`, + ]); + }); + + test('various errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from a_index | stats avg(numberField) by percentile(numberField)', [ + 'STATS BY does not support function percentile', + ]); + await expectErrors( + 'from a_index | stats avg(numberField) by stringField, percentile(numberField) by ipField', + [ + "SyntaxError: mismatched input 'by' expecting ", + 'STATS BY does not support function percentile', + ] + ); + }); + + describe('constant-only parameters', () => { + test('no errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors('from index | stats by bucket(dateField, pi(), "", "")', []); + await expectErrors( + 'from index | stats by bucket(dateField, 1 + 30 / 10, "", "")', + [] + ); + await expectErrors( + 'from index | stats by bucket(dateField, 1 + 30 / 10, concat("", ""), "")', + [] + ); + }); + + test('errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors( + 'from index | stats by bucket(dateField, abs(numberField), "", "")', + ['Argument of [bucket] must be a constant, received [abs(numberField)]'] + ); + await expectErrors( + 'from index | stats by bucket(dateField, abs(length(numberField)), "", "")', + ['Argument of [bucket] must be a constant, received [abs(length(numberField))]'] + ); + await expectErrors( + 'from index | stats by bucket(dateField, numberField, stringField, stringField)', + [ + 'Argument of [bucket] must be a constant, received [numberField]', + 'Argument of [bucket] must be a constant, received [stringField]', + 'Argument of [bucket] must be a constant, received [stringField]', + ] + ); + }); + }); + }); + + describe('nesting', () => { + const NESTING_LEVELS = 4; + const NESTED_DEPTHS = Array(NESTING_LEVELS) + .fill(0) + .map((_, i) => i + 1); + + for (const nesting of NESTED_DEPTHS) { + describe(`depth = ${nesting}`, () => { + describe('builtin', () => { + const builtinWrapping = Array(nesting).fill('+1').join(''); + + test('no errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors( + `from a_index | stats 5 + avg(numberField) ${builtinWrapping}`, + [] + ); + await expectErrors( + `from a_index | stats 5 ${builtinWrapping} + avg(numberField)`, + [] + ); + }); + + test('errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors(`from a_index | stats 5 ${builtinWrapping} + numberField`, [ + `At least one aggregation function required in [STATS], found [5${builtinWrapping}+numberField]`, + ]); + await expectErrors(`from a_index | stats 5 + numberField ${builtinWrapping}`, [ + `At least one aggregation function required in [STATS], found [5+numberField${builtinWrapping}]`, + ]); + await expectErrors( + `from a_index | stats 5 + numberField ${builtinWrapping}, var0 = sum(numberField)`, + [ + `At least one aggregation function required in [STATS], found [5+numberField${builtinWrapping}]`, + ] + ); + }); + }); + + describe('EVAL', () => { + const evalWrapping = Array(nesting).fill('round(').join(''); + const closingWrapping = Array(nesting).fill(')').join(''); + + test('no errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors( + `from a_index | stats ${evalWrapping} sum(numberField) ${closingWrapping}`, + [] + ); + await expectErrors( + `from a_index | stats ${evalWrapping} sum(numberField) ${closingWrapping} + ${evalWrapping} sum(numberField) ${closingWrapping}`, + [] + ); + await expectErrors( + `from a_index | stats ${evalWrapping} sum(numberField + numberField) ${closingWrapping}`, + [] + ); + await expectErrors( + `from a_index | stats ${evalWrapping} sum(numberField + round(numberField)) ${closingWrapping}`, + [] + ); + await expectErrors( + `from a_index | stats ${evalWrapping} sum(numberField + round(numberField)) ${closingWrapping} + ${evalWrapping} sum(numberField + round(numberField)) ${closingWrapping}`, + [] + ); + await expectErrors( + `from a_index | stats sum(${evalWrapping} numberField ${closingWrapping} )`, + [] + ); + await expectErrors( + `from a_index | stats sum(${evalWrapping} numberField ${closingWrapping} ) + sum(${evalWrapping} numberField ${closingWrapping} )`, + [] + ); + }); + + test('errors', async () => { + const { expectErrors } = await setup(); + + await expectErrors( + `from a_index | stats ${evalWrapping} numberField + sum(numberField) ${closingWrapping}`, + [ + `Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}numberField+sum(numberField)${closingWrapping}]`, + ] + ); + await expectErrors( + `from a_index | stats ${evalWrapping} numberField + sum(numberField) ${closingWrapping}, var0 = sum(numberField)`, + [ + `Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}numberField+sum(numberField)${closingWrapping}]`, + ] + ); + await expectErrors( + `from a_index | stats var0 = ${evalWrapping} numberField + sum(numberField) ${closingWrapping}, var1 = sum(numberField)`, + [ + `Cannot combine aggregation and non-aggregation values in [STATS], found [${evalWrapping}numberField+sum(numberField)${closingWrapping}]`, + ] + ); + }); + }); + }); + } + }); + }); + }); + }); +}; diff --git a/packages/analytics/shippers/elastic_v3/browser/jest.config.js b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/types.ts similarity index 67% rename from packages/analytics/shippers/elastic_v3/browser/jest.config.js rename to packages/kbn-esql-validation-autocomplete/src/validation/__tests__/types.ts index 20067863aad97..fa1bf59bcb1f7 100644 --- a/packages/analytics/shippers/elastic_v3/browser/jest.config.js +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/types.ts @@ -6,8 +6,12 @@ * Side Public License, v 1. */ -module.exports = { - preset: '@kbn/test/jest_node', - rootDir: '../../../../../', - roots: ['/packages/analytics/shippers/elastic_v3/browser'], -}; +export interface ValidationTestCase { + query: string; + error: string[]; + warning: string[]; +} + +export interface ValidationTestSuite { + testCases: ValidationTestCase[]; +} diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.command.from.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.command.from.test.ts new file mode 100644 index 0000000000000..9182bd2fac0dd --- /dev/null +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.command.from.test.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as helpers from './helpers'; +import { validationFromCommandTestSuite } from './test_suites/validation.command.from'; + +validationFromCommandTestSuite(helpers.setup); diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.command.metrics.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.command.metrics.test.ts new file mode 100644 index 0000000000000..24430e52c0754 --- /dev/null +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.command.metrics.test.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as helpers from './helpers'; +import { validationMetricsCommandTestSuite } from './test_suites/validation.command.metrics'; + +validationMetricsCommandTestSuite(helpers.setup); diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.command.stats.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.command.stats.test.ts new file mode 100644 index 0000000000000..cc4a33cb72b88 --- /dev/null +++ b/packages/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.command.stats.test.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as helpers from './helpers'; +import { validationStatsCommandTestSuite } from './test_suites/validation.command.stats'; + +validationStatsCommandTestSuite(helpers.setup); diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/errors.ts b/packages/kbn-esql-validation-autocomplete/src/validation/errors.ts index 705df80996a34..b351a495e73c1 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/errors.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/errors.ts @@ -7,7 +7,13 @@ */ import { i18n } from '@kbn/i18n'; -import type { ESQLLocation, ESQLMessage } from '@kbn/esql-ast'; +import type { + ESQLColumn, + ESQLCommand, + ESQLFunction, + ESQLLocation, + ESQLMessage, +} from '@kbn/esql-ast'; import type { ErrorTypes, ErrorValues } from './types'; function getMessageAndTypeFromId({ @@ -370,6 +376,46 @@ function getMessageAndTypeFromId({ ), type: 'error', }; + case 'noAggFunction': + return { + message: i18n.translate('kbn-esql-validation-autocomplete.esql.validation.noAggFunction', { + defaultMessage: + 'At least one aggregation function required in [{command}], found [{expression}]', + values: { + command: out.commandName.toUpperCase(), + expression: out.expression, + }, + }), + type: 'error', + }; + case 'expressionNotAggClosed': + return { + message: i18n.translate( + 'kbn-esql-validation-autocomplete.esql.validation.expressionNotAggClosed', + { + defaultMessage: + 'Cannot combine aggregation and non-aggregation values in [{command}], found [{expression}]', + values: { + command: out.commandName.toUpperCase(), + expression: out.expression, + }, + } + ), + type: 'error', + }; + case 'aggInAggFunction': + return { + message: i18n.translate( + 'kbn-esql-validation-autocomplete.esql.validation.aggInAggFunction', + { + defaultMessage: + 'The aggregation function [{nestedAgg}] cannot be used as an argument in another aggregation function', + values: { + nestedAgg: out.nestedAgg, + }, + } + ), + }; } return { message: '' }; } @@ -391,7 +437,7 @@ export function createMessage( message: string, location: ESQLLocation, messageId: string -) { +): ESQLMessage { return { type, text: message, @@ -400,6 +446,65 @@ export function createMessage( }; } +const createError = (messageId: string, location: ESQLLocation, message: string = '') => + createMessage('error', message, location, messageId); + +export const errors = { + unexpected: ( + location: ESQLLocation, + message: string = i18n.translate( + 'kbn-esql-validation-autocomplete.esql.validation.errors.unexpected.message', + { + defaultMessage: 'Unexpected error, this should never happen.', + } + ) + ): ESQLMessage => { + return createError('unexpected', location, message); + }, + + byId: ( + id: K, + location: ESQLLocation, + values: ErrorValues + ): ESQLMessage => + getMessageFromId({ + messageId: id, + values, + locations: location, + }), + + unknownFunction: (fn: ESQLFunction): ESQLMessage => + errors.byId('unknownFunction', fn.location, fn), + + unknownColumn: (column: ESQLColumn): ESQLMessage => + errors.byId('unknownColumn', column.location, { + name: column.name, + }), + + noAggFunction: (cmd: ESQLCommand, fn: ESQLFunction): ESQLMessage => + errors.byId('noAggFunction', fn.location, { + commandName: cmd.name, + expression: fn.text, + }), + + expressionNotAggClosed: (cmd: ESQLCommand, fn: ESQLFunction): ESQLMessage => + errors.byId('expressionNotAggClosed', fn.location, { + commandName: cmd.name, + expression: fn.text, + }), + + unknownAggFunction: (col: ESQLColumn, type: string = 'FieldAttribute'): ESQLMessage => + errors.byId('unknownAggregateFunction', col.location, { + value: col.name, + type, + }), + + aggInAggFunction: (fn: ESQLFunction): ESQLMessage => + errors.byId('aggInAggFunction', fn.location, { + nestedAgg: fn.name, + }), +}; + export function getUnknownTypeLabel() { return i18n.translate('kbn-esql-validation-autocomplete.esql.validation.unknownColumnType', { defaultMessage: 'Unknown type', diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json index 68dbb6ce73041..9b03737786c12 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json +++ b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json @@ -189,1083 +189,770 @@ "warning": [] }, { - "query": "f", + "query": "row", "error": [ - "SyntaxError: mismatched input 'f' expecting {'explain', 'from', 'meta', 'metrics', 'row', 'show'}" + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, { - "query": "from ", + "query": "row missing_column", "error": [ - "SyntaxError: missing INDEX_UNQUOTED_IDENTIFIER at ''" + "Unknown column [missing_column]" ], "warning": [] }, { - "query": "from index,", + "query": "row fn()", "error": [ - "SyntaxError: missing INDEX_UNQUOTED_IDENTIFIER at ''" + "Unknown function [fn]" ], "warning": [] }, { - "query": "from assignment = 1", + "query": "row missing_column, missing_column2", "error": [ - "SyntaxError: mismatched input '=' expecting ", - "Unknown index [assignment]" + "Unknown column [missing_column]", + "Unknown column [missing_column2]" ], "warning": [] }, { - "query": "from index", - "error": [], - "warning": [] - }, - { - "query": "FROM index", - "error": [], - "warning": [] - }, - { - "query": "FrOm index", + "query": "row a=1", "error": [], "warning": [] }, { - "query": "from `index`", + "query": "row a=1, missing_column", "error": [ - "SyntaxError: token recognition error at: '`'", - "SyntaxError: token recognition error at: '`'" + "Unknown column [missing_column]" ], "warning": [] }, { - "query": "from index, other_index", - "error": [], - "warning": [] - }, - { - "query": "from index, missingIndex", + "query": "row a=1, b = average()", "error": [ - "Unknown index [missingIndex]" + "Unknown function [average]" ], "warning": [] }, { - "query": "from fn()", - "error": [ - "Unknown index [fn()]" - ], + "query": "row a = [1, 2, 3]", + "error": [], "warning": [] }, { - "query": "from average()", - "error": [ - "Unknown index [average()]" - ], + "query": "row a = [true, false]", + "error": [], "warning": [] }, { - "query": "from index [METADATA _id]", + "query": "row a = [\"a\", \"b\"]", "error": [], - "warning": [ - "Square brackets '[]' need to be removed from FROM METADATA declaration" - ] + "warning": [] }, { - "query": "from index [metadata _id]", + "query": "row a = null", "error": [], - "warning": [ - "Square brackets '[]' need to be removed from FROM METADATA declaration" - ] + "warning": [] }, { - "query": "from index [METADATA _id, _source]", + "query": "row a = (1)", "error": [], - "warning": [ - "Square brackets '[]' need to be removed from FROM METADATA declaration" - ] - }, - { - "query": "from index [METADATA _id, _source2]", - "error": [ - "Metadata field [_source2] is not available. Available metadata fields are: [_version, _id, _index, _source]" - ], - "warning": [ - "Square brackets '[]' need to be removed from FROM METADATA declaration" - ] + "warning": [] }, { - "query": "from index [metadata _id, _source] [METADATA _id2]", + "query": "row a = (1, 2, 3)", "error": [ - "SyntaxError: mismatched input '[' expecting " + "SyntaxError: no viable alternative at input '(1,'", + "SyntaxError: extraneous input ')' expecting " ], - "warning": [ - "Square brackets '[]' need to be removed from FROM METADATA declaration" - ] + "warning": [] }, { - "query": "from remote-ccs:indexes [METADATA _id]", + "query": "row a=NOT true", "error": [], - "warning": [ - "Square brackets '[]' need to be removed from FROM METADATA declaration" - ] + "warning": [] }, { - "query": "from *:indexes [METADATA _id]", + "query": "row NOT true", "error": [], - "warning": [ - "Square brackets '[]' need to be removed from FROM METADATA declaration" - ] + "warning": [] }, { - "query": "from index METADATA _id", + "query": "row a=NOT false", "error": [], "warning": [] }, { - "query": "from index metadata _id", + "query": "row NOT false", "error": [], "warning": [] }, { - "query": "from index METADATA _id, _source", - "error": [], + "query": "row var = 1 in ", + "error": [ + "SyntaxError: mismatched input '' expecting '('" + ], "warning": [] }, { - "query": "from index METADATA _id, _source2", + "query": "row var = 1 in (", "error": [ - "Metadata field [_source2] is not available. Available metadata fields are: [_version, _id, _index, _source]" + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "Error: [in] function expects exactly 2 arguments, got 1." ], "warning": [] }, { - "query": "from index metadata _id, _source METADATA _id2", + "query": "row var = 1 not in ", "error": [ - "SyntaxError: mismatched input 'METADATA' expecting " + "SyntaxError: mismatched input '' expecting '('" ], "warning": [] }, { - "query": "from remote-ccs:indexes METADATA _id", + "query": "row var = 1 in (1, 2, 3)", "error": [], "warning": [] }, { - "query": "from *:indexes METADATA _id", + "query": "row var = 5 in (1, 2, 3)", "error": [], "warning": [] }, { - "query": "from index (metadata _id)", - "error": [ - "SyntaxError: mismatched input '(metadata' expecting " - ], + "query": "row var = 5 not in (1, 2, 3)", + "error": [], "warning": [] }, { - "query": "from ind*, other*", + "query": "row var = 1 in (1, 2, 3, round(5))", "error": [], "warning": [] }, { - "query": "from index*", + "query": "row var = \"a\" in (\"a\", \"b\", \"c\")", + "error": [], + "warning": [] + }, + { + "query": "row var = \"a\" in (\"a\", \"b\", \"c\")", "error": [], "warning": [] }, { - "query": "from *a_i*dex*", + "query": "row var = \"a\" not in (\"a\", \"b\", \"c\")", "error": [], "warning": [] }, { - "query": "from in*ex*", + "query": "row var = 1 in (\"a\", \"b\", \"c\")", "error": [], "warning": [] }, { - "query": "from *n*ex", + "query": "row var = 5 in (\"a\", \"b\", \"c\")", "error": [], "warning": [] }, { - "query": "from *n*ex*", + "query": "row var = 5 not in (\"a\", \"b\", \"c\")", "error": [], "warning": [] }, { - "query": "from i*d*x*", + "query": "row var = 5 not in (1, 2, 3, \"a\")", "error": [], "warning": [] }, { - "query": "from i*d*x", + "query": "row bool_var = true and false", "error": [], "warning": [] }, { - "query": "from i***x*", + "query": "row bool_var = true and null", "error": [], "warning": [] }, { - "query": "from i****", + "query": "row bool_var = null and false", "error": [], "warning": [] }, { - "query": "from i**", + "query": "row bool_var = null and null", "error": [], "warning": [] }, { - "query": "from index**", + "query": "row bool_var = true or false", "error": [], "warning": [] }, { - "query": "from *ex", + "query": "row bool_var = true or null", "error": [], "warning": [] }, { - "query": "from *ex*", + "query": "row bool_var = null or false", "error": [], "warning": [] }, { - "query": "from in*ex", + "query": "row bool_var = null or null", "error": [], "warning": [] }, { - "query": "from ind*ex", + "query": "row var = 5 > 0", "error": [], "warning": [] }, { - "query": "from *,-.*", + "query": "row var = NOT 5 > 0", "error": [], "warning": [] }, { - "query": "from indexes*", + "query": "row var = (numberField > 0)", "error": [ - "Unknown index [indexes*]" + "Unknown column [numberField]" ], "warning": [] }, { - "query": "from remote-*:indexes*", + "query": "row var = (NOT (5 > 0))", "error": [], "warning": [] }, { - "query": "from remote-*:indexes", + "query": "row var = to_ip(\"127.0.0.1\") > to_ip(\"127.0.0.1\")", "error": [], "warning": [] }, { - "query": "from remote-ccs:indexes", + "query": "row var = now() > now()", "error": [], "warning": [] }, { - "query": "from a_index, remote-ccs:indexes", + "query": "row var = false > false", + "error": [ + "Argument of [>] must be [number], found value [false] type [boolean]", + "Argument of [>] must be [number], found value [false] type [boolean]" + ], + "warning": [] + }, + { + "query": "row var = now() > \"2022\"", "error": [], "warning": [] }, { - "query": "from .secret_index", + "query": "row var = \"2022\" > now()", "error": [], "warning": [] }, { - "query": "from my-index", + "query": "row var = 5 >= 0", "error": [], "warning": [] }, { - "query": "from numberField", - "error": [ - "Unknown index [numberField]" - ], + "query": "row var = NOT 5 >= 0", + "error": [], "warning": [] }, { - "query": "from policy", + "query": "row var = (numberField >= 0)", "error": [ - "Unknown index [policy]" + "Unknown column [numberField]" ], "warning": [] }, { - "query": "row", - "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" - ], + "query": "row var = (NOT (5 >= 0))", + "error": [], "warning": [] }, { - "query": "row missing_column", - "error": [ - "Unknown column [missing_column]" - ], + "query": "row var = to_ip(\"127.0.0.1\") >= to_ip(\"127.0.0.1\")", + "error": [], "warning": [] }, { - "query": "row fn()", - "error": [ - "Unknown function [fn]" - ], + "query": "row var = now() >= now()", + "error": [], "warning": [] }, { - "query": "row missing_column, missing_column2", + "query": "row var = false >= false", "error": [ - "Unknown column [missing_column]", - "Unknown column [missing_column2]" + "Argument of [>=] must be [number], found value [false] type [boolean]", + "Argument of [>=] must be [number], found value [false] type [boolean]" ], "warning": [] }, { - "query": "row a=1", + "query": "row var = now() >= \"2022\"", "error": [], "warning": [] }, { - "query": "row a=1, missing_column", - "error": [ - "Unknown column [missing_column]" - ], + "query": "row var = \"2022\" >= now()", + "error": [], "warning": [] }, { - "query": "row a=1, b = average()", - "error": [ - "Unknown function [average]" - ], + "query": "row var = 5 < 0", + "error": [], "warning": [] }, { - "query": "row a = [1, 2, 3]", + "query": "row var = NOT 5 < 0", "error": [], "warning": [] }, { - "query": "row a = [true, false]", - "error": [], + "query": "row var = (numberField < 0)", + "error": [ + "Unknown column [numberField]" + ], "warning": [] }, { - "query": "row a = [\"a\", \"b\"]", + "query": "row var = (NOT (5 < 0))", "error": [], "warning": [] }, { - "query": "row a = null", + "query": "row var = to_ip(\"127.0.0.1\") < to_ip(\"127.0.0.1\")", "error": [], "warning": [] }, { - "query": "row a = (1)", + "query": "row var = now() < now()", "error": [], "warning": [] }, { - "query": "row a = (1, 2, 3)", + "query": "row var = false < false", "error": [ - "SyntaxError: no viable alternative at input '(1,'", - "SyntaxError: extraneous input ')' expecting " + "Argument of [<] must be [number], found value [false] type [boolean]", + "Argument of [<] must be [number], found value [false] type [boolean]" ], "warning": [] }, { - "query": "row a=NOT true", + "query": "row var = now() < \"2022\"", "error": [], "warning": [] }, { - "query": "row NOT true", + "query": "row var = \"2022\" < now()", "error": [], "warning": [] }, { - "query": "row a=NOT false", + "query": "row var = 5 <= 0", "error": [], "warning": [] }, { - "query": "row NOT false", + "query": "row var = NOT 5 <= 0", "error": [], "warning": [] }, { - "query": "row var = 1 in ", + "query": "row var = (numberField <= 0)", "error": [ - "SyntaxError: mismatched input '' expecting '('" + "Unknown column [numberField]" ], "warning": [] }, { - "query": "row var = 1 in (", - "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", - "Error: [in] function expects exactly 2 arguments, got 1." - ], + "query": "row var = (NOT (5 <= 0))", + "error": [], "warning": [] }, { - "query": "row var = 1 not in ", - "error": [ - "SyntaxError: mismatched input '' expecting '('" - ], - "warning": [] - }, - { - "query": "row var = 1 in (1, 2, 3)", + "query": "row var = to_ip(\"127.0.0.1\") <= to_ip(\"127.0.0.1\")", "error": [], "warning": [] }, { - "query": "row var = 5 in (1, 2, 3)", + "query": "row var = now() <= now()", "error": [], "warning": [] }, { - "query": "row var = 5 not in (1, 2, 3)", - "error": [], + "query": "row var = false <= false", + "error": [ + "Argument of [<=] must be [number], found value [false] type [boolean]", + "Argument of [<=] must be [number], found value [false] type [boolean]" + ], "warning": [] }, { - "query": "row var = 1 in (1, 2, 3, round(5))", + "query": "row var = now() <= \"2022\"", "error": [], "warning": [] }, { - "query": "row var = \"a\" in (\"a\", \"b\", \"c\")", + "query": "row var = \"2022\" <= now()", "error": [], "warning": [] }, { - "query": "row var = \"a\" in (\"a\", \"b\", \"c\")", + "query": "row var = 5 == 0", "error": [], "warning": [] }, { - "query": "row var = \"a\" not in (\"a\", \"b\", \"c\")", + "query": "row var = NOT 5 == 0", "error": [], "warning": [] }, { - "query": "row var = 1 in (\"a\", \"b\", \"c\")", - "error": [], + "query": "row var = (numberField == 0)", + "error": [ + "Unknown column [numberField]" + ], "warning": [] }, { - "query": "row var = 5 in (\"a\", \"b\", \"c\")", + "query": "row var = (NOT (5 == 0))", "error": [], "warning": [] }, { - "query": "row var = 5 not in (\"a\", \"b\", \"c\")", + "query": "row var = to_ip(\"127.0.0.1\") == to_ip(\"127.0.0.1\")", "error": [], "warning": [] }, { - "query": "row var = 5 not in (1, 2, 3, \"a\")", + "query": "row var = now() == now()", "error": [], "warning": [] }, { - "query": "row bool_var = true and false", + "query": "row var = false == false", "error": [], "warning": [] }, { - "query": "row bool_var = true and null", + "query": "row var = now() == \"2022\"", "error": [], "warning": [] }, { - "query": "row bool_var = null and false", + "query": "row var = \"2022\" == now()", "error": [], "warning": [] }, { - "query": "row bool_var = null and null", + "query": "row var = 5 != 0", "error": [], "warning": [] }, { - "query": "row bool_var = true or false", + "query": "row var = NOT 5 != 0", "error": [], "warning": [] }, { - "query": "row bool_var = true or null", - "error": [], + "query": "row var = (numberField != 0)", + "error": [ + "Unknown column [numberField]" + ], "warning": [] }, { - "query": "row bool_var = null or false", + "query": "row var = (NOT (5 != 0))", "error": [], "warning": [] }, { - "query": "row bool_var = null or null", + "query": "row var = to_ip(\"127.0.0.1\") != to_ip(\"127.0.0.1\")", "error": [], "warning": [] }, { - "query": "row var = 5 > 0", + "query": "row var = now() != now()", "error": [], "warning": [] }, { - "query": "row var = NOT 5 > 0", + "query": "row var = false != false", "error": [], "warning": [] }, { - "query": "row var = (numberField > 0)", - "error": [ - "Unknown column [numberField]" - ], + "query": "row var = now() != \"2022\"", + "error": [], "warning": [] }, { - "query": "row var = (NOT (5 > 0))", + "query": "row var = \"2022\" != now()", "error": [], "warning": [] }, { - "query": "row var = to_ip(\"127.0.0.1\") > to_ip(\"127.0.0.1\")", + "query": "row var = 1 + 1", "error": [], "warning": [] }, { - "query": "row var = now() > now()", + "query": "row var = (5 + 1)", "error": [], "warning": [] }, { - "query": "row var = false > false", + "query": "row var = now() + now()", "error": [ - "Argument of [>] must be [number], found value [false] type [boolean]", - "Argument of [>] must be [number], found value [false] type [boolean]" + "Argument of [+] must be [time_literal], found value [now()] type [date]" ], "warning": [] }, { - "query": "row var = now() > \"2022\"", - "error": [], - "warning": [] - }, - { - "query": "row var = \"2022\" > now()", - "error": [], - "warning": [] - }, - { - "query": "row var = 5 >= 0", + "query": "row var = 1 - 1", "error": [], "warning": [] }, { - "query": "row var = NOT 5 >= 0", + "query": "row var = (5 - 1)", "error": [], "warning": [] }, { - "query": "row var = (numberField >= 0)", + "query": "row var = now() - now()", "error": [ - "Unknown column [numberField]" + "Argument of [-] must be [time_literal], found value [now()] type [date]" ], "warning": [] }, { - "query": "row var = (NOT (5 >= 0))", - "error": [], - "warning": [] - }, - { - "query": "row var = to_ip(\"127.0.0.1\") >= to_ip(\"127.0.0.1\")", + "query": "row var = 1 * 1", "error": [], "warning": [] }, { - "query": "row var = now() >= now()", + "query": "row var = (5 * 1)", "error": [], "warning": [] }, { - "query": "row var = false >= false", + "query": "row var = now() * now()", "error": [ - "Argument of [>=] must be [number], found value [false] type [boolean]", - "Argument of [>=] must be [number], found value [false] type [boolean]" + "Argument of [*] must be [number], found value [now()] type [date]", + "Argument of [*] must be [number], found value [now()] type [date]" ], "warning": [] }, { - "query": "row var = now() >= \"2022\"", - "error": [], - "warning": [] - }, - { - "query": "row var = \"2022\" >= now()", - "error": [], - "warning": [] - }, - { - "query": "row var = 5 < 0", + "query": "row var = 1 / 1", "error": [], "warning": [] }, { - "query": "row var = NOT 5 < 0", + "query": "row var = (5 / 1)", "error": [], "warning": [] }, { - "query": "row var = (numberField < 0)", + "query": "row var = now() / now()", "error": [ - "Unknown column [numberField]" + "Argument of [/] must be [number], found value [now()] type [date]", + "Argument of [/] must be [number], found value [now()] type [date]" ], "warning": [] }, { - "query": "row var = (NOT (5 < 0))", - "error": [], - "warning": [] - }, - { - "query": "row var = to_ip(\"127.0.0.1\") < to_ip(\"127.0.0.1\")", + "query": "row var = 1 % 1", "error": [], "warning": [] }, { - "query": "row var = now() < now()", + "query": "row var = (5 % 1)", "error": [], "warning": [] }, { - "query": "row var = false < false", + "query": "row var = now() % now()", "error": [ - "Argument of [<] must be [number], found value [false] type [boolean]", - "Argument of [<] must be [number], found value [false] type [boolean]" + "Argument of [%] must be [number], found value [now()] type [date]", + "Argument of [%] must be [number], found value [now()] type [date]" ], "warning": [] }, { - "query": "row var = now() < \"2022\"", + "query": "row var = \"a\" like \"?a\"", "error": [], "warning": [] }, { - "query": "row var = \"2022\" < now()", + "query": "row var = \"a\" NOT like \"?a\"", "error": [], "warning": [] }, { - "query": "row var = 5 <= 0", + "query": "row var = NOT \"a\" like \"?a\"", "error": [], "warning": [] }, { - "query": "row var = NOT 5 <= 0", + "query": "row var = NOT \"a\" NOT like \"?a\"", "error": [], "warning": [] }, { - "query": "row var = (numberField <= 0)", + "query": "row var = 5 like \"?a\"", "error": [ - "Unknown column [numberField]" + "Argument of [like] must be [string], found value [5] type [number]" ], "warning": [] }, { - "query": "row var = (NOT (5 <= 0))", - "error": [], - "warning": [] - }, - { - "query": "row var = to_ip(\"127.0.0.1\") <= to_ip(\"127.0.0.1\")", - "error": [], + "query": "row var = 5 NOT like \"?a\"", + "error": [ + "Argument of [not_like] must be [string], found value [5] type [number]" + ], "warning": [] }, { - "query": "row var = now() <= now()", - "error": [], + "query": "row var = NOT 5 like \"?a\"", + "error": [ + "Argument of [like] must be [string], found value [5] type [number]" + ], "warning": [] }, { - "query": "row var = false <= false", + "query": "row var = NOT 5 NOT like \"?a\"", "error": [ - "Argument of [<=] must be [number], found value [false] type [boolean]", - "Argument of [<=] must be [number], found value [false] type [boolean]" + "Argument of [not_like] must be [string], found value [5] type [number]" ], "warning": [] }, { - "query": "row var = now() <= \"2022\"", + "query": "row var = \"a\" rlike \"?a\"", "error": [], "warning": [] }, { - "query": "row var = \"2022\" <= now()", + "query": "row var = \"a\" NOT rlike \"?a\"", "error": [], "warning": [] }, { - "query": "row var = 5 == 0", + "query": "row var = NOT \"a\" rlike \"?a\"", "error": [], "warning": [] }, { - "query": "row var = NOT 5 == 0", + "query": "row var = NOT \"a\" NOT rlike \"?a\"", "error": [], "warning": [] }, { - "query": "row var = (numberField == 0)", + "query": "row var = 5 rlike \"?a\"", "error": [ - "Unknown column [numberField]" + "Argument of [rlike] must be [string], found value [5] type [number]" ], "warning": [] }, { - "query": "row var = (NOT (5 == 0))", - "error": [], + "query": "row var = 5 NOT rlike \"?a\"", + "error": [ + "Argument of [not_rlike] must be [string], found value [5] type [number]" + ], "warning": [] }, { - "query": "row var = to_ip(\"127.0.0.1\") == to_ip(\"127.0.0.1\")", - "error": [], + "query": "row var = NOT 5 rlike \"?a\"", + "error": [ + "Argument of [rlike] must be [string], found value [5] type [number]" + ], "warning": [] }, { - "query": "row var = now() == now()", - "error": [], + "query": "row var = NOT 5 NOT rlike \"?a\"", + "error": [ + "Argument of [not_rlike] must be [string], found value [5] type [number]" + ], "warning": [] }, { - "query": "row var = false == false", + "query": "row var = mv_sort([\"a\", \"b\"], \"bogus\")", "error": [], - "warning": [] + "warning": [ + "Invalid option [\"bogus\"] for mv_sort. Supported options: [\"asc\", \"desc\"]." + ] }, { - "query": "row var = now() == \"2022\"", + "query": "row var = mv_sort([\"a\", \"b\"], \"ASC\")", "error": [], "warning": [] }, { - "query": "row var = \"2022\" == now()", + "query": "row var = mv_sort([\"a\", \"b\"], \"DESC\")", "error": [], "warning": [] }, { - "query": "row var = 5 != 0", - "error": [], + "query": "row 1 anno", + "error": [ + "ROW does not support [date_period] in expression [1 anno]" + ], "warning": [] }, { - "query": "row var = NOT 5 != 0", - "error": [], + "query": "row var = 1 anno", + "error": [ + "Unexpected time interval qualifier: 'anno'" + ], "warning": [] }, { - "query": "row var = (numberField != 0)", + "query": "row now() + 1 anno", "error": [ - "Unknown column [numberField]" + "Unexpected time interval qualifier: 'anno'" ], "warning": [] }, { - "query": "row var = (NOT (5 != 0))", - "error": [], + "query": "row 1 year", + "error": [ + "ROW does not support [date_period] in expression [1 year]" + ], "warning": [] }, { - "query": "row var = to_ip(\"127.0.0.1\") != to_ip(\"127.0.0.1\")", - "error": [], + "query": "row 1 year", + "error": [ + "ROW does not support [date_period] in expression [1 year]" + ], "warning": [] }, { - "query": "row var = now() != now()", + "query": "row var = now() - 1 year", "error": [], "warning": [] }, { - "query": "row var = false != false", + "query": "row var = now() - 1 YEAR", "error": [], "warning": [] }, { - "query": "row var = now() != \"2022\"", + "query": "row var = now() - 1 Year", "error": [], "warning": [] }, { - "query": "row var = \"2022\" != now()", - "error": [], - "warning": [] - }, - { - "query": "row var = 1 + 1", - "error": [], - "warning": [] - }, - { - "query": "row var = (5 + 1)", - "error": [], - "warning": [] - }, - { - "query": "row var = now() + now()", - "error": [ - "Argument of [+] must be [time_literal], found value [now()] type [date]" - ], - "warning": [] - }, - { - "query": "row var = 1 - 1", - "error": [], - "warning": [] - }, - { - "query": "row var = (5 - 1)", - "error": [], - "warning": [] - }, - { - "query": "row var = now() - now()", - "error": [ - "Argument of [-] must be [time_literal], found value [now()] type [date]" - ], - "warning": [] - }, - { - "query": "row var = 1 * 1", - "error": [], - "warning": [] - }, - { - "query": "row var = (5 * 1)", - "error": [], - "warning": [] - }, - { - "query": "row var = now() * now()", - "error": [ - "Argument of [*] must be [number], found value [now()] type [date]", - "Argument of [*] must be [number], found value [now()] type [date]" - ], - "warning": [] - }, - { - "query": "row var = 1 / 1", - "error": [], - "warning": [] - }, - { - "query": "row var = (5 / 1)", - "error": [], - "warning": [] - }, - { - "query": "row var = now() / now()", - "error": [ - "Argument of [/] must be [number], found value [now()] type [date]", - "Argument of [/] must be [number], found value [now()] type [date]" - ], - "warning": [] - }, - { - "query": "row var = 1 % 1", - "error": [], - "warning": [] - }, - { - "query": "row var = (5 % 1)", - "error": [], - "warning": [] - }, - { - "query": "row var = now() % now()", - "error": [ - "Argument of [%] must be [number], found value [now()] type [date]", - "Argument of [%] must be [number], found value [now()] type [date]" - ], - "warning": [] - }, - { - "query": "row var = \"a\" like \"?a\"", - "error": [], - "warning": [] - }, - { - "query": "row var = \"a\" NOT like \"?a\"", - "error": [], - "warning": [] - }, - { - "query": "row var = NOT \"a\" like \"?a\"", - "error": [], - "warning": [] - }, - { - "query": "row var = NOT \"a\" NOT like \"?a\"", - "error": [], - "warning": [] - }, - { - "query": "row var = 5 like \"?a\"", - "error": [ - "Argument of [like] must be [string], found value [5] type [number]" - ], - "warning": [] - }, - { - "query": "row var = 5 NOT like \"?a\"", - "error": [ - "Argument of [not_like] must be [string], found value [5] type [number]" - ], - "warning": [] - }, - { - "query": "row var = NOT 5 like \"?a\"", - "error": [ - "Argument of [like] must be [string], found value [5] type [number]" - ], - "warning": [] - }, - { - "query": "row var = NOT 5 NOT like \"?a\"", - "error": [ - "Argument of [not_like] must be [string], found value [5] type [number]" - ], - "warning": [] - }, - { - "query": "row var = \"a\" rlike \"?a\"", - "error": [], - "warning": [] - }, - { - "query": "row var = \"a\" NOT rlike \"?a\"", - "error": [], - "warning": [] - }, - { - "query": "row var = NOT \"a\" rlike \"?a\"", - "error": [], - "warning": [] - }, - { - "query": "row var = NOT \"a\" NOT rlike \"?a\"", - "error": [], - "warning": [] - }, - { - "query": "row var = 5 rlike \"?a\"", - "error": [ - "Argument of [rlike] must be [string], found value [5] type [number]" - ], - "warning": [] - }, - { - "query": "row var = 5 NOT rlike \"?a\"", - "error": [ - "Argument of [not_rlike] must be [string], found value [5] type [number]" - ], - "warning": [] - }, - { - "query": "row var = NOT 5 rlike \"?a\"", - "error": [ - "Argument of [rlike] must be [string], found value [5] type [number]" - ], - "warning": [] - }, - { - "query": "row var = NOT 5 NOT rlike \"?a\"", - "error": [ - "Argument of [not_rlike] must be [string], found value [5] type [number]" - ], - "warning": [] - }, - { - "query": "row var = mv_sort([\"a\", \"b\"], \"bogus\")", - "error": [], - "warning": [ - "Invalid option [\"bogus\"] for mv_sort. Supported options: [\"asc\", \"desc\"]." - ] - }, - { - "query": "row var = mv_sort([\"a\", \"b\"], \"ASC\")", - "error": [], - "warning": [] - }, - { - "query": "row var = mv_sort([\"a\", \"b\"], \"DESC\")", - "error": [], - "warning": [] - }, - { - "query": "row 1 anno", - "error": [ - "ROW does not support [date_period] in expression [1 anno]" - ], - "warning": [] - }, - { - "query": "row var = 1 anno", - "error": [ - "Unexpected time interval qualifier: 'anno'" - ], - "warning": [] - }, - { - "query": "row now() + 1 anno", - "error": [ - "Unexpected time interval qualifier: 'anno'" - ], - "warning": [] - }, - { - "query": "row 1 year", - "error": [ - "ROW does not support [date_period] in expression [1 year]" - ], - "warning": [] - }, - { - "query": "row 1 year", - "error": [ - "ROW does not support [date_period] in expression [1 year]" - ], - "warning": [] - }, - { - "query": "row var = now() - 1 year", - "error": [], - "warning": [] - }, - { - "query": "row var = now() - 1 YEAR", - "error": [], - "warning": [] - }, - { - "query": "row var = now() - 1 Year", - "error": [], - "warning": [] - }, - { - "query": "row var = now() + 1 year", + "query": "row var = now() + 1 year", "error": [], "warning": [] }, @@ -2846,7 +2533,7 @@ { "query": "from a_index | dissect", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, @@ -2902,7 +2589,7 @@ { "query": "from a_index | dissect stringField \"%{firstWord}\" option = ", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', 'null', '?', 'true', '+', '-', OPENING_BRACKET}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET}", "Invalid option for DISSECT: [option]" ], "warning": [] @@ -2941,7 +2628,7 @@ { "query": "from a_index | grok", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, @@ -3743,21 +3430,21 @@ { "query": "from a_index | where *+ numberField", "error": [ - "SyntaxError: extraneous input '*' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: extraneous input '*' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, { "query": "from a_index | where /+ numberField", "error": [ - "SyntaxError: extraneous input '/' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: extraneous input '/' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, { "query": "from a_index | where %+ numberField", "error": [ - "SyntaxError: extraneous input '%' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: extraneous input '%' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, @@ -4324,7 +4011,7 @@ { "query": "from a_index | eval ", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, @@ -4367,7 +4054,7 @@ { "query": "from a_index | eval a=b, ", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", "Unknown column [b]" ], "warning": [] @@ -4394,7 +4081,7 @@ { "query": "from a_index | eval a=round(numberField), ", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, @@ -5220,21 +4907,21 @@ { "query": "from a_index | eval *+ numberField", "error": [ - "SyntaxError: extraneous input '*' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: extraneous input '*' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, { "query": "from a_index | eval /+ numberField", "error": [ - "SyntaxError: extraneous input '/' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: extraneous input '/' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, { "query": "from a_index | eval %+ numberField", "error": [ - "SyntaxError: extraneous input '%' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: extraneous input '%' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, @@ -6558,7 +6245,7 @@ { "query": "from a_index | eval not", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", "Error: [not] function expects exactly one argument, got 0." ], "warning": [] @@ -6566,7 +6253,7 @@ { "query": "from a_index | eval in", "error": [ - "SyntaxError: mismatched input 'in' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "SyntaxError: mismatched input 'in' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, @@ -7773,18003 +7460,17843 @@ "warning": [] }, { - "query": "from a_index | stats ", + "query": "from a_index | sort ", "error": [ - "At least one aggregation or grouping expression required in [STATS]" + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, { - "query": "from a_index | stats by stringField", + "query": "from a_index | sort \"field\" ", "error": [], "warning": [] }, { - "query": "from a_index | stats by ", + "query": "from a_index | sort wrongField ", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "Unknown column [wrongField]" ], "warning": [] }, { - "query": "from a_index | stats numberField ", + "query": "from a_index | sort numberField, ", "error": [ - "Expected an aggregate function or group but got [numberField] of type [FieldAttribute]" + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" ], "warning": [] }, { - "query": "from a_index | stats numberField=", - "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" - ], + "query": "from a_index | sort numberField, stringField", + "error": [], "warning": [] }, { - "query": "from a_index | stats numberField=5 by ", - "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" - ], + "query": "from a_index | sort \"field\" desc ", + "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField) by wrongField", - "error": [ - "Unknown column [wrongField]" - ], + "query": "from a_index | sort numberField desc ", + "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField) by wrongField + 1", + "query": "from a_index | sort numberField desc nulls ", "error": [ - "Unknown column [wrongField]" + "SyntaxError: missing {'first', 'last'} at ''" ], "warning": [] }, { - "query": "from a_index | stats avg(numberField) by var0 = wrongField + 1", + "query": "from a_index | sort numberField desc nulls first", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort numberField desc first", "error": [ - "Unknown column [wrongField]" + "SyntaxError: extraneous input 'first' expecting " ], "warning": [] }, { - "query": "from a_index | stats avg(numberField) by 1", + "query": "from a_index | sort numberField desc nulls last", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField) by percentile(numberField)", + "query": "from a_index | sort numberField desc last", "error": [ - "STATS BY does not support function percentile" + "SyntaxError: extraneous input 'last' expecting " ], "warning": [] }, { - "query": "from a_index | stats count(`numberField`)", + "query": "from a_index | sort \"field\" asc ", "error": [], "warning": [] }, { - "query": "from a_index | stats count(stringField == \"a\" or null)", + "query": "from a_index | sort numberField asc ", "error": [], "warning": [] }, { - "query": "from a_index | stats count(`numberField`) | keep `count(``numberField``)` ", - "error": [], + "query": "from a_index | sort numberField asc nulls ", + "error": [ + "SyntaxError: missing {'first', 'last'} at ''" + ], "warning": [] }, { - "query": "from a_index | stats count(`numberField`) | drop `count(``numberField``)` ", + "query": "from a_index | sort numberField asc nulls first", "error": [], "warning": [] }, { - "query": "from a_index | stats count(`numberField`) | eval `count(``numberField``)` ", + "query": "from a_index | sort numberField asc first", + "error": [ + "SyntaxError: extraneous input 'first' expecting " + ], + "warning": [] + }, + { + "query": "from a_index | sort numberField asc nulls last", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField) by stringField, percentile(numberField) by ipField", + "query": "from a_index | sort numberField asc last", "error": [ - "SyntaxError: mismatched input 'by' expecting ", - "STATS BY does not support function percentile" + "SyntaxError: extraneous input 'last' expecting " ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), percentile(numberField, 50) by ipField", + "query": "from a_index | sort numberField nulls first", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), percentile(numberField, 50) BY ipField", - "error": [], + "query": "from a_index | sort numberField first", + "error": [ + "SyntaxError: extraneous input 'first' expecting " + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField) + percentile(numberField, 50) BY ipField", + "query": "from a_index | sort numberField nulls last", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField) - percentile(numberField, 50) BY ipField", - "error": [], + "query": "from a_index | sort numberField last", + "error": [ + "SyntaxError: extraneous input 'last' expecting " + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField) * percentile(numberField, 50) BY ipField", + "query": "row a = 1 | stats COUNT(*) | sort `COUNT(*)`", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField) / percentile(numberField, 50) BY ipField", + "query": "ROW a = 1 | STATS couNt(*) | SORT `couNt(*)`", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField) % percentile(numberField, 50) BY ipField", + "query": "from a_index | sort abs(numberField) - to_long(stringField) desc nulls first", "error": [], "warning": [] }, { - "query": "from a_index | stats count(* + 1) BY ipField", + "query": "from a_index | sort sin(stringField)", "error": [ - "SyntaxError: no viable alternative at input 'count(* +'" + "Argument of [sin] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | stats count(* + round(numberField)) BY ipField", + "query": "from a_index | sort numberField + stringField", "error": [ - "SyntaxError: no viable alternative at input 'count(* +'" + "Argument of [+] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | stats count(round(*)) BY ipField", + "query": "from a_index | enrich", "error": [ - "Using wildcards (*) in round is not allowed" + "SyntaxError: missing ENRICH_POLICY_NAME at ''" ], "warning": [] }, { - "query": "from a_index | stats count(count(*)) BY ipField", + "query": "from a_index | enrich _", "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [number]" + "Unknown policy [_]" ], "warning": [] }, { - "query": "from a_index | stats numberField + 1", + "query": "from a_index | enrich _:", "error": [ - "At least one aggregation function required in [STATS], found [numberField+1]" + "SyntaxError: token recognition error at: ':'", + "Unknown policy [_]" ], "warning": [] }, { - "query": "from a_index | stats 5 + avg(numberField) +1", - "error": [], + "query": "from a_index | enrich _:policy", + "error": [ + "Unrecognized value [_] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]" + ], "warning": [] }, { - "query": "from a_index | stats 5 +1 + avg(numberField)", - "error": [], + "query": "from a_index | enrich :policy", + "error": [ + "SyntaxError: token recognition error at: ':'" + ], "warning": [] }, { - "query": "from a_index | stats 5 +1 + numberField", + "query": "from a_index | enrich any:", "error": [ - "At least one aggregation function required in [STATS], found [5+1+numberField]" + "SyntaxError: token recognition error at: ':'", + "Unknown policy [any]" ], "warning": [] }, { - "query": "from a_index | stats 5 + numberField +1", + "query": "from a_index | enrich _any:", "error": [ - "At least one aggregation function required in [STATS], found [5+numberField+1]" + "SyntaxError: token recognition error at: ':'", + "Unknown policy [_any]" ], "warning": [] }, { - "query": "from a_index | stats 5 + numberField +1, var0 = sum(numberField)", + "query": "from a_index | enrich any:policy", "error": [ - "At least one aggregation function required in [STATS], found [5+numberField+1]" + "Unrecognized value [any] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]" ], "warning": [] }, { - "query": "from a_index | stats round( sum(numberField) )", + "query": "from a_index | enrich policy ", "error": [], "warning": [] }, { - "query": "from a_index | stats round( sum(numberField) ) + round( sum(numberField) )", - "error": [], + "query": "from a_index | enrich `this``is fine`", + "error": [ + "SyntaxError: mismatched input '`this``is fine`' expecting ENRICH_POLICY_NAME" + ], "warning": [] }, { - "query": "from a_index | stats round( numberField + sum(numberField) )", + "query": "from a_index | enrich this is fine", "error": [ - "Cannot combine aggregation and non-aggregation values in [STATS], found [round(numberField+sum(numberField))]" + "SyntaxError: mismatched input 'is' expecting ", + "Unknown policy [this]" ], "warning": [] }, { - "query": "from a_index | stats round( numberField + sum(numberField) ), var0 = sum(numberField)", + "query": "from a_index | enrich _any:policy ", + "error": [], + "warning": [] + }, + { + "query": "from a_index | enrich _any : policy ", "error": [ - "Cannot combine aggregation and non-aggregation values in [STATS], found [round(numberField+sum(numberField))]" + "SyntaxError: token recognition error at: ':'", + "SyntaxError: extraneous input 'policy' expecting ", + "Unknown policy [_any]" ], "warning": [] }, { - "query": "from a_index | stats var0 = round( numberField + sum(numberField) ), var1 = sum(numberField)", + "query": "from a_index | enrich _any: policy ", "error": [ - "Cannot combine aggregation and non-aggregation values in [STATS], found [round(numberField+sum(numberField))]" + "SyntaxError: token recognition error at: ':'", + "SyntaxError: extraneous input 'policy' expecting ", + "Unknown policy [_any]" ], "warning": [] }, { - "query": "from a_index | stats round( sum(numberField + numberField) )", + "query": "from a_index | enrich _any:policy ", "error": [], "warning": [] }, { - "query": "from a_index | stats round( sum(numberField + round(numberField)) )", + "query": "from a_index | enrich _ANY:policy ", "error": [], "warning": [] }, { - "query": "from a_index | stats round( sum(numberField + round(numberField)) ) + round( sum(numberField + round(numberField)) )", + "query": "from a_index | enrich _coordinator:policy ", "error": [], "warning": [] }, { - "query": "from a_index | stats sum(round( numberField ) )", - "error": [], + "query": "from a_index | enrich _coordinator : policy ", + "error": [ + "SyntaxError: token recognition error at: ':'", + "SyntaxError: extraneous input 'policy' expecting ", + "Unknown policy [_coordinator]" + ], "warning": [] }, { - "query": "from a_index | stats sum(round( numberField ) ) + sum(round( numberField ) )", - "error": [], + "query": "from a_index | enrich _coordinator: policy ", + "error": [ + "SyntaxError: token recognition error at: ':'", + "SyntaxError: extraneous input 'policy' expecting ", + "Unknown policy [_coordinator]" + ], "warning": [] }, { - "query": "from a_index | stats 5 + avg(numberField) +1+1", + "query": "from a_index | enrich _coordinator:policy ", "error": [], "warning": [] }, { - "query": "from a_index | stats 5 +1+1 + avg(numberField)", + "query": "from a_index | enrich _COORDINATOR:policy ", "error": [], "warning": [] }, { - "query": "from a_index | stats 5 +1+1 + numberField", - "error": [ - "At least one aggregation function required in [STATS], found [5+1+1+numberField]" - ], + "query": "from a_index | enrich _remote:policy ", + "error": [], "warning": [] }, { - "query": "from a_index | stats 5 + numberField +1+1", + "query": "from a_index | enrich _remote : policy ", "error": [ - "At least one aggregation function required in [STATS], found [5+numberField+1+1]" + "SyntaxError: token recognition error at: ':'", + "SyntaxError: extraneous input 'policy' expecting ", + "Unknown policy [_remote]" ], "warning": [] }, { - "query": "from a_index | stats 5 + numberField +1+1, var0 = sum(numberField)", + "query": "from a_index | enrich _remote: policy ", "error": [ - "At least one aggregation function required in [STATS], found [5+numberField+1+1]" + "SyntaxError: token recognition error at: ':'", + "SyntaxError: extraneous input 'policy' expecting ", + "Unknown policy [_remote]" ], "warning": [] }, { - "query": "from a_index | stats round(round( sum(numberField) ))", + "query": "from a_index | enrich _remote:policy ", "error": [], "warning": [] }, { - "query": "from a_index | stats round(round( sum(numberField) )) + round(round( sum(numberField) ))", + "query": "from a_index | enrich _REMOTE:policy ", "error": [], "warning": [] }, { - "query": "from a_index | stats round(round( numberField + sum(numberField) ))", + "query": "from a_index | enrich _unknown:policy", "error": [ - "Cannot combine aggregation and non-aggregation values in [STATS], found [round(round(numberField+sum(numberField)))]" + "Unrecognized value [_unknown] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]" ], "warning": [] }, { - "query": "from a_index | stats round(round( numberField + sum(numberField) )), var0 = sum(numberField)", + "query": "from a_index |enrich missing-policy ", "error": [ - "Cannot combine aggregation and non-aggregation values in [STATS], found [round(round(numberField+sum(numberField)))]" + "Unknown policy [missing-policy]" ], "warning": [] }, { - "query": "from a_index | stats var0 = round(round( numberField + sum(numberField) )), var1 = sum(numberField)", + "query": "from a_index |enrich policy on ", "error": [ - "Cannot combine aggregation and non-aggregation values in [STATS], found [round(round(numberField+sum(numberField)))]" + "SyntaxError: missing ID_PATTERN at ''" ], "warning": [] }, { - "query": "from a_index | stats round(round( sum(numberField + numberField) ))", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats round(round( sum(numberField + round(numberField)) ))", - "error": [], + "query": "from a_index | enrich policy on b ", + "error": [ + "Unknown column [b]" + ], "warning": [] }, { - "query": "from a_index | stats round(round( sum(numberField + round(numberField)) )) + round(round( sum(numberField + round(numberField)) ))", - "error": [], + "query": "from a_index | enrich policy on `this``is fine`", + "error": [ + "Unknown column [this`is fine]" + ], "warning": [] }, { - "query": "from a_index | stats sum(round(round( numberField )) )", - "error": [], + "query": "from a_index | enrich policy on this is fine", + "error": [ + "SyntaxError: mismatched input 'is' expecting ", + "Unknown column [this]" + ], "warning": [] }, { - "query": "from a_index | stats sum(round(round( numberField )) ) + sum(round(round( numberField )) )", - "error": [], + "query": "from a_index | enrich policy on stringField with ", + "error": [ + "SyntaxError: mismatched input '' expecting ID_PATTERN" + ], "warning": [] }, { - "query": "from a_index | stats 5 + avg(numberField) +1+1+1", - "error": [], + "query": "from a_index | enrich policy on stringField with var0 ", + "error": [ + "Unknown column [var0]" + ], "warning": [] }, { - "query": "from a_index | stats 5 +1+1+1 + avg(numberField)", - "error": [], + "query": "from a_index |enrich policy on numberField with var0 = ", + "error": [ + "SyntaxError: missing ID_PATTERN at ''", + "Unknown column [var0]" + ], "warning": [] }, { - "query": "from a_index | stats 5 +1+1+1 + numberField", + "query": "from a_index | enrich policy on stringField with var0 = c ", "error": [ - "At least one aggregation function required in [STATS], found [5+1+1+1+numberField]" + "Unknown column [var0]", + "Unknown column [c]" ], "warning": [] }, { - "query": "from a_index | stats 5 + numberField +1+1+1", + "query": "from a_index |enrich policy on numberField with var0 = , ", "error": [ - "At least one aggregation function required in [STATS], found [5+numberField+1+1+1]" + "SyntaxError: missing ID_PATTERN at ','", + "SyntaxError: mismatched input '' expecting ID_PATTERN", + "Unknown column [var0]" ], "warning": [] }, { - "query": "from a_index | stats 5 + numberField +1+1+1, var0 = sum(numberField)", + "query": "from a_index | enrich policy on stringField with var0 = otherField, var1 ", "error": [ - "At least one aggregation function required in [STATS], found [5+numberField+1+1+1]" + "Unknown column [var1]" ], "warning": [] }, { - "query": "from a_index | stats round(round(round( sum(numberField) )))", + "query": "from a_index | enrich policy on stringField with var0 = otherField ", "error": [], "warning": [] }, { - "query": "from a_index | stats round(round(round( sum(numberField) ))) + round(round(round( sum(numberField) )))", + "query": "from a_index | enrich policy on stringField with var0 = otherField, yetAnotherField ", "error": [], "warning": [] }, { - "query": "from a_index | stats round(round(round( numberField + sum(numberField) )))", + "query": "from a_index |enrich policy on numberField with var0 = otherField, var1 = ", "error": [ - "Cannot combine aggregation and non-aggregation values in [STATS], found [round(round(round(numberField+sum(numberField))))]" + "SyntaxError: missing ID_PATTERN at ''", + "Unknown column [var1]" ], "warning": [] }, { - "query": "from a_index | stats round(round(round( numberField + sum(numberField) ))), var0 = sum(numberField)", - "error": [ - "Cannot combine aggregation and non-aggregation values in [STATS], found [round(round(round(numberField+sum(numberField))))]" - ], + "query": "from a_index | enrich policy on stringField with var0 = otherField, var1 = yetAnotherField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | enrich policy on stringField with var0 = otherField, `this``is fine` = yetAnotherField", + "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = round(round(round( numberField + sum(numberField) ))), var1 = sum(numberField)", + "query": "from a_index | enrich policy with ", "error": [ - "Cannot combine aggregation and non-aggregation values in [STATS], found [round(round(round(numberField+sum(numberField))))]" + "SyntaxError: mismatched input '' expecting ID_PATTERN" ], "warning": [] }, { - "query": "from a_index | stats round(round(round( sum(numberField + numberField) )))", + "query": "from a_index | enrich policy with otherField", "error": [], "warning": [] }, { - "query": "from a_index | stats round(round(round( sum(numberField + round(numberField)) )))", + "query": "from a_index | enrich policy | eval otherField", "error": [], "warning": [] }, { - "query": "from a_index | stats round(round(round( sum(numberField + round(numberField)) ))) + round(round(round( sum(numberField + round(numberField)) )))", + "query": "from a_index | enrich policy with var0 = otherField | eval var0", "error": [], "warning": [] }, { - "query": "from a_index | stats sum(round(round(round( numberField ))) )", + "query": "from a_index | enrich my-pol*", + "error": [ + "Using wildcards (*) in ENRICH is not allowed [my-pol*]" + ], + "warning": [] + }, + { + "query": "from a_index | eval stringField = 5", + "error": [], + "warning": [ + "Column [stringField] of type string has been overwritten as new type: number" + ] + }, + { + "query": "from a_index | eval numberField = \"5\"", + "error": [], + "warning": [ + "Column [numberField] of type number has been overwritten as new type: string" + ] + }, + { + "query": "from a_index | eval round(numberField) + 1 | eval `round(numberField) + 1` + 1 | keep ```round(numberField) + 1`` + 1`", "error": [], "warning": [] }, { - "query": "from a_index | stats sum(round(round(round( numberField ))) ) + sum(round(round(round( numberField ))) )", + "query": "from a_index | eval round(numberField) + 1 | eval `round(numberField) + 1` + 1 | eval ```round(numberField) + 1`` + 1` + 1 | keep ```````round(numberField) + 1```` + 1`` + 1`", "error": [], "warning": [] }, { - "query": "from a_index | stats 5 + avg(numberField) +1+1+1+1", + "query": "from a_index | eval round(numberField) + 1 | eval `round(numberField) + 1` + 1 | eval ```round(numberField) + 1`` + 1` + 1 | eval ```````round(numberField) + 1```` + 1`` + 1` + 1 | keep ```````````````round(numberField) + 1```````` + 1```` + 1`` + 1`", "error": [], "warning": [] }, { - "query": "from a_index | stats 5 +1+1+1+1 + avg(numberField)", + "query": "from a_index | eval round(numberField) + 1 | eval `round(numberField) + 1` + 1 | eval ```round(numberField) + 1`` + 1` + 1 | eval ```````round(numberField) + 1```` + 1`` + 1` + 1 | eval ```````````````round(numberField) + 1```````` + 1```` + 1`` + 1` + 1 | keep ```````````````````````````````round(numberField) + 1```````````````` + 1```````` + 1```` + 1`` + 1`", "error": [], "warning": [] }, { - "query": "from a_index | stats 5 +1+1+1+1 + numberField", - "error": [ - "At least one aggregation function required in [STATS], found [5+1+1+1+1+numberField]" - ], + "query": "from a_index | eval 1::string", + "error": [], "warning": [] }, { - "query": "from a_index | stats 5 + numberField +1+1+1+1", - "error": [ - "At least one aggregation function required in [STATS], found [5+numberField+1+1+1+1]" - ], + "query": "from a_index | eval 1::string::long::double", + "error": [], "warning": [] }, { - "query": "from a_index | stats 5 + numberField +1+1+1+1, var0 = sum(numberField)", + "query": "from a_index | eval trim(\"23\"::double)", "error": [ - "At least one aggregation function required in [STATS], found [5+numberField+1+1+1+1]" + "Argument of [trim] must be [string], found value [\"23\"::double] type [double]" ], "warning": [] }, { - "query": "from a_index | stats round(round(round(round( sum(numberField) ))))", + "query": "from a_index | eval trim(23::string)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(round(round(round( sum(numberField) )))) + round(round(round(round( sum(numberField) ))))", + "query": "from a_index | eval 1 + \"2\"::long", "error": [], "warning": [] }, { - "query": "from a_index | stats round(round(round(round( numberField + sum(numberField) ))))", + "query": "from a_index | eval 1 + \"2\"", "error": [ - "Cannot combine aggregation and non-aggregation values in [STATS], found [round(round(round(round(numberField+sum(numberField)))))]" + "Argument of [+] must be [number], found value [\"2\"] type [string]" ], "warning": [] }, { - "query": "from a_index | stats round(round(round(round( numberField + sum(numberField) )))), var0 = sum(numberField)", + "query": "from a_index | eval trim(to_double(\"23\")::string::double::long::string::double)", "error": [ - "Cannot combine aggregation and non-aggregation values in [STATS], found [round(round(round(round(numberField+sum(numberField)))))]" + "Argument of [trim] must be [string], found value [to_double(\"23\")::string::double::long::string::double] type [double]" ], "warning": [] }, { - "query": "from a_index | stats var0 = round(round(round(round( numberField + sum(numberField) )))), var1 = sum(numberField)", - "error": [ - "Cannot combine aggregation and non-aggregation values in [STATS], found [round(round(round(round(numberField+sum(numberField)))))]" - ], + "query": "from a_index | eval CEIL(23::long)", + "error": [], "warning": [] }, { - "query": "from a_index | stats round(round(round(round( sum(numberField + numberField) ))))", + "query": "from a_index | eval CEIL(23::unsigned_long)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(round(round(round( sum(numberField + round(numberField)) ))))", + "query": "from a_index | eval CEIL(23::int)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(round(round(round( sum(numberField + round(numberField)) )))) + round(round(round(round( sum(numberField + round(numberField)) ))))", + "query": "from a_index | eval CEIL(23::integer)", "error": [], "warning": [] }, { - "query": "from a_index | stats sum(round(round(round(round( numberField )))) )", + "query": "from a_index | eval CEIL(23::double)", "error": [], "warning": [] }, { - "query": "from a_index | stats sum(round(round(round(round( numberField )))) ) + sum(round(round(round(round( numberField )))) )", + "query": "from a_index | eval TRIM(23::string)", "error": [], "warning": [] }, { - "query": "from a_index | stats 5 + numberField + 1", - "error": [ - "At least one aggregation function required in [STATS], found [5+numberField+1]" - ], + "query": "from a_index | eval TRIM(23::text)", + "error": [], "warning": [] }, { - "query": "from a_index | stats numberField + 1 by ipField", - "error": [ - "At least one aggregation function required in [STATS], found [numberField+1]" - ], + "query": "from a_index | eval TRIM(23::keyword)", + "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), percentile(numberField, 50) + 1 by ipField", + "query": "from a_index | eval true AND \"false\"::boolean", "error": [], "warning": [] }, { - "query": "from a_index | stats count(*)", + "query": "from a_index | eval true AND \"false\"::bool", "error": [], "warning": [] }, { - "query": "from a_index | stats count()", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats var0 = count(*)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats var0 = count()", - "error": [], + "query": "from a_index | eval true AND \"false\"", + "error": [ + "Argument of [and] must be [boolean], found value [\"false\"] type [string]" + ], "warning": [] }, { - "query": "from a_index | stats var0 = avg(numberField), count(*)", - "error": [], + "query": "from a_index | eval to_lower(trim(numberField)::string)", + "error": [ + "Argument of [trim] must be [string], found value [numberField] type [number]" + ], "warning": [] }, { - "query": "from a_index | stats var0 = avg(fn(number)), count(*)", + "query": "from a_index | eval to_upper(trim(numberField)::string::string::string::string)", "error": [ - "Unknown function [fn]" + "Argument of [trim] must be [string], found value [numberField] type [number]" ], "warning": [] }, { - "query": "from a_index | STATS sum( numberField ) + abs( numberField ) ", + "query": "from a_index | eval to_lower(to_upper(trim(numberField)::string)::string)", "error": [ - "Cannot combine aggregation and non-aggregation values in [STATS], found [sum(numberField)+abs(numberField)]" + "Argument of [trim] must be [string], found value [numberField] type [number]" ], "warning": [] }, { - "query": "from a_index | STATS abs( numberField + sum( numberField )) ", - "error": [ - "Cannot combine aggregation and non-aggregation values in [STATS], found [abs(numberField+sum(numberField))]" - ], + "query": "row var = date_diff(\"month\", \"2023-12-02T11:00:00.000Z\", \"2023-12-02T11:00:00.000Z\")", + "error": [], "warning": [] }, { - "query": "FROM index\n | EVAL numberField * 3.281\n | STATS avg_numberField = AVG(`numberField * 3.281`)", + "query": "row var = date_diff(\"mm\", \"2023-12-02T11:00:00.000Z\", \"2023-12-02T11:00:00.000Z\")", "error": [], "warning": [] }, { - "query": "FROM index | STATS AVG(numberField) by round(numberField) + 1 | EVAL `round(numberField) + 1` / 2", + "query": "row var = date_diff(\"bogus\", \"2023-12-02T11:00:00.000Z\", \"2023-12-02T11:00:00.000Z\")", + "error": [], + "warning": [ + "Invalid option [\"bogus\"] for date_diff. Supported options: [\"year\", \"years\", \"yy\", \"yyyy\", \"quarter\", \"quarters\", \"qq\", \"q\", \"month\", \"months\", \"mm\", \"m\", \"dayofyear\", \"dy\", \"y\", \"day\", \"days\", \"dd\", \"d\", \"week\", \"weeks\", \"wk\", \"ww\", \"weekday\", \"weekdays\", \"dw\", \"hour\", \"hours\", \"hh\", \"minute\", \"minutes\", \"mi\", \"n\", \"second\", \"seconds\", \"ss\", \"s\", \"millisecond\", \"milliseconds\", \"ms\", \"microsecond\", \"microseconds\", \"mcs\", \"nanosecond\", \"nanoseconds\", \"ns\"]." + ] + }, + { + "query": "from a_index | eval date_diff(stringField, \"2023-12-02T11:00:00.000Z\", \"2023-12-02T11:00:00.000Z\")", "error": [], "warning": [] }, { - "query": "from a_index | stats sum(case(false, 0, 1))", + "query": "from a_index | eval date_diff(\"month\", dateField, \"2023-12-02T11:00:00.000Z\")", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = sum( case(false, 0, 1))", + "query": "from a_index | eval date_diff(\"month\", \"2023-12-02T11:00:00.000Z\", dateField)", "error": [], "warning": [] }, { - "query": "from index | stats by bucket(dateField, abs(numberField), \"\", \"\")", + "query": "from a_index | eval date_diff(\"month\", stringField, dateField)", "error": [ - "Argument of [bucket] must be a constant, received [abs(numberField)]" + "Argument of [date_diff] must be [date], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from index | stats by bucket(dateField, abs(length(numberField)), \"\", \"\")", + "query": "from a_index | eval date_diff(\"month\", dateField, stringField)", "error": [ - "Argument of [bucket] must be a constant, received [abs(length(numberField))]" + "Argument of [date_diff] must be [date], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from index | stats by bucket(dateField, pi(), \"\", \"\")", + "query": "from a_index | eval var = date_diff(\"year\", dateField, dateField)", "error": [], "warning": [] }, { - "query": "from index | stats by bucket(dateField, 1 + 30 / 10, \"\", \"\")", + "query": "from a_index | eval date_diff(\"year\", dateField, dateField)", "error": [], "warning": [] }, { - "query": "from index | stats by bucket(dateField, 1 + 30 / 10, concat(\"\", \"\"), \"\")", + "query": "from a_index | eval var = date_diff(\"year\", to_datetime(stringField), to_datetime(stringField))", "error": [], "warning": [] }, { - "query": "from index | stats by bucket(dateField, numberField, stringField, stringField)", + "query": "from a_index | eval date_diff(numberField, stringField, stringField)", "error": [ - "Argument of [bucket] must be a constant, received [numberField]", - "Argument of [bucket] must be a constant, received [stringField]", - "Argument of [bucket] must be a constant, received [stringField]" + "Argument of [date_diff] must be [string], found value [numberField] type [number]", + "Argument of [date_diff] must be [date], found value [stringField] type [string]", + "Argument of [date_diff] must be [date], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | sort ", + "query": "from a_index | eval date_diff(\"year\", dateField, dateField, extraArg)", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "Error: [date_diff] function expects exactly 3 arguments, got 4." ], "warning": [] }, { - "query": "from a_index | sort \"field\" ", + "query": "from a_index | sort date_diff(\"year\", dateField, dateField)", "error": [], "warning": [] }, { - "query": "from a_index | sort wrongField ", - "error": [ - "Unknown column [wrongField]" - ], + "query": "from a_index | eval var = date_diff(\"year\", to_datetime(dateField), to_datetime(dateField))", + "error": [], "warning": [] }, { - "query": "from a_index | sort numberField, ", + "query": "from a_index | eval date_diff(booleanField, booleanField, booleanField)", "error": [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}" + "Argument of [date_diff] must be [string], found value [booleanField] type [boolean]", + "Argument of [date_diff] must be [date], found value [booleanField] type [boolean]", + "Argument of [date_diff] must be [date], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort numberField, stringField", + "query": "from a_index | eval date_diff(null, null, null)", "error": [], "warning": [] }, { - "query": "from a_index | sort \"field\" desc ", + "query": "row nullVar = null | eval date_diff(nullVar, nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | sort numberField desc ", + "query": "from a_index | eval date_diff(\"year\", \"2022\", \"2022\")", "error": [], "warning": [] }, { - "query": "from a_index | sort numberField desc nulls ", + "query": "from a_index | eval date_diff(\"year\", concat(\"20\", \"22\"), concat(\"20\", \"22\"))", "error": [ - "SyntaxError: missing {'first', 'last'} at ''" + "Argument of [date_diff] must be [date], found value [concat(\"20\", \"22\")] type [string]", + "Argument of [date_diff] must be [date], found value [concat(\"20\", \"22\")] type [string]" ], "warning": [] }, { - "query": "from a_index | sort numberField desc nulls first", + "query": "row var = abs(5)", "error": [], "warning": [] }, { - "query": "from a_index | sort numberField desc first", + "query": "row abs(5)", + "error": [], + "warning": [] + }, + { + "query": "row var = abs(to_integer(\"a\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = abs(\"a\")", "error": [ - "SyntaxError: extraneous input 'first' expecting " + "Argument of [abs] must be [number], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | sort numberField desc nulls last", + "query": "from a_index | where abs(numberField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | sort numberField desc last", + "query": "from a_index | where abs(stringField) > 0", "error": [ - "SyntaxError: extraneous input 'last' expecting " + "Argument of [abs] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | sort \"field\" asc ", + "query": "from a_index | eval var = abs(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | sort numberField asc ", + "query": "from a_index | eval abs(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | sort numberField asc nulls ", + "query": "from a_index | eval var = abs(to_integer(stringField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval abs(stringField)", "error": [ - "SyntaxError: missing {'first', 'last'} at ''" + "Argument of [abs] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | sort numberField asc nulls first", - "error": [], + "query": "from a_index | eval abs(numberField, extraArg)", + "error": [ + "Error: [abs] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | sort numberField asc first", + "query": "from a_index | eval var = abs(*)", "error": [ - "SyntaxError: extraneous input 'first' expecting " + "Using wildcards (*) in abs is not allowed" ], "warning": [] }, { - "query": "from a_index | sort numberField asc nulls last", + "query": "from a_index | sort abs(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | sort numberField asc last", + "query": "row var = abs(to_integer(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = abs(true)", "error": [ - "SyntaxError: extraneous input 'last' expecting " + "Argument of [abs] must be [number], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort numberField nulls first", + "query": "from a_index | where abs(booleanField) > 0", + "error": [ + "Argument of [abs] must be [number], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = abs(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | sort numberField first", + "query": "from a_index | eval abs(booleanField)", "error": [ - "SyntaxError: extraneous input 'first' expecting " + "Argument of [abs] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort numberField nulls last", + "query": "from a_index | eval abs(null)", "error": [], "warning": [] }, { - "query": "from a_index | sort numberField last", - "error": [ - "SyntaxError: extraneous input 'last' expecting " - ], + "query": "row nullVar = null | eval abs(nullVar)", + "error": [], "warning": [] }, { - "query": "row a = 1 | stats COUNT(*) | sort `COUNT(*)`", + "query": "row var = acos(5)", "error": [], "warning": [] }, { - "query": "ROW a = 1 | STATS couNt(*) | SORT `couNt(*)`", + "query": "row acos(5)", "error": [], "warning": [] }, { - "query": "from a_index | sort abs(numberField) - to_long(stringField) desc nulls first", + "query": "row var = acos(to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | sort sin(stringField)", + "query": "row var = acos(\"a\")", "error": [ - "Argument of [sin] must be [number], found value [stringField] type [string]" + "Argument of [acos] must be [number], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | sort numberField + stringField", - "error": [ - "Argument of [+] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | where acos(numberField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | enrich", + "query": "from a_index | where acos(stringField) > 0", "error": [ - "SyntaxError: missing ENRICH_POLICY_NAME at ''" + "Argument of [acos] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | enrich _", - "error": [ - "Unknown policy [_]" - ], + "query": "from a_index | eval var = acos(numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | enrich _:", - "error": [ - "SyntaxError: token recognition error at: ':'", - "Unknown policy [_]" - ], + "query": "from a_index | eval acos(numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | enrich _:policy", - "error": [ - "Unrecognized value [_] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]" - ], + "query": "from a_index | eval var = acos(to_integer(stringField))", + "error": [], "warning": [] }, { - "query": "from a_index | enrich :policy", + "query": "from a_index | eval acos(stringField)", "error": [ - "SyntaxError: token recognition error at: ':'" + "Argument of [acos] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | enrich any:", + "query": "from a_index | eval acos(numberField, extraArg)", "error": [ - "SyntaxError: token recognition error at: ':'", - "Unknown policy [any]" + "Error: [acos] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | enrich _any:", + "query": "from a_index | eval var = acos(*)", "error": [ - "SyntaxError: token recognition error at: ':'", - "Unknown policy [_any]" + "Using wildcards (*) in acos is not allowed" ], "warning": [] }, { - "query": "from a_index | enrich any:policy", - "error": [ - "Unrecognized value [any] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]" - ], + "query": "from a_index | sort acos(numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | enrich policy ", + "query": "row var = acos(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | enrich `this``is fine`", + "query": "row var = acos(true)", "error": [ - "SyntaxError: mismatched input '`this``is fine`' expecting ENRICH_POLICY_NAME" + "Argument of [acos] must be [number], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | enrich this is fine", + "query": "from a_index | where acos(booleanField) > 0", "error": [ - "SyntaxError: mismatched input 'is' expecting ", - "Unknown policy [this]" + "Argument of [acos] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | enrich _any:policy ", + "query": "from a_index | eval var = acos(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | enrich _any : policy ", + "query": "from a_index | eval acos(booleanField)", "error": [ - "SyntaxError: token recognition error at: ':'", - "SyntaxError: extraneous input 'policy' expecting ", - "Unknown policy [_any]" + "Argument of [acos] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | enrich _any: policy ", - "error": [ - "SyntaxError: token recognition error at: ':'", - "SyntaxError: extraneous input 'policy' expecting ", - "Unknown policy [_any]" - ], + "query": "from a_index | eval acos(null)", + "error": [], "warning": [] }, { - "query": "from a_index | enrich _any:policy ", + "query": "row nullVar = null | eval acos(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | enrich _ANY:policy ", + "query": "row var = asin(5)", "error": [], "warning": [] }, { - "query": "from a_index | enrich _coordinator:policy ", + "query": "row asin(5)", "error": [], "warning": [] }, { - "query": "from a_index | enrich _coordinator : policy ", + "query": "row var = asin(to_integer(\"a\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = asin(\"a\")", "error": [ - "SyntaxError: token recognition error at: ':'", - "SyntaxError: extraneous input 'policy' expecting ", - "Unknown policy [_coordinator]" + "Argument of [asin] must be [number], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | enrich _coordinator: policy ", - "error": [ - "SyntaxError: token recognition error at: ':'", - "SyntaxError: extraneous input 'policy' expecting ", - "Unknown policy [_coordinator]" + "query": "from a_index | where asin(numberField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where asin(stringField) > 0", + "error": [ + "Argument of [asin] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | enrich _coordinator:policy ", + "query": "from a_index | eval var = asin(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | enrich _COORDINATOR:policy ", + "query": "from a_index | eval asin(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | enrich _remote:policy ", + "query": "from a_index | eval var = asin(to_integer(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | enrich _remote : policy ", + "query": "from a_index | eval asin(stringField)", "error": [ - "SyntaxError: token recognition error at: ':'", - "SyntaxError: extraneous input 'policy' expecting ", - "Unknown policy [_remote]" + "Argument of [asin] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | enrich _remote: policy ", + "query": "from a_index | eval asin(numberField, extraArg)", "error": [ - "SyntaxError: token recognition error at: ':'", - "SyntaxError: extraneous input 'policy' expecting ", - "Unknown policy [_remote]" + "Error: [asin] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | enrich _remote:policy ", + "query": "from a_index | eval var = asin(*)", + "error": [ + "Using wildcards (*) in asin is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | sort asin(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | enrich _REMOTE:policy ", + "query": "row var = asin(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | enrich _unknown:policy", + "query": "row var = asin(true)", "error": [ - "Unrecognized value [_unknown] for ENRICH, mode needs to be one of [_ANY, _COORDINATOR, _REMOTE]" + "Argument of [asin] must be [number], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index |enrich missing-policy ", + "query": "from a_index | where asin(booleanField) > 0", "error": [ - "Unknown policy [missing-policy]" + "Argument of [asin] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index |enrich policy on ", - "error": [ - "SyntaxError: missing ID_PATTERN at ''" - ], + "query": "from a_index | eval var = asin(to_integer(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | enrich policy on b ", + "query": "from a_index | eval asin(booleanField)", "error": [ - "Unknown column [b]" + "Argument of [asin] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | enrich policy on `this``is fine`", - "error": [ - "Unknown column [this`is fine]" - ], + "query": "from a_index | eval asin(null)", + "error": [], "warning": [] }, { - "query": "from a_index | enrich policy on this is fine", - "error": [ - "SyntaxError: mismatched input 'is' expecting ", - "Unknown column [this]" - ], + "query": "row nullVar = null | eval asin(nullVar)", + "error": [], "warning": [] }, { - "query": "from a_index | enrich policy on stringField with ", - "error": [ - "SyntaxError: mismatched input '' expecting ID_PATTERN" - ], + "query": "row var = atan(5)", + "error": [], "warning": [] }, { - "query": "from a_index | enrich policy on stringField with var0 ", - "error": [ - "Unknown column [var0]" - ], + "query": "row atan(5)", + "error": [], "warning": [] }, { - "query": "from a_index |enrich policy on numberField with var0 = ", - "error": [ - "SyntaxError: missing ID_PATTERN at ''", - "Unknown column [var0]" - ], + "query": "row var = atan(to_integer(\"a\"))", + "error": [], "warning": [] }, { - "query": "from a_index | enrich policy on stringField with var0 = c ", + "query": "row var = atan(\"a\")", "error": [ - "Unknown column [var0]", - "Unknown column [c]" + "Argument of [atan] must be [number], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index |enrich policy on numberField with var0 = , ", - "error": [ - "SyntaxError: missing ID_PATTERN at ','", - "SyntaxError: mismatched input '' expecting ID_PATTERN", - "Unknown column [var0]" - ], + "query": "from a_index | where atan(numberField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | enrich policy on stringField with var0 = otherField, var1 ", + "query": "from a_index | where atan(stringField) > 0", "error": [ - "Unknown column [var1]" + "Argument of [atan] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | enrich policy on stringField with var0 = otherField ", + "query": "from a_index | eval var = atan(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | enrich policy on stringField with var0 = otherField, yetAnotherField ", + "query": "from a_index | eval atan(numberField)", "error": [], "warning": [] }, { - "query": "from a_index |enrich policy on numberField with var0 = otherField, var1 = ", - "error": [ - "SyntaxError: missing ID_PATTERN at ''", - "Unknown column [var1]" - ], + "query": "from a_index | eval var = atan(to_integer(stringField))", + "error": [], "warning": [] }, { - "query": "from a_index | enrich policy on stringField with var0 = otherField, var1 = yetAnotherField", - "error": [], + "query": "from a_index | eval atan(stringField)", + "error": [ + "Argument of [atan] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "from a_index | enrich policy on stringField with var0 = otherField, `this``is fine` = yetAnotherField", - "error": [], + "query": "from a_index | eval atan(numberField, extraArg)", + "error": [ + "Error: [atan] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | enrich policy with ", + "query": "from a_index | eval var = atan(*)", "error": [ - "SyntaxError: mismatched input '' expecting ID_PATTERN" + "Using wildcards (*) in atan is not allowed" ], "warning": [] }, { - "query": "from a_index | enrich policy with otherField", + "query": "from a_index | sort atan(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | enrich policy | eval otherField", + "query": "row var = atan(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | enrich policy with var0 = otherField | eval var0", - "error": [], + "query": "row var = atan(true)", + "error": [ + "Argument of [atan] must be [number], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | enrich my-pol*", + "query": "from a_index | where atan(booleanField) > 0", "error": [ - "Using wildcards (*) in ENRICH is not allowed [my-pol*]" + "Argument of [atan] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval stringField = 5", + "query": "from a_index | eval var = atan(to_integer(booleanField))", "error": [], - "warning": [ - "Column [stringField] of type string has been overwritten as new type: number" - ] + "warning": [] }, { - "query": "from a_index | eval numberField = \"5\"", - "error": [], - "warning": [ - "Column [numberField] of type number has been overwritten as new type: string" - ] + "query": "from a_index | eval atan(booleanField)", + "error": [ + "Argument of [atan] must be [number], found value [booleanField] type [boolean]" + ], + "warning": [] }, { - "query": "from a_index | eval round(numberField) + 1 | eval `round(numberField) + 1` + 1 | keep ```round(numberField) + 1`` + 1`", + "query": "from a_index | eval atan(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval round(numberField) + 1 | eval `round(numberField) + 1` + 1 | eval ```round(numberField) + 1`` + 1` + 1 | keep ```````round(numberField) + 1```` + 1`` + 1`", + "query": "row nullVar = null | eval atan(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval round(numberField) + 1 | eval `round(numberField) + 1` + 1 | eval ```round(numberField) + 1`` + 1` + 1 | eval ```````round(numberField) + 1```` + 1`` + 1` + 1 | keep ```````````````round(numberField) + 1```````` + 1```` + 1`` + 1`", + "query": "row var = atan2(5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval round(numberField) + 1 | eval `round(numberField) + 1` + 1 | eval ```round(numberField) + 1`` + 1` + 1 | eval ```````round(numberField) + 1```` + 1`` + 1` + 1 | eval ```````````````round(numberField) + 1```````` + 1```` + 1`` + 1` + 1 | keep ```````````````````````````````round(numberField) + 1```````````````` + 1```````` + 1```` + 1`` + 1`", + "query": "row atan2(5, 5)", "error": [], "warning": [] }, { - "query": "row var = date_diff(\"month\", \"2023-12-02T11:00:00.000Z\", \"2023-12-02T11:00:00.000Z\")", + "query": "row var = atan2(to_integer(\"a\"), to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = date_diff(\"mm\", \"2023-12-02T11:00:00.000Z\", \"2023-12-02T11:00:00.000Z\")", - "error": [], + "query": "row var = atan2(\"a\", \"a\")", + "error": [ + "Argument of [atan2] must be [number], found value [\"a\"] type [string]", + "Argument of [atan2] must be [number], found value [\"a\"] type [string]" + ], "warning": [] }, { - "query": "row var = date_diff(\"bogus\", \"2023-12-02T11:00:00.000Z\", \"2023-12-02T11:00:00.000Z\")", + "query": "from a_index | where atan2(numberField, numberField) > 0", "error": [], - "warning": [ - "Invalid option [\"bogus\"] for date_diff. Supported options: [\"year\", \"years\", \"yy\", \"yyyy\", \"quarter\", \"quarters\", \"qq\", \"q\", \"month\", \"months\", \"mm\", \"m\", \"dayofyear\", \"dy\", \"y\", \"day\", \"days\", \"dd\", \"d\", \"week\", \"weeks\", \"wk\", \"ww\", \"weekday\", \"weekdays\", \"dw\", \"hour\", \"hours\", \"hh\", \"minute\", \"minutes\", \"mi\", \"n\", \"second\", \"seconds\", \"ss\", \"s\", \"millisecond\", \"milliseconds\", \"ms\", \"microsecond\", \"microseconds\", \"mcs\", \"nanosecond\", \"nanoseconds\", \"ns\"]." - ] + "warning": [] }, { - "query": "from a_index | eval date_diff(stringField, \"2023-12-02T11:00:00.000Z\", \"2023-12-02T11:00:00.000Z\")", + "query": "from a_index | where atan2(stringField, stringField) > 0", + "error": [ + "Argument of [atan2] must be [number], found value [stringField] type [string]", + "Argument of [atan2] must be [number], found value [stringField] type [string]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = atan2(numberField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_diff(\"month\", dateField, \"2023-12-02T11:00:00.000Z\")", + "query": "from a_index | eval atan2(numberField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_diff(\"month\", \"2023-12-02T11:00:00.000Z\", dateField)", + "query": "from a_index | eval var = atan2(to_integer(stringField), to_integer(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval date_diff(\"month\", stringField, dateField)", + "query": "from a_index | eval atan2(stringField, stringField)", "error": [ - "Argument of [date_diff] must be [date], found value [stringField] type [string]" + "Argument of [atan2] must be [number], found value [stringField] type [string]", + "Argument of [atan2] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval date_diff(\"month\", dateField, stringField)", + "query": "from a_index | eval atan2(numberField, numberField, extraArg)", "error": [ - "Argument of [date_diff] must be [date], found value [stringField] type [string]" + "Error: [atan2] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | eval var = date_diff(\"year\", dateField, dateField)", + "query": "from a_index | sort atan2(numberField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_diff(\"year\", dateField, dateField)", + "query": "row var = atan2(to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_diff(\"year\", to_datetime(stringField), to_datetime(stringField))", - "error": [], + "query": "row var = atan2(true, true)", + "error": [ + "Argument of [atan2] must be [number], found value [true] type [boolean]", + "Argument of [atan2] must be [number], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval date_diff(numberField, stringField, stringField)", + "query": "from a_index | where atan2(booleanField, booleanField) > 0", "error": [ - "Argument of [date_diff] must be [string], found value [numberField] type [number]", - "Argument of [date_diff] must be [date], found value [stringField] type [string]", - "Argument of [date_diff] must be [date], found value [stringField] type [string]" + "Argument of [atan2] must be [number], found value [booleanField] type [boolean]", + "Argument of [atan2] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval date_diff(\"year\", dateField, dateField, extraArg)", + "query": "from a_index | eval var = atan2(to_integer(booleanField), to_integer(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval atan2(booleanField, booleanField)", "error": [ - "Error: [date_diff] function expects exactly 3 arguments, got 4." + "Argument of [atan2] must be [number], found value [booleanField] type [boolean]", + "Argument of [atan2] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort date_diff(\"year\", dateField, dateField)", + "query": "from a_index | eval atan2(null, null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_diff(\"year\", to_datetime(dateField), to_datetime(dateField))", + "query": "row nullVar = null | eval atan2(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_diff(booleanField, booleanField, booleanField)", - "error": [ - "Argument of [date_diff] must be [string], found value [booleanField] type [boolean]", - "Argument of [date_diff] must be [date], found value [booleanField] type [boolean]", - "Argument of [date_diff] must be [date], found value [booleanField] type [boolean]" - ], + "query": "row var = case(true, \"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval date_diff(null, null, null)", + "query": "row case(true, \"a\")", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval date_diff(nullVar, nullVar, nullVar)", + "query": "from a_index | eval var = case(booleanField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_diff(\"year\", \"2022\", \"2022\")", + "query": "from a_index | eval case(booleanField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_diff(\"year\", concat(\"20\", \"22\"), concat(\"20\", \"22\"))", + "query": "from a_index | sort case(booleanField, stringField)", + "error": [], + "warning": [] + }, + { + "query": "row var = case(to_cartesianpoint(\"POINT (30 10)\"), true)", "error": [ - "Argument of [date_diff] must be [date], found value [concat(\"20\", \"22\")] type [string]", - "Argument of [date_diff] must be [date], found value [concat(\"20\", \"22\")] type [string]" + "Argument of [case] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" ], "warning": [] }, { - "query": "row var = abs(5)", + "query": "from a_index | eval case(null, null)", "error": [], "warning": [] }, { - "query": "row abs(5)", + "query": "row nullVar = null | eval case(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row var = abs(to_integer(\"a\"))", + "query": "row var = ceil(5)", "error": [], "warning": [] }, { - "query": "row var = abs(\"a\")", + "query": "row ceil(5)", + "error": [], + "warning": [] + }, + { + "query": "row var = ceil(to_integer(\"a\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = ceil(\"a\")", "error": [ - "Argument of [abs] must be [number], found value [\"a\"] type [string]" + "Argument of [ceil] must be [number], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | where abs(numberField) > 0", + "query": "from a_index | where ceil(numberField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where abs(stringField) > 0", + "query": "from a_index | where ceil(stringField) > 0", "error": [ - "Argument of [abs] must be [number], found value [stringField] type [string]" + "Argument of [ceil] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = abs(numberField)", + "query": "from a_index | eval var = ceil(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval abs(numberField)", + "query": "from a_index | eval ceil(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = abs(to_integer(stringField))", + "query": "from a_index | eval var = ceil(to_integer(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval abs(stringField)", + "query": "from a_index | eval ceil(stringField)", "error": [ - "Argument of [abs] must be [number], found value [stringField] type [string]" + "Argument of [ceil] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval abs(numberField, extraArg)", + "query": "from a_index | eval ceil(numberField, extraArg)", "error": [ - "Error: [abs] function expects exactly one argument, got 2." + "Error: [ceil] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | eval var = abs(*)", + "query": "from a_index | eval var = ceil(*)", "error": [ - "Using wildcards (*) in abs is not allowed" + "Using wildcards (*) in ceil is not allowed" ], "warning": [] }, { - "query": "from a_index | sort abs(numberField)", + "query": "from a_index | sort ceil(numberField)", "error": [], "warning": [] }, { - "query": "row var = abs(to_integer(true))", + "query": "row var = ceil(to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = abs(true)", + "query": "row var = ceil(true)", "error": [ - "Argument of [abs] must be [number], found value [true] type [boolean]" + "Argument of [ceil] must be [number], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where abs(booleanField) > 0", + "query": "from a_index | where ceil(booleanField) > 0", "error": [ - "Argument of [abs] must be [number], found value [booleanField] type [boolean]" + "Argument of [ceil] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = abs(to_integer(booleanField))", + "query": "from a_index | eval var = ceil(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval abs(booleanField)", + "query": "from a_index | eval ceil(booleanField)", "error": [ - "Argument of [abs] must be [number], found value [booleanField] type [boolean]" + "Argument of [ceil] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval abs(null)", + "query": "from a_index | eval ceil(null)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval abs(nullVar)", + "query": "row nullVar = null | eval ceil(nullVar)", "error": [], "warning": [] }, { - "query": "row var = acos(5)", + "query": "row var = cidr_match(to_ip(\"127.0.0.1\"), \"a\")", "error": [], "warning": [] }, { - "query": "row acos(5)", + "query": "row cidr_match(to_ip(\"127.0.0.1\"), \"a\")", "error": [], "warning": [] }, { - "query": "row var = acos(to_integer(\"a\"))", + "query": "row var = cidr_match(to_ip(\"a\"), to_string(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = acos(\"a\")", + "query": "row var = cidr_match(\"a\", 5)", "error": [ - "Argument of [acos] must be [number], found value [\"a\"] type [string]" + "Argument of [cidr_match] must be [ip], found value [\"a\"] type [string]", + "Argument of [cidr_match] must be [string], found value [5] type [number]" ], "warning": [] }, { - "query": "from a_index | where acos(numberField) > 0", + "query": "from a_index | eval var = cidr_match(ipField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | where acos(stringField) > 0", - "error": [ - "Argument of [acos] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval cidr_match(ipField, stringField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = acos(numberField)", + "query": "from a_index | eval var = cidr_match(to_ip(stringField), to_string(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval acos(numberField)", + "query": "from a_index | eval cidr_match(stringField, numberField)", + "error": [ + "Argument of [cidr_match] must be [ip], found value [stringField] type [string]", + "Argument of [cidr_match] must be [string], found value [numberField] type [number]" + ], + "warning": [] + }, + { + "query": "from a_index | sort cidr_match(ipField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = acos(to_integer(stringField))", + "query": "row var = cidr_match(to_ip(to_ip(\"127.0.0.1\")), to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval acos(stringField)", + "query": "row var = cidr_match(true, true)", "error": [ - "Argument of [acos] must be [number], found value [stringField] type [string]" + "Argument of [cidr_match] must be [ip], found value [true] type [boolean]", + "Argument of [cidr_match] must be [string], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval acos(numberField, extraArg)", - "error": [ - "Error: [acos] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval var = cidr_match(to_ip(ipField), to_string(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = acos(*)", + "query": "from a_index | eval cidr_match(booleanField, booleanField)", "error": [ - "Using wildcards (*) in acos is not allowed" + "Argument of [cidr_match] must be [ip], found value [booleanField] type [boolean]", + "Argument of [cidr_match] must be [string], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort acos(numberField)", + "query": "from a_index | eval cidr_match(null, null)", "error": [], "warning": [] }, { - "query": "row var = acos(to_integer(true))", + "query": "row nullVar = null | eval cidr_match(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row var = acos(true)", - "error": [ - "Argument of [acos] must be [number], found value [true] type [boolean]" - ], + "query": "row var = coalesce(5)", + "error": [], "warning": [] }, { - "query": "from a_index | where acos(booleanField) > 0", - "error": [ - "Argument of [acos] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row coalesce(5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = acos(to_integer(booleanField))", + "query": "row var = coalesce(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval acos(booleanField)", - "error": [ - "Argument of [acos] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = coalesce(5, 5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval acos(null)", + "query": "row coalesce(5, 5)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval acos(nullVar)", + "query": "row var = coalesce(to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = asin(5)", + "query": "row var = coalesce(now())", "error": [], "warning": [] }, { - "query": "row asin(5)", + "query": "row coalesce(now())", "error": [], "warning": [] }, { - "query": "row var = asin(to_integer(\"a\"))", + "query": "row var = coalesce(to_datetime(now()))", "error": [], "warning": [] }, { - "query": "row var = asin(\"a\")", - "error": [ - "Argument of [asin] must be [number], found value [\"a\"] type [string]" - ], + "query": "row var = coalesce(now(), now())", + "error": [], "warning": [] }, { - "query": "from a_index | where asin(numberField) > 0", + "query": "row coalesce(now(), now())", "error": [], "warning": [] }, { - "query": "from a_index | where asin(stringField) > 0", - "error": [ - "Argument of [asin] must be [number], found value [stringField] type [string]" - ], + "query": "row var = coalesce(to_datetime(now()), to_datetime(now()))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = asin(numberField)", + "query": "row var = coalesce(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval asin(numberField)", + "query": "row coalesce(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = asin(to_integer(stringField))", + "query": "row var = coalesce(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval asin(stringField)", - "error": [ - "Argument of [asin] must be [number], found value [stringField] type [string]" - ], + "query": "row var = coalesce(\"a\", \"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval asin(numberField, extraArg)", - "error": [ - "Error: [asin] function expects exactly one argument, got 2." - ], + "query": "row coalesce(\"a\", \"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = asin(*)", - "error": [ - "Using wildcards (*) in asin is not allowed" - ], + "query": "row var = coalesce(to_string(true), to_string(true))", + "error": [], "warning": [] }, { - "query": "from a_index | sort asin(numberField)", + "query": "row var = coalesce(true)", "error": [], "warning": [] }, { - "query": "row var = asin(to_integer(true))", + "query": "row coalesce(true)", "error": [], "warning": [] }, { - "query": "row var = asin(true)", - "error": [ - "Argument of [asin] must be [number], found value [true] type [boolean]" - ], + "query": "row var = coalesce(to_boolean(true))", + "error": [], "warning": [] }, { - "query": "from a_index | where asin(booleanField) > 0", - "error": [ - "Argument of [asin] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = coalesce(true, true)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = asin(to_integer(booleanField))", + "query": "row coalesce(true, true)", "error": [], "warning": [] }, { - "query": "from a_index | eval asin(booleanField)", - "error": [ - "Argument of [asin] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = coalesce(to_boolean(true), to_boolean(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval asin(null)", + "query": "row var = coalesce(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval asin(nullVar)", + "query": "row coalesce(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "row var = atan(5)", + "query": "row var = coalesce(to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "row atan(5)", + "query": "row var = coalesce(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "row var = atan(to_integer(\"a\"))", + "query": "row coalesce(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "row var = atan(\"a\")", - "error": [ - "Argument of [atan] must be [number], found value [\"a\"] type [string]" - ], + "query": "row var = coalesce(to_ip(to_ip(\"127.0.0.1\")), to_ip(to_ip(\"127.0.0.1\")))", + "error": [], "warning": [] }, { - "query": "from a_index | where atan(numberField) > 0", + "query": "row var = coalesce(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | where atan(stringField) > 0", - "error": [ - "Argument of [atan] must be [number], found value [stringField] type [string]" - ], + "query": "row coalesce(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = atan(numberField)", + "query": "row var = coalesce(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval atan(numberField)", + "query": "row var = coalesce(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = atan(to_integer(stringField))", + "query": "row coalesce(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval atan(stringField)", - "error": [ - "Argument of [atan] must be [number], found value [stringField] type [string]" - ], - "warning": [] - }, - { - "query": "from a_index | eval atan(numberField, extraArg)", - "error": [ - "Error: [atan] function expects exactly one argument, got 2." - ], + "query": "row var = coalesce(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = atan(*)", - "error": [ - "Using wildcards (*) in atan is not allowed" - ], + "query": "row var = coalesce(to_cartesianshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | sort atan(numberField)", + "query": "row coalesce(to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = atan(to_integer(true))", + "query": "row var = coalesce(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = atan(true)", - "error": [ - "Argument of [atan] must be [number], found value [true] type [boolean]" - ], + "query": "row var = coalesce(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where atan(booleanField) > 0", - "error": [ - "Argument of [atan] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row coalesce(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = atan(to_integer(booleanField))", + "query": "row var = coalesce(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval atan(booleanField)", - "error": [ - "Argument of [atan] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = coalesce(to_geopoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval atan(null)", + "query": "row coalesce(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval atan(nullVar)", + "query": "row var = coalesce(to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = atan2(5, 5)", + "query": "row var = coalesce(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row atan2(5, 5)", + "query": "row coalesce(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = atan2(to_integer(\"a\"), to_integer(\"a\"))", + "query": "row var = coalesce(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = atan2(\"a\", \"a\")", - "error": [ - "Argument of [atan2] must be [number], found value [\"a\"] type [string]", - "Argument of [atan2] must be [number], found value [\"a\"] type [string]" - ], + "query": "row var = coalesce(to_geoshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where atan2(numberField, numberField) > 0", + "query": "row coalesce(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | where atan2(stringField, stringField) > 0", - "error": [ - "Argument of [atan2] must be [number], found value [stringField] type [string]", - "Argument of [atan2] must be [number], found value [stringField] type [string]" - ], + "query": "row var = coalesce(to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = atan2(numberField, numberField)", + "query": "row var = coalesce(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval atan2(numberField, numberField)", + "query": "row coalesce(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = atan2(to_integer(stringField), to_integer(stringField))", + "query": "row var = coalesce(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval atan2(stringField, stringField)", - "error": [ - "Argument of [atan2] must be [number], found value [stringField] type [string]", - "Argument of [atan2] must be [number], found value [stringField] type [string]" - ], + "query": "row var = coalesce(to_version(\"1.0.0\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval atan2(numberField, numberField, extraArg)", - "error": [ - "Error: [atan2] function expects exactly 2 arguments, got 3." - ], + "query": "row coalesce(to_version(\"1.0.0\"))", + "error": [], "warning": [] }, { - "query": "from a_index | sort atan2(numberField, numberField)", + "query": "row var = coalesce(to_version(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = atan2(to_integer(true), to_integer(true))", + "query": "row var = coalesce(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "row var = atan2(true, true)", - "error": [ - "Argument of [atan2] must be [number], found value [true] type [boolean]", - "Argument of [atan2] must be [number], found value [true] type [boolean]" - ], + "query": "row coalesce(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where atan2(booleanField, booleanField) > 0", - "error": [ - "Argument of [atan2] must be [number], found value [booleanField] type [boolean]", - "Argument of [atan2] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = coalesce(to_version(\"a\"), to_version(\"a\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = atan2(to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | where coalesce(numberField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval atan2(booleanField, booleanField)", - "error": [ - "Argument of [atan2] must be [number], found value [booleanField] type [boolean]", - "Argument of [atan2] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | where coalesce(numberField, numberField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval atan2(null, null)", + "query": "from a_index | where length(coalesce(stringField)) > 0", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval atan2(nullVar, nullVar)", + "query": "from a_index | where length(coalesce(stringField, stringField)) > 0", "error": [], "warning": [] }, { - "query": "row var = case(true, \"a\")", + "query": "from a_index | eval var = coalesce(numberField)", "error": [], "warning": [] }, { - "query": "row case(true, \"a\")", + "query": "from a_index | eval coalesce(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = case(booleanField, stringField)", + "query": "from a_index | eval var = coalesce(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval case(booleanField, stringField)", + "query": "from a_index | eval var = coalesce(numberField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | sort case(booleanField, stringField)", + "query": "from a_index | eval coalesce(numberField, numberField)", "error": [], "warning": [] }, { - "query": "row var = case(to_cartesianpoint(\"POINT (30 10)\"), true)", - "error": [ - "Argument of [case] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" - ], + "query": "from a_index | eval var = coalesce(to_integer(booleanField), to_integer(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval case(null, null)", + "query": "from a_index | eval var = coalesce(dateField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval case(nullVar, nullVar)", + "query": "from a_index | eval coalesce(dateField)", "error": [], "warning": [] }, { - "query": "row var = ceil(5)", + "query": "from a_index | eval var = coalesce(to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "row ceil(5)", + "query": "from a_index | eval var = coalesce(dateField, dateField)", "error": [], "warning": [] }, { - "query": "row var = ceil(to_integer(\"a\"))", + "query": "from a_index | eval coalesce(dateField, dateField)", "error": [], "warning": [] }, { - "query": "row var = ceil(\"a\")", - "error": [ - "Argument of [ceil] must be [number], found value [\"a\"] type [string]" - ], + "query": "from a_index | eval var = coalesce(to_datetime(dateField), to_datetime(dateField))", + "error": [], "warning": [] }, { - "query": "from a_index | where ceil(numberField) > 0", + "query": "from a_index | eval var = coalesce(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | where ceil(stringField) > 0", - "error": [ - "Argument of [ceil] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval coalesce(stringField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = ceil(numberField)", + "query": "from a_index | eval var = coalesce(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval ceil(numberField)", + "query": "from a_index | eval var = coalesce(stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = ceil(to_integer(stringField))", + "query": "from a_index | eval coalesce(stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval ceil(stringField)", - "error": [ - "Argument of [ceil] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval var = coalesce(to_string(booleanField), to_string(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval ceil(numberField, extraArg)", - "error": [ - "Error: [ceil] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval var = coalesce(booleanField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = ceil(*)", - "error": [ - "Using wildcards (*) in ceil is not allowed" - ], + "query": "from a_index | eval coalesce(booleanField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort ceil(numberField)", + "query": "from a_index | eval var = coalesce(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "row var = ceil(to_integer(true))", + "query": "from a_index | eval var = coalesce(booleanField, booleanField)", "error": [], "warning": [] }, { - "query": "row var = ceil(true)", - "error": [ - "Argument of [ceil] must be [number], found value [true] type [boolean]" - ], + "query": "from a_index | eval coalesce(booleanField, booleanField)", + "error": [], "warning": [] }, { - "query": "from a_index | where ceil(booleanField) > 0", - "error": [ - "Argument of [ceil] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = coalesce(to_boolean(booleanField), to_boolean(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = ceil(to_integer(booleanField))", + "query": "from a_index | eval var = coalesce(ipField)", "error": [], "warning": [] }, { - "query": "from a_index | eval ceil(booleanField)", - "error": [ - "Argument of [ceil] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval coalesce(ipField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval ceil(null)", + "query": "from a_index | eval var = coalesce(to_ip(ipField))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval ceil(nullVar)", + "query": "from a_index | eval var = coalesce(ipField, ipField)", "error": [], "warning": [] }, { - "query": "row var = cidr_match(to_ip(\"127.0.0.1\"), \"a\")", + "query": "from a_index | eval coalesce(ipField, ipField)", "error": [], "warning": [] }, { - "query": "row cidr_match(to_ip(\"127.0.0.1\"), \"a\")", + "query": "from a_index | eval var = coalesce(to_ip(ipField), to_ip(ipField))", "error": [], "warning": [] }, { - "query": "row var = cidr_match(to_ip(\"a\"), to_string(\"a\"))", + "query": "from a_index | eval var = coalesce(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = cidr_match(\"a\", 5)", - "error": [ - "Argument of [cidr_match] must be [ip], found value [\"a\"] type [string]", - "Argument of [cidr_match] must be [string], found value [5] type [number]" - ], + "query": "from a_index | eval coalesce(cartesianPointField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = cidr_match(ipField, stringField)", + "query": "from a_index | eval var = coalesce(to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval cidr_match(ipField, stringField)", + "query": "from a_index | eval var = coalesce(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = cidr_match(to_ip(stringField), to_string(stringField))", + "query": "from a_index | eval coalesce(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval cidr_match(stringField, numberField)", - "error": [ - "Argument of [cidr_match] must be [ip], found value [stringField] type [string]", - "Argument of [cidr_match] must be [string], found value [numberField] type [number]" - ], + "query": "from a_index | eval var = coalesce(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | sort cidr_match(ipField, stringField)", + "query": "from a_index | eval var = coalesce(cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = cidr_match(to_ip(to_ip(\"127.0.0.1\")), to_string(true))", + "query": "from a_index | eval coalesce(cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = cidr_match(true, true)", - "error": [ - "Argument of [cidr_match] must be [ip], found value [true] type [boolean]", - "Argument of [cidr_match] must be [string], found value [true] type [boolean]" - ], + "query": "from a_index | eval var = coalesce(to_cartesianshape(cartesianPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = cidr_match(to_ip(ipField), to_string(booleanField))", + "query": "from a_index | eval var = coalesce(cartesianShapeField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval cidr_match(booleanField, booleanField)", - "error": [ - "Argument of [cidr_match] must be [ip], found value [booleanField] type [boolean]", - "Argument of [cidr_match] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval coalesce(cartesianShapeField, cartesianShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval cidr_match(null, null)", + "query": "from a_index | eval var = coalesce(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval cidr_match(nullVar, nullVar)", + "query": "from a_index | eval var = coalesce(geoPointField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(5)", + "query": "from a_index | eval coalesce(geoPointField)", "error": [], "warning": [] }, { - "query": "row coalesce(5)", + "query": "from a_index | eval var = coalesce(to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_integer(true))", + "query": "from a_index | eval var = coalesce(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(5, 5)", + "query": "from a_index | eval coalesce(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "row coalesce(5, 5)", + "query": "from a_index | eval var = coalesce(to_geopoint(geoPointField), to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_integer(true), to_integer(true))", + "query": "from a_index | eval var = coalesce(geoShapeField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(now())", + "query": "from a_index | eval coalesce(geoShapeField)", "error": [], "warning": [] }, { - "query": "row coalesce(now())", + "query": "from a_index | eval var = coalesce(to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_datetime(now()))", + "query": "from a_index | eval var = coalesce(geoShapeField, geoShapeField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(now(), now())", + "query": "from a_index | eval coalesce(geoShapeField, geoShapeField)", "error": [], "warning": [] }, { - "query": "row coalesce(now(), now())", + "query": "from a_index | eval var = coalesce(to_geoshape(geoPointField), to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_datetime(now()), to_datetime(now()))", + "query": "from a_index | eval var = coalesce(versionField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(\"a\")", + "query": "from a_index | eval coalesce(versionField)", "error": [], "warning": [] }, { - "query": "row coalesce(\"a\")", + "query": "from a_index | eval var = coalesce(to_version(stringField))", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_string(true))", + "query": "from a_index | eval var = coalesce(versionField, versionField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(\"a\", \"a\")", + "query": "from a_index | eval coalesce(versionField, versionField)", "error": [], "warning": [] }, { - "query": "row coalesce(\"a\", \"a\")", + "query": "from a_index | eval var = coalesce(to_version(stringField), to_version(stringField))", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_string(true), to_string(true))", + "query": "from a_index | sort coalesce(numberField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(true)", + "query": "from a_index | eval coalesce(null)", "error": [], "warning": [] }, { - "query": "row coalesce(true)", + "query": "row nullVar = null | eval coalesce(nullVar)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_boolean(true))", + "query": "from a_index | sort coalesce(booleanField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(true, true)", + "query": "row var = concat(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "row coalesce(true, true)", + "query": "row concat(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_boolean(true), to_boolean(true))", + "query": "row var = concat(to_string(\"a\"), to_string(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_ip(\"127.0.0.1\"))", - "error": [], + "query": "row var = concat(5, 5)", + "error": [ + "Argument of [concat] must be [string], found value [5] type [number]", + "Argument of [concat] must be [string], found value [5] type [number]" + ], "warning": [] }, { - "query": "row coalesce(to_ip(\"127.0.0.1\"))", + "query": "from a_index | where length(concat(stringField, stringField)) > 0", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_ip(to_ip(\"127.0.0.1\")))", - "error": [], + "query": "from a_index | where length(concat(numberField, numberField)) > 0", + "error": [ + "Argument of [concat] must be [string], found value [numberField] type [number]", + "Argument of [concat] must be [string], found value [numberField] type [number]" + ], "warning": [] }, { - "query": "row var = coalesce(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval var = concat(stringField, stringField)", "error": [], "warning": [] }, { - "query": "row coalesce(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval concat(stringField, stringField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_ip(to_ip(\"127.0.0.1\")), to_ip(to_ip(\"127.0.0.1\")))", + "query": "from a_index | eval var = concat(to_string(stringField), to_string(stringField))", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_cartesianpoint(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval concat(numberField, numberField)", + "error": [ + "Argument of [concat] must be [string], found value [numberField] type [number]", + "Argument of [concat] must be [string], found value [numberField] type [number]" + ], "warning": [] }, { - "query": "row coalesce(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | sort concat(stringField, stringField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = concat(to_string(true), to_string(true))", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", - "error": [], + "query": "row var = concat(true, true)", + "error": [ + "Argument of [concat] must be [string], found value [true] type [boolean]", + "Argument of [concat] must be [string], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row coalesce(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | where length(concat(booleanField, booleanField)) > 0", + "error": [ + "Argument of [concat] must be [string], found value [booleanField] type [boolean]", + "Argument of [concat] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = coalesce(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = concat(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_cartesianshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval concat(booleanField, booleanField)", + "error": [ + "Argument of [concat] must be [string], found value [booleanField] type [boolean]", + "Argument of [concat] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row coalesce(to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval concat(null, null)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row nullVar = null | eval concat(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = cos(5)", "error": [], "warning": [] }, { - "query": "row coalesce(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "row cos(5)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = cos(to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_geopoint(\"POINT (30 10)\"))", - "error": [], + "query": "row var = cos(\"a\")", + "error": [ + "Argument of [cos] must be [number], found value [\"a\"] type [string]" + ], "warning": [] }, { - "query": "row coalesce(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | where cos(numberField) > 0", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_geopoint(to_geopoint(\"POINT (30 10)\")))", - "error": [], + "query": "from a_index | where cos(stringField) > 0", + "error": [ + "Argument of [cos] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "row var = coalesce(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = cos(numberField)", "error": [], "warning": [] }, { - "query": "row coalesce(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval cos(numberField)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = cos(to_integer(stringField))", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_geoshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval cos(stringField)", + "error": [ + "Argument of [cos] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "row coalesce(to_geoshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval cos(numberField, extraArg)", + "error": [ + "Error: [cos] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "row var = coalesce(to_geoshape(to_geopoint(\"POINT (30 10)\")))", - "error": [], + "query": "from a_index | eval var = cos(*)", + "error": [ + "Using wildcards (*) in cos is not allowed" + ], "warning": [] }, { - "query": "row var = coalesce(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | sort cos(numberField)", "error": [], "warning": [] }, { - "query": "row coalesce(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "row var = cos(to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", - "error": [], + "query": "row var = cos(true)", + "error": [ + "Argument of [cos] must be [number], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row var = coalesce(to_version(\"1.0.0\"))", - "error": [], + "query": "from a_index | where cos(booleanField) > 0", + "error": [ + "Argument of [cos] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row coalesce(to_version(\"1.0.0\"))", + "query": "from a_index | eval var = cos(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_version(\"a\"))", - "error": [], + "query": "from a_index | eval cos(booleanField)", + "error": [ + "Argument of [cos] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = coalesce(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", + "query": "from a_index | eval cos(null)", "error": [], "warning": [] }, { - "query": "row coalesce(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", + "query": "row nullVar = null | eval cos(nullVar)", "error": [], "warning": [] }, { - "query": "row var = coalesce(to_version(\"a\"), to_version(\"a\"))", + "query": "row var = cosh(5)", "error": [], "warning": [] }, { - "query": "from a_index | where coalesce(numberField) > 0", + "query": "row cosh(5)", "error": [], "warning": [] }, { - "query": "from a_index | where coalesce(numberField, numberField) > 0", + "query": "row var = cosh(to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | where length(coalesce(stringField)) > 0", - "error": [], + "query": "row var = cosh(\"a\")", + "error": [ + "Argument of [cosh] must be [number], found value [\"a\"] type [string]" + ], "warning": [] }, { - "query": "from a_index | where length(coalesce(stringField, stringField)) > 0", + "query": "from a_index | where cosh(numberField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(numberField)", - "error": [], + "query": "from a_index | where cosh(stringField) > 0", + "error": [ + "Argument of [cosh] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "from a_index | eval coalesce(numberField)", + "query": "from a_index | eval var = cosh(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_integer(booleanField))", + "query": "from a_index | eval cosh(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(numberField, numberField)", + "query": "from a_index | eval var = cosh(to_integer(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(numberField, numberField)", - "error": [], + "query": "from a_index | eval cosh(stringField)", + "error": [ + "Argument of [cosh] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_integer(booleanField), to_integer(booleanField))", - "error": [], + "query": "from a_index | eval cosh(numberField, extraArg)", + "error": [ + "Error: [cosh] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | eval var = coalesce(dateField)", - "error": [], + "query": "from a_index | eval var = cosh(*)", + "error": [ + "Using wildcards (*) in cosh is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval coalesce(dateField)", + "query": "from a_index | sort cosh(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_datetime(dateField))", + "query": "row var = cosh(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(dateField, dateField)", - "error": [], + "query": "row var = cosh(true)", + "error": [ + "Argument of [cosh] must be [number], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval coalesce(dateField, dateField)", - "error": [], + "query": "from a_index | where cosh(booleanField) > 0", + "error": [ + "Argument of [cosh] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_datetime(dateField), to_datetime(dateField))", + "query": "from a_index | eval var = cosh(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(stringField)", - "error": [], + "query": "from a_index | eval cosh(booleanField)", + "error": [ + "Argument of [cosh] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval coalesce(stringField)", + "query": "from a_index | eval cosh(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_string(booleanField))", + "query": "row nullVar = null | eval cosh(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(stringField, stringField)", + "query": "row var = date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", now())", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(stringField, stringField)", + "query": "row date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", now())", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_string(booleanField), to_string(booleanField))", + "query": "from a_index | eval var = date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(booleanField)", + "query": "from a_index | eval date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(booleanField)", + "query": "from a_index | eval var = date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", to_datetime(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_boolean(booleanField))", - "error": [], + "query": "from a_index | eval date_extract(stringField, stringField)", + "error": [ + "Argument of [date_extract] must be [chrono_literal], found value [stringField] type [string]", + "Argument of [date_extract] must be [date], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "from a_index | eval var = coalesce(booleanField, booleanField)", - "error": [], + "query": "from a_index | eval date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", dateField, extraArg)", + "error": [ + "Error: [date_extract] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "from a_index | eval coalesce(booleanField, booleanField)", + "query": "from a_index | sort date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_boolean(booleanField), to_boolean(booleanField))", - "error": [], + "query": "row var = date_extract(true, true)", + "error": [ + "Argument of [date_extract] must be [chrono_literal], found value [true] type [boolean]", + "Argument of [date_extract] must be [date], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = coalesce(ipField)", + "query": "from a_index | eval var = date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(ipField)", - "error": [], + "query": "from a_index | eval date_extract(booleanField, booleanField)", + "error": [ + "Argument of [date_extract] must be [chrono_literal], found value [booleanField] type [boolean]", + "Argument of [date_extract] must be [date], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_ip(ipField))", + "query": "from a_index | eval date_extract(null, null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(ipField, ipField)", + "query": "row nullVar = null | eval date_extract(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(ipField, ipField)", + "query": "from a_index | eval date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", \"2022\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_ip(ipField), to_ip(ipField))", - "error": [], + "query": "from a_index | eval date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", concat(\"20\", \"22\"))", + "error": [ + "Argument of [date_extract] must be [date], found value [concat(\"20\", \"22\")] type [string]" + ], "warning": [] }, { - "query": "from a_index | eval var = coalesce(cartesianPointField)", + "query": "row var = date_format(\"a\", now())", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(cartesianPointField)", + "query": "row date_format(\"a\", now())", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_cartesianpoint(cartesianPointField))", + "query": "from a_index | eval var = date_format(stringField, dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(cartesianPointField, cartesianPointField)", + "query": "from a_index | eval date_format(stringField, dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(cartesianPointField, cartesianPointField)", + "query": "from a_index | eval var = date_format(to_string(stringField), to_datetime(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", - "error": [], + "query": "from a_index | eval date_format(stringField, numberField)", + "error": [ + "Argument of [date_format] must be [date], found value [numberField] type [number]" + ], "warning": [] }, { - "query": "from a_index | eval var = coalesce(cartesianShapeField)", - "error": [], + "query": "from a_index | eval date_format(stringField, dateField, extraArg)", + "error": [ + "Error: [date_format] function expects no more than 2 arguments, got 3." + ], "warning": [] }, { - "query": "from a_index | eval coalesce(cartesianShapeField)", + "query": "from a_index | sort date_format(stringField, dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_cartesianshape(cartesianPointField))", - "error": [], + "query": "row var = date_format(true, true)", + "error": [ + "Argument of [date_format] must be [string], found value [true] type [boolean]", + "Argument of [date_format] must be [date], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = coalesce(cartesianShapeField, cartesianShapeField)", + "query": "from a_index | eval var = date_format(to_string(booleanField), to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(cartesianShapeField, cartesianShapeField)", - "error": [], + "query": "from a_index | eval date_format(booleanField, booleanField)", + "error": [ + "Argument of [date_format] must be [string], found value [booleanField] type [boolean]", + "Argument of [date_format] must be [date], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "query": "from a_index | eval date_format(null, null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(geoPointField)", + "query": "row nullVar = null | eval date_format(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(geoPointField)", + "query": "from a_index | eval date_format(stringField, \"2022\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_geopoint(geoPointField))", - "error": [], + "query": "from a_index | eval date_format(stringField, concat(\"20\", \"22\"))", + "error": [ + "Argument of [date_format] must be [date], found value [concat(\"20\", \"22\")] type [string]" + ], "warning": [] }, { - "query": "from a_index | eval var = coalesce(geoPointField, geoPointField)", + "query": "row var = date_parse(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(geoPointField, geoPointField)", + "query": "row var = date_parse(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "query": "row date_parse(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(geoShapeField)", + "query": "row var = date_parse(to_string(\"a\"), to_string(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(geoShapeField)", - "error": [], + "query": "row var = date_parse(5, 5)", + "error": [ + "Argument of [date_parse] must be [string], found value [5] type [number]", + "Argument of [date_parse] must be [string], found value [5] type [number]" + ], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_geoshape(geoPointField))", + "query": "from a_index | eval var = date_parse(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(geoShapeField, geoShapeField)", + "query": "from a_index | eval var = date_parse(stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(geoShapeField, geoShapeField)", + "query": "from a_index | eval date_parse(stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_geoshape(geoPointField), to_geoshape(geoPointField))", + "query": "from a_index | eval var = date_parse(to_string(stringField), to_string(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(versionField)", - "error": [], + "query": "from a_index | eval date_parse(numberField, numberField)", + "error": [ + "Argument of [date_parse] must be [string], found value [numberField] type [number]", + "Argument of [date_parse] must be [string], found value [numberField] type [number]" + ], "warning": [] }, { - "query": "from a_index | eval coalesce(versionField)", - "error": [], + "query": "from a_index | eval date_parse(stringField, stringField, extraArg)", + "error": [ + "Error: [date_parse] function expects no more than 2 arguments, got 3." + ], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_version(stringField))", + "query": "from a_index | sort date_parse(stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = coalesce(versionField, versionField)", + "query": "row var = date_parse(to_string(true), to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval coalesce(versionField, versionField)", - "error": [], + "query": "row var = date_parse(true, true)", + "error": [ + "Argument of [date_parse] must be [string], found value [true] type [boolean]", + "Argument of [date_parse] must be [string], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = coalesce(to_version(stringField), to_version(stringField))", + "query": "from a_index | eval var = date_parse(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | sort coalesce(numberField)", - "error": [], + "query": "from a_index | eval date_parse(booleanField, booleanField)", + "error": [ + "Argument of [date_parse] must be [string], found value [booleanField] type [boolean]", + "Argument of [date_parse] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval coalesce(null)", + "query": "from a_index | eval date_parse(null, null)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval coalesce(nullVar)", + "query": "row nullVar = null | eval date_parse(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | sort coalesce(booleanField)", + "query": "row var = date_trunc(1 year, now())", "error": [], "warning": [] }, { - "query": "row var = concat(\"a\", \"a\")", + "query": "row date_trunc(1 year, now())", "error": [], "warning": [] }, { - "query": "row concat(\"a\", \"a\")", + "query": "from a_index | eval var = date_trunc(1 year, dateField)", "error": [], "warning": [] }, { - "query": "row var = concat(to_string(\"a\"), to_string(\"a\"))", + "query": "from a_index | eval date_trunc(1 year, dateField)", "error": [], "warning": [] }, { - "query": "row var = concat(5, 5)", - "error": [ - "Argument of [concat] must be [string], found value [5] type [number]", - "Argument of [concat] must be [string], found value [5] type [number]" - ], + "query": "from a_index | eval var = date_trunc(1 year, to_datetime(stringField))", + "error": [], "warning": [] }, { - "query": "from a_index | where length(concat(stringField, stringField)) > 0", - "error": [], + "query": "from a_index | eval date_trunc(stringField, stringField)", + "error": [ + "Argument of [date_trunc] must be [time_literal], found value [stringField] type [string]", + "Argument of [date_trunc] must be [date], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "from a_index | where length(concat(numberField, numberField)) > 0", + "query": "from a_index | eval date_trunc(1 year, dateField, extraArg)", "error": [ - "Argument of [concat] must be [string], found value [numberField] type [number]", - "Argument of [concat] must be [string], found value [numberField] type [number]" + "Error: [date_trunc] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | eval var = concat(stringField, stringField)", + "query": "from a_index | sort date_trunc(1 year, dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval concat(stringField, stringField)", + "query": "row var = date_trunc(now(), now())", "error": [], "warning": [] }, { - "query": "from a_index | eval var = concat(to_string(stringField), to_string(stringField))", + "query": "row date_trunc(now(), now())", "error": [], "warning": [] }, { - "query": "from a_index | eval concat(numberField, numberField)", + "query": "row var = date_trunc(true, true)", "error": [ - "Argument of [concat] must be [string], found value [numberField] type [number]", - "Argument of [concat] must be [string], found value [numberField] type [number]" + "Argument of [date_trunc] must be [time_literal], found value [true] type [boolean]", + "Argument of [date_trunc] must be [date], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort concat(stringField, stringField)", - "error": [], - "warning": [] - }, - { - "query": "row var = concat(to_string(true), to_string(true))", + "query": "from a_index | eval var = date_trunc(1 year, to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "row var = concat(true, true)", - "error": [ - "Argument of [concat] must be [string], found value [true] type [boolean]", - "Argument of [concat] must be [string], found value [true] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | where length(concat(booleanField, booleanField)) > 0", + "query": "from a_index | eval date_trunc(booleanField, booleanField)", "error": [ - "Argument of [concat] must be [string], found value [booleanField] type [boolean]", - "Argument of [concat] must be [string], found value [booleanField] type [boolean]" + "Argument of [date_trunc] must be [time_literal], found value [booleanField] type [boolean]", + "Argument of [date_trunc] must be [date], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = concat(to_string(booleanField), to_string(booleanField))", + "query": "from a_index | eval var = date_trunc(dateField, dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval concat(booleanField, booleanField)", - "error": [ - "Argument of [concat] must be [string], found value [booleanField] type [boolean]", - "Argument of [concat] must be [string], found value [booleanField] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | eval concat(null, null)", + "query": "from a_index | eval date_trunc(dateField, dateField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval concat(nullVar, nullVar)", + "query": "from a_index | eval var = date_trunc(to_datetime(dateField), to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "row var = cos(5)", + "query": "from a_index | eval date_trunc(null, null)", "error": [], "warning": [] }, { - "query": "row cos(5)", + "query": "row nullVar = null | eval date_trunc(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row var = cos(to_integer(\"a\"))", + "query": "from a_index | eval date_trunc(1 year, \"2022\")", "error": [], "warning": [] }, { - "query": "row var = cos(\"a\")", + "query": "from a_index | eval date_trunc(1 year, concat(\"20\", \"22\"))", "error": [ - "Argument of [cos] must be [number], found value [\"a\"] type [string]" + "Argument of [date_trunc] must be [date], found value [concat(\"20\", \"22\")] type [string]" ], "warning": [] }, { - "query": "from a_index | where cos(numberField) > 0", + "query": "from a_index | eval date_trunc(\"2022\", \"2022\")", "error": [], "warning": [] }, { - "query": "from a_index | where cos(stringField) > 0", + "query": "from a_index | eval date_trunc(concat(\"20\", \"22\"), concat(\"20\", \"22\"))", "error": [ - "Argument of [cos] must be [number], found value [stringField] type [string]" + "Argument of [date_trunc] must be [time_literal], found value [concat(\"20\", \"22\")] type [string]", + "Argument of [date_trunc] must be [date], found value [concat(\"20\", \"22\")] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = cos(numberField)", + "query": "row var = e()", "error": [], "warning": [] }, { - "query": "from a_index | eval cos(numberField)", + "query": "row e()", "error": [], "warning": [] }, { - "query": "from a_index | eval var = cos(to_integer(stringField))", + "query": "from a_index | where e() > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval cos(stringField)", - "error": [ - "Argument of [cos] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval var = e()", + "error": [], "warning": [] }, { - "query": "from a_index | eval cos(numberField, extraArg)", - "error": [ - "Error: [cos] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval e()", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = cos(*)", + "query": "from a_index | eval e(extraArg)", "error": [ - "Using wildcards (*) in cos is not allowed" + "Error: [e] function expects exactly 0 arguments, got 1." ], "warning": [] }, { - "query": "from a_index | sort cos(numberField)", + "query": "from a_index | sort e()", "error": [], "warning": [] }, { - "query": "row var = cos(to_integer(true))", + "query": "row nullVar = null | eval e()", "error": [], "warning": [] }, { - "query": "row var = cos(true)", - "error": [ - "Argument of [cos] must be [number], found value [true] type [boolean]" - ], + "query": "row var = ends_with(\"a\", \"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | where cos(booleanField) > 0", - "error": [ - "Argument of [cos] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row ends_with(\"a\", \"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = cos(to_integer(booleanField))", + "query": "row var = ends_with(to_string(\"a\"), to_string(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval cos(booleanField)", + "query": "row var = ends_with(5, 5)", "error": [ - "Argument of [cos] must be [number], found value [booleanField] type [boolean]" + "Argument of [ends_with] must be [string], found value [5] type [number]", + "Argument of [ends_with] must be [string], found value [5] type [number]" ], "warning": [] }, { - "query": "from a_index | eval cos(null)", - "error": [], - "warning": [] - }, - { - "query": "row nullVar = null | eval cos(nullVar)", + "query": "from a_index | eval var = ends_with(stringField, stringField)", "error": [], "warning": [] }, { - "query": "row var = cosh(5)", + "query": "from a_index | eval ends_with(stringField, stringField)", "error": [], "warning": [] }, { - "query": "row cosh(5)", + "query": "from a_index | eval var = ends_with(to_string(stringField), to_string(stringField))", "error": [], "warning": [] }, { - "query": "row var = cosh(to_integer(\"a\"))", - "error": [], + "query": "from a_index | eval ends_with(numberField, numberField)", + "error": [ + "Argument of [ends_with] must be [string], found value [numberField] type [number]", + "Argument of [ends_with] must be [string], found value [numberField] type [number]" + ], "warning": [] }, { - "query": "row var = cosh(\"a\")", + "query": "from a_index | eval ends_with(stringField, stringField, extraArg)", "error": [ - "Argument of [cosh] must be [number], found value [\"a\"] type [string]" + "Error: [ends_with] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | where cosh(numberField) > 0", + "query": "from a_index | sort ends_with(stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | where cosh(stringField) > 0", - "error": [ - "Argument of [cosh] must be [number], found value [stringField] type [string]" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = cosh(numberField)", + "query": "row var = ends_with(to_string(true), to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval cosh(numberField)", - "error": [], + "query": "row var = ends_with(true, true)", + "error": [ + "Argument of [ends_with] must be [string], found value [true] type [boolean]", + "Argument of [ends_with] must be [string], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = cosh(to_integer(stringField))", + "query": "from a_index | eval var = ends_with(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval cosh(stringField)", + "query": "from a_index | eval ends_with(booleanField, booleanField)", "error": [ - "Argument of [cosh] must be [number], found value [stringField] type [string]" + "Argument of [ends_with] must be [string], found value [booleanField] type [boolean]", + "Argument of [ends_with] must be [string], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval cosh(numberField, extraArg)", - "error": [ - "Error: [cosh] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval ends_with(null, null)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = cosh(*)", - "error": [ - "Using wildcards (*) in cosh is not allowed" - ], + "query": "row nullVar = null | eval ends_with(nullVar, nullVar)", + "error": [], "warning": [] }, { - "query": "from a_index | sort cosh(numberField)", + "query": "row var = floor(5)", "error": [], "warning": [] }, { - "query": "row var = cosh(to_integer(true))", + "query": "row floor(5)", "error": [], "warning": [] }, { - "query": "row var = cosh(true)", - "error": [ - "Argument of [cosh] must be [number], found value [true] type [boolean]" - ], + "query": "row var = floor(to_integer(\"a\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where cosh(booleanField) > 0", + "query": "row var = floor(\"a\")", "error": [ - "Argument of [cosh] must be [number], found value [booleanField] type [boolean]" + "Argument of [floor] must be [number], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = cosh(to_integer(booleanField))", + "query": "from a_index | where floor(numberField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval cosh(booleanField)", + "query": "from a_index | where floor(stringField) > 0", "error": [ - "Argument of [cosh] must be [number], found value [booleanField] type [boolean]" + "Argument of [floor] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval cosh(null)", + "query": "from a_index | eval var = floor(numberField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval cosh(nullVar)", + "query": "from a_index | eval floor(numberField)", "error": [], "warning": [] }, { - "query": "row var = date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", now())", + "query": "from a_index | eval var = floor(to_integer(stringField))", "error": [], "warning": [] }, { - "query": "row date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", now())", - "error": [], + "query": "from a_index | eval floor(stringField)", + "error": [ + "Argument of [floor] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "from a_index | eval var = date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", dateField)", - "error": [], + "query": "from a_index | eval floor(numberField, extraArg)", + "error": [ + "Error: [floor] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | eval date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", dateField)", + "query": "from a_index | eval var = floor(*)", + "error": [ + "Using wildcards (*) in floor is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | sort floor(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", to_datetime(stringField))", + "query": "row var = floor(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval date_extract(stringField, stringField)", + "query": "row var = floor(true)", "error": [ - "Argument of [date_extract] must be [chrono_literal], found value [stringField] type [string]", - "Argument of [date_extract] must be [date], found value [stringField] type [string]" + "Argument of [floor] must be [number], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", dateField, extraArg)", + "query": "from a_index | where floor(booleanField) > 0", "error": [ - "Error: [date_extract] function expects exactly 2 arguments, got 3." + "Argument of [floor] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", dateField)", + "query": "from a_index | eval var = floor(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = date_extract(true, true)", + "query": "from a_index | eval floor(booleanField)", "error": [ - "Argument of [date_extract] must be [chrono_literal], found value [true] type [boolean]", - "Argument of [date_extract] must be [date], found value [true] type [boolean]" + "Argument of [floor] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", to_datetime(dateField))", + "query": "from a_index | eval floor(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_extract(booleanField, booleanField)", - "error": [ - "Argument of [date_extract] must be [chrono_literal], found value [booleanField] type [boolean]", - "Argument of [date_extract] must be [date], found value [booleanField] type [boolean]" - ], + "query": "row nullVar = null | eval floor(nullVar)", + "error": [], "warning": [] }, { - "query": "from a_index | eval date_extract(null, null)", + "query": "row var = greatest(\"a\")", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval date_extract(nullVar, nullVar)", + "query": "row greatest(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", \"2022\")", + "query": "from a_index | eval var = greatest(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_extract(\"ALIGNED_DAY_OF_WEEK_IN_MONTH\", concat(\"20\", \"22\"))", - "error": [ - "Argument of [date_extract] must be [date], found value [concat(\"20\", \"22\")] type [string]" - ], + "query": "from a_index | eval greatest(stringField)", + "error": [], "warning": [] }, { - "query": "row var = date_format(\"a\", now())", + "query": "from a_index | sort greatest(stringField)", "error": [], "warning": [] }, { - "query": "row date_format(\"a\", now())", + "query": "row var = greatest(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_format(stringField, dateField)", + "query": "row greatest(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_format(stringField, dateField)", + "query": "row var = greatest(to_boolean(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_format(to_string(stringField), to_datetime(stringField))", + "query": "row var = greatest(true, true)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_format(stringField, numberField)", - "error": [ - "Argument of [date_format] must be [date], found value [numberField] type [number]" - ], + "query": "row greatest(true, true)", + "error": [], "warning": [] }, { - "query": "from a_index | eval date_format(stringField, dateField, extraArg)", - "error": [ - "Error: [date_format] function expects no more than 2 arguments, got 3." - ], + "query": "row var = greatest(to_boolean(true), to_boolean(true))", + "error": [], "warning": [] }, { - "query": "from a_index | sort date_format(stringField, dateField)", + "query": "row var = greatest(5, 5)", "error": [], "warning": [] }, { - "query": "row var = date_format(true, true)", - "error": [ - "Argument of [date_format] must be [string], found value [true] type [boolean]", - "Argument of [date_format] must be [date], found value [true] type [boolean]" - ], + "query": "row greatest(5, 5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_format(to_string(booleanField), to_datetime(dateField))", + "query": "row var = greatest(to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval date_format(booleanField, booleanField)", - "error": [ - "Argument of [date_format] must be [string], found value [booleanField] type [boolean]", - "Argument of [date_format] must be [date], found value [booleanField] type [boolean]" - ], + "query": "row var = greatest(5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval date_format(null, null)", + "query": "row greatest(5)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval date_format(nullVar, nullVar)", + "query": "row var = greatest(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval date_format(stringField, \"2022\")", + "query": "row var = greatest(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval date_format(stringField, concat(\"20\", \"22\"))", - "error": [ - "Argument of [date_format] must be [date], found value [concat(\"20\", \"22\")] type [string]" - ], + "query": "row greatest(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", + "error": [], "warning": [] }, { - "query": "row var = date_parse(\"a\", \"a\")", + "query": "row var = greatest(to_ip(to_ip(\"127.0.0.1\")), to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "row var = date_parse(\"a\")", + "query": "row var = greatest(to_string(true))", "error": [], "warning": [] }, { - "query": "row date_parse(\"a\", \"a\")", + "query": "row var = greatest(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "row var = date_parse(to_string(\"a\"), to_string(\"a\"))", + "query": "row greatest(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "row var = date_parse(5, 5)", - "error": [ - "Argument of [date_parse] must be [string], found value [5] type [number]", - "Argument of [date_parse] must be [string], found value [5] type [number]" - ], + "query": "row var = greatest(to_string(true), to_string(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_parse(stringField)", + "query": "row var = greatest(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_parse(stringField, stringField)", + "query": "row greatest(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval date_parse(stringField, stringField)", + "query": "row var = greatest(to_version(\"a\"), to_version(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_parse(to_string(stringField), to_string(stringField))", - "error": [], + "query": "row var = greatest(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [ + "Argument of [greatest] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]", + "Argument of [greatest] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + ], "warning": [] }, { - "query": "from a_index | eval date_parse(numberField, numberField)", - "error": [ - "Argument of [date_parse] must be [string], found value [numberField] type [number]", - "Argument of [date_parse] must be [string], found value [numberField] type [number]" - ], + "query": "from a_index | where greatest(numberField, numberField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval date_parse(stringField, stringField, extraArg)", + "query": "from a_index | where greatest(cartesianPointField, cartesianPointField) > 0", "error": [ - "Error: [date_parse] function expects no more than 2 arguments, got 3." + "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]", + "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | sort date_parse(stringField, stringField)", + "query": "from a_index | where greatest(numberField) > 0", "error": [], "warning": [] }, { - "query": "row var = date_parse(to_string(true), to_string(true))", + "query": "from a_index | where greatest(cartesianPointField) > 0", + "error": [ + "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | where length(greatest(stringField)) > 0", "error": [], "warning": [] }, { - "query": "row var = date_parse(true, true)", + "query": "from a_index | where length(greatest(cartesianPointField)) > 0", "error": [ - "Argument of [date_parse] must be [string], found value [true] type [boolean]", - "Argument of [date_parse] must be [string], found value [true] type [boolean]" + "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | eval var = date_parse(to_string(booleanField), to_string(booleanField))", + "query": "from a_index | where length(greatest(stringField, stringField)) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval date_parse(booleanField, booleanField)", + "query": "from a_index | where length(greatest(cartesianPointField, cartesianPointField)) > 0", "error": [ - "Argument of [date_parse] must be [string], found value [booleanField] type [boolean]", - "Argument of [date_parse] must be [string], found value [booleanField] type [boolean]" + "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]", + "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | eval date_parse(null, null)", + "query": "from a_index | eval var = greatest(booleanField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval date_parse(nullVar, nullVar)", + "query": "from a_index | eval greatest(booleanField)", "error": [], "warning": [] }, { - "query": "row var = date_trunc(1 year, now())", + "query": "from a_index | eval var = greatest(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "row date_trunc(1 year, now())", - "error": [], + "query": "from a_index | eval greatest(cartesianPointField)", + "error": [ + "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], "warning": [] }, { - "query": "from a_index | eval var = date_trunc(1 year, dateField)", + "query": "from a_index | eval var = greatest(booleanField, booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_trunc(1 year, dateField)", + "query": "from a_index | eval greatest(booleanField, booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_trunc(1 year, to_datetime(stringField))", + "query": "from a_index | eval var = greatest(to_boolean(booleanField), to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval date_trunc(stringField, stringField)", + "query": "from a_index | eval greatest(cartesianPointField, cartesianPointField)", "error": [ - "Argument of [date_trunc] must be [time_literal], found value [stringField] type [string]", - "Argument of [date_trunc] must be [date], found value [stringField] type [string]" + "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]", + "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | eval date_trunc(1 year, dateField, extraArg)", - "error": [ - "Error: [date_trunc] function expects exactly 2 arguments, got 3." - ], + "query": "from a_index | eval var = greatest(numberField, numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort date_trunc(1 year, dateField)", + "query": "from a_index | eval greatest(numberField, numberField)", "error": [], "warning": [] }, { - "query": "row var = date_trunc(now(), now())", + "query": "from a_index | eval var = greatest(to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row date_trunc(now(), now())", + "query": "from a_index | eval var = greatest(numberField)", "error": [], "warning": [] }, { - "query": "row var = date_trunc(true, true)", - "error": [ - "Argument of [date_trunc] must be [time_literal], found value [true] type [boolean]", - "Argument of [date_trunc] must be [date], found value [true] type [boolean]" - ], + "query": "from a_index | eval greatest(numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_trunc(1 year, to_datetime(dateField))", + "query": "from a_index | eval var = greatest(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval date_trunc(booleanField, booleanField)", - "error": [ - "Argument of [date_trunc] must be [time_literal], found value [booleanField] type [boolean]", - "Argument of [date_trunc] must be [date], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = greatest(ipField, ipField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_trunc(dateField, dateField)", + "query": "from a_index | eval greatest(ipField, ipField)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_trunc(dateField, dateField)", + "query": "from a_index | eval var = greatest(to_ip(ipField), to_ip(ipField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = date_trunc(to_datetime(dateField), to_datetime(dateField))", + "query": "from a_index | eval var = greatest(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval date_trunc(null, null)", + "query": "from a_index | eval var = greatest(stringField, stringField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval date_trunc(nullVar, nullVar)", + "query": "from a_index | eval greatest(stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_trunc(1 year, \"2022\")", + "query": "from a_index | eval var = greatest(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval date_trunc(1 year, concat(\"20\", \"22\"))", - "error": [ - "Argument of [date_trunc] must be [date], found value [concat(\"20\", \"22\")] type [string]" - ], + "query": "from a_index | eval var = greatest(versionField, versionField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval date_trunc(\"2022\", \"2022\")", + "query": "from a_index | eval greatest(versionField, versionField)", "error": [], "warning": [] }, { - "query": "from a_index | eval date_trunc(concat(\"20\", \"22\"), concat(\"20\", \"22\"))", - "error": [ - "Argument of [date_trunc] must be [time_literal], found value [concat(\"20\", \"22\")] type [string]", - "Argument of [date_trunc] must be [date], found value [concat(\"20\", \"22\")] type [string]" - ], + "query": "from a_index | eval var = greatest(to_version(stringField), to_version(stringField))", + "error": [], "warning": [] }, { - "query": "row var = e()", + "query": "from a_index | sort greatest(booleanField)", "error": [], "warning": [] }, { - "query": "row e()", + "query": "from a_index | eval greatest(null)", "error": [], "warning": [] }, { - "query": "from a_index | where e() > 0", + "query": "row nullVar = null | eval greatest(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = e()", + "query": "row var = least(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval e()", + "query": "row least(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval e(extraArg)", - "error": [ - "Error: [e] function expects exactly 0 arguments, got 1." - ], + "query": "from a_index | eval var = least(stringField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort e()", + "query": "from a_index | eval least(stringField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval e()", + "query": "from a_index | sort least(stringField)", "error": [], "warning": [] }, { - "query": "row var = ends_with(\"a\", \"a\")", + "query": "row var = least(true)", "error": [], "warning": [] }, { - "query": "row ends_with(\"a\", \"a\")", + "query": "row least(true)", "error": [], "warning": [] }, { - "query": "row var = ends_with(to_string(\"a\"), to_string(\"a\"))", + "query": "row var = least(to_boolean(true))", "error": [], "warning": [] }, { - "query": "row var = ends_with(5, 5)", - "error": [ - "Argument of [ends_with] must be [string], found value [5] type [number]", - "Argument of [ends_with] must be [string], found value [5] type [number]" - ], + "query": "row var = least(true, true)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = ends_with(stringField, stringField)", + "query": "row least(true, true)", "error": [], "warning": [] }, { - "query": "from a_index | eval ends_with(stringField, stringField)", + "query": "row var = least(to_boolean(true), to_boolean(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = ends_with(to_string(stringField), to_string(stringField))", + "query": "row var = least(5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval ends_with(numberField, numberField)", - "error": [ - "Argument of [ends_with] must be [string], found value [numberField] type [number]", - "Argument of [ends_with] must be [string], found value [numberField] type [number]" - ], + "query": "row least(5, 5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval ends_with(stringField, stringField, extraArg)", - "error": [ - "Error: [ends_with] function expects exactly 2 arguments, got 3." - ], + "query": "row var = least(to_integer(true), to_integer(true))", + "error": [], "warning": [] }, { - "query": "from a_index | sort ends_with(stringField, stringField)", + "query": "row var = least(5)", "error": [], "warning": [] }, { - "query": "row var = ends_with(to_string(true), to_string(true))", + "query": "row least(5)", "error": [], "warning": [] }, { - "query": "row var = ends_with(true, true)", - "error": [ - "Argument of [ends_with] must be [string], found value [true] type [boolean]", - "Argument of [ends_with] must be [string], found value [true] type [boolean]" - ], + "query": "row var = least(to_integer(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = ends_with(to_string(booleanField), to_string(booleanField))", + "query": "row var = least(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval ends_with(booleanField, booleanField)", - "error": [ - "Argument of [ends_with] must be [string], found value [booleanField] type [boolean]", - "Argument of [ends_with] must be [string], found value [booleanField] type [boolean]" - ], + "query": "row least(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval ends_with(null, null)", + "query": "row var = least(to_ip(to_ip(\"127.0.0.1\")), to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval ends_with(nullVar, nullVar)", + "query": "row var = least(to_string(true))", "error": [], "warning": [] }, { - "query": "row var = floor(5)", + "query": "row var = least(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "row floor(5)", + "query": "row least(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "row var = floor(to_integer(\"a\"))", + "query": "row var = least(to_string(true), to_string(true))", "error": [], "warning": [] }, { - "query": "row var = floor(\"a\")", - "error": [ - "Argument of [floor] must be [number], found value [\"a\"] type [string]" - ], + "query": "row var = least(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where floor(numberField) > 0", + "query": "row least(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | where floor(stringField) > 0", + "query": "row var = least(to_version(\"a\"), to_version(\"a\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = least(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [ - "Argument of [floor] must be [number], found value [stringField] type [string]" + "Argument of [least] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]", + "Argument of [least] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | eval var = floor(numberField)", + "query": "from a_index | where least(numberField, numberField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval floor(numberField)", - "error": [], + "query": "from a_index | where least(cartesianPointField, cartesianPointField) > 0", + "error": [ + "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]", + "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], "warning": [] }, { - "query": "from a_index | eval var = floor(to_integer(stringField))", + "query": "from a_index | where least(numberField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval floor(stringField)", + "query": "from a_index | where least(cartesianPointField) > 0", "error": [ - "Argument of [floor] must be [number], found value [stringField] type [string]" + "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | eval floor(numberField, extraArg)", - "error": [ - "Error: [floor] function expects exactly one argument, got 2." - ], + "query": "from a_index | where length(least(stringField)) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = floor(*)", + "query": "from a_index | where length(least(cartesianPointField)) > 0", "error": [ - "Using wildcards (*) in floor is not allowed" + "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | sort floor(numberField)", + "query": "from a_index | where length(least(stringField, stringField)) > 0", "error": [], "warning": [] }, { - "query": "row var = floor(to_integer(true))", - "error": [], - "warning": [] - }, - { - "query": "row var = floor(true)", - "error": [ - "Argument of [floor] must be [number], found value [true] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | where floor(booleanField) > 0", + "query": "from a_index | where length(least(cartesianPointField, cartesianPointField)) > 0", "error": [ - "Argument of [floor] must be [number], found value [booleanField] type [boolean]" + "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]", + "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | eval var = floor(to_integer(booleanField))", + "query": "from a_index | eval var = least(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval floor(booleanField)", - "error": [ - "Argument of [floor] must be [number], found value [booleanField] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | eval floor(null)", + "query": "from a_index | eval least(booleanField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval floor(nullVar)", + "query": "from a_index | eval var = least(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "row var = greatest(\"a\")", - "error": [], + "query": "from a_index | eval least(cartesianPointField)", + "error": [ + "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], "warning": [] }, { - "query": "row greatest(\"a\")", + "query": "from a_index | eval var = least(booleanField, booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(stringField)", + "query": "from a_index | eval least(booleanField, booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval greatest(stringField)", + "query": "from a_index | eval var = least(to_boolean(booleanField), to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | sort greatest(stringField)", - "error": [], + "query": "from a_index | eval least(cartesianPointField, cartesianPointField)", + "error": [ + "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]", + "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], "warning": [] }, { - "query": "row var = greatest(true)", + "query": "from a_index | eval var = least(numberField, numberField)", "error": [], "warning": [] }, { - "query": "row greatest(true)", + "query": "from a_index | eval least(numberField, numberField)", "error": [], "warning": [] }, { - "query": "row var = greatest(to_boolean(true))", + "query": "from a_index | eval var = least(to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = greatest(true, true)", + "query": "from a_index | eval var = least(numberField)", "error": [], "warning": [] }, { - "query": "row greatest(true, true)", + "query": "from a_index | eval least(numberField)", "error": [], "warning": [] }, { - "query": "row var = greatest(to_boolean(true), to_boolean(true))", + "query": "from a_index | eval var = least(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = greatest(5, 5)", + "query": "from a_index | eval var = least(ipField, ipField)", "error": [], "warning": [] }, { - "query": "row greatest(5, 5)", + "query": "from a_index | eval least(ipField, ipField)", "error": [], "warning": [] }, { - "query": "row var = greatest(to_integer(true), to_integer(true))", + "query": "from a_index | eval var = least(to_ip(ipField), to_ip(ipField))", "error": [], "warning": [] }, { - "query": "row var = greatest(5)", + "query": "from a_index | eval var = least(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row greatest(5)", + "query": "from a_index | eval var = least(stringField, stringField)", "error": [], "warning": [] }, { - "query": "row var = greatest(to_integer(true))", + "query": "from a_index | eval least(stringField, stringField)", "error": [], "warning": [] }, { - "query": "row var = greatest(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval var = least(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row greatest(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval var = least(versionField, versionField)", "error": [], "warning": [] }, { - "query": "row var = greatest(to_ip(to_ip(\"127.0.0.1\")), to_ip(to_ip(\"127.0.0.1\")))", + "query": "from a_index | eval least(versionField, versionField)", "error": [], "warning": [] }, { - "query": "row var = greatest(to_string(true))", + "query": "from a_index | eval var = least(to_version(stringField), to_version(stringField))", "error": [], "warning": [] }, { - "query": "row var = greatest(\"a\", \"a\")", + "query": "from a_index | sort least(booleanField)", "error": [], "warning": [] }, { - "query": "row greatest(\"a\", \"a\")", + "query": "from a_index | eval least(null)", "error": [], "warning": [] }, { - "query": "row var = greatest(to_string(true), to_string(true))", + "query": "row nullVar = null | eval least(nullVar)", "error": [], "warning": [] }, { - "query": "row var = greatest(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", + "query": "row var = left(\"a\", 5)", "error": [], "warning": [] }, { - "query": "row greatest(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", + "query": "row left(\"a\", 5)", "error": [], "warning": [] }, { - "query": "row var = greatest(to_version(\"a\"), to_version(\"a\"))", + "query": "row var = left(to_string(\"a\"), to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = greatest(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row var = left(5, \"a\")", "error": [ - "Argument of [greatest] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]", - "Argument of [greatest] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + "Argument of [left] must be [string], found value [5] type [number]", + "Argument of [left] must be [number], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | where greatest(numberField, numberField) > 0", + "query": "from a_index | where length(left(stringField, numberField)) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where greatest(cartesianPointField, cartesianPointField) > 0", + "query": "from a_index | where length(left(numberField, stringField)) > 0", "error": [ - "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]", - "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Argument of [left] must be [string], found value [numberField] type [number]", + "Argument of [left] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | where greatest(numberField) > 0", + "query": "from a_index | eval var = left(stringField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | where greatest(cartesianPointField) > 0", - "error": [ - "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "from a_index | eval left(stringField, numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | where length(greatest(stringField)) > 0", + "query": "from a_index | eval var = left(to_string(stringField), to_integer(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | where length(greatest(cartesianPointField)) > 0", + "query": "from a_index | eval left(numberField, stringField)", "error": [ - "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Argument of [left] must be [string], found value [numberField] type [number]", + "Argument of [left] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | where length(greatest(stringField, stringField)) > 0", - "error": [], - "warning": [] - }, - { - "query": "from a_index | where length(greatest(cartesianPointField, cartesianPointField)) > 0", + "query": "from a_index | eval left(stringField, numberField, extraArg)", "error": [ - "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]", - "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Error: [left] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | eval var = greatest(booleanField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval greatest(booleanField)", + "query": "from a_index | sort left(stringField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(to_boolean(booleanField))", + "query": "row var = left(to_string(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval greatest(cartesianPointField)", + "query": "row var = left(true, true)", "error": [ - "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Argument of [left] must be [string], found value [true] type [boolean]", + "Argument of [left] must be [number], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = greatest(booleanField, booleanField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval greatest(booleanField, booleanField)", - "error": [], + "query": "from a_index | where length(left(booleanField, booleanField)) > 0", + "error": [ + "Argument of [left] must be [string], found value [booleanField] type [boolean]", + "Argument of [left] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = greatest(to_boolean(booleanField), to_boolean(booleanField))", + "query": "from a_index | eval var = left(to_string(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval greatest(cartesianPointField, cartesianPointField)", + "query": "from a_index | eval left(booleanField, booleanField)", "error": [ - "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]", - "Argument of [greatest] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Argument of [left] must be [string], found value [booleanField] type [boolean]", + "Argument of [left] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = greatest(numberField, numberField)", + "query": "from a_index | eval left(null, null)", "error": [], "warning": [] }, { - "query": "from a_index | eval greatest(numberField, numberField)", + "query": "row nullVar = null | eval left(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(to_integer(booleanField), to_integer(booleanField))", + "query": "row var = length(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(numberField)", + "query": "row length(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval greatest(numberField)", + "query": "row var = length(to_string(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(to_integer(booleanField))", - "error": [], + "query": "row var = length(5)", + "error": [ + "Argument of [length] must be [string], found value [5] type [number]" + ], "warning": [] }, { - "query": "from a_index | eval var = greatest(ipField, ipField)", + "query": "from a_index | where length(stringField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval greatest(ipField, ipField)", - "error": [], + "query": "from a_index | where length(numberField) > 0", + "error": [ + "Argument of [length] must be [string], found value [numberField] type [number]" + ], "warning": [] }, { - "query": "from a_index | eval var = greatest(to_ip(ipField), to_ip(ipField))", + "query": "from a_index | eval var = length(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(to_string(booleanField))", + "query": "from a_index | eval length(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(stringField, stringField)", + "query": "from a_index | eval var = length(to_string(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval greatest(stringField, stringField)", - "error": [], + "query": "from a_index | eval length(numberField)", + "error": [ + "Argument of [length] must be [string], found value [numberField] type [number]" + ], "warning": [] }, { - "query": "from a_index | eval var = greatest(to_string(booleanField), to_string(booleanField))", - "error": [], + "query": "from a_index | eval length(stringField, extraArg)", + "error": [ + "Error: [length] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | eval var = greatest(versionField, versionField)", - "error": [], + "query": "from a_index | eval var = length(*)", + "error": [ + "Using wildcards (*) in length is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval greatest(versionField, versionField)", + "query": "from a_index | sort length(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = greatest(to_version(stringField), to_version(stringField))", + "query": "row var = length(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | sort greatest(booleanField)", - "error": [], + "query": "row var = length(true)", + "error": [ + "Argument of [length] must be [string], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval greatest(null)", - "error": [], + "query": "from a_index | where length(booleanField) > 0", + "error": [ + "Argument of [length] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row nullVar = null | eval greatest(nullVar)", + "query": "from a_index | eval var = length(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = least(\"a\")", - "error": [], + "query": "from a_index | eval length(booleanField)", + "error": [ + "Argument of [length] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row least(\"a\")", + "query": "from a_index | eval length(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(stringField)", + "query": "row nullVar = null | eval length(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval least(stringField)", + "query": "row var = log(5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | sort least(stringField)", + "query": "row log(5, 5)", "error": [], "warning": [] }, { - "query": "row var = least(true)", + "query": "row var = log(to_integer(\"a\"), to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "row least(true)", - "error": [], + "query": "row var = log(\"a\", \"a\")", + "error": [ + "Argument of [log] must be [number], found value [\"a\"] type [string]", + "Argument of [log] must be [number], found value [\"a\"] type [string]" + ], "warning": [] }, { - "query": "row var = least(to_boolean(true))", + "query": "from a_index | where log(numberField, numberField) > 0", "error": [], "warning": [] }, { - "query": "row var = least(true, true)", - "error": [], - "warning": [] + "query": "from a_index | where log(stringField, stringField) > 0", + "error": [ + "Argument of [log] must be [number], found value [stringField] type [string]", + "Argument of [log] must be [number], found value [stringField] type [string]" + ], + "warning": [] }, { - "query": "row least(true, true)", + "query": "from a_index | eval var = log(numberField, numberField)", "error": [], "warning": [] }, { - "query": "row var = least(to_boolean(true), to_boolean(true))", + "query": "from a_index | eval log(numberField, numberField)", "error": [], "warning": [] }, { - "query": "row var = least(5, 5)", + "query": "from a_index | eval var = log(to_integer(stringField), to_integer(stringField))", "error": [], "warning": [] }, { - "query": "row least(5, 5)", - "error": [], + "query": "from a_index | eval log(stringField, stringField)", + "error": [ + "Argument of [log] must be [number], found value [stringField] type [string]", + "Argument of [log] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "row var = least(to_integer(true), to_integer(true))", - "error": [], + "query": "from a_index | eval log(numberField, numberField, extraArg)", + "error": [ + "Error: [log] function expects no more than 2 arguments, got 3." + ], "warning": [] }, { - "query": "row var = least(5)", + "query": "from a_index | sort log(numberField, numberField)", "error": [], "warning": [] }, { - "query": "row least(5)", + "query": "row var = log(5)", "error": [], "warning": [] }, { - "query": "row var = least(to_integer(true))", + "query": "row log(5)", "error": [], "warning": [] }, { - "query": "row var = least(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", + "query": "row var = log(to_integer(true))", "error": [], "warning": [] }, { - "query": "row least(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", + "query": "row var = log(to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = least(to_ip(to_ip(\"127.0.0.1\")), to_ip(to_ip(\"127.0.0.1\")))", - "error": [], + "query": "row var = log(true, true)", + "error": [ + "Argument of [log] must be [number], found value [true] type [boolean]", + "Argument of [log] must be [number], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row var = least(to_string(true))", + "query": "from a_index | where log(numberField) > 0", "error": [], "warning": [] }, { - "query": "row var = least(\"a\", \"a\")", - "error": [], + "query": "from a_index | where log(booleanField) > 0", + "error": [ + "Argument of [log] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row least(\"a\", \"a\")", - "error": [], + "query": "from a_index | where log(booleanField, booleanField) > 0", + "error": [ + "Argument of [log] must be [number], found value [booleanField] type [boolean]", + "Argument of [log] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = least(to_string(true), to_string(true))", + "query": "from a_index | eval var = log(numberField)", "error": [], "warning": [] }, { - "query": "row var = least(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", + "query": "from a_index | eval log(numberField)", "error": [], "warning": [] }, { - "query": "row least(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", + "query": "from a_index | eval var = log(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = least(to_version(\"a\"), to_version(\"a\"))", - "error": [], + "query": "from a_index | eval log(booleanField)", + "error": [ + "Argument of [log] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = least(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = log(*)", "error": [ - "Argument of [least] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]", - "Argument of [least] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + "Using wildcards (*) in log is not allowed" ], "warning": [] }, { - "query": "from a_index | where least(numberField, numberField) > 0", + "query": "from a_index | eval var = log(to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | where least(cartesianPointField, cartesianPointField) > 0", + "query": "from a_index | eval log(booleanField, booleanField)", "error": [ - "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]", - "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Argument of [log] must be [number], found value [booleanField] type [boolean]", + "Argument of [log] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where least(numberField) > 0", + "query": "from a_index | sort log(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | where least(cartesianPointField) > 0", - "error": [ - "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "from a_index | eval log(null, null)", + "error": [], "warning": [] }, { - "query": "from a_index | where length(least(stringField)) > 0", + "query": "row nullVar = null | eval log(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | where length(least(cartesianPointField)) > 0", + "query": "row var = log10(5)", + "error": [], + "warning": [] + }, + { + "query": "row log10(5)", + "error": [], + "warning": [] + }, + { + "query": "row var = log10(to_integer(\"a\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = log10(\"a\")", "error": [ - "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Argument of [log10] must be [number], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | where length(least(stringField, stringField)) > 0", + "query": "from a_index | where log10(numberField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where length(least(cartesianPointField, cartesianPointField)) > 0", + "query": "from a_index | where log10(stringField) > 0", "error": [ - "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]", - "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Argument of [log10] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = least(booleanField)", + "query": "from a_index | eval var = log10(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval least(booleanField)", + "query": "from a_index | eval log10(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(to_boolean(booleanField))", + "query": "from a_index | eval var = log10(to_integer(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval least(cartesianPointField)", + "query": "from a_index | eval log10(stringField)", "error": [ - "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Argument of [log10] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = least(booleanField, booleanField)", - "error": [], + "query": "from a_index | eval log10(numberField, extraArg)", + "error": [ + "Error: [log10] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | eval least(booleanField, booleanField)", + "query": "from a_index | eval var = log10(*)", + "error": [ + "Using wildcards (*) in log10 is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | sort log10(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(to_boolean(booleanField), to_boolean(booleanField))", + "query": "row var = log10(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval least(cartesianPointField, cartesianPointField)", + "query": "row var = log10(true)", "error": [ - "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]", - "Argument of [least] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Argument of [log10] must be [number], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = least(numberField, numberField)", - "error": [], + "query": "from a_index | where log10(booleanField) > 0", + "error": [ + "Argument of [log10] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval least(numberField, numberField)", + "query": "from a_index | eval var = log10(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(to_integer(booleanField), to_integer(booleanField))", - "error": [], + "query": "from a_index | eval log10(booleanField)", + "error": [ + "Argument of [log10] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = least(numberField)", + "query": "from a_index | eval log10(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval least(numberField)", + "query": "row nullVar = null | eval log10(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(to_integer(booleanField))", + "query": "row var = ltrim(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(ipField, ipField)", + "query": "row ltrim(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval least(ipField, ipField)", + "query": "row var = ltrim(to_string(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(to_ip(ipField), to_ip(ipField))", - "error": [], + "query": "row var = ltrim(5)", + "error": [ + "Argument of [ltrim] must be [string], found value [5] type [number]" + ], "warning": [] }, { - "query": "from a_index | eval var = least(to_string(booleanField))", + "query": "from a_index | where length(ltrim(stringField)) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(stringField, stringField)", - "error": [], + "query": "from a_index | where length(ltrim(numberField)) > 0", + "error": [ + "Argument of [ltrim] must be [string], found value [numberField] type [number]" + ], "warning": [] }, { - "query": "from a_index | eval least(stringField, stringField)", + "query": "from a_index | eval var = ltrim(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(to_string(booleanField), to_string(booleanField))", + "query": "from a_index | eval ltrim(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = least(versionField, versionField)", + "query": "from a_index | eval var = ltrim(to_string(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval least(versionField, versionField)", - "error": [], + "query": "from a_index | eval ltrim(numberField)", + "error": [ + "Argument of [ltrim] must be [string], found value [numberField] type [number]" + ], "warning": [] }, { - "query": "from a_index | eval var = least(to_version(stringField), to_version(stringField))", - "error": [], + "query": "from a_index | eval ltrim(stringField, extraArg)", + "error": [ + "Error: [ltrim] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | sort least(booleanField)", - "error": [], + "query": "from a_index | eval var = ltrim(*)", + "error": [ + "Using wildcards (*) in ltrim is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval least(null)", + "query": "from a_index | sort ltrim(stringField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval least(nullVar)", + "query": "row var = ltrim(to_string(true))", "error": [], "warning": [] }, { - "query": "row var = left(\"a\", 5)", - "error": [], + "query": "row var = ltrim(true)", + "error": [ + "Argument of [ltrim] must be [string], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row left(\"a\", 5)", - "error": [], + "query": "from a_index | where length(ltrim(booleanField)) > 0", + "error": [ + "Argument of [ltrim] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = left(to_string(\"a\"), to_integer(\"a\"))", + "query": "from a_index | eval var = ltrim(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = left(5, \"a\")", + "query": "from a_index | eval ltrim(booleanField)", "error": [ - "Argument of [left] must be [string], found value [5] type [number]", - "Argument of [left] must be [number], found value [\"a\"] type [string]" + "Argument of [ltrim] must be [string], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where length(left(stringField, numberField)) > 0", + "query": "from a_index | eval ltrim(null)", "error": [], "warning": [] }, { - "query": "from a_index | where length(left(numberField, stringField)) > 0", - "error": [ - "Argument of [left] must be [string], found value [numberField] type [number]", - "Argument of [left] must be [number], found value [stringField] type [string]" - ], + "query": "row nullVar = null | eval ltrim(nullVar)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = left(stringField, numberField)", + "query": "row var = mv_avg(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval left(stringField, numberField)", + "query": "row mv_avg(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = left(to_string(stringField), to_integer(stringField))", + "query": "row var = mv_avg(to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval left(numberField, stringField)", + "query": "row var = mv_avg(\"a\")", "error": [ - "Argument of [left] must be [string], found value [numberField] type [number]", - "Argument of [left] must be [number], found value [stringField] type [string]" + "Argument of [mv_avg] must be [number], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | eval left(stringField, numberField, extraArg)", + "query": "from a_index | where mv_avg(numberField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where mv_avg(stringField) > 0", "error": [ - "Error: [left] function expects exactly 2 arguments, got 3." + "Argument of [mv_avg] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | sort left(stringField, numberField)", + "query": "from a_index | eval var = mv_avg(numberField)", "error": [], "warning": [] }, { - "query": "row var = left(to_string(true), to_integer(true))", + "query": "from a_index | eval mv_avg(numberField)", "error": [], "warning": [] }, { - "query": "row var = left(true, true)", - "error": [ - "Argument of [left] must be [string], found value [true] type [boolean]", - "Argument of [left] must be [number], found value [true] type [boolean]" - ], + "query": "from a_index | eval var = mv_avg(to_integer(stringField))", + "error": [], "warning": [] }, { - "query": "from a_index | where length(left(booleanField, booleanField)) > 0", + "query": "from a_index | eval mv_avg(stringField)", "error": [ - "Argument of [left] must be [string], found value [booleanField] type [boolean]", - "Argument of [left] must be [number], found value [booleanField] type [boolean]" + "Argument of [mv_avg] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = left(to_string(booleanField), to_integer(booleanField))", - "error": [], + "query": "from a_index | eval mv_avg(numberField, extraArg)", + "error": [ + "Error: [mv_avg] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | eval left(booleanField, booleanField)", + "query": "from a_index | eval var = mv_avg(*)", "error": [ - "Argument of [left] must be [string], found value [booleanField] type [boolean]", - "Argument of [left] must be [number], found value [booleanField] type [boolean]" + "Using wildcards (*) in mv_avg is not allowed" ], "warning": [] }, { - "query": "from a_index | eval left(null, null)", + "query": "from a_index | sort mv_avg(numberField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval left(nullVar, nullVar)", + "query": "row var = mv_avg(to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = length(\"a\")", - "error": [], + "query": "row var = mv_avg(true)", + "error": [ + "Argument of [mv_avg] must be [number], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row length(\"a\")", - "error": [], + "query": "from a_index | where mv_avg(booleanField) > 0", + "error": [ + "Argument of [mv_avg] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = length(to_string(\"a\"))", + "query": "from a_index | eval var = mv_avg(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = length(5)", + "query": "from a_index | eval mv_avg(booleanField)", "error": [ - "Argument of [length] must be [string], found value [5] type [number]" + "Argument of [mv_avg] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where length(stringField) > 0", + "query": "from a_index | eval mv_avg(null)", "error": [], "warning": [] }, { - "query": "from a_index | where length(numberField) > 0", - "error": [ - "Argument of [length] must be [string], found value [numberField] type [number]" - ], + "query": "row nullVar = null | eval mv_avg(nullVar)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = length(stringField)", + "query": "row var = mv_concat(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval length(stringField)", + "query": "row mv_concat(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = length(to_string(stringField))", + "query": "row var = mv_concat(to_string(\"a\"), to_string(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval length(numberField)", + "query": "row var = mv_concat(5, 5)", "error": [ - "Argument of [length] must be [string], found value [numberField] type [number]" + "Argument of [mv_concat] must be [string], found value [5] type [number]", + "Argument of [mv_concat] must be [string], found value [5] type [number]" ], "warning": [] }, { - "query": "from a_index | eval length(stringField, extraArg)", - "error": [ - "Error: [length] function expects exactly one argument, got 2." - ], + "query": "from a_index | where length(mv_concat(stringField, stringField)) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = length(*)", + "query": "from a_index | where length(mv_concat(numberField, numberField)) > 0", "error": [ - "Using wildcards (*) in length is not allowed" + "Argument of [mv_concat] must be [string], found value [numberField] type [number]", + "Argument of [mv_concat] must be [string], found value [numberField] type [number]" ], "warning": [] }, { - "query": "from a_index | sort length(stringField)", + "query": "from a_index | eval var = mv_concat(stringField, stringField)", "error": [], "warning": [] }, { - "query": "row var = length(to_string(true))", + "query": "from a_index | eval mv_concat(stringField, stringField)", "error": [], "warning": [] }, { - "query": "row var = length(true)", - "error": [ - "Argument of [length] must be [string], found value [true] type [boolean]" - ], + "query": "from a_index | eval var = mv_concat(to_string(stringField), to_string(stringField))", + "error": [], "warning": [] }, { - "query": "from a_index | where length(booleanField) > 0", + "query": "from a_index | eval mv_concat(numberField, numberField)", "error": [ - "Argument of [length] must be [string], found value [booleanField] type [boolean]" + "Argument of [mv_concat] must be [string], found value [numberField] type [number]", + "Argument of [mv_concat] must be [string], found value [numberField] type [number]" ], "warning": [] }, { - "query": "from a_index | eval var = length(to_string(booleanField))", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval length(booleanField)", + "query": "from a_index | eval mv_concat(stringField, stringField, extraArg)", "error": [ - "Argument of [length] must be [string], found value [booleanField] type [boolean]" + "Error: [mv_concat] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | eval length(null)", + "query": "from a_index | sort mv_concat(stringField, stringField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval length(nullVar)", + "query": "row var = mv_concat(to_string(true), to_string(true))", "error": [], "warning": [] }, { - "query": "row var = log(5, 5)", - "error": [], + "query": "row var = mv_concat(true, true)", + "error": [ + "Argument of [mv_concat] must be [string], found value [true] type [boolean]", + "Argument of [mv_concat] must be [string], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row log(5, 5)", - "error": [], + "query": "from a_index | where length(mv_concat(booleanField, booleanField)) > 0", + "error": [ + "Argument of [mv_concat] must be [string], found value [booleanField] type [boolean]", + "Argument of [mv_concat] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = log(to_integer(\"a\"), to_integer(\"a\"))", + "query": "from a_index | eval var = mv_concat(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = log(\"a\", \"a\")", + "query": "from a_index | eval mv_concat(booleanField, booleanField)", "error": [ - "Argument of [log] must be [number], found value [\"a\"] type [string]", - "Argument of [log] must be [number], found value [\"a\"] type [string]" + "Argument of [mv_concat] must be [string], found value [booleanField] type [boolean]", + "Argument of [mv_concat] must be [string], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where log(numberField, numberField) > 0", + "query": "from a_index | eval mv_concat(null, null)", "error": [], "warning": [] }, { - "query": "from a_index | where log(stringField, stringField) > 0", - "error": [ - "Argument of [log] must be [number], found value [stringField] type [string]", - "Argument of [log] must be [number], found value [stringField] type [string]" - ], + "query": "row nullVar = null | eval mv_concat(nullVar, nullVar)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = log(numberField, numberField)", + "query": "row var = mv_count(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval log(numberField, numberField)", + "query": "row mv_count(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = log(to_integer(stringField), to_integer(stringField))", + "query": "from a_index | eval var = mv_count(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval log(stringField, stringField)", - "error": [ - "Argument of [log] must be [number], found value [stringField] type [string]", - "Argument of [log] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval mv_count(stringField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval log(numberField, numberField, extraArg)", + "query": "from a_index | eval var = mv_count(*)", "error": [ - "Error: [log] function expects no more than 2 arguments, got 3." + "Using wildcards (*) in mv_count is not allowed" ], "warning": [] }, { - "query": "from a_index | sort log(numberField, numberField)", + "query": "from a_index | sort mv_count(stringField)", "error": [], "warning": [] }, { - "query": "row var = log(5)", + "query": "row var = mv_count(true)", "error": [], "warning": [] }, { - "query": "row log(5)", + "query": "row mv_count(true)", "error": [], "warning": [] }, { - "query": "row var = log(to_integer(true))", + "query": "row var = mv_count(to_boolean(true))", "error": [], "warning": [] }, { - "query": "row var = log(to_integer(true), to_integer(true))", + "query": "row var = mv_count(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = log(true, true)", - "error": [ - "Argument of [log] must be [number], found value [true] type [boolean]", - "Argument of [log] must be [number], found value [true] type [boolean]" - ], + "query": "row mv_count(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where log(numberField) > 0", + "query": "row var = mv_count(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | where log(booleanField) > 0", - "error": [ - "Argument of [log] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = mv_count(to_cartesianshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where log(booleanField, booleanField) > 0", - "error": [ - "Argument of [log] must be [number], found value [booleanField] type [boolean]", - "Argument of [log] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row mv_count(to_cartesianshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = log(numberField)", + "query": "row var = mv_count(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval log(numberField)", + "query": "row var = mv_count(now())", "error": [], "warning": [] }, { - "query": "from a_index | eval var = log(to_integer(booleanField))", + "query": "row mv_count(now())", "error": [], "warning": [] }, { - "query": "from a_index | eval log(booleanField)", - "error": [ - "Argument of [log] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = mv_count(to_datetime(now()))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = log(*)", - "error": [ - "Using wildcards (*) in log is not allowed" - ], + "query": "row var = mv_count(5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = log(to_integer(booleanField), to_integer(booleanField))", + "query": "row mv_count(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval log(booleanField, booleanField)", - "error": [ - "Argument of [log] must be [number], found value [booleanField] type [boolean]", - "Argument of [log] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = mv_count(to_integer(true))", + "error": [], "warning": [] }, { - "query": "from a_index | sort log(numberField)", + "query": "row var = mv_count(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval log(null, null)", + "query": "row mv_count(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval log(nullVar, nullVar)", + "query": "row var = mv_count(to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = log10(5)", + "query": "row var = mv_count(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row log10(5)", + "query": "row mv_count(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = log10(to_integer(\"a\"))", + "query": "row var = mv_count(to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = log10(\"a\")", - "error": [ - "Argument of [log10] must be [number], found value [\"a\"] type [string]" - ], + "query": "row var = mv_count(to_ip(\"127.0.0.1\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where log10(numberField) > 0", + "query": "row mv_count(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | where log10(stringField) > 0", - "error": [ - "Argument of [log10] must be [number], found value [stringField] type [string]" - ], + "query": "row var = mv_count(to_ip(to_ip(\"127.0.0.1\")))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = log10(numberField)", + "query": "row var = mv_count(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval log10(numberField)", + "query": "row var = mv_count(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = log10(to_integer(stringField))", + "query": "row mv_count(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval log10(stringField)", - "error": [ - "Argument of [log10] must be [number], found value [stringField] type [string]" - ], - "warning": [] - }, - { - "query": "from a_index | eval log10(numberField, extraArg)", - "error": [ - "Error: [log10] function expects exactly one argument, got 2." - ], + "query": "row var = mv_count(to_version(\"a\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = log10(*)", - "error": [ - "Using wildcards (*) in log10 is not allowed" - ], + "query": "from a_index | where mv_count(booleanField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | sort log10(numberField)", + "query": "from a_index | where mv_count(cartesianPointField) > 0", "error": [], "warning": [] }, { - "query": "row var = log10(to_integer(true))", + "query": "from a_index | where mv_count(cartesianShapeField) > 0", "error": [], "warning": [] }, { - "query": "row var = log10(true)", - "error": [ - "Argument of [log10] must be [number], found value [true] type [boolean]" - ], + "query": "from a_index | where mv_count(dateField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | where log10(booleanField) > 0", - "error": [ - "Argument of [log10] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | where mv_count(numberField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = log10(to_integer(booleanField))", + "query": "from a_index | where mv_count(geoPointField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval log10(booleanField)", - "error": [ - "Argument of [log10] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | where mv_count(geoShapeField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval log10(null)", + "query": "from a_index | where mv_count(ipField) > 0", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval log10(nullVar)", + "query": "from a_index | where mv_count(stringField) > 0", "error": [], "warning": [] }, { - "query": "row var = ltrim(\"a\")", + "query": "from a_index | where mv_count(versionField) > 0", "error": [], "warning": [] }, { - "query": "row ltrim(\"a\")", + "query": "from a_index | eval var = mv_count(booleanField)", "error": [], "warning": [] }, { - "query": "row var = ltrim(to_string(\"a\"))", + "query": "from a_index | eval mv_count(booleanField)", "error": [], "warning": [] }, { - "query": "row var = ltrim(5)", - "error": [ - "Argument of [ltrim] must be [string], found value [5] type [number]" - ], + "query": "from a_index | eval var = mv_count(to_boolean(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | where length(ltrim(stringField)) > 0", + "query": "from a_index | eval var = mv_count(cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | where length(ltrim(numberField)) > 0", - "error": [ - "Argument of [ltrim] must be [string], found value [numberField] type [number]" - ], + "query": "from a_index | eval mv_count(cartesianPointField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = ltrim(stringField)", + "query": "from a_index | eval var = mv_count(to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval ltrim(stringField)", + "query": "from a_index | eval var = mv_count(cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = ltrim(to_string(stringField))", + "query": "from a_index | eval mv_count(cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval ltrim(numberField)", - "error": [ - "Argument of [ltrim] must be [string], found value [numberField] type [number]" - ], + "query": "from a_index | eval var = mv_count(to_cartesianshape(cartesianPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval ltrim(stringField, extraArg)", - "error": [ - "Error: [ltrim] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval var = mv_count(dateField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = ltrim(*)", - "error": [ - "Using wildcards (*) in ltrim is not allowed" - ], + "query": "from a_index | eval mv_count(dateField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort ltrim(stringField)", + "query": "from a_index | eval var = mv_count(to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "row var = ltrim(to_string(true))", + "query": "from a_index | eval var = mv_count(numberField)", "error": [], "warning": [] }, { - "query": "row var = ltrim(true)", - "error": [ - "Argument of [ltrim] must be [string], found value [true] type [boolean]" - ], + "query": "from a_index | eval mv_count(numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | where length(ltrim(booleanField)) > 0", - "error": [ - "Argument of [ltrim] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_count(to_integer(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = ltrim(to_string(booleanField))", + "query": "from a_index | eval var = mv_count(geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval ltrim(booleanField)", - "error": [ - "Argument of [ltrim] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval mv_count(geoPointField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval ltrim(null)", + "query": "from a_index | eval var = mv_count(to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval ltrim(nullVar)", + "query": "from a_index | eval var = mv_count(geoShapeField)", "error": [], "warning": [] }, { - "query": "row var = mv_avg(5)", + "query": "from a_index | eval mv_count(geoShapeField)", "error": [], "warning": [] }, { - "query": "row mv_avg(5)", + "query": "from a_index | eval var = mv_count(to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "row var = mv_avg(to_integer(\"a\"))", + "query": "from a_index | eval var = mv_count(ipField)", "error": [], "warning": [] }, { - "query": "row var = mv_avg(\"a\")", - "error": [ - "Argument of [mv_avg] must be [number], found value [\"a\"] type [string]" - ], + "query": "from a_index | eval mv_count(ipField)", + "error": [], "warning": [] }, { - "query": "from a_index | where mv_avg(numberField) > 0", + "query": "from a_index | eval var = mv_count(to_ip(ipField))", "error": [], "warning": [] }, { - "query": "from a_index | where mv_avg(stringField) > 0", - "error": [ - "Argument of [mv_avg] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval var = mv_count(to_string(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_avg(numberField)", + "query": "from a_index | eval var = mv_count(versionField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_avg(numberField)", + "query": "from a_index | eval mv_count(versionField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_avg(to_integer(stringField))", + "query": "from a_index | eval var = mv_count(to_version(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_avg(stringField)", + "query": "from a_index | eval mv_count(booleanField, extraArg)", "error": [ - "Argument of [mv_avg] must be [number], found value [stringField] type [string]" + "Error: [mv_count] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | eval mv_avg(numberField, extraArg)", - "error": [ - "Error: [mv_avg] function expects exactly one argument, got 2." - ], + "query": "from a_index | sort mv_count(booleanField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_avg(*)", - "error": [ - "Using wildcards (*) in mv_avg is not allowed" - ], + "query": "from a_index | eval mv_count(null)", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_avg(numberField)", + "query": "row nullVar = null | eval mv_count(nullVar)", "error": [], "warning": [] }, { - "query": "row var = mv_avg(to_integer(true))", + "query": "row var = mv_dedupe(\"a\")", "error": [], "warning": [] }, { - "query": "row var = mv_avg(true)", - "error": [ - "Argument of [mv_avg] must be [number], found value [true] type [boolean]" - ], + "query": "row mv_dedupe(\"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | where mv_avg(booleanField) > 0", - "error": [ - "Argument of [mv_avg] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_dedupe(stringField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_avg(to_integer(booleanField))", + "query": "from a_index | eval mv_dedupe(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_avg(booleanField)", + "query": "from a_index | eval var = mv_dedupe(*)", "error": [ - "Argument of [mv_avg] must be [number], found value [booleanField] type [boolean]" + "Using wildcards (*) in mv_dedupe is not allowed" ], "warning": [] }, { - "query": "from a_index | eval mv_avg(null)", + "query": "from a_index | sort mv_dedupe(stringField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_avg(nullVar)", + "query": "row var = mv_dedupe(true)", "error": [], "warning": [] }, { - "query": "row var = mv_concat(\"a\", \"a\")", + "query": "row mv_dedupe(true)", "error": [], "warning": [] }, { - "query": "row mv_concat(\"a\", \"a\")", + "query": "row var = mv_dedupe(to_boolean(true))", "error": [], "warning": [] }, { - "query": "row var = mv_concat(to_string(\"a\"), to_string(\"a\"))", + "query": "row var = mv_dedupe(now())", "error": [], "warning": [] }, { - "query": "row var = mv_concat(5, 5)", - "error": [ - "Argument of [mv_concat] must be [string], found value [5] type [number]", - "Argument of [mv_concat] must be [string], found value [5] type [number]" - ], + "query": "row mv_dedupe(now())", + "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_concat(stringField, stringField)) > 0", + "query": "row var = mv_dedupe(to_datetime(now()))", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_concat(numberField, numberField)) > 0", - "error": [ - "Argument of [mv_concat] must be [string], found value [numberField] type [number]", - "Argument of [mv_concat] must be [string], found value [numberField] type [number]" - ], + "query": "row var = mv_dedupe(5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_concat(stringField, stringField)", + "query": "row mv_dedupe(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_concat(stringField, stringField)", + "query": "row var = mv_dedupe(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_concat(to_string(stringField), to_string(stringField))", + "query": "row var = mv_dedupe(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_concat(numberField, numberField)", - "error": [ - "Argument of [mv_concat] must be [string], found value [numberField] type [number]", - "Argument of [mv_concat] must be [string], found value [numberField] type [number]" - ], + "query": "row mv_dedupe(to_ip(\"127.0.0.1\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval mv_concat(stringField, stringField, extraArg)", - "error": [ - "Error: [mv_concat] function expects exactly 2 arguments, got 3." - ], + "query": "row var = mv_dedupe(to_ip(to_ip(\"127.0.0.1\")))", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_concat(stringField, stringField)", + "query": "row var = mv_dedupe(to_string(true))", "error": [], "warning": [] }, { - "query": "row var = mv_concat(to_string(true), to_string(true))", + "query": "row var = mv_dedupe(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "row var = mv_concat(true, true)", - "error": [ - "Argument of [mv_concat] must be [string], found value [true] type [boolean]", - "Argument of [mv_concat] must be [string], found value [true] type [boolean]" - ], + "query": "row mv_dedupe(to_version(\"1.0.0\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_concat(booleanField, booleanField)) > 0", - "error": [ - "Argument of [mv_concat] must be [string], found value [booleanField] type [boolean]", - "Argument of [mv_concat] must be [string], found value [booleanField] type [boolean]" - ], + "query": "row var = mv_dedupe(to_version(\"a\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_concat(to_string(booleanField), to_string(booleanField))", + "query": "from a_index | where mv_dedupe(numberField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_concat(booleanField, booleanField)", - "error": [ - "Argument of [mv_concat] must be [string], found value [booleanField] type [boolean]", - "Argument of [mv_concat] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | where length(mv_dedupe(stringField)) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval mv_concat(null, null)", + "query": "from a_index | eval var = mv_dedupe(booleanField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_concat(nullVar, nullVar)", + "query": "from a_index | eval mv_dedupe(booleanField)", "error": [], "warning": [] }, { - "query": "row var = mv_count(\"a\")", + "query": "from a_index | eval var = mv_dedupe(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "row mv_count(\"a\")", + "query": "from a_index | eval var = mv_dedupe(dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(stringField)", + "query": "from a_index | eval mv_dedupe(dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(stringField)", + "query": "from a_index | eval var = mv_dedupe(to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(*)", - "error": [ - "Using wildcards (*) in mv_count is not allowed" - ], + "query": "from a_index | eval var = mv_dedupe(numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_count(stringField)", + "query": "from a_index | eval mv_dedupe(numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_count(true)", + "query": "from a_index | eval var = mv_dedupe(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row mv_count(true)", + "query": "from a_index | eval var = mv_dedupe(ipField)", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_boolean(true))", + "query": "from a_index | eval mv_dedupe(ipField)", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_dedupe(to_ip(ipField))", "error": [], "warning": [] }, { - "query": "row mv_count(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_dedupe(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = mv_dedupe(versionField)", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_dedupe(versionField)", "error": [], "warning": [] }, { - "query": "row mv_count(to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_dedupe(to_version(stringField))", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", - "error": [], + "query": "from a_index | eval mv_dedupe(booleanField, extraArg)", + "error": [ + "Error: [mv_dedupe] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "row var = mv_count(now())", + "query": "from a_index | sort mv_dedupe(booleanField)", "error": [], "warning": [] }, { - "query": "row mv_count(now())", + "query": "row mv_dedupe(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_datetime(now()))", + "query": "row var = mv_dedupe(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = mv_count(5)", + "query": "row var = mv_dedupe(to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row mv_count(5)", + "query": "row mv_dedupe(to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_integer(true))", + "query": "row var = mv_dedupe(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_geopoint(\"POINT (30 10)\"))", + "query": "row var = mv_dedupe(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row mv_count(to_geopoint(\"POINT (30 10)\"))", + "query": "row mv_dedupe(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "row var = mv_dedupe(to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_geoshape(\"POINT (30 10)\"))", + "query": "row var = mv_dedupe(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row mv_count(to_geoshape(\"POINT (30 10)\"))", + "query": "row mv_dedupe(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "row var = mv_dedupe(to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval var = mv_dedupe(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row mv_count(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval var = mv_dedupe(to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_ip(to_ip(\"127.0.0.1\")))", + "query": "from a_index | eval var = mv_dedupe(cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_string(true))", + "query": "from a_index | eval mv_dedupe(cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_version(\"1.0.0\"))", + "query": "from a_index | eval var = mv_dedupe(to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "row mv_count(to_version(\"1.0.0\"))", + "query": "from a_index | eval var = mv_dedupe(geoPointField)", "error": [], "warning": [] }, { - "query": "row var = mv_count(to_version(\"a\"))", + "query": "from a_index | eval mv_dedupe(geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_count(booleanField) > 0", + "query": "from a_index | eval var = mv_dedupe(to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | where mv_count(cartesianPointField) > 0", + "query": "from a_index | eval var = mv_dedupe(geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_count(cartesianShapeField) > 0", + "query": "from a_index | eval mv_dedupe(geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_count(dateField) > 0", + "query": "from a_index | eval var = mv_dedupe(to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | where mv_count(numberField) > 0", - "error": [], + "query": "from a_index | eval mv_dedupe(numberField, extraArg)", + "error": [ + "Error: [mv_dedupe] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | where mv_count(geoPointField) > 0", + "query": "from a_index | sort mv_dedupe(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_count(geoShapeField) > 0", + "query": "row var = mv_dedupe(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | where mv_count(ipField) > 0", + "query": "from a_index | eval mv_dedupe(cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_count(stringField) > 0", + "query": "from a_index | eval mv_dedupe(null)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_count(versionField) > 0", + "query": "row nullVar = null | eval mv_dedupe(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(booleanField)", + "query": "row var = mv_first(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(booleanField)", + "query": "row mv_first(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_boolean(booleanField))", + "query": "from a_index | eval var = mv_first(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(cartesianPointField)", + "query": "from a_index | eval mv_first(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(cartesianPointField)", - "error": [], + "query": "from a_index | eval var = mv_first(*)", + "error": [ + "Using wildcards (*) in mv_first is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_cartesianpoint(cartesianPointField))", + "query": "from a_index | sort mv_first(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(cartesianShapeField)", + "query": "row var = mv_first(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(cartesianShapeField)", + "query": "row mv_first(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_cartesianshape(cartesianPointField))", + "query": "row var = mv_first(to_boolean(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(dateField)", + "query": "row var = mv_first(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(dateField)", + "query": "row mv_first(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_datetime(dateField))", + "query": "row var = mv_first(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(numberField)", + "query": "row var = mv_first(to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(numberField)", + "query": "row mv_first(to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_integer(booleanField))", + "query": "row var = mv_first(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(geoPointField)", + "query": "row var = mv_first(now())", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(geoPointField)", + "query": "row mv_first(now())", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_geopoint(geoPointField))", + "query": "row var = mv_first(to_datetime(now()))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(geoShapeField)", + "query": "row var = mv_first(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(geoShapeField)", + "query": "row mv_first(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_geoshape(geoPointField))", + "query": "row var = mv_first(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(ipField)", + "query": "row var = mv_first(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(ipField)", + "query": "row mv_first(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_ip(ipField))", + "query": "row var = mv_first(to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_string(booleanField))", + "query": "row var = mv_first(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(versionField)", + "query": "row mv_first(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(versionField)", + "query": "row var = mv_first(to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_count(to_version(stringField))", + "query": "row var = mv_first(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(booleanField, extraArg)", - "error": [ - "Error: [mv_count] function expects exactly one argument, got 2." - ], + "query": "row mv_first(to_ip(\"127.0.0.1\"))", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_count(booleanField)", + "query": "row var = mv_first(to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_count(null)", + "query": "row var = mv_first(to_string(true))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_count(nullVar)", + "query": "row var = mv_first(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(\"a\")", + "query": "row mv_first(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "row mv_dedupe(\"a\")", + "query": "row var = mv_first(to_version(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(stringField)", + "query": "from a_index | where mv_first(numberField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(stringField)", + "query": "from a_index | where length(mv_first(stringField)) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(*)", - "error": [ - "Using wildcards (*) in mv_dedupe is not allowed" - ], + "query": "from a_index | eval var = mv_first(booleanField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_dedupe(stringField)", + "query": "from a_index | eval mv_first(booleanField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(true)", + "query": "from a_index | eval var = mv_first(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "row mv_dedupe(true)", + "query": "from a_index | eval var = mv_first(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_boolean(true))", + "query": "from a_index | eval mv_first(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(now())", + "query": "from a_index | eval var = mv_first(to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "row mv_dedupe(now())", + "query": "from a_index | eval var = mv_first(cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_datetime(now()))", + "query": "from a_index | eval mv_first(cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(5)", + "query": "from a_index | eval var = mv_first(to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "row mv_dedupe(5)", + "query": "from a_index | eval var = mv_first(dateField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_integer(true))", + "query": "from a_index | eval mv_first(dateField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval var = mv_first(to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "row mv_dedupe(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval var = mv_first(numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_ip(to_ip(\"127.0.0.1\")))", + "query": "from a_index | eval mv_first(numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_string(true))", + "query": "from a_index | eval var = mv_first(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_version(\"1.0.0\"))", + "query": "from a_index | eval var = mv_first(geoPointField)", "error": [], "warning": [] }, { - "query": "row mv_dedupe(to_version(\"1.0.0\"))", + "query": "from a_index | eval mv_first(geoPointField)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_version(\"a\"))", + "query": "from a_index | eval var = mv_first(to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | where mv_dedupe(numberField) > 0", + "query": "from a_index | eval var = mv_first(geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_dedupe(stringField)) > 0", + "query": "from a_index | eval mv_first(geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(booleanField)", + "query": "from a_index | eval var = mv_first(to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(booleanField)", + "query": "from a_index | eval var = mv_first(ipField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_boolean(booleanField))", + "query": "from a_index | eval mv_first(ipField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(dateField)", + "query": "from a_index | eval var = mv_first(to_ip(ipField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(dateField)", + "query": "from a_index | eval var = mv_first(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_datetime(dateField))", + "query": "from a_index | eval var = mv_first(versionField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(numberField)", + "query": "from a_index | eval mv_first(versionField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(numberField)", + "query": "from a_index | eval var = mv_first(to_version(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_integer(booleanField))", - "error": [], + "query": "from a_index | eval mv_first(booleanField, extraArg)", + "error": [ + "Error: [mv_first] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(ipField)", + "query": "from a_index | sort mv_first(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(ipField)", + "query": "from a_index | eval mv_first(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_ip(ipField))", + "query": "row nullVar = null | eval mv_first(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_string(booleanField))", + "query": "row var = mv_last(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(versionField)", + "query": "row mv_last(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(versionField)", + "query": "from a_index | eval var = mv_last(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_version(stringField))", + "query": "from a_index | eval mv_last(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(booleanField, extraArg)", + "query": "from a_index | eval var = mv_last(*)", "error": [ - "Error: [mv_dedupe] function expects exactly one argument, got 2." + "Using wildcards (*) in mv_last is not allowed" ], "warning": [] }, { - "query": "from a_index | sort mv_dedupe(booleanField)", + "query": "from a_index | sort mv_last(stringField)", "error": [], "warning": [] }, { - "query": "row mv_dedupe(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row var = mv_last(true)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row mv_last(true)", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = mv_last(to_boolean(true))", "error": [], "warning": [] }, { - "query": "row mv_dedupe(to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = mv_last(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row mv_last(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_geopoint(\"POINT (30 10)\"))", + "query": "row var = mv_last(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row mv_dedupe(to_geopoint(\"POINT (30 10)\"))", + "query": "row var = mv_last(to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "row mv_last(to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_geoshape(\"POINT (30 10)\"))", + "query": "row var = mv_last(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row mv_dedupe(to_geoshape(\"POINT (30 10)\"))", + "query": "row var = mv_last(now())", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "row mv_last(now())", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(cartesianPointField)", + "query": "row var = mv_last(to_datetime(now()))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_cartesianpoint(cartesianPointField))", + "query": "row var = mv_last(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(cartesianShapeField)", + "query": "row mv_last(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(cartesianShapeField)", + "query": "row var = mv_last(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_cartesianshape(cartesianPointField))", + "query": "row var = mv_last(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(geoPointField)", + "query": "row mv_last(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(geoPointField)", + "query": "row var = mv_last(to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_geopoint(geoPointField))", + "query": "row var = mv_last(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(geoShapeField)", + "query": "row mv_last(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(geoShapeField)", + "query": "row var = mv_last(to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_dedupe(to_geoshape(geoPointField))", + "query": "row var = mv_last(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(numberField, extraArg)", - "error": [ - "Error: [mv_dedupe] function expects exactly one argument, got 2." - ], + "query": "row mv_last(to_ip(\"127.0.0.1\"))", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_dedupe(numberField)", + "query": "row var = mv_last(to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "row var = mv_dedupe(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row var = mv_last(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(cartesianPointField)", + "query": "row var = mv_last(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_dedupe(null)", + "query": "row mv_last(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_dedupe(nullVar)", + "query": "row var = mv_last(to_version(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = mv_first(\"a\")", + "query": "from a_index | where mv_last(numberField) > 0", "error": [], "warning": [] }, { - "query": "row mv_first(\"a\")", + "query": "from a_index | where length(mv_last(stringField)) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(stringField)", + "query": "from a_index | eval var = mv_last(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(stringField)", + "query": "from a_index | eval mv_last(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(*)", - "error": [ - "Using wildcards (*) in mv_first is not allowed" - ], + "query": "from a_index | eval var = mv_last(to_boolean(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_first(stringField)", + "query": "from a_index | eval var = mv_last(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(true)", + "query": "from a_index | eval mv_last(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row mv_first(true)", + "query": "from a_index | eval var = mv_last(to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_boolean(true))", + "query": "from a_index | eval var = mv_last(cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_last(cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row mv_first(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_last(to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = mv_last(dateField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_last(dateField)", "error": [], "warning": [] }, { - "query": "row mv_first(to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_last(to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = mv_last(numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(now())", + "query": "from a_index | eval mv_last(numberField)", "error": [], "warning": [] }, { - "query": "row mv_first(now())", + "query": "from a_index | eval var = mv_last(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_datetime(now()))", + "query": "from a_index | eval var = mv_last(geoPointField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(5)", + "query": "from a_index | eval mv_last(geoPointField)", "error": [], "warning": [] }, { - "query": "row mv_first(5)", + "query": "from a_index | eval var = mv_last(to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_integer(true))", + "query": "from a_index | eval var = mv_last(geoShapeField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_last(geoShapeField)", "error": [], "warning": [] }, { - "query": "row mv_first(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_last(to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = mv_last(ipField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_last(ipField)", "error": [], "warning": [] }, { - "query": "row mv_first(to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_last(to_ip(ipField))", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = mv_last(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval var = mv_last(versionField)", "error": [], "warning": [] }, { - "query": "row mv_first(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval mv_last(versionField)", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_ip(to_ip(\"127.0.0.1\")))", + "query": "from a_index | eval var = mv_last(to_version(stringField))", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_string(true))", - "error": [], + "query": "from a_index | eval mv_last(booleanField, extraArg)", + "error": [ + "Error: [mv_last] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "row var = mv_first(to_version(\"1.0.0\"))", + "query": "from a_index | sort mv_last(booleanField)", "error": [], "warning": [] }, { - "query": "row mv_first(to_version(\"1.0.0\"))", + "query": "from a_index | eval mv_last(null)", "error": [], "warning": [] }, { - "query": "row var = mv_first(to_version(\"a\"))", + "query": "row nullVar = null | eval mv_last(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_first(numberField) > 0", + "query": "row var = mv_max(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_first(stringField)) > 0", + "query": "row mv_max(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(booleanField)", + "query": "from a_index | eval var = mv_max(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(booleanField)", + "query": "from a_index | eval mv_max(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_boolean(booleanField))", - "error": [], + "query": "from a_index | eval var = mv_max(*)", + "error": [ + "Using wildcards (*) in mv_max is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_first(cartesianPointField)", + "query": "from a_index | sort mv_max(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(cartesianPointField)", + "query": "row var = mv_max(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_cartesianpoint(cartesianPointField))", + "query": "row mv_max(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(cartesianShapeField)", + "query": "row var = mv_max(to_boolean(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(cartesianShapeField)", + "query": "row var = mv_max(now())", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_cartesianshape(cartesianPointField))", + "query": "row mv_max(now())", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(dateField)", + "query": "row var = mv_max(to_datetime(now()))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(dateField)", + "query": "row var = mv_max(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_datetime(dateField))", + "query": "row mv_max(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(numberField)", + "query": "row var = mv_max(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(numberField)", + "query": "row var = mv_max(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_integer(booleanField))", + "query": "row mv_max(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(geoPointField)", + "query": "row var = mv_max(to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(geoPointField)", + "query": "row var = mv_max(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_geopoint(geoPointField))", + "query": "row var = mv_max(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(geoShapeField)", + "query": "row mv_max(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(geoShapeField)", + "query": "row var = mv_max(to_version(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_geoshape(geoPointField))", - "error": [], + "query": "row var = mv_max(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [ + "Argument of [mv_max] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_first(ipField)", + "query": "from a_index | where mv_max(numberField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(ipField)", - "error": [], + "query": "from a_index | where mv_max(cartesianPointField) > 0", + "error": [ + "Argument of [mv_max] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_ip(ipField))", + "query": "from a_index | where length(mv_max(stringField)) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_string(booleanField))", - "error": [], + "query": "from a_index | where length(mv_max(cartesianPointField)) > 0", + "error": [ + "Argument of [mv_max] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_first(versionField)", + "query": "from a_index | eval var = mv_max(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(versionField)", + "query": "from a_index | eval mv_max(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_first(to_version(stringField))", + "query": "from a_index | eval var = mv_max(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(booleanField, extraArg)", + "query": "from a_index | eval mv_max(cartesianPointField)", "error": [ - "Error: [mv_first] function expects exactly one argument, got 2." + "Argument of [mv_max] must be [boolean], found value [cartesianPointField] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | sort mv_first(booleanField)", + "query": "from a_index | eval var = mv_max(dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_first(null)", + "query": "from a_index | eval mv_max(dateField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_first(nullVar)", + "query": "from a_index | eval var = mv_max(to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "row var = mv_last(\"a\")", + "query": "from a_index | eval var = mv_max(numberField)", "error": [], "warning": [] }, { - "query": "row mv_last(\"a\")", + "query": "from a_index | eval mv_max(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(stringField)", + "query": "from a_index | eval var = mv_max(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(stringField)", + "query": "from a_index | eval var = mv_max(ipField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(*)", - "error": [ - "Using wildcards (*) in mv_last is not allowed" - ], + "query": "from a_index | eval mv_max(ipField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_last(stringField)", + "query": "from a_index | eval var = mv_max(to_ip(ipField))", "error": [], "warning": [] }, { - "query": "row var = mv_last(true)", + "query": "from a_index | eval var = mv_max(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row mv_last(true)", + "query": "from a_index | eval var = mv_max(versionField)", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_boolean(true))", + "query": "from a_index | eval mv_max(versionField)", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_max(to_version(stringField))", "error": [], "warning": [] }, { - "query": "row mv_last(to_cartesianpoint(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval mv_max(booleanField, extraArg)", + "error": [ + "Error: [mv_max] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "row var = mv_last(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | sort mv_max(booleanField)", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_max(null)", "error": [], "warning": [] }, { - "query": "row mv_last(to_cartesianshape(\"POINT (30 10)\"))", + "query": "row nullVar = null | eval mv_max(nullVar)", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = mv_median(5)", "error": [], "warning": [] }, { - "query": "row var = mv_last(now())", + "query": "row mv_median(5)", "error": [], "warning": [] }, { - "query": "row mv_last(now())", + "query": "row var = mv_median(to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_datetime(now()))", - "error": [], + "query": "row var = mv_median(\"a\")", + "error": [ + "Argument of [mv_median] must be [number], found value [\"a\"] type [string]" + ], "warning": [] }, { - "query": "row var = mv_last(5)", + "query": "from a_index | where mv_median(numberField) > 0", "error": [], "warning": [] }, { - "query": "row mv_last(5)", - "error": [], + "query": "from a_index | where mv_median(stringField) > 0", + "error": [ + "Argument of [mv_median] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "row var = mv_last(to_integer(true))", + "query": "from a_index | eval var = mv_median(numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_median(numberField)", "error": [], "warning": [] }, { - "query": "row mv_last(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = mv_median(to_integer(stringField))", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_geopoint(to_geopoint(\"POINT (30 10)\")))", - "error": [], + "query": "from a_index | eval mv_median(stringField)", + "error": [ + "Argument of [mv_median] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "row var = mv_last(to_geoshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval mv_median(numberField, extraArg)", + "error": [ + "Error: [mv_median] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "row mv_last(to_geoshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval var = mv_median(*)", + "error": [ + "Using wildcards (*) in mv_median is not allowed" + ], "warning": [] }, { - "query": "row var = mv_last(to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | sort mv_median(numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_ip(\"127.0.0.1\"))", + "query": "row var = mv_median(to_integer(true))", "error": [], "warning": [] }, { - "query": "row mv_last(to_ip(\"127.0.0.1\"))", - "error": [], + "query": "row var = mv_median(true)", + "error": [ + "Argument of [mv_median] must be [number], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row var = mv_last(to_ip(to_ip(\"127.0.0.1\")))", - "error": [], + "query": "from a_index | where mv_median(booleanField) > 0", + "error": [ + "Argument of [mv_median] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = mv_last(to_string(true))", + "query": "from a_index | eval var = mv_median(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_version(\"1.0.0\"))", - "error": [], + "query": "from a_index | eval mv_median(booleanField)", + "error": [ + "Argument of [mv_median] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row mv_last(to_version(\"1.0.0\"))", + "query": "from a_index | eval mv_median(null)", "error": [], "warning": [] }, { - "query": "row var = mv_last(to_version(\"a\"))", + "query": "row nullVar = null | eval mv_median(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_last(numberField) > 0", + "query": "row var = mv_min(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_last(stringField)) > 0", + "query": "row mv_min(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(booleanField)", + "query": "from a_index | eval var = mv_min(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(booleanField)", + "query": "from a_index | eval mv_min(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_boolean(booleanField))", - "error": [], + "query": "from a_index | eval var = mv_min(*)", + "error": [ + "Using wildcards (*) in mv_min is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_last(cartesianPointField)", + "query": "from a_index | sort mv_min(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(cartesianPointField)", + "query": "row var = mv_min(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_cartesianpoint(cartesianPointField))", + "query": "row mv_min(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(cartesianShapeField)", + "query": "row var = mv_min(to_boolean(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(cartesianShapeField)", + "query": "row var = mv_min(now())", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_cartesianshape(cartesianPointField))", + "query": "row mv_min(now())", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(dateField)", + "query": "row var = mv_min(to_datetime(now()))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(dateField)", + "query": "row var = mv_min(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_datetime(dateField))", + "query": "row mv_min(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(numberField)", + "query": "row var = mv_min(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(numberField)", + "query": "row var = mv_min(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_integer(booleanField))", + "query": "row mv_min(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(geoPointField)", + "query": "row var = mv_min(to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(geoPointField)", + "query": "row var = mv_min(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_geopoint(geoPointField))", + "query": "row var = mv_min(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(geoShapeField)", + "query": "row mv_min(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(geoShapeField)", + "query": "row var = mv_min(to_version(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_geoshape(geoPointField))", - "error": [], + "query": "row var = mv_min(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [ + "Argument of [mv_min] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_last(ipField)", + "query": "from a_index | where mv_min(numberField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(ipField)", - "error": [], + "query": "from a_index | where mv_min(cartesianPointField) > 0", + "error": [ + "Argument of [mv_min] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_ip(ipField))", + "query": "from a_index | where length(mv_min(stringField)) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_string(booleanField))", - "error": [], + "query": "from a_index | where length(mv_min(cartesianPointField)) > 0", + "error": [ + "Argument of [mv_min] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_last(versionField)", + "query": "from a_index | eval var = mv_min(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(versionField)", + "query": "from a_index | eval mv_min(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_last(to_version(stringField))", + "query": "from a_index | eval var = mv_min(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(booleanField, extraArg)", + "query": "from a_index | eval mv_min(cartesianPointField)", "error": [ - "Error: [mv_last] function expects exactly one argument, got 2." + "Argument of [mv_min] must be [boolean], found value [cartesianPointField] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | sort mv_last(booleanField)", + "query": "from a_index | eval var = mv_min(dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_last(null)", + "query": "from a_index | eval mv_min(dateField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_last(nullVar)", + "query": "from a_index | eval var = mv_min(to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "row var = mv_max(\"a\")", + "query": "from a_index | eval var = mv_min(numberField)", "error": [], "warning": [] }, { - "query": "row mv_max(\"a\")", + "query": "from a_index | eval mv_min(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(stringField)", + "query": "from a_index | eval var = mv_min(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_max(stringField)", + "query": "from a_index | eval var = mv_min(ipField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(*)", - "error": [ - "Using wildcards (*) in mv_max is not allowed" - ], + "query": "from a_index | eval mv_min(ipField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_max(stringField)", + "query": "from a_index | eval var = mv_min(to_ip(ipField))", "error": [], "warning": [] }, { - "query": "row var = mv_max(true)", + "query": "from a_index | eval var = mv_min(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row mv_max(true)", + "query": "from a_index | eval var = mv_min(versionField)", "error": [], "warning": [] }, { - "query": "row var = mv_max(to_boolean(true))", + "query": "from a_index | eval mv_min(versionField)", "error": [], "warning": [] }, { - "query": "row var = mv_max(now())", + "query": "from a_index | eval var = mv_min(to_version(stringField))", "error": [], "warning": [] }, { - "query": "row mv_max(now())", + "query": "from a_index | eval mv_min(booleanField, extraArg)", + "error": [ + "Error: [mv_min] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort mv_min(booleanField)", "error": [], "warning": [] }, { - "query": "row var = mv_max(to_datetime(now()))", + "query": "from a_index | eval mv_min(null)", "error": [], "warning": [] }, { - "query": "row var = mv_max(5)", + "query": "row nullVar = null | eval mv_min(nullVar)", "error": [], "warning": [] }, { - "query": "row mv_max(5)", + "query": "row var = mv_slice(\"a\", 5, 5)", "error": [], "warning": [] }, { - "query": "row var = mv_max(to_integer(true))", + "query": "row mv_slice(\"a\", 5, 5)", "error": [], "warning": [] }, { - "query": "row var = mv_max(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval var = mv_slice(stringField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "row mv_max(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval mv_slice(stringField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_max(to_ip(to_ip(\"127.0.0.1\")))", + "query": "from a_index | sort mv_slice(stringField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_max(to_string(true))", + "query": "row var = mv_slice(true, 5, 5)", "error": [], "warning": [] }, { - "query": "row var = mv_max(to_version(\"1.0.0\"))", + "query": "row mv_slice(true, 5, 5)", "error": [], "warning": [] }, { - "query": "row mv_max(to_version(\"1.0.0\"))", + "query": "row var = mv_slice(to_boolean(true), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = mv_max(to_version(\"a\"))", + "query": "row var = mv_slice(to_cartesianpoint(\"POINT (30 10)\"), 5, 5)", "error": [], "warning": [] }, { - "query": "row var = mv_max(to_cartesianpoint(\"POINT (30 10)\"))", - "error": [ - "Argument of [mv_max] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" - ], + "query": "row mv_slice(to_cartesianpoint(\"POINT (30 10)\"), 5, 5)", + "error": [], "warning": [] }, { - "query": "from a_index | where mv_max(numberField) > 0", + "query": "row var = mv_slice(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | where mv_max(cartesianPointField) > 0", - "error": [ - "Argument of [mv_max] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "row var = mv_slice(to_cartesianshape(\"POINT (30 10)\"), 5, 5)", + "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_max(stringField)) > 0", + "query": "row mv_slice(to_cartesianshape(\"POINT (30 10)\"), 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_max(cartesianPointField)) > 0", - "error": [ - "Argument of [mv_max] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "row var = mv_slice(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_integer(true), to_integer(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(booleanField)", + "query": "row var = mv_slice(now(), 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_max(booleanField)", + "query": "row mv_slice(now(), 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(to_boolean(booleanField))", + "query": "row var = mv_slice(to_datetime(now()), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_max(cartesianPointField)", - "error": [ - "Argument of [mv_max] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = mv_max(dateField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval mv_max(dateField)", + "query": "row var = mv_slice(5, 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(to_datetime(dateField))", + "query": "row mv_slice(5, 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(numberField)", + "query": "row var = mv_slice(to_integer(true), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_max(numberField)", + "query": "row var = mv_slice(to_geopoint(\"POINT (30 10)\"), 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(to_integer(booleanField))", + "query": "row mv_slice(to_geopoint(\"POINT (30 10)\"), 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(ipField)", + "query": "row var = mv_slice(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_max(ipField)", + "query": "row var = mv_slice(to_geoshape(\"POINT (30 10)\"), 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(to_ip(ipField))", + "query": "row mv_slice(to_geoshape(\"POINT (30 10)\"), 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(to_string(booleanField))", + "query": "row var = mv_slice(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(versionField)", + "query": "row var = mv_slice(to_ip(\"127.0.0.1\"), 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_max(versionField)", + "query": "row mv_slice(to_ip(\"127.0.0.1\"), 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_max(to_version(stringField))", + "query": "row var = mv_slice(to_ip(to_ip(\"127.0.0.1\")), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_max(booleanField, extraArg)", - "error": [ - "Error: [mv_max] function expects exactly one argument, got 2." - ], - "warning": [] - }, - { - "query": "from a_index | sort mv_max(booleanField)", + "query": "row var = mv_slice(to_string(true), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_max(null)", + "query": "row var = mv_slice(to_version(\"1.0.0\"), 5, 5)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_max(nullVar)", + "query": "row mv_slice(to_version(\"1.0.0\"), 5, 5)", "error": [], "warning": [] }, { - "query": "row var = mv_median(5)", + "query": "row var = mv_slice(to_version(\"a\"), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row mv_median(5)", - "error": [], + "query": "row var = mv_slice(to_version(\"1.0.0\"), true, true)", + "error": [ + "Argument of [mv_slice] must be [number], found value [true] type [boolean]", + "Argument of [mv_slice] must be [number], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row var = mv_median(to_integer(\"a\"))", + "query": "from a_index | where mv_slice(numberField, numberField, numberField) > 0", "error": [], "warning": [] }, { - "query": "row var = mv_median(\"a\")", + "query": "from a_index | where mv_slice(numberField, booleanField, booleanField) > 0", "error": [ - "Argument of [mv_median] must be [number], found value [\"a\"] type [string]" + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where mv_median(numberField) > 0", + "query": "from a_index | where length(mv_slice(stringField, numberField, numberField)) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where mv_median(stringField) > 0", + "query": "from a_index | where length(mv_slice(stringField, booleanField, booleanField)) > 0", "error": [ - "Argument of [mv_median] must be [number], found value [stringField] type [string]" + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = mv_median(numberField)", + "query": "from a_index | eval var = mv_slice(booleanField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_median(numberField)", + "query": "from a_index | eval mv_slice(booleanField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_median(to_integer(stringField))", + "query": "from a_index | eval var = mv_slice(to_boolean(booleanField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_median(stringField)", - "error": [ - "Argument of [mv_median] must be [number], found value [stringField] type [string]" - ], - "warning": [] - }, - { - "query": "from a_index | eval mv_median(numberField, extraArg)", + "query": "from a_index | eval mv_slice(booleanField, booleanField, booleanField)", "error": [ - "Error: [mv_median] function expects exactly one argument, got 2." + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = mv_median(*)", - "error": [ - "Using wildcards (*) in mv_median is not allowed" - ], + "query": "from a_index | eval var = mv_slice(cartesianPointField, numberField, numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_median(numberField)", + "query": "from a_index | eval mv_slice(cartesianPointField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_median(to_integer(true))", + "query": "from a_index | eval var = mv_slice(to_cartesianpoint(cartesianPointField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_median(true)", + "query": "from a_index | eval mv_slice(cartesianPointField, booleanField, booleanField)", "error": [ - "Argument of [mv_median] must be [number], found value [true] type [boolean]" + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where mv_median(booleanField) > 0", - "error": [ - "Argument of [mv_median] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_slice(cartesianShapeField, numberField, numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_median(to_integer(booleanField))", + "query": "from a_index | eval mv_slice(cartesianShapeField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_median(booleanField)", + "query": "from a_index | eval var = mv_slice(to_cartesianshape(cartesianPointField), to_integer(booleanField), to_integer(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval mv_slice(cartesianShapeField, booleanField, booleanField)", "error": [ - "Argument of [mv_median] must be [number], found value [booleanField] type [boolean]" + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval mv_median(null)", + "query": "from a_index | eval var = mv_slice(dateField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_median(nullVar)", + "query": "from a_index | eval mv_slice(dateField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_min(\"a\")", + "query": "from a_index | eval var = mv_slice(to_datetime(dateField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row mv_min(\"a\")", + "query": "from a_index | eval mv_slice(dateField, booleanField, booleanField)", + "error": [ + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = mv_slice(numberField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(stringField)", + "query": "from a_index | eval mv_slice(numberField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_min(stringField)", + "query": "from a_index | eval var = mv_slice(to_integer(booleanField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(*)", + "query": "from a_index | eval mv_slice(numberField, booleanField, booleanField)", "error": [ - "Using wildcards (*) in mv_min is not allowed" + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort mv_min(stringField)", + "query": "from a_index | eval var = mv_slice(geoPointField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_min(true)", + "query": "from a_index | eval mv_slice(geoPointField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "row mv_min(true)", + "query": "from a_index | eval var = mv_slice(to_geopoint(geoPointField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_min(to_boolean(true))", - "error": [], + "query": "from a_index | eval mv_slice(geoPointField, booleanField, booleanField)", + "error": [ + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = mv_min(now())", + "query": "from a_index | eval var = mv_slice(geoShapeField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "row mv_min(now())", + "query": "from a_index | eval mv_slice(geoShapeField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_min(to_datetime(now()))", + "query": "from a_index | eval var = mv_slice(to_geoshape(geoPointField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_min(5)", - "error": [], + "query": "from a_index | eval mv_slice(geoShapeField, booleanField, booleanField)", + "error": [ + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row mv_min(5)", + "query": "from a_index | eval var = mv_slice(ipField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_min(to_integer(true))", + "query": "from a_index | eval mv_slice(ipField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_min(to_ip(\"127.0.0.1\"))", + "query": "from a_index | eval var = mv_slice(to_ip(ipField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row mv_min(to_ip(\"127.0.0.1\"))", - "error": [], + "query": "from a_index | eval mv_slice(ipField, booleanField, booleanField)", + "error": [ + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = mv_min(to_ip(to_ip(\"127.0.0.1\")))", + "query": "from a_index | eval var = mv_slice(to_string(booleanField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_min(to_string(true))", - "error": [], + "query": "from a_index | eval mv_slice(stringField, booleanField, booleanField)", + "error": [ + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = mv_min(to_version(\"1.0.0\"))", + "query": "from a_index | eval var = mv_slice(versionField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "row mv_min(to_version(\"1.0.0\"))", + "query": "from a_index | eval mv_slice(versionField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_min(to_version(\"a\"))", + "query": "from a_index | eval var = mv_slice(to_version(stringField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_min(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval mv_slice(versionField, booleanField, booleanField)", "error": [ - "Argument of [mv_min] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", + "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where mv_min(numberField) > 0", - "error": [], - "warning": [] - }, - { - "query": "from a_index | where mv_min(cartesianPointField) > 0", + "query": "from a_index | eval mv_slice(booleanField, numberField, numberField, extraArg)", "error": [ - "Argument of [mv_min] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Error: [mv_slice] function expects no more than 3 arguments, got 4." ], "warning": [] }, { - "query": "from a_index | where length(mv_min(stringField)) > 0", + "query": "from a_index | sort mv_slice(booleanField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_min(cartesianPointField)) > 0", - "error": [ - "Argument of [mv_min] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "from a_index | eval mv_slice(null, null, null)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(booleanField)", + "query": "row nullVar = null | eval mv_slice(nullVar, nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_min(booleanField)", + "query": "row var = mv_sort(\"a\", \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(to_boolean(booleanField))", + "query": "row mv_sort(\"a\", \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_min(cartesianPointField)", - "error": [ - "Argument of [mv_min] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "from a_index | eval var = mv_sort(stringField, \"asc\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(dateField)", + "query": "from a_index | eval mv_sort(stringField, \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_min(dateField)", + "query": "from a_index | sort mv_sort(stringField, \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(to_datetime(dateField))", + "query": "row var = mv_sort(true, \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(numberField)", + "query": "row mv_sort(true, \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_min(numberField)", + "query": "row var = mv_sort(now(), \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(to_integer(booleanField))", + "query": "row mv_sort(now(), \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(ipField)", + "query": "row var = mv_sort(5, \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_min(ipField)", + "query": "row mv_sort(5, \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(to_ip(ipField))", + "query": "row var = mv_sort(to_ip(\"127.0.0.1\"), \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(to_string(booleanField))", + "query": "row mv_sort(to_ip(\"127.0.0.1\"), \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_min(versionField)", + "query": "row var = mv_sort(to_version(\"1.0.0\"), \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_min(versionField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval var = mv_min(to_version(stringField))", + "query": "row mv_sort(to_version(\"1.0.0\"), \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_min(booleanField, extraArg)", + "query": "row var = mv_sort(to_cartesianpoint(\"POINT (30 10)\"), true)", "error": [ - "Error: [mv_min] function expects exactly one argument, got 2." + "Argument of [mv_sort] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]", + "Argument of [mv_sort] must be [string], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort mv_min(booleanField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval mv_min(null)", + "query": "from a_index | where mv_sort(numberField, \"asc\") > 0", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_min(nullVar)", - "error": [], + "query": "from a_index | where mv_sort(cartesianPointField, booleanField) > 0", + "error": [ + "Argument of [mv_sort] must be [boolean], found value [cartesianPointField] type [cartesian_point]", + "Argument of [mv_sort] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = mv_slice(\"a\", 5, 5)", + "query": "from a_index | where length(mv_sort(stringField, \"asc\")) > 0", "error": [], "warning": [] }, { - "query": "row mv_slice(\"a\", 5, 5)", - "error": [], + "query": "from a_index | where length(mv_sort(cartesianPointField, booleanField)) > 0", + "error": [ + "Argument of [mv_sort] must be [boolean], found value [cartesianPointField] type [cartesian_point]", + "Argument of [mv_sort] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(stringField, numberField, numberField)", + "query": "from a_index | eval var = mv_sort(booleanField, \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(stringField, numberField, numberField)", + "query": "from a_index | eval mv_sort(booleanField, \"asc\")", "error": [], "warning": [] }, { - "query": "from a_index | sort mv_slice(stringField, numberField, numberField)", + "query": "from a_index | eval var = mv_sort(dateField, \"asc\")", "error": [], "warning": [] }, { - "query": "row var = mv_slice(true, 5, 5)", + "query": "from a_index | eval mv_sort(dateField, \"asc\")", "error": [], "warning": [] }, { - "query": "row mv_slice(true, 5, 5)", + "query": "from a_index | eval var = mv_sort(numberField, \"asc\")", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_boolean(true), to_integer(true), to_integer(true))", + "query": "from a_index | eval mv_sort(numberField, \"asc\")", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_cartesianpoint(\"POINT (30 10)\"), 5, 5)", + "query": "from a_index | eval var = mv_sort(ipField, \"asc\")", "error": [], "warning": [] }, { - "query": "row mv_slice(to_cartesianpoint(\"POINT (30 10)\"), 5, 5)", + "query": "from a_index | eval mv_sort(ipField, \"asc\")", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_integer(true), to_integer(true))", + "query": "from a_index | eval var = mv_sort(versionField, \"asc\")", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_cartesianshape(\"POINT (30 10)\"), 5, 5)", + "query": "from a_index | eval mv_sort(versionField, \"asc\")", "error": [], "warning": [] }, { - "query": "row mv_slice(to_cartesianshape(\"POINT (30 10)\"), 5, 5)", - "error": [], + "query": "from a_index | eval mv_sort(booleanField, \"asc\", extraArg)", + "error": [ + "Error: [mv_sort] function expects no more than 2 arguments, got 3." + ], "warning": [] }, { - "query": "row var = mv_slice(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_integer(true), to_integer(true))", + "query": "from a_index | sort mv_sort(booleanField, \"asc\")", "error": [], "warning": [] }, { - "query": "row var = mv_slice(now(), 5, 5)", + "query": "from a_index | eval mv_sort(null, null)", "error": [], "warning": [] }, { - "query": "row mv_slice(now(), 5, 5)", + "query": "row nullVar = null | eval mv_sort(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_datetime(now()), to_integer(true), to_integer(true))", + "query": "row var = mv_sum(5)", "error": [], "warning": [] }, { - "query": "row var = mv_slice(5, 5, 5)", + "query": "row mv_sum(5)", "error": [], "warning": [] }, { - "query": "row mv_slice(5, 5, 5)", + "query": "row var = mv_sum(to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_integer(true), to_integer(true), to_integer(true))", - "error": [], + "query": "row var = mv_sum(\"a\")", + "error": [ + "Argument of [mv_sum] must be [number], found value [\"a\"] type [string]" + ], "warning": [] }, { - "query": "row var = mv_slice(to_geopoint(\"POINT (30 10)\"), 5, 5)", + "query": "from a_index | where mv_sum(numberField) > 0", "error": [], "warning": [] }, { - "query": "row mv_slice(to_geopoint(\"POINT (30 10)\"), 5, 5)", - "error": [], + "query": "from a_index | where mv_sum(stringField) > 0", + "error": [ + "Argument of [mv_sum] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "row var = mv_slice(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_integer(true), to_integer(true))", + "query": "from a_index | eval var = mv_sum(numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_geoshape(\"POINT (30 10)\"), 5, 5)", + "query": "from a_index | eval mv_sum(numberField)", "error": [], "warning": [] }, { - "query": "row mv_slice(to_geoshape(\"POINT (30 10)\"), 5, 5)", + "query": "from a_index | eval var = mv_sum(to_integer(stringField))", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_integer(true), to_integer(true))", - "error": [], + "query": "from a_index | eval mv_sum(stringField)", + "error": [ + "Argument of [mv_sum] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "row var = mv_slice(to_ip(\"127.0.0.1\"), 5, 5)", - "error": [], + "query": "from a_index | eval mv_sum(numberField, extraArg)", + "error": [ + "Error: [mv_sum] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "row mv_slice(to_ip(\"127.0.0.1\"), 5, 5)", - "error": [], + "query": "from a_index | eval var = mv_sum(*)", + "error": [ + "Using wildcards (*) in mv_sum is not allowed" + ], "warning": [] }, { - "query": "row var = mv_slice(to_ip(to_ip(\"127.0.0.1\")), to_integer(true), to_integer(true))", + "query": "from a_index | sort mv_sum(numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_string(true), to_integer(true), to_integer(true))", + "query": "row var = mv_sum(to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_version(\"1.0.0\"), 5, 5)", - "error": [], + "query": "row var = mv_sum(true)", + "error": [ + "Argument of [mv_sum] must be [number], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row mv_slice(to_version(\"1.0.0\"), 5, 5)", - "error": [], + "query": "from a_index | where mv_sum(booleanField) > 0", + "error": [ + "Argument of [mv_sum] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = mv_slice(to_version(\"a\"), to_integer(true), to_integer(true))", + "query": "from a_index | eval var = mv_sum(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_slice(to_version(\"1.0.0\"), true, true)", + "query": "from a_index | eval mv_sum(booleanField)", "error": [ - "Argument of [mv_slice] must be [number], found value [true] type [boolean]", - "Argument of [mv_slice] must be [number], found value [true] type [boolean]" + "Argument of [mv_sum] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where mv_slice(numberField, numberField, numberField) > 0", + "query": "from a_index | eval mv_sum(null)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_slice(numberField, booleanField, booleanField) > 0", - "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row nullVar = null | eval mv_sum(nullVar)", + "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_slice(stringField, numberField, numberField)) > 0", + "query": "row var = mv_zip(\"a\", \"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_slice(stringField, booleanField, booleanField)) > 0", - "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = mv_zip(\"a\", \"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(booleanField, numberField, numberField)", + "query": "row mv_zip(\"a\", \"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(booleanField, numberField, numberField)", + "query": "row mv_zip(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_boolean(booleanField), to_integer(booleanField), to_integer(booleanField))", + "query": "row var = mv_zip(to_string(\"a\"), to_string(\"a\"), to_string(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(booleanField, booleanField, booleanField)", + "query": "row var = mv_zip(5, 5, 5)", "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" + "Argument of [mv_zip] must be [string], found value [5] type [number]", + "Argument of [mv_zip] must be [string], found value [5] type [number]", + "Argument of [mv_zip] must be [string], found value [5] type [number]" ], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(cartesianPointField, numberField, numberField)", + "query": "from a_index | where length(mv_zip(stringField, stringField, stringField)) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(cartesianPointField, numberField, numberField)", - "error": [], + "query": "from a_index | where length(mv_zip(numberField, numberField, numberField)) > 0", + "error": [ + "Argument of [mv_zip] must be [string], found value [numberField] type [number]", + "Argument of [mv_zip] must be [string], found value [numberField] type [number]", + "Argument of [mv_zip] must be [string], found value [numberField] type [number]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_cartesianpoint(cartesianPointField), to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | eval var = mv_zip(stringField, stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(cartesianPointField, booleanField, booleanField)", - "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = mv_slice(cartesianShapeField, numberField, numberField)", + "query": "from a_index | eval mv_zip(stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(cartesianShapeField, numberField, numberField)", + "query": "from a_index | eval mv_zip(stringField, stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_cartesianshape(cartesianPointField), to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | eval var = mv_zip(to_string(stringField), to_string(stringField), to_string(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(cartesianShapeField, booleanField, booleanField)", + "query": "from a_index | eval mv_zip(numberField, numberField, numberField)", "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" + "Argument of [mv_zip] must be [string], found value [numberField] type [number]", + "Argument of [mv_zip] must be [string], found value [numberField] type [number]", + "Argument of [mv_zip] must be [string], found value [numberField] type [number]" ], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(dateField, numberField, numberField)", - "error": [], + "query": "from a_index | eval mv_zip(stringField, stringField, stringField, extraArg)", + "error": [ + "Error: [mv_zip] function expects no more than 3 arguments, got 4." + ], "warning": [] }, { - "query": "from a_index | eval mv_slice(dateField, numberField, numberField)", + "query": "from a_index | sort mv_zip(stringField, stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_datetime(dateField), to_integer(booleanField), to_integer(booleanField))", + "query": "row var = mv_zip(to_string(true), to_string(true), to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(dateField, booleanField, booleanField)", + "query": "row var = mv_zip(true, true, true)", "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" + "Argument of [mv_zip] must be [string], found value [true] type [boolean]", + "Argument of [mv_zip] must be [string], found value [true] type [boolean]", + "Argument of [mv_zip] must be [string], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(numberField, numberField, numberField)", - "error": [], + "query": "from a_index | where length(mv_zip(booleanField, booleanField, booleanField)) > 0", + "error": [ + "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]", + "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]", + "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval mv_slice(numberField, numberField, numberField)", + "query": "from a_index | eval var = mv_zip(to_string(booleanField), to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_integer(booleanField), to_integer(booleanField), to_integer(booleanField))", - "error": [], + "query": "from a_index | eval mv_zip(booleanField, booleanField, booleanField)", + "error": [ + "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]", + "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]", + "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval mv_slice(numberField, booleanField, booleanField)", - "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval mv_zip(null, null, null)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(geoPointField, numberField, numberField)", + "query": "row nullVar = null | eval mv_zip(nullVar, nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(geoPointField, numberField, numberField)", + "query": "row var = mv_zip(to_string(true), to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_geopoint(geoPointField), to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | where length(mv_zip(stringField, stringField)) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(geoPointField, booleanField, booleanField)", + "query": "from a_index | where length(mv_zip(booleanField, booleanField)) > 0", "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" + "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]", + "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(geoShapeField, numberField, numberField)", + "query": "from a_index | eval var = mv_zip(stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(geoShapeField, numberField, numberField)", + "query": "from a_index | eval var = mv_zip(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_geoshape(geoPointField), to_integer(booleanField), to_integer(booleanField))", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval mv_slice(geoShapeField, booleanField, booleanField)", + "query": "from a_index | eval mv_zip(booleanField, booleanField)", "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" + "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]", + "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(ipField, numberField, numberField)", + "query": "from a_index | sort mv_zip(stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(ipField, numberField, numberField)", + "query": "row var = now()", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_ip(ipField), to_integer(booleanField), to_integer(booleanField))", + "query": "row now()", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(ipField, booleanField, booleanField)", - "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = now()", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_string(booleanField), to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | eval now()", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(stringField, booleanField, booleanField)", + "query": "from a_index | eval now(extraArg)", "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" + "Error: [now] function expects exactly 0 arguments, got 1." ], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(versionField, numberField, numberField)", + "query": "from a_index | sort now()", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(versionField, numberField, numberField)", + "query": "row nullVar = null | eval now()", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_slice(to_version(stringField), to_integer(booleanField), to_integer(booleanField))", + "query": "row var = pi()", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(versionField, booleanField, booleanField)", - "error": [ - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]", - "Argument of [mv_slice] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row pi()", + "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(booleanField, numberField, numberField, extraArg)", - "error": [ - "Error: [mv_slice] function expects no more than 3 arguments, got 4." - ], + "query": "from a_index | where pi() > 0", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_slice(booleanField, numberField, numberField)", + "query": "from a_index | eval var = pi()", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_slice(null, null, null)", + "query": "from a_index | eval pi()", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_slice(nullVar, nullVar, nullVar)", - "error": [], + "query": "from a_index | eval pi(extraArg)", + "error": [ + "Error: [pi] function expects exactly 0 arguments, got 1." + ], "warning": [] }, { - "query": "row var = mv_sort(\"a\", \"asc\")", + "query": "from a_index | sort pi()", "error": [], "warning": [] }, { - "query": "row mv_sort(\"a\", \"asc\")", + "query": "row nullVar = null | eval pi()", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_sort(stringField, \"asc\")", + "query": "row var = pow(5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sort(stringField, \"asc\")", + "query": "row pow(5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | sort mv_sort(stringField, \"asc\")", + "query": "row var = pow(to_integer(\"a\"), to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = mv_sort(true, \"asc\")", - "error": [], + "query": "row var = pow(\"a\", \"a\")", + "error": [ + "Argument of [pow] must be [number], found value [\"a\"] type [string]", + "Argument of [pow] must be [number], found value [\"a\"] type [string]" + ], "warning": [] }, { - "query": "row mv_sort(true, \"asc\")", + "query": "from a_index | where pow(numberField, numberField) > 0", "error": [], "warning": [] }, { - "query": "row var = mv_sort(now(), \"asc\")", - "error": [], + "query": "from a_index | where pow(stringField, stringField) > 0", + "error": [ + "Argument of [pow] must be [number], found value [stringField] type [string]", + "Argument of [pow] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "row mv_sort(now(), \"asc\")", + "query": "from a_index | eval var = pow(numberField, numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_sort(5, \"asc\")", + "query": "from a_index | eval pow(numberField, numberField)", "error": [], "warning": [] }, { - "query": "row mv_sort(5, \"asc\")", + "query": "from a_index | eval var = pow(to_integer(stringField), to_integer(stringField))", "error": [], "warning": [] }, { - "query": "row var = mv_sort(to_ip(\"127.0.0.1\"), \"asc\")", - "error": [], + "query": "from a_index | eval pow(stringField, stringField)", + "error": [ + "Argument of [pow] must be [number], found value [stringField] type [string]", + "Argument of [pow] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "row mv_sort(to_ip(\"127.0.0.1\"), \"asc\")", - "error": [], + "query": "from a_index | eval pow(numberField, numberField, extraArg)", + "error": [ + "Error: [pow] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "row var = mv_sort(to_version(\"1.0.0\"), \"asc\")", + "query": "from a_index | sort pow(numberField, numberField)", "error": [], "warning": [] }, { - "query": "row mv_sort(to_version(\"1.0.0\"), \"asc\")", + "query": "row var = pow(to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = mv_sort(to_cartesianpoint(\"POINT (30 10)\"), true)", + "query": "row var = pow(true, true)", "error": [ - "Argument of [mv_sort] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]", - "Argument of [mv_sort] must be [string], found value [true] type [boolean]" + "Argument of [pow] must be [number], found value [true] type [boolean]", + "Argument of [pow] must be [number], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where mv_sort(numberField, \"asc\") > 0", - "error": [], - "warning": [] - }, - { - "query": "from a_index | where mv_sort(cartesianPointField, booleanField) > 0", + "query": "from a_index | where pow(booleanField, booleanField) > 0", "error": [ - "Argument of [mv_sort] must be [boolean], found value [cartesianPointField] type [cartesian_point]", - "Argument of [mv_sort] must be [string], found value [booleanField] type [boolean]" + "Argument of [pow] must be [number], found value [booleanField] type [boolean]", + "Argument of [pow] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where length(mv_sort(stringField, \"asc\")) > 0", + "query": "from a_index | eval var = pow(to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_sort(cartesianPointField, booleanField)) > 0", + "query": "from a_index | eval pow(booleanField, booleanField)", "error": [ - "Argument of [mv_sort] must be [boolean], found value [cartesianPointField] type [cartesian_point]", - "Argument of [mv_sort] must be [string], found value [booleanField] type [boolean]" + "Argument of [pow] must be [number], found value [booleanField] type [boolean]", + "Argument of [pow] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = mv_sort(booleanField, \"asc\")", + "query": "from a_index | eval pow(null, null)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sort(booleanField, \"asc\")", + "query": "row nullVar = null | eval pow(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_sort(dateField, \"asc\")", + "query": "row var = replace(\"a\", \"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sort(dateField, \"asc\")", + "query": "row replace(\"a\", \"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_sort(numberField, \"asc\")", + "query": "row var = replace(to_string(\"a\"), to_string(\"a\"), to_string(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sort(numberField, \"asc\")", - "error": [], + "query": "row var = replace(5, 5, 5)", + "error": [ + "Argument of [replace] must be [string], found value [5] type [number]", + "Argument of [replace] must be [string], found value [5] type [number]", + "Argument of [replace] must be [string], found value [5] type [number]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_sort(ipField, \"asc\")", + "query": "from a_index | where length(replace(stringField, stringField, stringField)) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sort(ipField, \"asc\")", + "query": "from a_index | where length(replace(numberField, numberField, numberField)) > 0", + "error": [ + "Argument of [replace] must be [string], found value [numberField] type [number]", + "Argument of [replace] must be [string], found value [numberField] type [number]", + "Argument of [replace] must be [string], found value [numberField] type [number]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = replace(stringField, stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_sort(versionField, \"asc\")", + "query": "from a_index | eval replace(stringField, stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sort(versionField, \"asc\")", + "query": "from a_index | eval var = replace(to_string(stringField), to_string(stringField), to_string(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sort(booleanField, \"asc\", extraArg)", + "query": "from a_index | eval replace(numberField, numberField, numberField)", "error": [ - "Error: [mv_sort] function expects no more than 2 arguments, got 3." + "Argument of [replace] must be [string], found value [numberField] type [number]", + "Argument of [replace] must be [string], found value [numberField] type [number]", + "Argument of [replace] must be [string], found value [numberField] type [number]" ], "warning": [] }, { - "query": "from a_index | sort mv_sort(booleanField, \"asc\")", - "error": [], + "query": "from a_index | eval replace(stringField, stringField, stringField, extraArg)", + "error": [ + "Error: [replace] function expects exactly 3 arguments, got 4." + ], "warning": [] }, { - "query": "from a_index | eval mv_sort(null, null)", + "query": "from a_index | sort replace(stringField, stringField, stringField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval mv_sort(nullVar, nullVar)", + "query": "row var = replace(to_string(true), to_string(true), to_string(true))", "error": [], "warning": [] }, { - "query": "row var = mv_sum(5)", - "error": [], + "query": "row var = replace(true, true, true)", + "error": [ + "Argument of [replace] must be [string], found value [true] type [boolean]", + "Argument of [replace] must be [string], found value [true] type [boolean]", + "Argument of [replace] must be [string], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row mv_sum(5)", - "error": [], + "query": "from a_index | where length(replace(booleanField, booleanField, booleanField)) > 0", + "error": [ + "Argument of [replace] must be [string], found value [booleanField] type [boolean]", + "Argument of [replace] must be [string], found value [booleanField] type [boolean]", + "Argument of [replace] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = mv_sum(to_integer(\"a\"))", + "query": "from a_index | eval var = replace(to_string(booleanField), to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_sum(\"a\")", + "query": "from a_index | eval replace(booleanField, booleanField, booleanField)", "error": [ - "Argument of [mv_sum] must be [number], found value [\"a\"] type [string]" + "Argument of [replace] must be [string], found value [booleanField] type [boolean]", + "Argument of [replace] must be [string], found value [booleanField] type [boolean]", + "Argument of [replace] must be [string], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where mv_sum(numberField) > 0", + "query": "from a_index | eval replace(null, null, null)", "error": [], "warning": [] }, { - "query": "from a_index | where mv_sum(stringField) > 0", - "error": [ - "Argument of [mv_sum] must be [number], found value [stringField] type [string]" - ], + "query": "row nullVar = null | eval replace(nullVar, nullVar, nullVar)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_sum(numberField)", + "query": "row var = right(\"a\", 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sum(numberField)", + "query": "row right(\"a\", 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_sum(to_integer(stringField))", + "query": "row var = right(to_string(\"a\"), to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_sum(stringField)", + "query": "row var = right(5, \"a\")", "error": [ - "Argument of [mv_sum] must be [number], found value [stringField] type [string]" + "Argument of [right] must be [string], found value [5] type [number]", + "Argument of [right] must be [number], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | eval mv_sum(numberField, extraArg)", - "error": [ - "Error: [mv_sum] function expects exactly one argument, got 2." - ], + "query": "from a_index | where length(right(stringField, numberField)) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_sum(*)", + "query": "from a_index | where length(right(numberField, stringField)) > 0", "error": [ - "Using wildcards (*) in mv_sum is not allowed" + "Argument of [right] must be [string], found value [numberField] type [number]", + "Argument of [right] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | sort mv_sum(numberField)", + "query": "from a_index | eval var = right(stringField, numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_sum(to_integer(true))", + "query": "from a_index | eval right(stringField, numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_sum(true)", - "error": [ - "Argument of [mv_sum] must be [number], found value [true] type [boolean]" - ], + "query": "from a_index | eval var = right(to_string(stringField), to_integer(stringField))", + "error": [], "warning": [] }, { - "query": "from a_index | where mv_sum(booleanField) > 0", + "query": "from a_index | eval right(numberField, stringField)", "error": [ - "Argument of [mv_sum] must be [number], found value [booleanField] type [boolean]" + "Argument of [right] must be [string], found value [numberField] type [number]", + "Argument of [right] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = mv_sum(to_integer(booleanField))", - "error": [], + "query": "from a_index | eval right(stringField, numberField, extraArg)", + "error": [ + "Error: [right] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "from a_index | eval mv_sum(booleanField)", + "query": "from a_index | sort right(stringField, numberField)", + "error": [], + "warning": [] + }, + { + "query": "row var = right(to_string(true), to_integer(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = right(true, true)", "error": [ - "Argument of [mv_sum] must be [number], found value [booleanField] type [boolean]" + "Argument of [right] must be [string], found value [true] type [boolean]", + "Argument of [right] must be [number], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval mv_sum(null)", - "error": [], + "query": "from a_index | where length(right(booleanField, booleanField)) > 0", + "error": [ + "Argument of [right] must be [string], found value [booleanField] type [boolean]", + "Argument of [right] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row nullVar = null | eval mv_sum(nullVar)", + "query": "from a_index | eval var = right(to_string(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_zip(\"a\", \"a\", \"a\")", + "query": "from a_index | eval right(booleanField, booleanField)", + "error": [ + "Argument of [right] must be [string], found value [booleanField] type [boolean]", + "Argument of [right] must be [number], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval right(null, null)", "error": [], "warning": [] }, { - "query": "row var = mv_zip(\"a\", \"a\")", + "query": "row nullVar = null | eval right(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row mv_zip(\"a\", \"a\", \"a\")", + "query": "row var = round(5, 5)", "error": [], "warning": [] }, { - "query": "row mv_zip(\"a\", \"a\")", + "query": "row round(5, 5)", "error": [], "warning": [] }, { - "query": "row var = mv_zip(to_string(\"a\"), to_string(\"a\"), to_string(\"a\"))", + "query": "row var = round(to_integer(\"a\"), to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = mv_zip(5, 5, 5)", + "query": "row var = round(\"a\", \"a\")", "error": [ - "Argument of [mv_zip] must be [string], found value [5] type [number]", - "Argument of [mv_zip] must be [string], found value [5] type [number]", - "Argument of [mv_zip] must be [string], found value [5] type [number]" + "Argument of [round] must be [number], found value [\"a\"] type [string]", + "Argument of [round] must be [number], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | where length(mv_zip(stringField, stringField, stringField)) > 0", + "query": "from a_index | where round(numberField, numberField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_zip(numberField, numberField, numberField)) > 0", + "query": "from a_index | where round(stringField, stringField) > 0", "error": [ - "Argument of [mv_zip] must be [string], found value [numberField] type [number]", - "Argument of [mv_zip] must be [string], found value [numberField] type [number]", - "Argument of [mv_zip] must be [string], found value [numberField] type [number]" + "Argument of [round] must be [number], found value [stringField] type [string]", + "Argument of [round] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = mv_zip(stringField, stringField, stringField)", + "query": "from a_index | eval var = round(numberField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_zip(stringField, stringField)", + "query": "from a_index | eval round(numberField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_zip(stringField, stringField, stringField)", + "query": "from a_index | eval var = round(to_integer(stringField), to_integer(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_zip(to_string(stringField), to_string(stringField), to_string(stringField))", - "error": [], + "query": "from a_index | eval round(stringField, stringField)", + "error": [ + "Argument of [round] must be [number], found value [stringField] type [string]", + "Argument of [round] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "from a_index | eval mv_zip(numberField, numberField, numberField)", + "query": "from a_index | eval round(numberField, numberField, extraArg)", "error": [ - "Argument of [mv_zip] must be [string], found value [numberField] type [number]", - "Argument of [mv_zip] must be [string], found value [numberField] type [number]", - "Argument of [mv_zip] must be [string], found value [numberField] type [number]" + "Error: [round] function expects no more than 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | eval mv_zip(stringField, stringField, stringField, extraArg)", - "error": [ - "Error: [mv_zip] function expects no more than 3 arguments, got 4." - ], + "query": "from a_index | sort round(numberField, numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort mv_zip(stringField, stringField, stringField)", + "query": "row var = round(5)", "error": [], "warning": [] }, { - "query": "row var = mv_zip(to_string(true), to_string(true), to_string(true))", + "query": "row round(5)", "error": [], "warning": [] }, { - "query": "row var = mv_zip(true, true, true)", - "error": [ - "Argument of [mv_zip] must be [string], found value [true] type [boolean]", - "Argument of [mv_zip] must be [string], found value [true] type [boolean]", - "Argument of [mv_zip] must be [string], found value [true] type [boolean]" - ], + "query": "row var = round(to_integer(true))", + "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_zip(booleanField, booleanField, booleanField)) > 0", + "query": "row var = round(to_integer(true), to_integer(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = round(true, true)", "error": [ - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]", - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]", - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]" + "Argument of [round] must be [number], found value [true] type [boolean]", + "Argument of [round] must be [number], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = mv_zip(to_string(booleanField), to_string(booleanField), to_string(booleanField))", + "query": "from a_index | where round(numberField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_zip(booleanField, booleanField, booleanField)", + "query": "from a_index | where round(booleanField) > 0", "error": [ - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]", - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]", - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]" + "Argument of [round] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval mv_zip(null, null, null)", - "error": [], + "query": "from a_index | where round(booleanField, booleanField) > 0", + "error": [ + "Argument of [round] must be [number], found value [booleanField] type [boolean]", + "Argument of [round] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row nullVar = null | eval mv_zip(nullVar, nullVar, nullVar)", + "query": "from a_index | eval var = round(numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_zip(to_string(true), to_string(true))", + "query": "from a_index | eval round(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_zip(stringField, stringField)) > 0", + "query": "from a_index | eval var = round(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_zip(booleanField, booleanField)) > 0", + "query": "from a_index | eval round(booleanField)", "error": [ - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]", - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]" + "Argument of [round] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = mv_zip(stringField, stringField)", - "error": [], + "query": "from a_index | eval var = round(*)", + "error": [ + "Using wildcards (*) in round is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_zip(to_string(booleanField), to_string(booleanField))", + "query": "from a_index | eval var = round(to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_zip(booleanField, booleanField)", + "query": "from a_index | eval round(booleanField, booleanField)", "error": [ - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]", - "Argument of [mv_zip] must be [string], found value [booleanField] type [boolean]" + "Argument of [round] must be [number], found value [booleanField] type [boolean]", + "Argument of [round] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort mv_zip(stringField, stringField)", + "query": "from a_index | sort round(numberField)", "error": [], "warning": [] }, { - "query": "row var = now()", + "query": "from a_index | eval round(null, null)", "error": [], "warning": [] }, { - "query": "row now()", + "query": "row nullVar = null | eval round(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = now()", + "query": "row var = rtrim(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval now()", + "query": "row rtrim(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval now(extraArg)", + "query": "row var = rtrim(to_string(\"a\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = rtrim(5)", "error": [ - "Error: [now] function expects exactly 0 arguments, got 1." + "Argument of [rtrim] must be [string], found value [5] type [number]" ], "warning": [] }, { - "query": "from a_index | sort now()", + "query": "from a_index | where length(rtrim(stringField)) > 0", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval now()", - "error": [], + "query": "from a_index | where length(rtrim(numberField)) > 0", + "error": [ + "Argument of [rtrim] must be [string], found value [numberField] type [number]" + ], "warning": [] }, { - "query": "row var = pi()", + "query": "from a_index | eval var = rtrim(stringField)", "error": [], "warning": [] }, { - "query": "row pi()", + "query": "from a_index | eval rtrim(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | where pi() > 0", + "query": "from a_index | eval var = rtrim(to_string(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = pi()", - "error": [], + "query": "from a_index | eval rtrim(numberField)", + "error": [ + "Argument of [rtrim] must be [string], found value [numberField] type [number]" + ], "warning": [] }, { - "query": "from a_index | eval pi()", - "error": [], + "query": "from a_index | eval rtrim(stringField, extraArg)", + "error": [ + "Error: [rtrim] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | eval pi(extraArg)", + "query": "from a_index | eval var = rtrim(*)", "error": [ - "Error: [pi] function expects exactly 0 arguments, got 1." + "Using wildcards (*) in rtrim is not allowed" ], "warning": [] }, { - "query": "from a_index | sort pi()", + "query": "from a_index | sort rtrim(stringField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval pi()", + "query": "row var = rtrim(to_string(true))", "error": [], "warning": [] }, { - "query": "row var = pow(5, 5)", - "error": [], + "query": "row var = rtrim(true)", + "error": [ + "Argument of [rtrim] must be [string], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row pow(5, 5)", - "error": [], + "query": "from a_index | where length(rtrim(booleanField)) > 0", + "error": [ + "Argument of [rtrim] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = pow(to_integer(\"a\"), to_integer(\"a\"))", + "query": "from a_index | eval var = rtrim(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = pow(\"a\", \"a\")", + "query": "from a_index | eval rtrim(booleanField)", "error": [ - "Argument of [pow] must be [number], found value [\"a\"] type [string]", - "Argument of [pow] must be [number], found value [\"a\"] type [string]" + "Argument of [rtrim] must be [string], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where pow(numberField, numberField) > 0", + "query": "from a_index | eval rtrim(null)", "error": [], "warning": [] }, { - "query": "from a_index | where pow(stringField, stringField) > 0", - "error": [ - "Argument of [pow] must be [number], found value [stringField] type [string]", - "Argument of [pow] must be [number], found value [stringField] type [string]" - ], + "query": "row nullVar = null | eval rtrim(nullVar)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = pow(numberField, numberField)", + "query": "row var = signum(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval pow(numberField, numberField)", + "query": "row signum(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = pow(to_integer(stringField), to_integer(stringField))", + "query": "row var = signum(to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval pow(stringField, stringField)", + "query": "row var = signum(\"a\")", "error": [ - "Argument of [pow] must be [number], found value [stringField] type [string]", - "Argument of [pow] must be [number], found value [stringField] type [string]" + "Argument of [signum] must be [number], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | eval pow(numberField, numberField, extraArg)", + "query": "from a_index | where signum(numberField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where signum(stringField) > 0", "error": [ - "Error: [pow] function expects exactly 2 arguments, got 3." + "Argument of [signum] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | sort pow(numberField, numberField)", + "query": "from a_index | eval var = signum(numberField)", "error": [], "warning": [] }, { - "query": "row var = pow(to_integer(true), to_integer(true))", + "query": "from a_index | eval signum(numberField)", "error": [], "warning": [] }, { - "query": "row var = pow(true, true)", + "query": "from a_index | eval var = signum(to_integer(stringField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval signum(stringField)", "error": [ - "Argument of [pow] must be [number], found value [true] type [boolean]", - "Argument of [pow] must be [number], found value [true] type [boolean]" + "Argument of [signum] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | where pow(booleanField, booleanField) > 0", + "query": "from a_index | eval signum(numberField, extraArg)", "error": [ - "Argument of [pow] must be [number], found value [booleanField] type [boolean]", - "Argument of [pow] must be [number], found value [booleanField] type [boolean]" + "Error: [signum] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | eval var = pow(to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | eval var = signum(*)", + "error": [ + "Using wildcards (*) in signum is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | sort signum(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval pow(booleanField, booleanField)", + "query": "row var = signum(to_integer(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = signum(true)", "error": [ - "Argument of [pow] must be [number], found value [booleanField] type [boolean]", - "Argument of [pow] must be [number], found value [booleanField] type [boolean]" + "Argument of [signum] must be [number], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval pow(null, null)", + "query": "from a_index | where signum(booleanField) > 0", + "error": [ + "Argument of [signum] must be [number], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = signum(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval pow(nullVar, nullVar)", + "query": "from a_index | eval signum(booleanField)", + "error": [ + "Argument of [signum] must be [number], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval signum(null)", "error": [], "warning": [] }, { - "query": "row var = replace(\"a\", \"a\", \"a\")", + "query": "row nullVar = null | eval signum(nullVar)", "error": [], "warning": [] }, { - "query": "row replace(\"a\", \"a\", \"a\")", + "query": "row var = sin(5)", "error": [], "warning": [] }, { - "query": "row var = replace(to_string(\"a\"), to_string(\"a\"), to_string(\"a\"))", + "query": "row sin(5)", "error": [], "warning": [] }, { - "query": "row var = replace(5, 5, 5)", + "query": "row var = sin(to_integer(\"a\"))", + "error": [], + "warning": [] + }, + { + "query": "row var = sin(\"a\")", "error": [ - "Argument of [replace] must be [string], found value [5] type [number]", - "Argument of [replace] must be [string], found value [5] type [number]", - "Argument of [replace] must be [string], found value [5] type [number]" + "Argument of [sin] must be [number], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | where length(replace(stringField, stringField, stringField)) > 0", + "query": "from a_index | where sin(numberField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where length(replace(numberField, numberField, numberField)) > 0", + "query": "from a_index | where sin(stringField) > 0", "error": [ - "Argument of [replace] must be [string], found value [numberField] type [number]", - "Argument of [replace] must be [string], found value [numberField] type [number]", - "Argument of [replace] must be [string], found value [numberField] type [number]" + "Argument of [sin] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = replace(stringField, stringField, stringField)", + "query": "from a_index | eval var = sin(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval replace(stringField, stringField, stringField)", + "query": "from a_index | eval sin(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = replace(to_string(stringField), to_string(stringField), to_string(stringField))", + "query": "from a_index | eval var = sin(to_integer(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval replace(numberField, numberField, numberField)", + "query": "from a_index | eval sin(stringField)", "error": [ - "Argument of [replace] must be [string], found value [numberField] type [number]", - "Argument of [replace] must be [string], found value [numberField] type [number]", - "Argument of [replace] must be [string], found value [numberField] type [number]" + "Argument of [sin] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval replace(stringField, stringField, stringField, extraArg)", + "query": "from a_index | eval sin(numberField, extraArg)", "error": [ - "Error: [replace] function expects exactly 3 arguments, got 4." + "Error: [sin] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | sort replace(stringField, stringField, stringField)", + "query": "from a_index | eval var = sin(*)", + "error": [ + "Using wildcards (*) in sin is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | sort sin(numberField)", "error": [], "warning": [] }, { - "query": "row var = replace(to_string(true), to_string(true), to_string(true))", + "query": "row var = sin(to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = replace(true, true, true)", + "query": "row var = sin(true)", "error": [ - "Argument of [replace] must be [string], found value [true] type [boolean]", - "Argument of [replace] must be [string], found value [true] type [boolean]", - "Argument of [replace] must be [string], found value [true] type [boolean]" + "Argument of [sin] must be [number], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where length(replace(booleanField, booleanField, booleanField)) > 0", + "query": "from a_index | where sin(booleanField) > 0", "error": [ - "Argument of [replace] must be [string], found value [booleanField] type [boolean]", - "Argument of [replace] must be [string], found value [booleanField] type [boolean]", - "Argument of [replace] must be [string], found value [booleanField] type [boolean]" + "Argument of [sin] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = replace(to_string(booleanField), to_string(booleanField), to_string(booleanField))", + "query": "from a_index | eval var = sin(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval replace(booleanField, booleanField, booleanField)", + "query": "from a_index | eval sin(booleanField)", "error": [ - "Argument of [replace] must be [string], found value [booleanField] type [boolean]", - "Argument of [replace] must be [string], found value [booleanField] type [boolean]", - "Argument of [replace] must be [string], found value [booleanField] type [boolean]" + "Argument of [sin] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval replace(null, null, null)", + "query": "from a_index | eval sin(null)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval replace(nullVar, nullVar, nullVar)", + "query": "row nullVar = null | eval sin(nullVar)", "error": [], "warning": [] }, { - "query": "row var = right(\"a\", 5)", + "query": "row var = sinh(5)", "error": [], "warning": [] }, { - "query": "row right(\"a\", 5)", + "query": "row sinh(5)", "error": [], "warning": [] }, { - "query": "row var = right(to_string(\"a\"), to_integer(\"a\"))", + "query": "row var = sinh(to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = right(5, \"a\")", + "query": "row var = sinh(\"a\")", "error": [ - "Argument of [right] must be [string], found value [5] type [number]", - "Argument of [right] must be [number], found value [\"a\"] type [string]" + "Argument of [sinh] must be [number], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | where length(right(stringField, numberField)) > 0", + "query": "from a_index | where sinh(numberField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where length(right(numberField, stringField)) > 0", + "query": "from a_index | where sinh(stringField) > 0", "error": [ - "Argument of [right] must be [string], found value [numberField] type [number]", - "Argument of [right] must be [number], found value [stringField] type [string]" + "Argument of [sinh] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = right(stringField, numberField)", + "query": "from a_index | eval var = sinh(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval right(stringField, numberField)", + "query": "from a_index | eval sinh(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = right(to_string(stringField), to_integer(stringField))", + "query": "from a_index | eval var = sinh(to_integer(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval right(numberField, stringField)", + "query": "from a_index | eval sinh(stringField)", "error": [ - "Argument of [right] must be [string], found value [numberField] type [number]", - "Argument of [right] must be [number], found value [stringField] type [string]" + "Argument of [sinh] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval right(stringField, numberField, extraArg)", + "query": "from a_index | eval sinh(numberField, extraArg)", "error": [ - "Error: [right] function expects exactly 2 arguments, got 3." + "Error: [sinh] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | sort right(stringField, numberField)", + "query": "from a_index | eval var = sinh(*)", + "error": [ + "Using wildcards (*) in sinh is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | sort sinh(numberField)", "error": [], "warning": [] }, { - "query": "row var = right(to_string(true), to_integer(true))", + "query": "row var = sinh(to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = right(true, true)", + "query": "row var = sinh(true)", "error": [ - "Argument of [right] must be [string], found value [true] type [boolean]", - "Argument of [right] must be [number], found value [true] type [boolean]" + "Argument of [sinh] must be [number], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where length(right(booleanField, booleanField)) > 0", + "query": "from a_index | where sinh(booleanField) > 0", "error": [ - "Argument of [right] must be [string], found value [booleanField] type [boolean]", - "Argument of [right] must be [number], found value [booleanField] type [boolean]" + "Argument of [sinh] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = right(to_string(booleanField), to_integer(booleanField))", + "query": "from a_index | eval var = sinh(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval right(booleanField, booleanField)", + "query": "from a_index | eval sinh(booleanField)", "error": [ - "Argument of [right] must be [string], found value [booleanField] type [boolean]", - "Argument of [right] must be [number], found value [booleanField] type [boolean]" + "Argument of [sinh] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval right(null, null)", + "query": "from a_index | eval sinh(null)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval right(nullVar, nullVar)", + "query": "row nullVar = null | eval sinh(nullVar)", "error": [], "warning": [] }, { - "query": "row var = round(5, 5)", + "query": "row var = split(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "row round(5, 5)", + "query": "row split(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "row var = round(to_integer(\"a\"), to_integer(\"a\"))", + "query": "row var = split(to_string(\"a\"), to_string(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = round(\"a\", \"a\")", + "query": "row var = split(5, 5)", "error": [ - "Argument of [round] must be [number], found value [\"a\"] type [string]", - "Argument of [round] must be [number], found value [\"a\"] type [string]" + "Argument of [split] must be [string], found value [5] type [number]", + "Argument of [split] must be [string], found value [5] type [number]" ], "warning": [] }, { - "query": "from a_index | where round(numberField, numberField) > 0", + "query": "from a_index | where length(split(stringField, stringField)) > 0", "error": [], "warning": [] }, { - "query": "from a_index | where round(stringField, stringField) > 0", + "query": "from a_index | where length(split(numberField, numberField)) > 0", "error": [ - "Argument of [round] must be [number], found value [stringField] type [string]", - "Argument of [round] must be [number], found value [stringField] type [string]" + "Argument of [split] must be [string], found value [numberField] type [number]", + "Argument of [split] must be [string], found value [numberField] type [number]" ], "warning": [] }, { - "query": "from a_index | eval var = round(numberField, numberField)", + "query": "from a_index | eval var = split(stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval round(numberField, numberField)", + "query": "from a_index | eval split(stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = round(to_integer(stringField), to_integer(stringField))", + "query": "from a_index | eval var = split(to_string(stringField), to_string(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval round(stringField, stringField)", + "query": "from a_index | eval split(numberField, numberField)", "error": [ - "Argument of [round] must be [number], found value [stringField] type [string]", - "Argument of [round] must be [number], found value [stringField] type [string]" + "Argument of [split] must be [string], found value [numberField] type [number]", + "Argument of [split] must be [string], found value [numberField] type [number]" ], "warning": [] }, { - "query": "from a_index | eval round(numberField, numberField, extraArg)", + "query": "from a_index | eval split(stringField, stringField, extraArg)", "error": [ - "Error: [round] function expects no more than 2 arguments, got 3." + "Error: [split] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | sort round(numberField, numberField)", + "query": "from a_index | sort split(stringField, stringField)", "error": [], "warning": [] }, { - "query": "row var = round(5)", + "query": "row var = split(to_string(true), to_string(true))", "error": [], "warning": [] }, { - "query": "row round(5)", - "error": [], + "query": "row var = split(true, true)", + "error": [ + "Argument of [split] must be [string], found value [true] type [boolean]", + "Argument of [split] must be [string], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row var = round(to_integer(true))", - "error": [], + "query": "from a_index | where length(split(booleanField, booleanField)) > 0", + "error": [ + "Argument of [split] must be [string], found value [booleanField] type [boolean]", + "Argument of [split] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = round(to_integer(true), to_integer(true))", + "query": "from a_index | eval var = split(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = round(true, true)", + "query": "from a_index | eval split(booleanField, booleanField)", "error": [ - "Argument of [round] must be [number], found value [true] type [boolean]", - "Argument of [round] must be [number], found value [true] type [boolean]" + "Argument of [split] must be [string], found value [booleanField] type [boolean]", + "Argument of [split] must be [string], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where round(numberField) > 0", + "query": "from a_index | eval split(null, null)", "error": [], "warning": [] }, { - "query": "from a_index | where round(booleanField) > 0", - "error": [ - "Argument of [round] must be [number], found value [booleanField] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | where round(booleanField, booleanField) > 0", - "error": [ - "Argument of [round] must be [number], found value [booleanField] type [boolean]", - "Argument of [round] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row nullVar = null | eval split(nullVar, nullVar)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = round(numberField)", + "query": "row var = sqrt(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval round(numberField)", + "query": "row sqrt(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = round(to_integer(booleanField))", + "query": "row var = sqrt(to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval round(booleanField)", + "query": "row var = sqrt(\"a\")", "error": [ - "Argument of [round] must be [number], found value [booleanField] type [boolean]" + "Argument of [sqrt] must be [number], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = round(*)", + "query": "from a_index | where sqrt(numberField) > 0", + "error": [], + "warning": [] + }, + { + "query": "from a_index | where sqrt(stringField) > 0", "error": [ - "Using wildcards (*) in round is not allowed" + "Argument of [sqrt] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = round(to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | eval var = sqrt(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval round(booleanField, booleanField)", - "error": [ - "Argument of [round] must be [number], found value [booleanField] type [boolean]", - "Argument of [round] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval sqrt(numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort round(numberField)", + "query": "from a_index | eval var = sqrt(to_integer(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval round(null, null)", - "error": [], + "query": "from a_index | eval sqrt(stringField)", + "error": [ + "Argument of [sqrt] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "row nullVar = null | eval round(nullVar, nullVar)", - "error": [], + "query": "from a_index | eval sqrt(numberField, extraArg)", + "error": [ + "Error: [sqrt] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "row var = rtrim(\"a\")", - "error": [], + "query": "from a_index | eval var = sqrt(*)", + "error": [ + "Using wildcards (*) in sqrt is not allowed" + ], "warning": [] }, { - "query": "row rtrim(\"a\")", + "query": "from a_index | sort sqrt(numberField)", "error": [], "warning": [] }, { - "query": "row var = rtrim(to_string(\"a\"))", + "query": "row var = sqrt(to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = rtrim(5)", + "query": "row var = sqrt(true)", "error": [ - "Argument of [rtrim] must be [string], found value [5] type [number]" + "Argument of [sqrt] must be [number], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where length(rtrim(stringField)) > 0", + "query": "from a_index | where sqrt(booleanField) > 0", + "error": [ + "Argument of [sqrt] must be [number], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = sqrt(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | where length(rtrim(numberField)) > 0", + "query": "from a_index | eval sqrt(booleanField)", "error": [ - "Argument of [rtrim] must be [string], found value [numberField] type [number]" + "Argument of [sqrt] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = rtrim(stringField)", + "query": "from a_index | eval sqrt(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval rtrim(stringField)", + "query": "row nullVar = null | eval sqrt(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = rtrim(to_string(stringField))", + "query": "row var = st_contains(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval rtrim(numberField)", - "error": [ - "Argument of [rtrim] must be [string], found value [numberField] type [number]" - ], + "query": "row st_contains(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval rtrim(stringField, extraArg)", - "error": [ - "Error: [rtrim] function expects exactly one argument, got 2." - ], + "query": "row var = st_contains(to_geopoint(\"a\"), to_geopoint(\"a\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = rtrim(*)", + "query": "row var = st_contains(\"a\", \"a\")", "error": [ - "Using wildcards (*) in rtrim is not allowed" + "Argument of [st_contains] must be [cartesian_point], found value [\"a\"] type [string]", + "Argument of [st_contains] must be [cartesian_point], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | sort rtrim(stringField)", + "query": "row var = st_contains(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = rtrim(to_string(true))", + "query": "row st_contains(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = rtrim(true)", - "error": [ - "Argument of [rtrim] must be [string], found value [true] type [boolean]" - ], + "query": "row var = st_contains(to_geopoint(\"a\"), to_geoshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where length(rtrim(booleanField)) > 0", - "error": [ - "Argument of [rtrim] must be [string], found value [booleanField] type [boolean]" - ], + "query": "row var = st_contains(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = rtrim(to_string(booleanField))", + "query": "row st_contains(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval rtrim(booleanField)", - "error": [ - "Argument of [rtrim] must be [string], found value [booleanField] type [boolean]" - ], + "query": "row var = st_contains(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"a\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval rtrim(null)", + "query": "row var = st_contains(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval rtrim(nullVar)", + "query": "row st_contains(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = signum(5)", + "query": "row var = st_contains(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row signum(5)", + "query": "row st_contains(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = signum(to_integer(\"a\"))", + "query": "row var = st_contains(to_cartesianpoint(\"a\"), to_cartesianpoint(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = signum(\"a\")", - "error": [ - "Argument of [signum] must be [number], found value [\"a\"] type [string]" - ], + "query": "row var = st_contains(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where signum(numberField) > 0", + "query": "row st_contains(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | where signum(stringField) > 0", - "error": [ - "Argument of [signum] must be [number], found value [stringField] type [string]" - ], + "query": "row var = st_contains(to_cartesianpoint(\"a\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = signum(numberField)", + "query": "row var = st_contains(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval signum(numberField)", + "query": "row st_contains(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = signum(to_integer(stringField))", + "query": "row var = st_contains(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval signum(stringField)", - "error": [ - "Argument of [signum] must be [number], found value [stringField] type [string]" - ], + "query": "row var = st_contains(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval signum(numberField, extraArg)", - "error": [ - "Error: [signum] function expects exactly one argument, got 2." - ], + "query": "row st_contains(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = signum(*)", - "error": [ - "Using wildcards (*) in signum is not allowed" - ], + "query": "from a_index | eval var = st_contains(geoPointField, geoPointField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort signum(numberField)", + "query": "from a_index | eval st_contains(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "row var = signum(to_integer(true))", + "query": "from a_index | eval var = st_contains(to_geopoint(stringField), to_geopoint(stringField))", "error": [], "warning": [] }, { - "query": "row var = signum(true)", + "query": "from a_index | eval st_contains(stringField, stringField)", "error": [ - "Argument of [signum] must be [number], found value [true] type [boolean]" + "Argument of [st_contains] must be [cartesian_point], found value [stringField] type [string]", + "Argument of [st_contains] must be [cartesian_point], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | where signum(booleanField) > 0", + "query": "from a_index | eval st_contains(geoPointField, geoPointField, extraArg)", "error": [ - "Argument of [signum] must be [number], found value [booleanField] type [boolean]" + "Error: [st_contains] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | eval var = signum(to_integer(booleanField))", + "query": "from a_index | eval var = st_contains(geoPointField, geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval signum(booleanField)", - "error": [ - "Argument of [signum] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval st_contains(geoPointField, geoShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval signum(null)", + "query": "from a_index | eval var = st_contains(to_geopoint(stringField), geoShapeField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval signum(nullVar)", - "error": [], + "query": "from a_index | eval st_contains(geoPointField, geoShapeField, extraArg)", + "error": [ + "Error: [st_contains] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "row var = sin(5)", + "query": "from a_index | eval var = st_contains(geoShapeField, geoPointField)", "error": [], "warning": [] }, { - "query": "row sin(5)", + "query": "from a_index | eval st_contains(geoShapeField, geoPointField)", "error": [], "warning": [] }, { - "query": "row var = sin(to_integer(\"a\"))", + "query": "from a_index | eval var = st_contains(geoShapeField, to_geopoint(stringField))", "error": [], "warning": [] }, { - "query": "row var = sin(\"a\")", + "query": "from a_index | eval st_contains(geoShapeField, geoPointField, extraArg)", "error": [ - "Argument of [sin] must be [number], found value [\"a\"] type [string]" + "Error: [st_contains] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | where sin(numberField) > 0", + "query": "from a_index | eval var = st_contains(geoShapeField, geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | where sin(stringField) > 0", - "error": [ - "Argument of [sin] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval st_contains(geoShapeField, geoShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = sin(numberField)", - "error": [], + "query": "from a_index | eval st_contains(geoShapeField, geoShapeField, extraArg)", + "error": [ + "Error: [st_contains] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "from a_index | eval sin(numberField)", + "query": "from a_index | eval var = st_contains(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = sin(to_integer(stringField))", + "query": "from a_index | eval st_contains(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval sin(stringField)", - "error": [ - "Argument of [sin] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval var = st_contains(to_cartesianpoint(stringField), to_cartesianpoint(stringField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval sin(numberField, extraArg)", + "query": "from a_index | eval st_contains(cartesianPointField, cartesianPointField, extraArg)", "error": [ - "Error: [sin] function expects exactly one argument, got 2." + "Error: [st_contains] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | eval var = sin(*)", - "error": [ - "Using wildcards (*) in sin is not allowed" - ], + "query": "from a_index | eval var = st_contains(cartesianPointField, cartesianShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort sin(numberField)", + "query": "from a_index | eval st_contains(cartesianPointField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = sin(to_integer(true))", + "query": "from a_index | eval var = st_contains(to_cartesianpoint(stringField), cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = sin(true)", + "query": "from a_index | eval st_contains(cartesianPointField, cartesianShapeField, extraArg)", "error": [ - "Argument of [sin] must be [number], found value [true] type [boolean]" + "Error: [st_contains] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | where sin(booleanField) > 0", - "error": [ - "Argument of [sin] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = st_contains(cartesianShapeField, cartesianPointField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = sin(to_integer(booleanField))", + "query": "from a_index | eval st_contains(cartesianShapeField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval sin(booleanField)", + "query": "from a_index | eval var = st_contains(cartesianShapeField, to_cartesianpoint(stringField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_contains(cartesianShapeField, cartesianPointField, extraArg)", "error": [ - "Argument of [sin] must be [number], found value [booleanField] type [boolean]" + "Error: [st_contains] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | eval sin(null)", + "query": "from a_index | eval var = st_contains(cartesianShapeField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval sin(nullVar)", + "query": "from a_index | eval st_contains(cartesianShapeField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = sinh(5)", + "query": "from a_index | eval st_contains(cartesianShapeField, cartesianShapeField, extraArg)", + "error": [ + "Error: [st_contains] function expects exactly 2 arguments, got 3." + ], + "warning": [] + }, + { + "query": "from a_index | sort st_contains(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "row sinh(5)", + "query": "row var = st_contains(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = sinh(to_integer(\"a\"))", + "query": "row var = st_contains(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = sinh(\"a\")", - "error": [ - "Argument of [sinh] must be [number], found value [\"a\"] type [string]" - ], + "query": "row var = st_contains(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], "warning": [] }, { - "query": "from a_index | where sinh(numberField) > 0", + "query": "row var = st_contains(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | where sinh(stringField) > 0", - "error": [ - "Argument of [sinh] must be [number], found value [stringField] type [string]" - ], + "query": "row var = st_contains(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = sinh(numberField)", + "query": "row var = st_contains(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval sinh(numberField)", + "query": "row var = st_contains(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = sinh(to_integer(stringField))", + "query": "row var = st_contains(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval sinh(stringField)", + "query": "row var = st_contains(true, true)", "error": [ - "Argument of [sinh] must be [number], found value [stringField] type [string]" + "Argument of [st_contains] must be [cartesian_point], found value [true] type [boolean]", + "Argument of [st_contains] must be [cartesian_point], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval sinh(numberField, extraArg)", - "error": [ - "Error: [sinh] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval var = st_contains(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = sinh(*)", + "query": "from a_index | eval st_contains(booleanField, booleanField)", "error": [ - "Using wildcards (*) in sinh is not allowed" + "Argument of [st_contains] must be [cartesian_point], found value [booleanField] type [boolean]", + "Argument of [st_contains] must be [cartesian_point], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort sinh(numberField)", + "query": "from a_index | eval var = st_contains(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "row var = sinh(to_integer(true))", + "query": "from a_index | eval var = st_contains(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "row var = sinh(true)", - "error": [ - "Argument of [sinh] must be [number], found value [true] type [boolean]" - ], + "query": "from a_index | eval var = st_contains(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | where sinh(booleanField) > 0", - "error": [ - "Argument of [sinh] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = st_contains(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = sinh(to_integer(booleanField))", + "query": "from a_index | eval var = st_contains(to_geopoint(geoPointField), to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval sinh(booleanField)", - "error": [ - "Argument of [sinh] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = st_contains(to_geoshape(geoPointField), to_geopoint(geoPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval sinh(null)", + "query": "from a_index | eval var = st_contains(to_geoshape(geoPointField), to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval sinh(nullVar)", + "query": "from a_index | sort st_contains(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = split(\"a\", \"a\")", + "query": "from a_index | eval st_contains(null, null)", "error": [], "warning": [] }, { - "query": "row split(\"a\", \"a\")", + "query": "row nullVar = null | eval st_contains(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row var = split(to_string(\"a\"), to_string(\"a\"))", + "query": "row var = st_disjoint(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = split(5, 5)", - "error": [ - "Argument of [split] must be [string], found value [5] type [number]", - "Argument of [split] must be [string], found value [5] type [number]" - ], + "query": "row st_disjoint(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where length(split(stringField, stringField)) > 0", + "query": "row var = st_disjoint(to_geopoint(\"a\"), to_geopoint(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | where length(split(numberField, numberField)) > 0", + "query": "row var = st_disjoint(\"a\", \"a\")", "error": [ - "Argument of [split] must be [string], found value [numberField] type [number]", - "Argument of [split] must be [string], found value [numberField] type [number]" + "Argument of [st_disjoint] must be [cartesian_point], found value [\"a\"] type [string]", + "Argument of [st_disjoint] must be [cartesian_point], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = split(stringField, stringField)", + "query": "row var = st_disjoint(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval split(stringField, stringField)", + "query": "row st_disjoint(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = split(to_string(stringField), to_string(stringField))", + "query": "row var = st_disjoint(to_geopoint(\"a\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval split(numberField, numberField)", - "error": [ - "Argument of [split] must be [string], found value [numberField] type [number]", - "Argument of [split] must be [string], found value [numberField] type [number]" - ], + "query": "row var = st_disjoint(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval split(stringField, stringField, extraArg)", - "error": [ - "Error: [split] function expects exactly 2 arguments, got 3." - ], + "query": "row st_disjoint(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | sort split(stringField, stringField)", + "query": "row var = st_disjoint(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = split(to_string(true), to_string(true))", + "query": "row var = st_disjoint(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = split(true, true)", - "error": [ - "Argument of [split] must be [string], found value [true] type [boolean]", - "Argument of [split] must be [string], found value [true] type [boolean]" - ], + "query": "row st_disjoint(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where length(split(booleanField, booleanField)) > 0", - "error": [ - "Argument of [split] must be [string], found value [booleanField] type [boolean]", - "Argument of [split] must be [string], found value [booleanField] type [boolean]" - ], + "query": "row var = st_disjoint(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = split(to_string(booleanField), to_string(booleanField))", + "query": "row st_disjoint(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval split(booleanField, booleanField)", - "error": [ - "Argument of [split] must be [string], found value [booleanField] type [boolean]", - "Argument of [split] must be [string], found value [booleanField] type [boolean]" - ], + "query": "row var = st_disjoint(to_cartesianpoint(\"a\"), to_cartesianpoint(\"a\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval split(null, null)", + "query": "row var = st_disjoint(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval split(nullVar, nullVar)", + "query": "row st_disjoint(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = sqrt(5)", + "query": "row var = st_disjoint(to_cartesianpoint(\"a\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row sqrt(5)", + "query": "row var = st_disjoint(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = sqrt(to_integer(\"a\"))", + "query": "row st_disjoint(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = sqrt(\"a\")", - "error": [ - "Argument of [sqrt] must be [number], found value [\"a\"] type [string]" - ], + "query": "row var = st_disjoint(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"a\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where sqrt(numberField) > 0", + "query": "row var = st_disjoint(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | where sqrt(stringField) > 0", - "error": [ - "Argument of [sqrt] must be [number], found value [stringField] type [string]" - ], + "query": "row st_disjoint(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = sqrt(numberField)", + "query": "from a_index | eval var = st_disjoint(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval sqrt(numberField)", + "query": "from a_index | eval st_disjoint(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = sqrt(to_integer(stringField))", + "query": "from a_index | eval var = st_disjoint(to_geopoint(stringField), to_geopoint(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval sqrt(stringField)", + "query": "from a_index | eval st_disjoint(stringField, stringField)", "error": [ - "Argument of [sqrt] must be [number], found value [stringField] type [string]" + "Argument of [st_disjoint] must be [cartesian_point], found value [stringField] type [string]", + "Argument of [st_disjoint] must be [cartesian_point], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval sqrt(numberField, extraArg)", + "query": "from a_index | eval st_disjoint(geoPointField, geoPointField, extraArg)", "error": [ - "Error: [sqrt] function expects exactly one argument, got 2." + "Error: [st_disjoint] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | eval var = sqrt(*)", - "error": [ - "Using wildcards (*) in sqrt is not allowed" - ], + "query": "from a_index | eval var = st_disjoint(geoPointField, geoShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort sqrt(numberField)", + "query": "from a_index | eval st_disjoint(geoPointField, geoShapeField)", "error": [], "warning": [] }, { - "query": "row var = sqrt(to_integer(true))", + "query": "from a_index | eval var = st_disjoint(to_geopoint(stringField), geoShapeField)", "error": [], "warning": [] }, { - "query": "row var = sqrt(true)", + "query": "from a_index | eval st_disjoint(geoPointField, geoShapeField, extraArg)", "error": [ - "Argument of [sqrt] must be [number], found value [true] type [boolean]" + "Error: [st_disjoint] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | where sqrt(booleanField) > 0", - "error": [ - "Argument of [sqrt] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = st_disjoint(geoShapeField, geoPointField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = sqrt(to_integer(booleanField))", + "query": "from a_index | eval st_disjoint(geoShapeField, geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval sqrt(booleanField)", + "query": "from a_index | eval var = st_disjoint(geoShapeField, to_geopoint(stringField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval st_disjoint(geoShapeField, geoPointField, extraArg)", "error": [ - "Argument of [sqrt] must be [number], found value [booleanField] type [boolean]" + "Error: [st_disjoint] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | eval sqrt(null)", + "query": "from a_index | eval var = st_disjoint(geoShapeField, geoShapeField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval sqrt(nullVar)", + "query": "from a_index | eval st_disjoint(geoShapeField, geoShapeField)", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval st_disjoint(geoShapeField, geoShapeField, extraArg)", + "error": [ + "Error: [st_disjoint] function expects exactly 2 arguments, got 3." + ], + "warning": [] + }, + { + "query": "from a_index | eval var = st_disjoint(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "row st_contains(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval st_disjoint(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geopoint(\"a\"), to_geopoint(\"a\"))", + "query": "from a_index | eval var = st_disjoint(to_cartesianpoint(stringField), to_cartesianpoint(stringField))", "error": [], "warning": [] }, { - "query": "row var = st_contains(\"a\", \"a\")", + "query": "from a_index | eval st_disjoint(cartesianPointField, cartesianPointField, extraArg)", "error": [ - "Argument of [st_contains] must be [cartesian_point], found value [\"a\"] type [string]", - "Argument of [st_contains] must be [cartesian_point], found value [\"a\"] type [string]" + "Error: [st_disjoint] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "row var = st_contains(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_disjoint(cartesianPointField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row st_contains(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval st_disjoint(cartesianPointField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geopoint(\"a\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_disjoint(to_cartesianpoint(stringField), cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval st_disjoint(cartesianPointField, cartesianShapeField, extraArg)", + "error": [ + "Error: [st_disjoint] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "row st_contains(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_disjoint(cartesianShapeField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"a\"))", + "query": "from a_index | eval st_disjoint(cartesianShapeField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_disjoint(cartesianShapeField, to_cartesianpoint(stringField))", "error": [], "warning": [] }, { - "query": "row st_contains(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval st_disjoint(cartesianShapeField, cartesianPointField, extraArg)", + "error": [ + "Error: [st_disjoint] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "row var = st_contains(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_disjoint(cartesianShapeField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row st_contains(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval st_disjoint(cartesianShapeField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_cartesianpoint(\"a\"), to_cartesianpoint(\"a\"))", - "error": [], + "query": "from a_index | eval st_disjoint(cartesianShapeField, cartesianShapeField, extraArg)", + "error": [ + "Error: [st_disjoint] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "row var = st_contains(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | sort st_disjoint(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "row st_contains(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = st_disjoint(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_cartesianpoint(\"a\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = st_disjoint(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row var = st_disjoint(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row st_contains(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row var = st_disjoint(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"a\"))", + "query": "row var = st_disjoint(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = st_disjoint(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row st_contains(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = st_disjoint(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(geoPointField, geoPointField)", + "query": "row var = st_disjoint(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(geoPointField, geoPointField)", - "error": [], + "query": "row var = st_disjoint(true, true)", + "error": [ + "Argument of [st_disjoint] must be [cartesian_point], found value [true] type [boolean]", + "Argument of [st_disjoint] must be [cartesian_point], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_geopoint(stringField), to_geopoint(stringField))", + "query": "from a_index | eval var = st_disjoint(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(stringField, stringField)", + "query": "from a_index | eval st_disjoint(booleanField, booleanField)", "error": [ - "Argument of [st_contains] must be [cartesian_point], found value [stringField] type [string]", - "Argument of [st_contains] must be [cartesian_point], found value [stringField] type [string]" + "Argument of [st_disjoint] must be [cartesian_point], found value [booleanField] type [boolean]", + "Argument of [st_disjoint] must be [cartesian_point], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval st_contains(geoPointField, geoPointField, extraArg)", - "error": [ - "Error: [st_contains] function expects exactly 2 arguments, got 3." - ], + "query": "from a_index | eval var = st_disjoint(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(geoPointField, geoShapeField)", + "query": "from a_index | eval var = st_disjoint(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(geoPointField, geoShapeField)", + "query": "from a_index | eval var = st_disjoint(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_geopoint(stringField), geoShapeField)", + "query": "from a_index | eval var = st_disjoint(to_geopoint(geoPointField), to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(geoPointField, geoShapeField, extraArg)", - "error": [ - "Error: [st_contains] function expects exactly 2 arguments, got 3." - ], + "query": "from a_index | eval var = st_disjoint(to_geopoint(geoPointField), to_geoshape(geoPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(geoShapeField, geoPointField)", + "query": "from a_index | eval var = st_disjoint(to_geoshape(geoPointField), to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(geoShapeField, geoPointField)", + "query": "from a_index | eval var = st_disjoint(to_geoshape(geoPointField), to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(geoShapeField, to_geopoint(stringField))", + "query": "from a_index | sort st_disjoint(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(geoShapeField, geoPointField, extraArg)", - "error": [ - "Error: [st_contains] function expects exactly 2 arguments, got 3." - ], + "query": "from a_index | eval st_disjoint(null, null)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(geoShapeField, geoShapeField)", + "query": "row nullVar = null | eval st_disjoint(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(geoShapeField, geoShapeField)", + "query": "row var = st_intersects(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(geoShapeField, geoShapeField, extraArg)", - "error": [ - "Error: [st_contains] function expects exactly 2 arguments, got 3." - ], + "query": "row st_intersects(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(cartesianPointField, cartesianPointField)", + "query": "row var = st_intersects(to_geopoint(\"a\"), to_geopoint(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(cartesianPointField, cartesianPointField)", - "error": [], + "query": "row var = st_intersects(\"a\", \"a\")", + "error": [ + "Argument of [st_intersects] must be [cartesian_point], found value [\"a\"] type [string]", + "Argument of [st_intersects] must be [cartesian_point], found value [\"a\"] type [string]" + ], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_cartesianpoint(stringField), to_cartesianpoint(stringField))", + "query": "row var = st_intersects(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(cartesianPointField, cartesianPointField, extraArg)", - "error": [ - "Error: [st_contains] function expects exactly 2 arguments, got 3." - ], + "query": "row st_intersects(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(cartesianPointField, cartesianShapeField)", + "query": "row var = st_intersects(to_geopoint(\"a\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(cartesianPointField, cartesianShapeField)", + "query": "row var = st_intersects(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_cartesianpoint(stringField), cartesianShapeField)", + "query": "row st_intersects(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(cartesianPointField, cartesianShapeField, extraArg)", - "error": [ - "Error: [st_contains] function expects exactly 2 arguments, got 3." - ], + "query": "row var = st_intersects(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"a\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(cartesianShapeField, cartesianPointField)", + "query": "row var = st_intersects(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(cartesianShapeField, cartesianPointField)", + "query": "row st_intersects(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(cartesianShapeField, to_cartesianpoint(stringField))", + "query": "row var = st_intersects(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(cartesianShapeField, cartesianPointField, extraArg)", - "error": [ - "Error: [st_contains] function expects exactly 2 arguments, got 3." - ], + "query": "row st_intersects(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(cartesianShapeField, cartesianShapeField)", + "query": "row var = st_intersects(to_cartesianpoint(\"a\"), to_cartesianpoint(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(cartesianShapeField, cartesianShapeField)", + "query": "row var = st_intersects(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_contains(cartesianShapeField, cartesianShapeField, extraArg)", - "error": [ - "Error: [st_contains] function expects exactly 2 arguments, got 3." - ], + "query": "row st_intersects(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | sort st_contains(geoPointField, geoPointField)", + "query": "row var = st_intersects(to_cartesianpoint(\"a\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = st_intersects(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row st_intersects(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = st_intersects(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = st_intersects(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "row st_intersects(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = st_intersects(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval st_intersects(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "row var = st_contains(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = st_intersects(to_geopoint(stringField), to_geopoint(stringField))", "error": [], "warning": [] }, { - "query": "row var = st_contains(true, true)", + "query": "from a_index | eval st_intersects(stringField, stringField)", "error": [ - "Argument of [st_contains] must be [cartesian_point], found value [true] type [boolean]", - "Argument of [st_contains] must be [cartesian_point], found value [true] type [boolean]" + "Argument of [st_intersects] must be [cartesian_point], found value [stringField] type [string]", + "Argument of [st_intersects] must be [cartesian_point], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval st_contains(booleanField, booleanField)", + "query": "from a_index | eval st_intersects(geoPointField, geoPointField, extraArg)", "error": [ - "Argument of [st_contains] must be [cartesian_point], found value [booleanField] type [boolean]", - "Argument of [st_contains] must be [cartesian_point], found value [booleanField] type [boolean]" + "Error: [st_intersects] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", + "query": "from a_index | eval var = st_intersects(geoPointField, geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "query": "from a_index | eval st_intersects(geoPointField, geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "query": "from a_index | eval var = st_intersects(to_geopoint(stringField), geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_geopoint(geoPointField), to_geopoint(geoPointField))", - "error": [], + "query": "from a_index | eval st_intersects(geoPointField, geoShapeField, extraArg)", + "error": [ + "Error: [st_intersects] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_geopoint(geoPointField), to_geoshape(geoPointField))", + "query": "from a_index | eval var = st_intersects(geoShapeField, geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_geoshape(geoPointField), to_geopoint(geoPointField))", + "query": "from a_index | eval st_intersects(geoShapeField, geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_contains(to_geoshape(geoPointField), to_geoshape(geoPointField))", + "query": "from a_index | eval var = st_intersects(geoShapeField, to_geopoint(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | sort st_contains(cartesianPointField, cartesianPointField)", - "error": [], + "query": "from a_index | eval st_intersects(geoShapeField, geoPointField, extraArg)", + "error": [ + "Error: [st_intersects] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "from a_index | eval st_contains(null, null)", + "query": "from a_index | eval var = st_intersects(geoShapeField, geoShapeField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval st_contains(nullVar, nullVar)", + "query": "from a_index | eval st_intersects(geoShapeField, geoShapeField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval st_intersects(geoShapeField, geoShapeField, extraArg)", + "error": [ + "Error: [st_intersects] function expects exactly 2 arguments, got 3." + ], + "warning": [] + }, + { + "query": "from a_index | eval var = st_intersects(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "row st_disjoint(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval st_intersects(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geopoint(\"a\"), to_geopoint(\"a\"))", + "query": "from a_index | eval var = st_intersects(to_cartesianpoint(stringField), to_cartesianpoint(stringField))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(\"a\", \"a\")", + "query": "from a_index | eval st_intersects(cartesianPointField, cartesianPointField, extraArg)", "error": [ - "Argument of [st_disjoint] must be [cartesian_point], found value [\"a\"] type [string]", - "Argument of [st_disjoint] must be [cartesian_point], found value [\"a\"] type [string]" + "Error: [st_intersects] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "row var = st_disjoint(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_intersects(cartesianPointField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row st_disjoint(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval st_intersects(cartesianPointField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geopoint(\"a\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_intersects(to_cartesianpoint(stringField), cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval st_intersects(cartesianPointField, cartesianShapeField, extraArg)", + "error": [ + "Error: [st_intersects] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "row st_disjoint(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_intersects(cartesianShapeField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"a\"))", + "query": "from a_index | eval st_intersects(cartesianShapeField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_intersects(cartesianShapeField, to_cartesianpoint(stringField))", "error": [], "warning": [] }, { - "query": "row st_disjoint(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval st_intersects(cartesianShapeField, cartesianPointField, extraArg)", + "error": [ + "Error: [st_intersects] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_intersects(cartesianShapeField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row st_disjoint(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval st_intersects(cartesianShapeField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianpoint(\"a\"), to_cartesianpoint(\"a\"))", - "error": [], + "query": "from a_index | eval st_intersects(cartesianShapeField, cartesianShapeField, extraArg)", + "error": [ + "Error: [st_intersects] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | sort st_intersects(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "row st_disjoint(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = st_intersects(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianpoint(\"a\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = st_intersects(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row var = st_intersects(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row st_disjoint(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row var = st_intersects(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"a\"))", + "query": "row var = st_intersects(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = st_intersects(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row st_disjoint(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = st_intersects(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(geoPointField, geoPointField)", + "query": "row var = st_intersects(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(geoPointField, geoPointField)", - "error": [], + "query": "row var = st_intersects(true, true)", + "error": [ + "Argument of [st_intersects] must be [cartesian_point], found value [true] type [boolean]", + "Argument of [st_intersects] must be [cartesian_point], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_geopoint(stringField), to_geopoint(stringField))", + "query": "from a_index | eval var = st_intersects(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(stringField, stringField)", + "query": "from a_index | eval st_intersects(booleanField, booleanField)", "error": [ - "Argument of [st_disjoint] must be [cartesian_point], found value [stringField] type [string]", - "Argument of [st_disjoint] must be [cartesian_point], found value [stringField] type [string]" + "Argument of [st_intersects] must be [cartesian_point], found value [booleanField] type [boolean]", + "Argument of [st_intersects] must be [cartesian_point], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval st_disjoint(geoPointField, geoPointField, extraArg)", - "error": [ - "Error: [st_disjoint] function expects exactly 2 arguments, got 3." - ], + "query": "from a_index | eval var = st_intersects(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(geoPointField, geoShapeField)", + "query": "from a_index | eval var = st_intersects(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(geoPointField, geoShapeField)", + "query": "from a_index | eval var = st_intersects(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_geopoint(stringField), geoShapeField)", + "query": "from a_index | eval var = st_intersects(to_geopoint(geoPointField), to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(geoPointField, geoShapeField, extraArg)", - "error": [ - "Error: [st_disjoint] function expects exactly 2 arguments, got 3." - ], + "query": "from a_index | eval var = st_intersects(to_geopoint(geoPointField), to_geoshape(geoPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(geoShapeField, geoPointField)", + "query": "from a_index | eval var = st_intersects(to_geoshape(geoPointField), to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(geoShapeField, geoPointField)", + "query": "from a_index | eval var = st_intersects(to_geoshape(geoPointField), to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(geoShapeField, to_geopoint(stringField))", + "query": "from a_index | sort st_intersects(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(geoShapeField, geoPointField, extraArg)", - "error": [ - "Error: [st_disjoint] function expects exactly 2 arguments, got 3." - ], + "query": "from a_index | eval st_intersects(null, null)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(geoShapeField, geoShapeField)", + "query": "row nullVar = null | eval st_intersects(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(geoShapeField, geoShapeField)", + "query": "row var = st_within(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(geoShapeField, geoShapeField, extraArg)", - "error": [ - "Error: [st_disjoint] function expects exactly 2 arguments, got 3." - ], + "query": "row st_within(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(cartesianPointField, cartesianPointField)", + "query": "row var = st_within(to_geopoint(\"a\"), to_geopoint(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(cartesianPointField, cartesianPointField)", - "error": [], + "query": "row var = st_within(\"a\", \"a\")", + "error": [ + "Argument of [st_within] must be [cartesian_point], found value [\"a\"] type [string]", + "Argument of [st_within] must be [cartesian_point], found value [\"a\"] type [string]" + ], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_cartesianpoint(stringField), to_cartesianpoint(stringField))", + "query": "row var = st_within(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(cartesianPointField, cartesianPointField, extraArg)", - "error": [ - "Error: [st_disjoint] function expects exactly 2 arguments, got 3." - ], + "query": "row st_within(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(cartesianPointField, cartesianShapeField)", + "query": "row var = st_within(to_geopoint(\"a\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(cartesianPointField, cartesianShapeField)", + "query": "row var = st_within(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_cartesianpoint(stringField), cartesianShapeField)", + "query": "row st_within(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(cartesianPointField, cartesianShapeField, extraArg)", - "error": [ - "Error: [st_disjoint] function expects exactly 2 arguments, got 3." - ], + "query": "row var = st_within(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"a\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(cartesianShapeField, cartesianPointField)", + "query": "row var = st_within(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(cartesianShapeField, cartesianPointField)", + "query": "row st_within(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(cartesianShapeField, to_cartesianpoint(stringField))", + "query": "row var = st_within(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(cartesianShapeField, cartesianPointField, extraArg)", - "error": [ - "Error: [st_disjoint] function expects exactly 2 arguments, got 3." - ], + "query": "row st_within(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(cartesianShapeField, cartesianShapeField)", + "query": "row var = st_within(to_cartesianpoint(\"a\"), to_cartesianpoint(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(cartesianShapeField, cartesianShapeField)", + "query": "row var = st_within(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_disjoint(cartesianShapeField, cartesianShapeField, extraArg)", - "error": [ - "Error: [st_disjoint] function expects exactly 2 arguments, got 3." - ], + "query": "row st_within(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | sort st_disjoint(geoPointField, geoPointField)", + "query": "row var = st_within(to_cartesianpoint(\"a\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = st_within(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row st_within(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = st_within(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = st_within(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "row st_within(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = st_within(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval st_within(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = st_within(to_geopoint(stringField), to_geopoint(stringField))", "error": [], "warning": [] }, { - "query": "row var = st_disjoint(true, true)", + "query": "from a_index | eval st_within(stringField, stringField)", "error": [ - "Argument of [st_disjoint] must be [cartesian_point], found value [true] type [boolean]", - "Argument of [st_disjoint] must be [cartesian_point], found value [true] type [boolean]" + "Argument of [st_within] must be [cartesian_point], found value [stringField] type [string]", + "Argument of [st_within] must be [cartesian_point], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval st_disjoint(booleanField, booleanField)", + "query": "from a_index | eval st_within(geoPointField, geoPointField, extraArg)", "error": [ - "Argument of [st_disjoint] must be [cartesian_point], found value [booleanField] type [boolean]", - "Argument of [st_disjoint] must be [cartesian_point], found value [booleanField] type [boolean]" + "Error: [st_within] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", + "query": "from a_index | eval var = st_within(geoPointField, geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "query": "from a_index | eval st_within(geoPointField, geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "query": "from a_index | eval var = st_within(to_geopoint(stringField), geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_geopoint(geoPointField), to_geopoint(geoPointField))", - "error": [], + "query": "from a_index | eval st_within(geoPointField, geoShapeField, extraArg)", + "error": [ + "Error: [st_within] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_geopoint(geoPointField), to_geoshape(geoPointField))", + "query": "from a_index | eval var = st_within(geoShapeField, geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_geoshape(geoPointField), to_geopoint(geoPointField))", + "query": "from a_index | eval st_within(geoShapeField, geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_disjoint(to_geoshape(geoPointField), to_geoshape(geoPointField))", + "query": "from a_index | eval var = st_within(geoShapeField, to_geopoint(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | sort st_disjoint(cartesianPointField, cartesianPointField)", - "error": [], + "query": "from a_index | eval st_within(geoShapeField, geoPointField, extraArg)", + "error": [ + "Error: [st_within] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "from a_index | eval st_disjoint(null, null)", + "query": "from a_index | eval var = st_within(geoShapeField, geoShapeField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval st_disjoint(nullVar, nullVar)", + "query": "from a_index | eval st_within(geoShapeField, geoShapeField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval st_within(geoShapeField, geoShapeField, extraArg)", + "error": [ + "Error: [st_within] function expects exactly 2 arguments, got 3." + ], + "warning": [] + }, + { + "query": "from a_index | eval var = st_within(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "row st_intersects(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval st_within(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_geopoint(\"a\"), to_geopoint(\"a\"))", + "query": "from a_index | eval var = st_within(to_cartesianpoint(stringField), to_cartesianpoint(stringField))", "error": [], "warning": [] }, { - "query": "row var = st_intersects(\"a\", \"a\")", + "query": "from a_index | eval st_within(cartesianPointField, cartesianPointField, extraArg)", "error": [ - "Argument of [st_intersects] must be [cartesian_point], found value [\"a\"] type [string]", - "Argument of [st_intersects] must be [cartesian_point], found value [\"a\"] type [string]" + "Error: [st_within] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "row var = st_intersects(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_within(cartesianPointField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row st_intersects(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval st_within(cartesianPointField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_geopoint(\"a\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_within(to_cartesianpoint(stringField), cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval st_within(cartesianPointField, cartesianShapeField, extraArg)", + "error": [ + "Error: [st_within] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "row st_intersects(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_within(cartesianShapeField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"a\"))", + "query": "from a_index | eval st_within(cartesianShapeField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_within(cartesianShapeField, to_cartesianpoint(stringField))", "error": [], "warning": [] }, { - "query": "row st_intersects(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval st_within(cartesianShapeField, cartesianPointField, extraArg)", + "error": [ + "Error: [st_within] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_within(cartesianShapeField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row st_intersects(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval st_within(cartesianShapeField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianpoint(\"a\"), to_cartesianpoint(\"a\"))", - "error": [], - "warning": [] - }, - { - "query": "row var = st_intersects(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval st_within(cartesianShapeField, cartesianShapeField, extraArg)", + "error": [ + "Error: [st_within] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "row st_intersects(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | sort st_within(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianpoint(\"a\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = st_within(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row var = st_within(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row st_intersects(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row var = st_within(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"a\"))", + "query": "row var = st_within(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = st_within(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row st_intersects(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = st_within(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(geoPointField, geoPointField)", + "query": "row var = st_within(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(geoPointField, geoPointField)", + "query": "row var = st_within(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_geopoint(stringField), to_geopoint(stringField))", - "error": [], + "query": "row var = st_within(true, true)", + "error": [ + "Argument of [st_within] must be [cartesian_point], found value [true] type [boolean]", + "Argument of [st_within] must be [cartesian_point], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval st_intersects(stringField, stringField)", - "error": [ - "Argument of [st_intersects] must be [cartesian_point], found value [stringField] type [string]", - "Argument of [st_intersects] must be [cartesian_point], found value [stringField] type [string]" - ], + "query": "from a_index | eval var = st_within(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(geoPointField, geoPointField, extraArg)", + "query": "from a_index | eval st_within(booleanField, booleanField)", "error": [ - "Error: [st_intersects] function expects exactly 2 arguments, got 3." + "Argument of [st_within] must be [cartesian_point], found value [booleanField] type [boolean]", + "Argument of [st_within] must be [cartesian_point], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(geoPointField, geoShapeField)", + "query": "from a_index | eval var = st_within(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(geoPointField, geoShapeField)", + "query": "from a_index | eval var = st_within(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_geopoint(stringField), geoShapeField)", + "query": "from a_index | eval var = st_within(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(geoPointField, geoShapeField, extraArg)", - "error": [ - "Error: [st_intersects] function expects exactly 2 arguments, got 3." - ], - "warning": [] - }, - { - "query": "from a_index | eval var = st_intersects(geoShapeField, geoPointField)", + "query": "from a_index | eval var = st_within(to_geopoint(geoPointField), to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(geoShapeField, geoPointField)", + "query": "from a_index | eval var = st_within(to_geopoint(geoPointField), to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(geoShapeField, to_geopoint(stringField))", + "query": "from a_index | eval var = st_within(to_geoshape(geoPointField), to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(geoShapeField, geoPointField, extraArg)", - "error": [ - "Error: [st_intersects] function expects exactly 2 arguments, got 3." - ], + "query": "from a_index | eval var = st_within(to_geoshape(geoPointField), to_geoshape(geoPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(geoShapeField, geoShapeField)", + "query": "from a_index | sort st_within(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(geoShapeField, geoShapeField)", + "query": "from a_index | eval st_within(null, null)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(geoShapeField, geoShapeField, extraArg)", - "error": [ - "Error: [st_intersects] function expects exactly 2 arguments, got 3." - ], + "query": "row nullVar = null | eval st_within(nullVar, nullVar)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(cartesianPointField, cartesianPointField)", + "query": "row var = st_x(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(cartesianPointField, cartesianPointField)", + "query": "row st_x(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_cartesianpoint(stringField), to_cartesianpoint(stringField))", + "query": "row var = st_x(to_geopoint(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(cartesianPointField, cartesianPointField, extraArg)", + "query": "row var = st_x(\"a\")", "error": [ - "Error: [st_intersects] function expects exactly 2 arguments, got 3." + "Argument of [st_x] must be [cartesian_point], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(cartesianPointField, cartesianShapeField)", + "query": "row var = st_x(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(cartesianPointField, cartesianShapeField)", + "query": "row st_x(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_cartesianpoint(stringField), cartesianShapeField)", + "query": "row var = st_x(to_cartesianpoint(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(cartesianPointField, cartesianShapeField, extraArg)", - "error": [ - "Error: [st_intersects] function expects exactly 2 arguments, got 3." - ], - "warning": [] - }, - { - "query": "from a_index | eval var = st_intersects(cartesianShapeField, cartesianPointField)", + "query": "from a_index | eval var = st_x(geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(cartesianShapeField, cartesianPointField)", + "query": "from a_index | eval st_x(geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(cartesianShapeField, to_cartesianpoint(stringField))", + "query": "from a_index | eval var = st_x(to_geopoint(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(cartesianShapeField, cartesianPointField, extraArg)", + "query": "from a_index | eval st_x(stringField)", "error": [ - "Error: [st_intersects] function expects exactly 2 arguments, got 3." + "Argument of [st_x] must be [cartesian_point], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(cartesianShapeField, cartesianShapeField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval st_intersects(cartesianShapeField, cartesianShapeField)", - "error": [], + "query": "from a_index | eval st_x(geoPointField, extraArg)", + "error": [ + "Error: [st_x] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | eval st_intersects(cartesianShapeField, cartesianShapeField, extraArg)", + "query": "from a_index | eval var = st_x(*)", "error": [ - "Error: [st_intersects] function expects exactly 2 arguments, got 3." + "Using wildcards (*) in st_x is not allowed" ], "warning": [] }, { - "query": "from a_index | sort st_intersects(geoPointField, geoPointField)", + "query": "from a_index | eval var = st_x(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval st_x(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = st_x(to_cartesianpoint(stringField))", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", - "error": [], + "query": "from a_index | eval st_x(cartesianPointField, extraArg)", + "error": [ + "Error: [st_x] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "row var = st_intersects(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | sort st_x(geoPointField)", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "row var = st_x(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "row var = st_x(to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = st_intersects(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", - "error": [], + "query": "row var = st_x(true)", + "error": [ + "Argument of [st_x] must be [cartesian_point], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row var = st_intersects(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = st_x(to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "row var = st_intersects(true, true)", + "query": "from a_index | eval st_x(booleanField)", "error": [ - "Argument of [st_intersects] must be [cartesian_point], found value [true] type [boolean]", - "Argument of [st_intersects] must be [cartesian_point], found value [true] type [boolean]" + "Argument of [st_x] must be [cartesian_point], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "query": "from a_index | eval var = st_x(to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(booleanField, booleanField)", - "error": [ - "Argument of [st_intersects] must be [cartesian_point], found value [booleanField] type [boolean]", - "Argument of [st_intersects] must be [cartesian_point], found value [booleanField] type [boolean]" - ], + "query": "from a_index | sort st_x(cartesianPointField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", + "query": "from a_index | eval st_x(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "query": "row nullVar = null | eval st_x(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "query": "row var = st_y(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "query": "row st_y(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_geopoint(geoPointField), to_geoshape(geoPointField))", + "query": "row var = st_y(to_geopoint(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_geoshape(geoPointField), to_geopoint(geoPointField))", - "error": [], + "query": "row var = st_y(\"a\")", + "error": [ + "Argument of [st_y] must be [cartesian_point], found value [\"a\"] type [string]" + ], "warning": [] }, { - "query": "from a_index | eval var = st_intersects(to_geoshape(geoPointField), to_geoshape(geoPointField))", + "query": "row var = st_y(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | sort st_intersects(cartesianPointField, cartesianPointField)", + "query": "row st_y(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_intersects(null, null)", + "query": "row var = st_y(to_cartesianpoint(\"a\"))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval st_intersects(nullVar, nullVar)", + "query": "from a_index | eval var = st_y(geoPointField)", "error": [], "warning": [] }, { - "query": "row var = st_within(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval st_y(geoPointField)", "error": [], "warning": [] }, { - "query": "row st_within(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_y(to_geopoint(stringField))", "error": [], "warning": [] }, { - "query": "row var = st_within(to_geopoint(\"a\"), to_geopoint(\"a\"))", - "error": [], + "query": "from a_index | eval st_y(stringField)", + "error": [ + "Argument of [st_y] must be [cartesian_point], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "row var = st_within(\"a\", \"a\")", + "query": "from a_index | eval st_y(geoPointField, extraArg)", "error": [ - "Argument of [st_within] must be [cartesian_point], found value [\"a\"] type [string]", - "Argument of [st_within] must be [cartesian_point], found value [\"a\"] type [string]" + "Error: [st_y] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "row var = st_within(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval var = st_y(*)", + "error": [ + "Using wildcards (*) in st_y is not allowed" + ], "warning": [] }, { - "query": "row st_within(to_geopoint(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_y(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = st_within(to_geopoint(\"a\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval st_y(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = st_within(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_y(to_cartesianpoint(stringField))", "error": [], "warning": [] }, { - "query": "row st_within(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval st_y(cartesianPointField, extraArg)", + "error": [ + "Error: [st_y] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "row var = st_within(to_geoshape(\"POINT (30 10)\"), to_geopoint(\"a\"))", + "query": "from a_index | sort st_y(geoPointField)", "error": [], "warning": [] }, { - "query": "row var = st_within(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "row var = st_y(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row st_within(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "row var = st_y(to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", - "error": [], + "query": "row var = st_y(true)", + "error": [ + "Argument of [st_y] must be [cartesian_point], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row st_within(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_y(to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianpoint(\"a\"), to_cartesianpoint(\"a\"))", - "error": [], + "query": "from a_index | eval st_y(booleanField)", + "error": [ + "Argument of [st_y] must be [cartesian_point], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = st_within(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = st_y(to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "row st_within(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | sort st_y(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianpoint(\"a\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval st_y(null)", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row nullVar = null | eval st_y(nullVar)", "error": [], "warning": [] }, { - "query": "row st_within(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row var = starts_with(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianshape(\"POINT (30 10)\"), to_cartesianpoint(\"a\"))", + "query": "row starts_with(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "row var = starts_with(to_string(\"a\"), to_string(\"a\"))", "error": [], "warning": [] }, { - "query": "row st_within(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", - "error": [], + "query": "row var = starts_with(5, 5)", + "error": [ + "Argument of [starts_with] must be [string], found value [5] type [number]", + "Argument of [starts_with] must be [string], found value [5] type [number]" + ], "warning": [] }, { - "query": "from a_index | eval var = st_within(geoPointField, geoPointField)", + "query": "from a_index | eval var = starts_with(stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(geoPointField, geoPointField)", + "query": "from a_index | eval starts_with(stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_geopoint(stringField), to_geopoint(stringField))", + "query": "from a_index | eval var = starts_with(to_string(stringField), to_string(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(stringField, stringField)", + "query": "from a_index | eval starts_with(numberField, numberField)", "error": [ - "Argument of [st_within] must be [cartesian_point], found value [stringField] type [string]", - "Argument of [st_within] must be [cartesian_point], found value [stringField] type [string]" + "Argument of [starts_with] must be [string], found value [numberField] type [number]", + "Argument of [starts_with] must be [string], found value [numberField] type [number]" ], "warning": [] }, { - "query": "from a_index | eval st_within(geoPointField, geoPointField, extraArg)", + "query": "from a_index | eval starts_with(stringField, stringField, extraArg)", "error": [ - "Error: [st_within] function expects exactly 2 arguments, got 3." + "Error: [starts_with] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | eval var = st_within(geoPointField, geoShapeField)", + "query": "from a_index | sort starts_with(stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(geoPointField, geoShapeField)", + "query": "row var = starts_with(to_string(true), to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_geopoint(stringField), geoShapeField)", + "query": "row var = starts_with(true, true)", + "error": [ + "Argument of [starts_with] must be [string], found value [true] type [boolean]", + "Argument of [starts_with] must be [string], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = starts_with(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(geoPointField, geoShapeField, extraArg)", + "query": "from a_index | eval starts_with(booleanField, booleanField)", "error": [ - "Error: [st_within] function expects exactly 2 arguments, got 3." + "Argument of [starts_with] must be [string], found value [booleanField] type [boolean]", + "Argument of [starts_with] must be [string], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = st_within(geoShapeField, geoPointField)", + "query": "from a_index | eval starts_with(null, null)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(geoShapeField, geoPointField)", + "query": "row nullVar = null | eval starts_with(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(geoShapeField, to_geopoint(stringField))", + "query": "row var = substring(\"a\", 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(geoShapeField, geoPointField, extraArg)", - "error": [ - "Error: [st_within] function expects exactly 2 arguments, got 3." - ], - "warning": [] - }, - { - "query": "from a_index | eval var = st_within(geoShapeField, geoShapeField)", + "query": "row var = substring(\"a\", 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(geoShapeField, geoShapeField)", + "query": "row substring(\"a\", 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(geoShapeField, geoShapeField, extraArg)", - "error": [ - "Error: [st_within] function expects exactly 2 arguments, got 3." - ], + "query": "row substring(\"a\", 5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(cartesianPointField, cartesianPointField)", + "query": "row var = substring(to_string(\"a\"), to_integer(\"a\"), to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(cartesianPointField, cartesianPointField)", - "error": [], + "query": "row var = substring(5, \"a\", \"a\")", + "error": [ + "Argument of [substring] must be [string], found value [5] type [number]", + "Argument of [substring] must be [number], found value [\"a\"] type [string]", + "Argument of [substring] must be [number], found value [\"a\"] type [string]" + ], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_cartesianpoint(stringField), to_cartesianpoint(stringField))", + "query": "from a_index | where length(substring(stringField, numberField, numberField)) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(cartesianPointField, cartesianPointField, extraArg)", + "query": "from a_index | where length(substring(numberField, stringField, stringField)) > 0", "error": [ - "Error: [st_within] function expects exactly 2 arguments, got 3." + "Argument of [substring] must be [string], found value [numberField] type [number]", + "Argument of [substring] must be [number], found value [stringField] type [string]", + "Argument of [substring] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = st_within(cartesianPointField, cartesianShapeField)", + "query": "from a_index | eval var = substring(stringField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(cartesianPointField, cartesianShapeField)", + "query": "from a_index | eval substring(stringField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_cartesianpoint(stringField), cartesianShapeField)", + "query": "from a_index | eval var = substring(to_string(stringField), to_integer(stringField), to_integer(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(cartesianPointField, cartesianShapeField, extraArg)", + "query": "from a_index | eval substring(numberField, stringField, stringField)", "error": [ - "Error: [st_within] function expects exactly 2 arguments, got 3." + "Argument of [substring] must be [string], found value [numberField] type [number]", + "Argument of [substring] must be [number], found value [stringField] type [string]", + "Argument of [substring] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = st_within(cartesianShapeField, cartesianPointField)", + "query": "from a_index | eval substring(stringField, numberField, numberField, extraArg)", + "error": [ + "Error: [substring] function expects no more than 3 arguments, got 4." + ], + "warning": [] + }, + { + "query": "from a_index | sort substring(stringField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(cartesianShapeField, cartesianPointField)", + "query": "from a_index | sort substring(stringField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(cartesianShapeField, to_cartesianpoint(stringField))", + "query": "row var = substring(to_string(true), to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(cartesianShapeField, cartesianPointField, extraArg)", + "query": "row var = substring(true, true, true)", "error": [ - "Error: [st_within] function expects exactly 2 arguments, got 3." + "Argument of [substring] must be [string], found value [true] type [boolean]", + "Argument of [substring] must be [number], found value [true] type [boolean]", + "Argument of [substring] must be [number], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = st_within(cartesianShapeField, cartesianShapeField)", - "error": [], + "query": "from a_index | where length(substring(booleanField, booleanField, booleanField)) > 0", + "error": [ + "Argument of [substring] must be [string], found value [booleanField] type [boolean]", + "Argument of [substring] must be [number], found value [booleanField] type [boolean]", + "Argument of [substring] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval st_within(cartesianShapeField, cartesianShapeField)", + "query": "from a_index | eval var = substring(to_string(booleanField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(cartesianShapeField, cartesianShapeField, extraArg)", + "query": "from a_index | eval substring(booleanField, booleanField, booleanField)", "error": [ - "Error: [st_within] function expects exactly 2 arguments, got 3." + "Argument of [substring] must be [string], found value [booleanField] type [boolean]", + "Argument of [substring] must be [number], found value [booleanField] type [boolean]", + "Argument of [substring] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort st_within(geoPointField, geoPointField)", + "query": "from a_index | eval substring(null, null, null)", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row nullVar = null | eval substring(nullVar, nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = tan(5)", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row tan(5)", "error": [], "warning": [] }, { - "query": "row var = st_within(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = tan(to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = st_within(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", - "error": [], + "query": "row var = tan(\"a\")", + "error": [ + "Argument of [tan] must be [number], found value [\"a\"] type [string]" + ], "warning": [] }, { - "query": "row var = st_within(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | where tan(numberField) > 0", "error": [], "warning": [] }, { - "query": "row var = st_within(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", - "error": [], + "query": "from a_index | where tan(stringField) > 0", + "error": [ + "Argument of [tan] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "row var = st_within(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = tan(numberField)", "error": [], "warning": [] }, { - "query": "row var = st_within(true, true)", - "error": [ - "Argument of [st_within] must be [cartesian_point], found value [true] type [boolean]", - "Argument of [st_within] must be [cartesian_point], found value [true] type [boolean]" - ], + "query": "from a_index | eval tan(numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "query": "from a_index | eval var = tan(to_integer(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_within(booleanField, booleanField)", + "query": "from a_index | eval tan(stringField)", "error": [ - "Argument of [st_within] must be [cartesian_point], found value [booleanField] type [boolean]", - "Argument of [st_within] must be [cartesian_point], found value [booleanField] type [boolean]" + "Argument of [tan] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_cartesianpoint(cartesianPointField), to_cartesianshape(cartesianPointField))", - "error": [], + "query": "from a_index | eval tan(numberField, extraArg)", + "error": [ + "Error: [tan] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_cartesianshape(cartesianPointField), to_cartesianpoint(cartesianPointField))", - "error": [], + "query": "from a_index | eval var = tan(*)", + "error": [ + "Using wildcards (*) in tan is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "query": "from a_index | sort tan(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "query": "row var = tan(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_geopoint(geoPointField), to_geoshape(geoPointField))", - "error": [], + "query": "row var = tan(true)", + "error": [ + "Argument of [tan] must be [number], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_geoshape(geoPointField), to_geopoint(geoPointField))", - "error": [], + "query": "from a_index | where tan(booleanField) > 0", + "error": [ + "Argument of [tan] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = st_within(to_geoshape(geoPointField), to_geoshape(geoPointField))", + "query": "from a_index | eval var = tan(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | sort st_within(cartesianPointField, cartesianPointField)", - "error": [], + "query": "from a_index | eval tan(booleanField)", + "error": [ + "Argument of [tan] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval st_within(null, null)", + "query": "from a_index | eval tan(null)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval st_within(nullVar, nullVar)", + "query": "row nullVar = null | eval tan(nullVar)", "error": [], "warning": [] }, { - "query": "row var = st_x(to_geopoint(\"POINT (30 10)\"))", + "query": "row var = tanh(5)", "error": [], "warning": [] }, { - "query": "row st_x(to_geopoint(\"POINT (30 10)\"))", + "query": "row tanh(5)", "error": [], "warning": [] }, { - "query": "row var = st_x(to_geopoint(\"a\"))", + "query": "row var = tanh(to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = st_x(\"a\")", + "query": "row var = tanh(\"a\")", "error": [ - "Argument of [st_x] must be [cartesian_point], found value [\"a\"] type [string]" + "Argument of [tanh] must be [number], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "row var = st_x(to_cartesianpoint(\"POINT (30 10)\"))", - "error": [], - "warning": [] - }, - { - "query": "row st_x(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | where tanh(numberField) > 0", "error": [], "warning": [] }, { - "query": "row var = st_x(to_cartesianpoint(\"a\"))", - "error": [], + "query": "from a_index | where tanh(stringField) > 0", + "error": [ + "Argument of [tanh] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "from a_index | eval var = st_x(geoPointField)", + "query": "from a_index | eval var = tanh(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_x(geoPointField)", + "query": "from a_index | eval tanh(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_x(to_geopoint(stringField))", + "query": "from a_index | eval var = tanh(to_integer(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_x(stringField)", + "query": "from a_index | eval tanh(stringField)", "error": [ - "Argument of [st_x] must be [cartesian_point], found value [stringField] type [string]" + "Argument of [tanh] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | eval st_x(geoPointField, extraArg)", + "query": "from a_index | eval tanh(numberField, extraArg)", "error": [ - "Error: [st_x] function expects exactly one argument, got 2." + "Error: [tanh] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | eval var = st_x(*)", + "query": "from a_index | eval var = tanh(*)", "error": [ - "Using wildcards (*) in st_x is not allowed" + "Using wildcards (*) in tanh is not allowed" ], "warning": [] }, { - "query": "from a_index | eval var = st_x(cartesianPointField)", + "query": "from a_index | sort tanh(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_x(cartesianPointField)", + "query": "row var = tanh(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_x(to_cartesianpoint(stringField))", + "query": "row var = tanh(true)", + "error": [ + "Argument of [tanh] must be [number], found value [true] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | where tanh(booleanField) > 0", + "error": [ + "Argument of [tanh] must be [number], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = tanh(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_x(cartesianPointField, extraArg)", + "query": "from a_index | eval tanh(booleanField)", "error": [ - "Error: [st_x] function expects exactly one argument, got 2." + "Argument of [tanh] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort st_x(geoPointField)", + "query": "from a_index | eval tanh(null)", "error": [], "warning": [] }, { - "query": "row var = st_x(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row nullVar = null | eval tanh(nullVar)", "error": [], "warning": [] }, { - "query": "row var = st_x(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "row var = tau()", "error": [], "warning": [] }, { - "query": "row var = st_x(true)", - "error": [ - "Argument of [st_x] must be [cartesian_point], found value [true] type [boolean]" - ], + "query": "row tau()", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_x(to_cartesianpoint(cartesianPointField))", + "query": "from a_index | where tau() > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval st_x(booleanField)", + "query": "from a_index | eval var = tau()", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval tau()", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval tau(extraArg)", "error": [ - "Argument of [st_x] must be [cartesian_point], found value [booleanField] type [boolean]" + "Error: [tau] function expects exactly 0 arguments, got 1." ], "warning": [] }, { - "query": "from a_index | eval var = st_x(to_geopoint(geoPointField))", + "query": "from a_index | sort tau()", "error": [], "warning": [] }, { - "query": "from a_index | sort st_x(cartesianPointField)", + "query": "row nullVar = null | eval tau()", "error": [], "warning": [] }, { - "query": "from a_index | eval st_x(null)", + "query": "row var = to_boolean(\"a\")", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval st_x(nullVar)", + "query": "row to_boolean(\"a\")", "error": [], "warning": [] }, { - "query": "row var = st_y(to_geopoint(\"POINT (30 10)\"))", + "query": "row var = to_bool(\"a\")", "error": [], "warning": [] }, { - "query": "row st_y(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = to_boolean(stringField)", "error": [], "warning": [] }, { - "query": "row var = st_y(to_geopoint(\"a\"))", + "query": "from a_index | eval to_boolean(stringField)", "error": [], "warning": [] }, { - "query": "row var = st_y(\"a\")", + "query": "from a_index | eval var = to_bool(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = to_boolean(*)", "error": [ - "Argument of [st_y] must be [cartesian_point], found value [\"a\"] type [string]" + "Using wildcards (*) in to_boolean is not allowed" ], "warning": [] }, { - "query": "row var = st_y(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | sort to_boolean(stringField)", "error": [], "warning": [] }, { - "query": "row st_y(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row var = to_boolean(true)", "error": [], "warning": [] }, { - "query": "row var = st_y(to_cartesianpoint(\"a\"))", + "query": "row to_boolean(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_y(geoPointField)", + "query": "row var = to_bool(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_y(geoPointField)", + "query": "row var = to_boolean(to_boolean(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_y(to_geopoint(stringField))", + "query": "row var = to_boolean(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_y(stringField)", - "error": [ - "Argument of [st_y] must be [cartesian_point], found value [stringField] type [string]" - ], + "query": "row to_boolean(5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval st_y(geoPointField, extraArg)", - "error": [ - "Error: [st_y] function expects exactly one argument, got 2." - ], + "query": "row var = to_bool(5)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_y(*)", + "query": "row var = to_boolean(to_integer(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_boolean(to_string(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_boolean(to_cartesianpoint(\"POINT (30 10)\"))", "error": [ - "Using wildcards (*) in st_y is not allowed" + "Argument of [to_boolean] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | eval var = st_y(cartesianPointField)", + "query": "from a_index | eval var = to_boolean(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_y(cartesianPointField)", + "query": "from a_index | eval to_boolean(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_y(to_cartesianpoint(stringField))", + "query": "from a_index | eval var = to_bool(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_y(cartesianPointField, extraArg)", + "query": "from a_index | eval var = to_boolean(to_boolean(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_boolean(cartesianPointField)", "error": [ - "Error: [st_y] function expects exactly one argument, got 2." + "Argument of [to_boolean] must be [boolean], found value [cartesianPointField] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | sort st_y(geoPointField)", + "query": "from a_index | eval var = to_boolean(numberField)", "error": [], "warning": [] }, { - "query": "row var = st_y(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval to_boolean(numberField)", "error": [], "warning": [] }, { - "query": "row var = st_y(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = to_bool(numberField)", "error": [], "warning": [] }, { - "query": "row var = st_y(true)", - "error": [ - "Argument of [st_y] must be [cartesian_point], found value [true] type [boolean]" - ], + "query": "from a_index | eval var = to_boolean(to_integer(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_y(to_cartesianpoint(cartesianPointField))", + "query": "from a_index | eval var = to_boolean(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval st_y(booleanField)", + "query": "from a_index | eval to_boolean(booleanField, extraArg)", "error": [ - "Argument of [st_y] must be [cartesian_point], found value [booleanField] type [boolean]" + "Error: [to_boolean] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | eval var = st_y(to_geopoint(geoPointField))", + "query": "from a_index | sort to_boolean(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | sort st_y(cartesianPointField)", + "query": "from a_index | eval to_boolean(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval st_y(null)", + "query": "row nullVar = null | eval to_boolean(nullVar)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval st_y(nullVar)", + "query": "row var = to_cartesianpoint(\"a\")", "error": [], "warning": [] }, { - "query": "row var = starts_with(\"a\", \"a\")", + "query": "row to_cartesianpoint(\"a\")", "error": [], "warning": [] }, { - "query": "row starts_with(\"a\", \"a\")", + "query": "from a_index | eval var = to_cartesianpoint(stringField)", "error": [], "warning": [] }, { - "query": "row var = starts_with(to_string(\"a\"), to_string(\"a\"))", + "query": "from a_index | eval to_cartesianpoint(stringField)", "error": [], "warning": [] }, { - "query": "row var = starts_with(5, 5)", + "query": "from a_index | eval var = to_cartesianpoint(*)", "error": [ - "Argument of [starts_with] must be [string], found value [5] type [number]", - "Argument of [starts_with] must be [string], found value [5] type [number]" + "Using wildcards (*) in to_cartesianpoint is not allowed" ], "warning": [] }, { - "query": "from a_index | eval var = starts_with(stringField, stringField)", + "query": "from a_index | sort to_cartesianpoint(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval starts_with(stringField, stringField)", + "query": "row var = to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = starts_with(to_string(stringField), to_string(stringField))", + "query": "row to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval starts_with(numberField, numberField)", - "error": [ - "Argument of [starts_with] must be [string], found value [numberField] type [number]", - "Argument of [starts_with] must be [string], found value [numberField] type [number]" - ], + "query": "row var = to_cartesianpoint(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], "warning": [] }, { - "query": "from a_index | eval starts_with(stringField, stringField, extraArg)", + "query": "row var = to_cartesianpoint(to_string(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = to_cartesianpoint(true)", "error": [ - "Error: [starts_with] function expects exactly 2 arguments, got 3." + "Argument of [to_cartesianpoint] must be [cartesian_point], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort starts_with(stringField, stringField)", + "query": "from a_index | eval var = to_cartesianpoint(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = starts_with(to_string(true), to_string(true))", + "query": "from a_index | eval to_cartesianpoint(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = starts_with(true, true)", + "query": "from a_index | eval var = to_cartesianpoint(to_cartesianpoint(cartesianPointField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_cartesianpoint(booleanField)", "error": [ - "Argument of [starts_with] must be [string], found value [true] type [boolean]", - "Argument of [starts_with] must be [string], found value [true] type [boolean]" + "Argument of [to_cartesianpoint] must be [cartesian_point], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = starts_with(to_string(booleanField), to_string(booleanField))", + "query": "from a_index | eval var = to_cartesianpoint(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval starts_with(booleanField, booleanField)", + "query": "from a_index | eval to_cartesianpoint(cartesianPointField, extraArg)", "error": [ - "Argument of [starts_with] must be [string], found value [booleanField] type [boolean]", - "Argument of [starts_with] must be [string], found value [booleanField] type [boolean]" + "Error: [to_cartesianpoint] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | eval starts_with(null, null)", + "query": "from a_index | sort to_cartesianpoint(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval starts_with(nullVar, nullVar)", + "query": "from a_index | eval to_cartesianpoint(null)", "error": [], "warning": [] }, { - "query": "row var = substring(\"a\", 5, 5)", + "query": "row nullVar = null | eval to_cartesianpoint(nullVar)", "error": [], "warning": [] }, { - "query": "row var = substring(\"a\", 5)", + "query": "row var = to_cartesianshape(\"a\")", "error": [], "warning": [] }, { - "query": "row substring(\"a\", 5, 5)", + "query": "row to_cartesianshape(\"a\")", "error": [], "warning": [] }, { - "query": "row substring(\"a\", 5)", + "query": "from a_index | eval var = to_cartesianshape(stringField)", "error": [], "warning": [] }, { - "query": "row var = substring(to_string(\"a\"), to_integer(\"a\"), to_integer(\"a\"))", + "query": "from a_index | eval to_cartesianshape(stringField)", "error": [], "warning": [] }, { - "query": "row var = substring(5, \"a\", \"a\")", + "query": "from a_index | eval var = to_cartesianshape(*)", "error": [ - "Argument of [substring] must be [string], found value [5] type [number]", - "Argument of [substring] must be [number], found value [\"a\"] type [string]", - "Argument of [substring] must be [number], found value [\"a\"] type [string]" + "Using wildcards (*) in to_cartesianshape is not allowed" ], "warning": [] }, { - "query": "from a_index | where length(substring(stringField, numberField, numberField)) > 0", + "query": "from a_index | sort to_cartesianshape(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | where length(substring(numberField, stringField, stringField)) > 0", - "error": [ - "Argument of [substring] must be [string], found value [numberField] type [number]", - "Argument of [substring] must be [number], found value [stringField] type [string]", - "Argument of [substring] must be [number], found value [stringField] type [string]" - ], + "query": "row var = to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = substring(stringField, numberField, numberField)", + "query": "row to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval substring(stringField, numberField, numberField)", + "query": "row var = to_cartesianshape(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = substring(to_string(stringField), to_integer(stringField), to_integer(stringField))", + "query": "row var = to_cartesianshape(to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval substring(numberField, stringField, stringField)", - "error": [ - "Argument of [substring] must be [string], found value [numberField] type [number]", - "Argument of [substring] must be [number], found value [stringField] type [string]", - "Argument of [substring] must be [number], found value [stringField] type [string]" - ], + "query": "row to_cartesianshape(to_cartesianshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval substring(stringField, numberField, numberField, extraArg)", - "error": [ - "Error: [substring] function expects no more than 3 arguments, got 4." - ], + "query": "row var = to_cartesianshape(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "error": [], "warning": [] }, { - "query": "from a_index | sort substring(stringField, numberField, numberField)", + "query": "row var = to_cartesianshape(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | sort substring(stringField, numberField)", - "error": [], + "query": "row var = to_cartesianshape(true)", + "error": [ + "Argument of [to_cartesianshape] must be [cartesian_point], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row var = substring(to_string(true), to_integer(true), to_integer(true))", + "query": "from a_index | eval var = to_cartesianshape(cartesianPointField)", "error": [], "warning": [] }, { - "query": "row var = substring(true, true, true)", - "error": [ - "Argument of [substring] must be [string], found value [true] type [boolean]", - "Argument of [substring] must be [number], found value [true] type [boolean]", - "Argument of [substring] must be [number], found value [true] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | where length(substring(booleanField, booleanField, booleanField)) > 0", - "error": [ - "Argument of [substring] must be [string], found value [booleanField] type [boolean]", - "Argument of [substring] must be [number], found value [booleanField] type [boolean]", - "Argument of [substring] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval to_cartesianshape(cartesianPointField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = substring(to_string(booleanField), to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | eval var = to_cartesianshape(to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval substring(booleanField, booleanField, booleanField)", + "query": "from a_index | eval to_cartesianshape(booleanField)", "error": [ - "Argument of [substring] must be [string], found value [booleanField] type [boolean]", - "Argument of [substring] must be [number], found value [booleanField] type [boolean]", - "Argument of [substring] must be [number], found value [booleanField] type [boolean]" + "Argument of [to_cartesianshape] must be [cartesian_point], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval substring(null, null, null)", - "error": [], - "warning": [] - }, - { - "query": "row nullVar = null | eval substring(nullVar, nullVar, nullVar)", + "query": "from a_index | eval var = to_cartesianshape(cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = tan(5)", + "query": "from a_index | eval to_cartesianshape(cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row tan(5)", + "query": "from a_index | eval var = to_cartesianshape(to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "row var = tan(to_integer(\"a\"))", + "query": "from a_index | eval var = to_cartesianshape(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = tan(\"a\")", + "query": "from a_index | eval to_cartesianshape(cartesianPointField, extraArg)", "error": [ - "Argument of [tan] must be [number], found value [\"a\"] type [string]" + "Error: [to_cartesianshape] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | where tan(numberField) > 0", + "query": "from a_index | sort to_cartesianshape(cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | where tan(stringField) > 0", - "error": [ - "Argument of [tan] must be [number], found value [stringField] type [string]" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = tan(numberField)", + "query": "from a_index | eval to_cartesianshape(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval tan(numberField)", + "query": "row nullVar = null | eval to_cartesianshape(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = tan(to_integer(stringField))", + "query": "row var = to_datetime(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval tan(stringField)", - "error": [ - "Argument of [tan] must be [number], found value [stringField] type [string]" - ], + "query": "row to_datetime(\"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval tan(numberField, extraArg)", - "error": [ - "Error: [tan] function expects exactly one argument, got 2." - ], + "query": "row var = to_dt(\"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = tan(*)", - "error": [ - "Using wildcards (*) in tan is not allowed" - ], + "query": "from a_index | eval var = to_datetime(stringField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort tan(numberField)", + "query": "from a_index | eval to_datetime(stringField)", "error": [], "warning": [] }, { - "query": "row var = tan(to_integer(true))", + "query": "from a_index | eval var = to_dt(stringField)", "error": [], "warning": [] }, { - "query": "row var = tan(true)", + "query": "from a_index | eval var = to_datetime(*)", "error": [ - "Argument of [tan] must be [number], found value [true] type [boolean]" + "Using wildcards (*) in to_datetime is not allowed" ], "warning": [] }, { - "query": "from a_index | where tan(booleanField) > 0", - "error": [ - "Argument of [tan] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | sort to_datetime(stringField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = tan(to_integer(booleanField))", + "query": "row var = to_datetime(now())", "error": [], "warning": [] }, { - "query": "from a_index | eval tan(booleanField)", - "error": [ - "Argument of [tan] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row to_datetime(now())", + "error": [], "warning": [] }, { - "query": "from a_index | eval tan(null)", + "query": "row var = to_dt(now())", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval tan(nullVar)", + "query": "row var = to_datetime(to_datetime(now()))", "error": [], "warning": [] }, { - "query": "row var = tanh(5)", + "query": "row var = to_datetime(5)", "error": [], "warning": [] }, { - "query": "row tanh(5)", + "query": "row to_datetime(5)", "error": [], "warning": [] }, { - "query": "row var = tanh(to_integer(\"a\"))", + "query": "row var = to_dt(5)", "error": [], "warning": [] }, { - "query": "row var = tanh(\"a\")", - "error": [ - "Argument of [tanh] must be [number], found value [\"a\"] type [string]" - ], + "query": "row var = to_datetime(to_integer(true))", + "error": [], "warning": [] }, { - "query": "from a_index | where tanh(numberField) > 0", + "query": "row var = to_datetime(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | where tanh(stringField) > 0", + "query": "row var = to_datetime(true)", "error": [ - "Argument of [tanh] must be [number], found value [stringField] type [string]" + "Argument of [to_datetime] must be [date], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = tanh(numberField)", + "query": "from a_index | eval var = to_datetime(dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval tanh(numberField)", + "query": "from a_index | eval to_datetime(dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = tanh(to_integer(stringField))", + "query": "from a_index | eval var = to_dt(dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval tanh(stringField)", - "error": [ - "Argument of [tanh] must be [number], found value [stringField] type [string]" - ], - "warning": [] - }, - { - "query": "from a_index | eval tanh(numberField, extraArg)", - "error": [ - "Error: [tanh] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval var = to_datetime(to_datetime(dateField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = tanh(*)", + "query": "from a_index | eval to_datetime(booleanField)", "error": [ - "Using wildcards (*) in tanh is not allowed" + "Argument of [to_datetime] must be [date], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort tanh(numberField)", + "query": "from a_index | eval var = to_datetime(numberField)", "error": [], "warning": [] }, { - "query": "row var = tanh(to_integer(true))", + "query": "from a_index | eval to_datetime(numberField)", "error": [], "warning": [] }, { - "query": "row var = tanh(true)", - "error": [ - "Argument of [tanh] must be [number], found value [true] type [boolean]" - ], + "query": "from a_index | eval var = to_dt(numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | where tanh(booleanField) > 0", - "error": [ - "Argument of [tanh] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = to_datetime(to_integer(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = tanh(to_integer(booleanField))", + "query": "from a_index | eval var = to_datetime(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval tanh(booleanField)", + "query": "from a_index | eval to_datetime(dateField, extraArg)", "error": [ - "Argument of [tanh] must be [number], found value [booleanField] type [boolean]" + "Error: [to_datetime] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | eval tanh(null)", + "query": "from a_index | sort to_datetime(dateField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval tanh(nullVar)", + "query": "from a_index | eval to_datetime(null)", "error": [], "warning": [] }, { - "query": "row var = tau()", + "query": "row nullVar = null | eval to_datetime(nullVar)", "error": [], "warning": [] }, { - "query": "row tau()", + "query": "row var = to_degrees(5)", "error": [], "warning": [] }, { - "query": "from a_index | where tau() > 0", + "query": "row to_degrees(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = tau()", + "query": "row var = to_degrees(to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval tau()", + "query": "row var = to_degrees(\"a\")", + "error": [ + "Argument of [to_degrees] must be [number], found value [\"a\"] type [string]" + ], + "warning": [] + }, + { + "query": "from a_index | where to_degrees(numberField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval tau(extraArg)", + "query": "from a_index | where to_degrees(stringField) > 0", "error": [ - "Error: [tau] function expects exactly 0 arguments, got 1." + "Argument of [to_degrees] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | sort tau()", + "query": "from a_index | eval var = to_degrees(numberField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval tau()", + "query": "from a_index | eval to_degrees(numberField)", "error": [], "warning": [] }, { - "query": "row var = to_boolean(\"a\")", + "query": "from a_index | eval var = to_degrees(to_integer(stringField))", "error": [], "warning": [] }, { - "query": "row to_boolean(\"a\")", - "error": [], + "query": "from a_index | eval to_degrees(stringField)", + "error": [ + "Argument of [to_degrees] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "row var = to_bool(\"a\")", - "error": [], + "query": "from a_index | eval to_degrees(numberField, extraArg)", + "error": [ + "Error: [to_degrees] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | eval var = to_boolean(stringField)", - "error": [], + "query": "from a_index | eval var = to_degrees(*)", + "error": [ + "Using wildcards (*) in to_degrees is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval to_boolean(stringField)", + "query": "from a_index | sort to_degrees(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_bool(stringField)", + "query": "row var = to_degrees(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_boolean(*)", + "query": "row var = to_degrees(true)", "error": [ - "Using wildcards (*) in to_boolean is not allowed" + "Argument of [to_degrees] must be [number], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort to_boolean(stringField)", - "error": [], + "query": "from a_index | where to_degrees(booleanField) > 0", + "error": [ + "Argument of [to_degrees] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = to_boolean(true)", + "query": "from a_index | eval var = to_degrees(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row to_boolean(true)", - "error": [], + "query": "from a_index | eval to_degrees(booleanField)", + "error": [ + "Argument of [to_degrees] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = to_bool(true)", + "query": "from a_index | eval to_degrees(null)", "error": [], "warning": [] }, { - "query": "row var = to_boolean(to_boolean(true))", + "query": "row nullVar = null | eval to_degrees(nullVar)", "error": [], "warning": [] }, { - "query": "row var = to_boolean(5)", + "query": "row var = to_double(\"a\")", "error": [], "warning": [] }, { - "query": "row to_boolean(5)", + "query": "row to_double(\"a\")", "error": [], "warning": [] }, { - "query": "row var = to_bool(5)", + "query": "row var = to_dbl(\"a\")", "error": [], "warning": [] }, { - "query": "row var = to_boolean(to_integer(true))", + "query": "from a_index | eval var = to_double(stringField)", "error": [], "warning": [] }, { - "query": "row var = to_boolean(to_string(true))", + "query": "from a_index | eval to_double(stringField)", "error": [], "warning": [] }, { - "query": "row var = to_boolean(to_cartesianpoint(\"POINT (30 10)\"))", - "error": [ - "Argument of [to_boolean] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = to_boolean(booleanField)", + "query": "from a_index | eval var = to_dbl(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_boolean(booleanField)", - "error": [], + "query": "from a_index | eval var = to_double(*)", + "error": [ + "Using wildcards (*) in to_double is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval var = to_bool(booleanField)", + "query": "from a_index | sort to_double(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_boolean(to_boolean(booleanField))", + "query": "row var = to_double(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_boolean(cartesianPointField)", - "error": [ - "Argument of [to_boolean] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = to_boolean(numberField)", + "query": "row to_double(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_boolean(numberField)", + "query": "row var = to_dbl(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_bool(numberField)", + "query": "row var = to_double(to_boolean(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_boolean(to_integer(booleanField))", + "query": "row var = to_double(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_boolean(to_string(booleanField))", + "query": "row to_double(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_boolean(booleanField, extraArg)", - "error": [ - "Error: [to_boolean] function expects exactly one argument, got 2." - ], - "warning": [] - }, - { - "query": "from a_index | sort to_boolean(booleanField)", + "query": "row var = to_dbl(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_boolean(null)", + "query": "row var = to_double(to_integer(true))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_boolean(nullVar)", + "query": "row var = to_double(now())", "error": [], "warning": [] }, { - "query": "row var = to_cartesianpoint(\"a\")", + "query": "row to_double(now())", "error": [], "warning": [] }, { - "query": "row to_cartesianpoint(\"a\")", + "query": "row var = to_dbl(now())", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianpoint(stringField)", + "query": "row var = to_double(to_datetime(now()))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianpoint(stringField)", + "query": "row var = to_double(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianpoint(*)", + "query": "row var = to_double(to_cartesianpoint(\"POINT (30 10)\"))", "error": [ - "Using wildcards (*) in to_cartesianpoint is not allowed" + "Argument of [to_double] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | sort to_cartesianpoint(stringField)", + "query": "from a_index | where to_double(booleanField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | where to_double(cartesianPointField) > 0", + "error": [ + "Argument of [to_double] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], "warning": [] }, { - "query": "row to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | where to_double(numberField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_cartesianpoint(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | where to_double(dateField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_cartesianpoint(to_string(true))", + "query": "from a_index | where to_double(stringField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_cartesianpoint(true)", - "error": [ - "Argument of [to_cartesianpoint] must be [cartesian_point], found value [true] type [boolean]" - ], + "query": "from a_index | eval var = to_double(booleanField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianpoint(cartesianPointField)", + "query": "from a_index | eval to_double(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianpoint(cartesianPointField)", + "query": "from a_index | eval var = to_dbl(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianpoint(to_cartesianpoint(cartesianPointField))", + "query": "from a_index | eval var = to_double(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianpoint(booleanField)", + "query": "from a_index | eval to_double(cartesianPointField)", "error": [ - "Argument of [to_cartesianpoint] must be [cartesian_point], found value [booleanField] type [boolean]" + "Argument of [to_double] must be [boolean], found value [cartesianPointField] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianpoint(to_string(booleanField))", + "query": "from a_index | eval var = to_double(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianpoint(cartesianPointField, extraArg)", - "error": [ - "Error: [to_cartesianpoint] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval to_double(numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_cartesianpoint(cartesianPointField)", + "query": "from a_index | eval var = to_dbl(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianpoint(null)", + "query": "from a_index | eval var = to_double(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_cartesianpoint(nullVar)", + "query": "from a_index | eval var = to_double(dateField)", "error": [], "warning": [] }, { - "query": "row var = to_cartesianshape(\"a\")", + "query": "from a_index | eval to_double(dateField)", "error": [], "warning": [] }, { - "query": "row to_cartesianshape(\"a\")", + "query": "from a_index | eval var = to_dbl(dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianshape(stringField)", + "query": "from a_index | eval var = to_double(to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianshape(stringField)", + "query": "from a_index | eval var = to_double(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianshape(*)", + "query": "from a_index | eval to_double(booleanField, extraArg)", "error": [ - "Using wildcards (*) in to_cartesianshape is not allowed" + "Error: [to_double] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | sort to_cartesianshape(stringField)", + "query": "from a_index | sort to_double(booleanField)", "error": [], "warning": [] }, { - "query": "row var = to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval to_double(null)", "error": [], "warning": [] }, { - "query": "row to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row nullVar = null | eval to_double(nullVar)", "error": [], "warning": [] }, { - "query": "row var = to_cartesianshape(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "row var = to_geopoint(\"a\")", "error": [], "warning": [] }, { - "query": "row var = to_cartesianshape(to_cartesianshape(\"POINT (30 10)\"))", + "query": "row to_geopoint(\"a\")", "error": [], "warning": [] }, { - "query": "row to_cartesianshape(to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = to_geopoint(stringField)", "error": [], "warning": [] }, { - "query": "row var = to_cartesianshape(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval to_geopoint(stringField)", "error": [], "warning": [] }, { - "query": "row var = to_cartesianshape(to_string(true))", + "query": "from a_index | eval var = to_geopoint(*)", + "error": [ + "Using wildcards (*) in to_geopoint is not allowed" + ], + "warning": [] + }, + { + "query": "from a_index | sort to_geopoint(stringField)", "error": [], "warning": [] }, { - "query": "row var = to_cartesianshape(true)", - "error": [ - "Argument of [to_cartesianshape] must be [cartesian_point], found value [true] type [boolean]" - ], + "query": "row var = to_geopoint(to_geopoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianshape(cartesianPointField)", + "query": "row to_geopoint(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianshape(cartesianPointField)", + "query": "row var = to_geopoint(to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianshape(to_cartesianpoint(cartesianPointField))", + "query": "row var = to_geopoint(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianshape(booleanField)", + "query": "row var = to_geopoint(true)", "error": [ - "Argument of [to_cartesianshape] must be [cartesian_point], found value [booleanField] type [boolean]" + "Argument of [to_geopoint] must be [geo_point], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianshape(cartesianShapeField)", + "query": "from a_index | eval var = to_geopoint(geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianshape(cartesianShapeField)", + "query": "from a_index | eval to_geopoint(geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianshape(to_cartesianshape(cartesianPointField))", + "query": "from a_index | eval var = to_geopoint(to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_cartesianshape(to_string(booleanField))", + "query": "from a_index | eval to_geopoint(booleanField)", + "error": [ + "Argument of [to_geopoint] must be [geo_point], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_geopoint(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianshape(cartesianPointField, extraArg)", + "query": "from a_index | eval to_geopoint(geoPointField, extraArg)", "error": [ - "Error: [to_cartesianshape] function expects exactly one argument, got 2." + "Error: [to_geopoint] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | sort to_cartesianshape(cartesianPointField)", + "query": "from a_index | sort to_geopoint(geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_cartesianshape(null)", + "query": "from a_index | eval to_geopoint(null)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_cartesianshape(nullVar)", + "query": "row nullVar = null | eval to_geopoint(nullVar)", "error": [], "warning": [] }, { - "query": "row var = to_datetime(\"a\")", + "query": "row var = to_geoshape(\"a\")", "error": [], "warning": [] }, { - "query": "row to_datetime(\"a\")", + "query": "row to_geoshape(\"a\")", "error": [], "warning": [] }, { - "query": "row var = to_dt(\"a\")", + "query": "from a_index | eval var = to_geoshape(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_datetime(stringField)", + "query": "from a_index | eval to_geoshape(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_datetime(stringField)", - "error": [], + "query": "from a_index | eval var = to_geoshape(*)", + "error": [ + "Using wildcards (*) in to_geoshape is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval var = to_dt(stringField)", + "query": "from a_index | sort to_geoshape(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_datetime(*)", - "error": [ - "Using wildcards (*) in to_datetime is not allowed" - ], + "query": "row var = to_geoshape(to_geopoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_datetime(stringField)", + "query": "row to_geoshape(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = to_datetime(now())", + "query": "row var = to_geoshape(to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row to_datetime(now())", + "query": "row var = to_geoshape(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = to_dt(now())", + "query": "row to_geoshape(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = to_datetime(to_datetime(now()))", + "query": "row var = to_geoshape(to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = to_datetime(5)", + "query": "row var = to_geoshape(to_string(true))", "error": [], "warning": [] }, { - "query": "row to_datetime(5)", - "error": [], + "query": "row var = to_geoshape(true)", + "error": [ + "Argument of [to_geoshape] must be [geo_point], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row var = to_dt(5)", + "query": "from a_index | eval var = to_geoshape(geoPointField)", "error": [], "warning": [] }, { - "query": "row var = to_datetime(to_integer(true))", + "query": "from a_index | eval to_geoshape(geoPointField)", "error": [], "warning": [] }, { - "query": "row var = to_datetime(to_string(true))", + "query": "from a_index | eval var = to_geoshape(to_geopoint(geoPointField))", "error": [], "warning": [] }, { - "query": "row var = to_datetime(true)", + "query": "from a_index | eval to_geoshape(booleanField)", "error": [ - "Argument of [to_datetime] must be [date], found value [true] type [boolean]" + "Argument of [to_geoshape] must be [geo_point], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = to_datetime(dateField)", + "query": "from a_index | eval var = to_geoshape(geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_datetime(dateField)", + "query": "from a_index | eval to_geoshape(geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_dt(dateField)", + "query": "from a_index | eval var = to_geoshape(to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_datetime(to_datetime(dateField))", + "query": "from a_index | eval var = to_geoshape(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_datetime(booleanField)", + "query": "from a_index | eval to_geoshape(geoPointField, extraArg)", "error": [ - "Argument of [to_datetime] must be [date], found value [booleanField] type [boolean]" + "Error: [to_geoshape] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | eval var = to_datetime(numberField)", + "query": "from a_index | sort to_geoshape(geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_datetime(numberField)", + "query": "from a_index | eval to_geoshape(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_dt(numberField)", + "query": "row nullVar = null | eval to_geoshape(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_datetime(to_integer(booleanField))", + "query": "row var = to_integer(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_datetime(to_string(booleanField))", + "query": "row to_integer(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval to_datetime(dateField, extraArg)", - "error": [ - "Error: [to_datetime] function expects exactly one argument, got 2." - ], + "query": "row var = to_int(\"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_datetime(dateField)", + "query": "from a_index | eval var = to_integer(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_datetime(null)", + "query": "from a_index | eval to_integer(stringField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_datetime(nullVar)", + "query": "from a_index | eval var = to_int(stringField)", "error": [], "warning": [] }, { - "query": "row var = to_degrees(5)", - "error": [], + "query": "from a_index | eval var = to_integer(*)", + "error": [ + "Using wildcards (*) in to_integer is not allowed" + ], "warning": [] }, { - "query": "row to_degrees(5)", + "query": "from a_index | sort to_integer(stringField)", "error": [], "warning": [] }, { - "query": "row var = to_degrees(to_integer(\"a\"))", + "query": "row var = to_integer(true)", "error": [], "warning": [] }, { - "query": "row var = to_degrees(\"a\")", - "error": [ - "Argument of [to_degrees] must be [number], found value [\"a\"] type [string]" - ], + "query": "row to_integer(true)", + "error": [], "warning": [] }, { - "query": "from a_index | where to_degrees(numberField) > 0", + "query": "row var = to_int(true)", "error": [], "warning": [] }, { - "query": "from a_index | where to_degrees(stringField) > 0", - "error": [ - "Argument of [to_degrees] must be [number], found value [stringField] type [string]" - ], + "query": "row var = to_integer(to_boolean(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_degrees(numberField)", + "query": "row var = to_integer(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_degrees(numberField)", + "query": "row to_integer(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_degrees(to_integer(stringField))", + "query": "row var = to_int(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_degrees(stringField)", - "error": [ - "Argument of [to_degrees] must be [number], found value [stringField] type [string]" - ], + "query": "row var = to_integer(to_integer(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval to_degrees(numberField, extraArg)", - "error": [ - "Error: [to_degrees] function expects exactly one argument, got 2." - ], + "query": "row var = to_integer(now())", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_degrees(*)", - "error": [ - "Using wildcards (*) in to_degrees is not allowed" - ], + "query": "row to_integer(now())", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_degrees(numberField)", + "query": "row var = to_int(now())", "error": [], "warning": [] }, { - "query": "row var = to_degrees(to_integer(true))", + "query": "row var = to_integer(to_datetime(now()))", "error": [], "warning": [] }, { - "query": "row var = to_degrees(true)", - "error": [ - "Argument of [to_degrees] must be [number], found value [true] type [boolean]" - ], + "query": "row var = to_integer(to_string(true))", + "error": [], "warning": [] }, { - "query": "from a_index | where to_degrees(booleanField) > 0", + "query": "row var = to_integer(to_cartesianpoint(\"POINT (30 10)\"))", "error": [ - "Argument of [to_degrees] must be [number], found value [booleanField] type [boolean]" + "Argument of [to_integer] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | eval var = to_degrees(to_integer(booleanField))", + "query": "from a_index | where to_integer(booleanField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval to_degrees(booleanField)", + "query": "from a_index | where to_integer(cartesianPointField) > 0", "error": [ - "Argument of [to_degrees] must be [number], found value [booleanField] type [boolean]" + "Argument of [to_integer] must be [boolean], found value [cartesianPointField] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | eval to_degrees(null)", + "query": "from a_index | where to_integer(numberField) > 0", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_degrees(nullVar)", + "query": "from a_index | where to_integer(dateField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_double(\"a\")", + "query": "from a_index | where to_integer(stringField) > 0", "error": [], "warning": [] }, { - "query": "row to_double(\"a\")", + "query": "from a_index | eval var = to_integer(booleanField)", "error": [], "warning": [] }, { - "query": "row var = to_dbl(\"a\")", + "query": "from a_index | eval to_integer(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_double(stringField)", + "query": "from a_index | eval var = to_int(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_double(stringField)", + "query": "from a_index | eval var = to_integer(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_dbl(stringField)", + "query": "from a_index | eval to_integer(cartesianPointField)", + "error": [ + "Argument of [to_integer] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], + "warning": [] + }, + { + "query": "from a_index | eval var = to_integer(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_double(*)", - "error": [ - "Using wildcards (*) in to_double is not allowed" - ], + "query": "from a_index | eval to_integer(numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_double(stringField)", + "query": "from a_index | eval var = to_int(numberField)", "error": [], "warning": [] }, { - "query": "row var = to_double(true)", + "query": "from a_index | eval var = to_integer(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row to_double(true)", + "query": "from a_index | eval var = to_integer(dateField)", "error": [], "warning": [] }, { - "query": "row var = to_dbl(true)", + "query": "from a_index | eval to_integer(dateField)", "error": [], "warning": [] }, { - "query": "row var = to_double(to_boolean(true))", + "query": "from a_index | eval var = to_int(dateField)", "error": [], "warning": [] }, { - "query": "row var = to_double(5)", + "query": "from a_index | eval var = to_integer(to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "row to_double(5)", + "query": "from a_index | eval var = to_integer(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_dbl(5)", + "query": "from a_index | eval to_integer(booleanField, extraArg)", + "error": [ + "Error: [to_integer] function expects exactly one argument, got 2." + ], + "warning": [] + }, + { + "query": "from a_index | sort to_integer(booleanField)", "error": [], "warning": [] }, { - "query": "row var = to_double(to_integer(true))", + "query": "from a_index | eval to_integer(null)", "error": [], "warning": [] }, { - "query": "row var = to_double(now())", + "query": "row nullVar = null | eval to_integer(nullVar)", "error": [], "warning": [] }, { - "query": "row to_double(now())", + "query": "row var = to_ip(\"a\")", "error": [], "warning": [] }, { - "query": "row var = to_dbl(now())", + "query": "row to_ip(\"a\")", "error": [], "warning": [] }, { - "query": "row var = to_double(to_datetime(now()))", + "query": "from a_index | eval var = to_ip(stringField)", "error": [], "warning": [] }, { - "query": "row var = to_double(to_string(true))", + "query": "from a_index | eval to_ip(stringField)", "error": [], "warning": [] }, { - "query": "row var = to_double(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = to_ip(*)", "error": [ - "Argument of [to_double] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + "Using wildcards (*) in to_ip is not allowed" ], "warning": [] }, { - "query": "from a_index | where to_double(booleanField) > 0", + "query": "from a_index | sort to_ip(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | where to_double(cartesianPointField) > 0", - "error": [ - "Argument of [to_double] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "row var = to_ip(to_ip(\"127.0.0.1\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where to_double(numberField) > 0", + "query": "row to_ip(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "from a_index | where to_double(dateField) > 0", + "query": "row var = to_ip(to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "from a_index | where to_double(stringField) > 0", + "query": "row var = to_ip(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_double(booleanField)", - "error": [], + "query": "row var = to_ip(true)", + "error": [ + "Argument of [to_ip] must be [ip], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval to_double(booleanField)", + "query": "from a_index | eval var = to_ip(ipField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_dbl(booleanField)", + "query": "from a_index | eval to_ip(ipField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_double(to_boolean(booleanField))", + "query": "from a_index | eval var = to_ip(to_ip(ipField))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_double(cartesianPointField)", + "query": "from a_index | eval to_ip(booleanField)", "error": [ - "Argument of [to_double] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Argument of [to_ip] must be [ip], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = to_double(numberField)", + "query": "from a_index | eval var = to_ip(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_double(numberField)", - "error": [], + "query": "from a_index | eval to_ip(ipField, extraArg)", + "error": [ + "Error: [to_ip] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | eval var = to_dbl(numberField)", + "query": "from a_index | sort to_ip(ipField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_double(to_integer(booleanField))", + "query": "from a_index | eval to_ip(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_double(dateField)", + "query": "row nullVar = null | eval to_ip(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_double(dateField)", + "query": "row var = to_long(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_dbl(dateField)", + "query": "row to_long(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_double(to_datetime(dateField))", + "query": "from a_index | eval var = to_long(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_double(to_string(booleanField))", + "query": "from a_index | eval to_long(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_double(booleanField, extraArg)", + "query": "from a_index | eval var = to_long(*)", "error": [ - "Error: [to_double] function expects exactly one argument, got 2." + "Using wildcards (*) in to_long is not allowed" ], "warning": [] }, { - "query": "from a_index | sort to_double(booleanField)", + "query": "from a_index | sort to_long(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_double(null)", + "query": "row var = to_long(true)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_double(nullVar)", + "query": "row to_long(true)", "error": [], "warning": [] }, { - "query": "row var = to_geopoint(\"a\")", + "query": "row var = to_long(to_boolean(true))", "error": [], "warning": [] }, { - "query": "row to_geopoint(\"a\")", + "query": "row var = to_long(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_geopoint(stringField)", + "query": "row to_long(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_geopoint(stringField)", + "query": "row var = to_long(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_geopoint(*)", - "error": [ - "Using wildcards (*) in to_geopoint is not allowed" - ], + "query": "row var = to_long(now())", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_geopoint(stringField)", + "query": "row to_long(now())", "error": [], "warning": [] }, { - "query": "row var = to_geopoint(to_geopoint(\"POINT (30 10)\"))", + "query": "row var = to_long(to_datetime(now()))", "error": [], "warning": [] }, { - "query": "row to_geopoint(to_geopoint(\"POINT (30 10)\"))", + "query": "row var = to_long(to_string(true))", "error": [], "warning": [] }, { - "query": "row var = to_geopoint(to_geopoint(to_geopoint(\"POINT (30 10)\")))", - "error": [], + "query": "row var = to_long(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [ + "Argument of [to_long] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + ], "warning": [] }, { - "query": "row var = to_geopoint(to_string(true))", + "query": "from a_index | where to_long(booleanField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_geopoint(true)", + "query": "from a_index | where to_long(cartesianPointField) > 0", "error": [ - "Argument of [to_geopoint] must be [geo_point], found value [true] type [boolean]" + "Argument of [to_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | eval var = to_geopoint(geoPointField)", + "query": "from a_index | where to_long(numberField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval to_geopoint(geoPointField)", + "query": "from a_index | where to_long(dateField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_geopoint(to_geopoint(geoPointField))", + "query": "from a_index | where to_long(stringField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval to_geopoint(booleanField)", - "error": [ - "Argument of [to_geopoint] must be [geo_point], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = to_long(booleanField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_geopoint(to_string(booleanField))", + "query": "from a_index | eval to_long(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_geopoint(geoPointField, extraArg)", + "query": "from a_index | eval var = to_long(to_boolean(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval to_long(cartesianPointField)", "error": [ - "Error: [to_geopoint] function expects exactly one argument, got 2." + "Argument of [to_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]" ], "warning": [] }, { - "query": "from a_index | sort to_geopoint(geoPointField)", + "query": "from a_index | eval var = to_long(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_geopoint(null)", + "query": "from a_index | eval to_long(numberField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_geopoint(nullVar)", + "query": "from a_index | eval var = to_long(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_geoshape(\"a\")", + "query": "from a_index | eval var = to_long(dateField)", "error": [], "warning": [] }, { - "query": "row to_geoshape(\"a\")", + "query": "from a_index | eval to_long(dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_geoshape(stringField)", + "query": "from a_index | eval var = to_long(to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_geoshape(stringField)", + "query": "from a_index | eval var = to_long(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_geoshape(*)", + "query": "from a_index | eval to_long(booleanField, extraArg)", "error": [ - "Using wildcards (*) in to_geoshape is not allowed" + "Error: [to_long] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | sort to_geoshape(stringField)", + "query": "from a_index | sort to_long(booleanField)", "error": [], "warning": [] }, { - "query": "row var = to_geoshape(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval to_long(null)", "error": [], "warning": [] }, { - "query": "row to_geoshape(to_geopoint(\"POINT (30 10)\"))", + "query": "row nullVar = null | eval to_long(nullVar)", "error": [], "warning": [] }, { - "query": "row var = to_geoshape(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "row var = to_lower(\"a\")", "error": [], "warning": [] }, { - "query": "row var = to_geoshape(to_geoshape(\"POINT (30 10)\"))", + "query": "row to_lower(\"a\")", "error": [], "warning": [] }, { - "query": "row to_geoshape(to_geoshape(\"POINT (30 10)\"))", + "query": "row var = to_lower(to_string(\"a\"))", "error": [], "warning": [] }, { - "query": "row var = to_geoshape(to_geoshape(to_geopoint(\"POINT (30 10)\")))", - "error": [], + "query": "row var = to_lower(5)", + "error": [ + "Argument of [to_lower] must be [string], found value [5] type [number]" + ], "warning": [] }, { - "query": "row var = to_geoshape(to_string(true))", + "query": "from a_index | where length(to_lower(stringField)) > 0", "error": [], "warning": [] }, { - "query": "row var = to_geoshape(true)", + "query": "from a_index | where length(to_lower(numberField)) > 0", "error": [ - "Argument of [to_geoshape] must be [geo_point], found value [true] type [boolean]" + "Argument of [to_lower] must be [string], found value [numberField] type [number]" ], "warning": [] }, { - "query": "from a_index | eval var = to_geoshape(geoPointField)", + "query": "from a_index | eval var = to_lower(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_geoshape(geoPointField)", + "query": "from a_index | eval to_lower(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_geoshape(to_geopoint(geoPointField))", + "query": "from a_index | eval var = to_lower(to_string(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_geoshape(booleanField)", + "query": "from a_index | eval to_lower(numberField)", "error": [ - "Argument of [to_geoshape] must be [geo_point], found value [booleanField] type [boolean]" + "Argument of [to_lower] must be [string], found value [numberField] type [number]" ], "warning": [] }, { - "query": "from a_index | eval var = to_geoshape(geoShapeField)", - "error": [], + "query": "from a_index | eval to_lower(stringField, extraArg)", + "error": [ + "Error: [to_lower] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | eval to_geoshape(geoShapeField)", - "error": [], + "query": "from a_index | eval var = to_lower(*)", + "error": [ + "Using wildcards (*) in to_lower is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval var = to_geoshape(to_geoshape(geoPointField))", + "query": "from a_index | sort to_lower(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_geoshape(to_string(booleanField))", + "query": "row var = to_lower(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_geoshape(geoPointField, extraArg)", + "query": "row var = to_lower(true)", "error": [ - "Error: [to_geoshape] function expects exactly one argument, got 2." + "Argument of [to_lower] must be [string], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | sort to_geoshape(geoPointField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval to_geoshape(null)", - "error": [], + "query": "from a_index | where length(to_lower(booleanField)) > 0", + "error": [ + "Argument of [to_lower] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row nullVar = null | eval to_geoshape(nullVar)", + "query": "from a_index | eval var = to_lower(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_integer(\"a\")", - "error": [], + "query": "from a_index | eval to_lower(booleanField)", + "error": [ + "Argument of [to_lower] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row to_integer(\"a\")", + "query": "from a_index | eval to_lower(null)", "error": [], "warning": [] }, { - "query": "row var = to_int(\"a\")", + "query": "row nullVar = null | eval to_lower(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_integer(stringField)", + "query": "row var = to_radians(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_integer(stringField)", + "query": "row to_radians(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_int(stringField)", + "query": "row var = to_radians(to_integer(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_integer(*)", + "query": "row var = to_radians(\"a\")", "error": [ - "Using wildcards (*) in to_integer is not allowed" + "Argument of [to_radians] must be [number], found value [\"a\"] type [string]" ], "warning": [] }, { - "query": "from a_index | sort to_integer(stringField)", - "error": [], - "warning": [] - }, - { - "query": "row var = to_integer(true)", + "query": "from a_index | where to_radians(numberField) > 0", "error": [], "warning": [] }, { - "query": "row to_integer(true)", - "error": [], + "query": "from a_index | where to_radians(stringField) > 0", + "error": [ + "Argument of [to_radians] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "row var = to_int(true)", + "query": "from a_index | eval var = to_radians(numberField)", "error": [], "warning": [] }, { - "query": "row var = to_integer(to_boolean(true))", + "query": "from a_index | eval to_radians(numberField)", "error": [], "warning": [] }, { - "query": "row var = to_integer(5)", + "query": "from a_index | eval var = to_radians(to_integer(stringField))", "error": [], "warning": [] }, { - "query": "row to_integer(5)", - "error": [], + "query": "from a_index | eval to_radians(stringField)", + "error": [ + "Argument of [to_radians] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "row var = to_int(5)", - "error": [], + "query": "from a_index | eval to_radians(numberField, extraArg)", + "error": [ + "Error: [to_radians] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "row var = to_integer(to_integer(true))", - "error": [], + "query": "from a_index | eval var = to_radians(*)", + "error": [ + "Using wildcards (*) in to_radians is not allowed" + ], "warning": [] }, { - "query": "row var = to_integer(now())", + "query": "from a_index | sort to_radians(numberField)", "error": [], "warning": [] }, { - "query": "row to_integer(now())", + "query": "row var = to_radians(to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = to_int(now())", - "error": [], + "query": "row var = to_radians(true)", + "error": [ + "Argument of [to_radians] must be [number], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row var = to_integer(to_datetime(now()))", - "error": [], + "query": "from a_index | where to_radians(booleanField) > 0", + "error": [ + "Argument of [to_radians] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = to_integer(to_string(true))", + "query": "from a_index | eval var = to_radians(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_integer(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval to_radians(booleanField)", "error": [ - "Argument of [to_integer] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + "Argument of [to_radians] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where to_integer(booleanField) > 0", + "query": "from a_index | eval to_radians(null)", "error": [], "warning": [] }, { - "query": "from a_index | where to_integer(cartesianPointField) > 0", - "error": [ - "Argument of [to_integer] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], - "warning": [] - }, - { - "query": "from a_index | where to_integer(numberField) > 0", + "query": "row nullVar = null | eval to_radians(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | where to_integer(dateField) > 0", + "query": "row var = to_string(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | where to_integer(stringField) > 0", + "query": "row to_string(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_integer(booleanField)", + "query": "row var = to_str(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval to_integer(booleanField)", + "query": "from a_index | eval var = to_string(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_int(booleanField)", + "query": "from a_index | eval to_string(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_integer(to_boolean(booleanField))", + "query": "from a_index | eval var = to_str(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_integer(cartesianPointField)", + "query": "from a_index | eval var = to_string(*)", "error": [ - "Argument of [to_integer] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Using wildcards (*) in to_string is not allowed" ], "warning": [] }, { - "query": "from a_index | eval var = to_integer(numberField)", + "query": "from a_index | sort to_string(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_integer(numberField)", + "query": "row var = to_string(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_int(numberField)", + "query": "row to_string(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_integer(to_integer(booleanField))", + "query": "row var = to_str(true)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_integer(dateField)", + "query": "row var = to_string(to_boolean(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_integer(dateField)", + "query": "row var = to_string(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_int(dateField)", + "query": "row to_string(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_integer(to_datetime(dateField))", + "query": "row var = to_str(to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_integer(to_string(booleanField))", + "query": "row var = to_string(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_integer(booleanField, extraArg)", - "error": [ - "Error: [to_integer] function expects exactly one argument, got 2." - ], + "query": "row var = to_string(to_cartesianshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_integer(booleanField)", + "query": "row to_string(to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_integer(null)", + "query": "row var = to_str(to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_integer(nullVar)", + "query": "row var = to_string(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = to_ip(\"a\")", + "query": "row var = to_string(now())", "error": [], "warning": [] }, { - "query": "row to_ip(\"a\")", + "query": "row to_string(now())", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ip(stringField)", + "query": "row var = to_str(now())", "error": [], "warning": [] }, { - "query": "from a_index | eval to_ip(stringField)", + "query": "row var = to_string(to_datetime(now()))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ip(*)", - "error": [ - "Using wildcards (*) in to_ip is not allowed" - ], + "query": "row var = to_string(5)", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_ip(stringField)", + "query": "row to_string(5)", "error": [], "warning": [] }, { - "query": "row var = to_ip(to_ip(\"127.0.0.1\"))", + "query": "row var = to_str(5)", "error": [], "warning": [] }, { - "query": "row to_ip(to_ip(\"127.0.0.1\"))", + "query": "row var = to_string(to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = to_ip(to_ip(to_ip(\"127.0.0.1\")))", + "query": "row var = to_string(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = to_ip(to_string(true))", + "query": "row to_string(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = to_ip(true)", - "error": [ - "Argument of [to_ip] must be [ip], found value [true] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = to_ip(ipField)", + "query": "row var = to_str(to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_ip(ipField)", + "query": "row var = to_string(to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ip(to_ip(ipField))", + "query": "row var = to_string(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_ip(booleanField)", - "error": [ - "Argument of [to_ip] must be [ip], found value [booleanField] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = to_ip(to_string(booleanField))", + "query": "row to_string(to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_ip(ipField, extraArg)", - "error": [ - "Error: [to_ip] function expects exactly one argument, got 2." - ], + "query": "row var = to_str(to_geoshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_ip(ipField)", + "query": "row var = to_string(to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_ip(null)", + "query": "row var = to_string(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_ip(nullVar)", + "query": "row to_string(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "row var = to_long(\"a\")", + "query": "row var = to_str(to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "row to_long(\"a\")", + "query": "row var = to_string(to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_long(stringField)", + "query": "row var = to_string(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_long(stringField)", + "query": "row var = to_string(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_long(*)", - "error": [ - "Using wildcards (*) in to_long is not allowed" - ], + "query": "row to_string(to_version(\"1.0.0\"))", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_long(stringField)", + "query": "row var = to_str(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "row var = to_long(true)", + "query": "row var = to_string(to_version(\"a\"))", "error": [], "warning": [] }, { - "query": "row to_long(true)", + "query": "from a_index | where length(to_string(booleanField)) > 0", "error": [], "warning": [] }, { - "query": "row var = to_long(to_boolean(true))", + "query": "from a_index | where length(to_string(cartesianPointField)) > 0", "error": [], "warning": [] }, { - "query": "row var = to_long(5)", + "query": "from a_index | where length(to_string(cartesianShapeField)) > 0", "error": [], "warning": [] }, { - "query": "row to_long(5)", + "query": "from a_index | where length(to_string(dateField)) > 0", "error": [], "warning": [] }, { - "query": "row var = to_long(to_integer(true))", + "query": "from a_index | where length(to_string(numberField)) > 0", "error": [], "warning": [] }, { - "query": "row var = to_long(now())", + "query": "from a_index | where length(to_string(geoPointField)) > 0", "error": [], "warning": [] }, { - "query": "row to_long(now())", + "query": "from a_index | where length(to_string(geoShapeField)) > 0", "error": [], "warning": [] }, { - "query": "row var = to_long(to_datetime(now()))", + "query": "from a_index | where length(to_string(ipField)) > 0", "error": [], "warning": [] }, { - "query": "row var = to_long(to_string(true))", + "query": "from a_index | where length(to_string(stringField)) > 0", "error": [], "warning": [] }, { - "query": "row var = to_long(to_cartesianpoint(\"POINT (30 10)\"))", - "error": [ - "Argument of [to_long] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" - ], + "query": "from a_index | where length(to_string(versionField)) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | where to_long(booleanField) > 0", + "query": "from a_index | eval var = to_string(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | where to_long(cartesianPointField) > 0", - "error": [ - "Argument of [to_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "from a_index | eval to_string(booleanField)", + "error": [], "warning": [] }, { - "query": "from a_index | where to_long(numberField) > 0", + "query": "from a_index | eval var = to_str(booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | where to_long(dateField) > 0", + "query": "from a_index | eval var = to_string(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | where to_long(stringField) > 0", + "query": "from a_index | eval var = to_string(cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_long(booleanField)", + "query": "from a_index | eval to_string(cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_long(booleanField)", + "query": "from a_index | eval var = to_str(cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_long(to_boolean(booleanField))", + "query": "from a_index | eval var = to_string(to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_long(cartesianPointField)", - "error": [ - "Argument of [to_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "from a_index | eval var = to_string(cartesianShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_long(numberField)", + "query": "from a_index | eval to_string(cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_long(numberField)", + "query": "from a_index | eval var = to_str(cartesianShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_long(to_integer(booleanField))", + "query": "from a_index | eval var = to_string(to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_long(dateField)", + "query": "from a_index | eval var = to_string(dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_long(dateField)", + "query": "from a_index | eval to_string(dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_long(to_datetime(dateField))", + "query": "from a_index | eval var = to_str(dateField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_long(to_string(booleanField))", + "query": "from a_index | eval var = to_string(to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_long(booleanField, extraArg)", - "error": [ - "Error: [to_long] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval var = to_string(numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_long(booleanField)", + "query": "from a_index | eval to_string(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_long(null)", + "query": "from a_index | eval var = to_str(numberField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_long(nullVar)", + "query": "from a_index | eval var = to_string(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_lower(\"a\")", + "query": "from a_index | eval var = to_string(geoPointField)", "error": [], "warning": [] }, { - "query": "row to_lower(\"a\")", + "query": "from a_index | eval to_string(geoPointField)", "error": [], "warning": [] }, { - "query": "row var = to_lower(to_string(\"a\"))", + "query": "from a_index | eval var = to_str(geoPointField)", "error": [], "warning": [] }, { - "query": "row var = to_lower(5)", - "error": [ - "Argument of [to_lower] must be [string], found value [5] type [number]" - ], + "query": "from a_index | eval var = to_string(to_geopoint(geoPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | where length(to_lower(stringField)) > 0", + "query": "from a_index | eval var = to_string(geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | where length(to_lower(numberField)) > 0", - "error": [ - "Argument of [to_lower] must be [string], found value [numberField] type [number]" - ], + "query": "from a_index | eval to_string(geoShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_lower(stringField)", + "query": "from a_index | eval var = to_str(geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_lower(stringField)", + "query": "from a_index | eval var = to_string(to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_lower(to_string(stringField))", + "query": "from a_index | eval var = to_string(ipField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_lower(numberField)", - "error": [ - "Argument of [to_lower] must be [string], found value [numberField] type [number]" - ], + "query": "from a_index | eval to_string(ipField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval to_lower(stringField, extraArg)", - "error": [ - "Error: [to_lower] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval var = to_str(ipField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_lower(*)", - "error": [ - "Using wildcards (*) in to_lower is not allowed" - ], + "query": "from a_index | eval var = to_string(to_ip(ipField))", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_lower(stringField)", + "query": "from a_index | eval var = to_string(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_lower(to_string(true))", + "query": "from a_index | eval var = to_string(versionField)", "error": [], "warning": [] }, { - "query": "row var = to_lower(true)", - "error": [ - "Argument of [to_lower] must be [string], found value [true] type [boolean]" - ], + "query": "from a_index | eval to_string(versionField)", + "error": [], "warning": [] }, { - "query": "from a_index | where length(to_lower(booleanField)) > 0", - "error": [ - "Argument of [to_lower] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = to_str(versionField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_lower(to_string(booleanField))", + "query": "from a_index | eval var = to_string(to_version(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_lower(booleanField)", + "query": "from a_index | eval to_string(booleanField, extraArg)", "error": [ - "Argument of [to_lower] must be [string], found value [booleanField] type [boolean]" + "Error: [to_string] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | eval to_lower(null)", + "query": "from a_index | sort to_string(booleanField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_lower(nullVar)", + "query": "from a_index | eval to_string(null)", "error": [], "warning": [] }, { - "query": "row var = to_radians(5)", + "query": "row nullVar = null | eval to_string(nullVar)", "error": [], "warning": [] }, { - "query": "row to_radians(5)", + "query": "row var = to_unsigned_long(\"a\")", "error": [], "warning": [] }, { - "query": "row var = to_radians(to_integer(\"a\"))", + "query": "row to_unsigned_long(\"a\")", "error": [], "warning": [] }, { - "query": "row var = to_radians(\"a\")", - "error": [ - "Argument of [to_radians] must be [number], found value [\"a\"] type [string]" - ], + "query": "row var = to_ul(\"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | where to_radians(numberField) > 0", + "query": "row var = to_ulong(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | where to_radians(stringField) > 0", - "error": [ - "Argument of [to_radians] must be [number], found value [stringField] type [string]" - ], + "query": "from a_index | eval var = to_unsigned_long(stringField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_radians(numberField)", + "query": "from a_index | eval to_unsigned_long(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_radians(numberField)", + "query": "from a_index | eval var = to_ul(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_radians(to_integer(stringField))", + "query": "from a_index | eval var = to_ulong(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_radians(stringField)", + "query": "from a_index | eval var = to_unsigned_long(*)", "error": [ - "Argument of [to_radians] must be [number], found value [stringField] type [string]" + "Using wildcards (*) in to_unsigned_long is not allowed" ], "warning": [] }, { - "query": "from a_index | eval to_radians(numberField, extraArg)", - "error": [ - "Error: [to_radians] function expects exactly one argument, got 2." - ], + "query": "from a_index | sort to_unsigned_long(stringField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_radians(*)", - "error": [ - "Using wildcards (*) in to_radians is not allowed" - ], + "query": "row var = to_unsigned_long(true)", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_radians(numberField)", + "query": "row to_unsigned_long(true)", "error": [], "warning": [] }, { - "query": "row var = to_radians(to_integer(true))", + "query": "row var = to_ul(true)", "error": [], "warning": [] }, { - "query": "row var = to_radians(true)", - "error": [ - "Argument of [to_radians] must be [number], found value [true] type [boolean]" - ], + "query": "row var = to_ulong(true)", + "error": [], "warning": [] }, { - "query": "from a_index | where to_radians(booleanField) > 0", - "error": [ - "Argument of [to_radians] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = to_unsigned_long(to_boolean(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_radians(to_integer(booleanField))", + "query": "row var = to_unsigned_long(now())", "error": [], "warning": [] }, { - "query": "from a_index | eval to_radians(booleanField)", - "error": [ - "Argument of [to_radians] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row to_unsigned_long(now())", + "error": [], "warning": [] }, { - "query": "from a_index | eval to_radians(null)", + "query": "row var = to_ul(now())", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_radians(nullVar)", + "query": "row var = to_ulong(now())", "error": [], "warning": [] }, { - "query": "row var = to_string(\"a\")", + "query": "row var = to_unsigned_long(to_datetime(now()))", "error": [], "warning": [] }, { - "query": "row to_string(\"a\")", + "query": "row var = to_unsigned_long(5)", "error": [], "warning": [] }, { - "query": "row var = to_str(\"a\")", + "query": "row to_unsigned_long(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(stringField)", + "query": "row var = to_ul(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(stringField)", + "query": "row var = to_ulong(5)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_str(stringField)", + "query": "row var = to_unsigned_long(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(*)", - "error": [ - "Using wildcards (*) in to_string is not allowed" - ], + "query": "row var = to_unsigned_long(to_string(true))", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_string(stringField)", - "error": [], + "query": "row var = to_unsigned_long(to_cartesianpoint(\"POINT (30 10)\"))", + "error": [ + "Argument of [to_unsigned_long] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + ], "warning": [] }, { - "query": "row var = to_string(true)", + "query": "from a_index | where to_unsigned_long(booleanField) > 0", "error": [], "warning": [] }, { - "query": "row to_string(true)", - "error": [], + "query": "from a_index | where to_unsigned_long(cartesianPointField) > 0", + "error": [ + "Argument of [to_unsigned_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], "warning": [] }, { - "query": "row var = to_str(true)", + "query": "from a_index | where to_unsigned_long(dateField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_string(to_boolean(true))", + "query": "from a_index | where to_unsigned_long(numberField) > 0", "error": [], "warning": [] }, { - "query": "row var = to_string(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | where to_unsigned_long(stringField) > 0", "error": [], "warning": [] }, { - "query": "row to_string(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = to_unsigned_long(booleanField)", "error": [], "warning": [] }, { - "query": "row var = to_str(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval to_unsigned_long(booleanField)", "error": [], "warning": [] }, { - "query": "row var = to_string(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = to_ul(booleanField)", "error": [], "warning": [] }, { - "query": "row var = to_string(to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = to_ulong(booleanField)", "error": [], "warning": [] }, { - "query": "row to_string(to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | eval var = to_unsigned_long(to_boolean(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_str(to_cartesianshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval to_unsigned_long(cartesianPointField)", + "error": [ + "Argument of [to_unsigned_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + ], "warning": [] }, { - "query": "row var = to_string(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = to_unsigned_long(dateField)", "error": [], "warning": [] }, { - "query": "row var = to_string(now())", + "query": "from a_index | eval to_unsigned_long(dateField)", "error": [], "warning": [] }, { - "query": "row to_string(now())", + "query": "from a_index | eval var = to_ul(dateField)", "error": [], "warning": [] }, { - "query": "row var = to_str(now())", + "query": "from a_index | eval var = to_ulong(dateField)", "error": [], "warning": [] }, { - "query": "row var = to_string(to_datetime(now()))", + "query": "from a_index | eval var = to_unsigned_long(to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "row var = to_string(5)", + "query": "from a_index | eval var = to_unsigned_long(numberField)", "error": [], "warning": [] }, { - "query": "row to_string(5)", + "query": "from a_index | eval to_unsigned_long(numberField)", "error": [], "warning": [] }, { - "query": "row var = to_str(5)", + "query": "from a_index | eval var = to_ul(numberField)", "error": [], "warning": [] }, { - "query": "row var = to_string(to_integer(true))", + "query": "from a_index | eval var = to_ulong(numberField)", "error": [], "warning": [] }, { - "query": "row var = to_string(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = to_unsigned_long(to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row to_string(to_geopoint(\"POINT (30 10)\"))", + "query": "from a_index | eval var = to_unsigned_long(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "row var = to_str(to_geopoint(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | eval to_unsigned_long(booleanField, extraArg)", + "error": [ + "Error: [to_unsigned_long] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "row var = to_string(to_geopoint(to_geopoint(\"POINT (30 10)\")))", + "query": "from a_index | sort to_unsigned_long(booleanField)", "error": [], "warning": [] }, { - "query": "row var = to_string(to_geoshape(\"POINT (30 10)\"))", + "query": "from a_index | eval to_unsigned_long(null)", "error": [], "warning": [] }, { - "query": "row to_string(to_geoshape(\"POINT (30 10)\"))", + "query": "row nullVar = null | eval to_unsigned_long(nullVar)", "error": [], "warning": [] }, { - "query": "row var = to_str(to_geoshape(\"POINT (30 10)\"))", + "query": "row var = to_upper(\"a\")", "error": [], "warning": [] }, { - "query": "row var = to_string(to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "row to_upper(\"a\")", "error": [], "warning": [] }, { - "query": "row var = to_string(to_ip(\"127.0.0.1\"))", + "query": "row var = to_upper(to_string(\"a\"))", "error": [], "warning": [] }, { - "query": "row to_string(to_ip(\"127.0.0.1\"))", - "error": [], + "query": "row var = to_upper(5)", + "error": [ + "Argument of [to_upper] must be [string], found value [5] type [number]" + ], "warning": [] }, { - "query": "row var = to_str(to_ip(\"127.0.0.1\"))", + "query": "from a_index | where length(to_upper(stringField)) > 0", "error": [], "warning": [] }, { - "query": "row var = to_string(to_ip(to_ip(\"127.0.0.1\")))", - "error": [], + "query": "from a_index | where length(to_upper(numberField)) > 0", + "error": [ + "Argument of [to_upper] must be [string], found value [numberField] type [number]" + ], "warning": [] }, { - "query": "row var = to_string(to_string(true))", + "query": "from a_index | eval var = to_upper(stringField)", "error": [], "warning": [] }, { - "query": "row var = to_string(to_version(\"1.0.0\"))", + "query": "from a_index | eval to_upper(stringField)", "error": [], "warning": [] }, { - "query": "row to_string(to_version(\"1.0.0\"))", + "query": "from a_index | eval var = to_upper(to_string(stringField))", "error": [], "warning": [] }, { - "query": "row var = to_str(to_version(\"1.0.0\"))", - "error": [], + "query": "from a_index | eval to_upper(numberField)", + "error": [ + "Argument of [to_upper] must be [string], found value [numberField] type [number]" + ], "warning": [] }, { - "query": "row var = to_string(to_version(\"a\"))", - "error": [], + "query": "from a_index | eval to_upper(stringField, extraArg)", + "error": [ + "Error: [to_upper] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | where length(to_string(booleanField)) > 0", - "error": [], + "query": "from a_index | eval var = to_upper(*)", + "error": [ + "Using wildcards (*) in to_upper is not allowed" + ], "warning": [] }, { - "query": "from a_index | where length(to_string(cartesianPointField)) > 0", + "query": "from a_index | sort to_upper(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | where length(to_string(cartesianShapeField)) > 0", + "query": "row var = to_upper(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | where length(to_string(dateField)) > 0", - "error": [], + "query": "row var = to_upper(true)", + "error": [ + "Argument of [to_upper] must be [string], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | where length(to_string(numberField)) > 0", - "error": [], + "query": "from a_index | where length(to_upper(booleanField)) > 0", + "error": [ + "Argument of [to_upper] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | where length(to_string(geoPointField)) > 0", + "query": "from a_index | eval var = to_upper(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | where length(to_string(geoShapeField)) > 0", - "error": [], + "query": "from a_index | eval to_upper(booleanField)", + "error": [ + "Argument of [to_upper] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | where length(to_string(ipField)) > 0", + "query": "from a_index | eval to_upper(null)", "error": [], "warning": [] }, { - "query": "from a_index | where length(to_string(stringField)) > 0", + "query": "row nullVar = null | eval to_upper(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | where length(to_string(versionField)) > 0", + "query": "row var = to_version(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(booleanField)", + "query": "row to_version(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(booleanField)", + "query": "row var = to_ver(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_str(booleanField)", + "query": "from a_index | eval var = to_version(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_boolean(booleanField))", + "query": "from a_index | eval to_version(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(cartesianPointField)", + "query": "from a_index | eval var = to_ver(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(cartesianPointField)", - "error": [], + "query": "from a_index | eval var = to_version(*)", + "error": [ + "Using wildcards (*) in to_version is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval var = to_str(cartesianPointField)", + "query": "from a_index | sort to_version(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_cartesianpoint(cartesianPointField))", + "query": "row var = to_version(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(cartesianShapeField)", + "query": "row to_version(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(cartesianShapeField)", + "query": "row var = to_ver(to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_str(cartesianShapeField)", - "error": [], + "query": "row var = to_version(true)", + "error": [ + "Argument of [to_version] must be [string], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_cartesianshape(cartesianPointField))", + "query": "from a_index | eval var = to_version(versionField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(dateField)", + "query": "from a_index | eval to_version(versionField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(dateField)", + "query": "from a_index | eval var = to_ver(versionField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_str(dateField)", - "error": [], + "query": "from a_index | eval to_version(stringField, extraArg)", + "error": [ + "Error: [to_version] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_datetime(dateField))", + "query": "from a_index | eval to_version(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(numberField)", + "query": "row nullVar = null | eval to_version(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(numberField)", + "query": "row var = trim(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_str(numberField)", + "query": "row trim(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_integer(booleanField))", + "query": "row var = trim(to_string(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(geoPointField)", - "error": [], + "query": "row var = trim(5)", + "error": [ + "Argument of [trim] must be [string], found value [5] type [number]" + ], "warning": [] }, { - "query": "from a_index | eval to_string(geoPointField)", + "query": "from a_index | where length(trim(stringField)) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_str(geoPointField)", - "error": [], + "query": "from a_index | where length(trim(numberField)) > 0", + "error": [ + "Argument of [trim] must be [string], found value [numberField] type [number]" + ], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_geopoint(geoPointField))", + "query": "from a_index | eval var = trim(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(geoShapeField)", + "query": "from a_index | eval trim(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(geoShapeField)", + "query": "from a_index | eval var = trim(to_string(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_str(geoShapeField)", - "error": [], + "query": "from a_index | eval trim(numberField)", + "error": [ + "Argument of [trim] must be [string], found value [numberField] type [number]" + ], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_geoshape(geoPointField))", - "error": [], + "query": "from a_index | eval trim(stringField, extraArg)", + "error": [ + "Error: [trim] function expects exactly one argument, got 2." + ], "warning": [] }, { - "query": "from a_index | eval var = to_string(ipField)", - "error": [], + "query": "from a_index | eval var = trim(*)", + "error": [ + "Using wildcards (*) in trim is not allowed" + ], "warning": [] }, { - "query": "from a_index | eval to_string(ipField)", + "query": "from a_index | sort trim(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_str(ipField)", + "query": "row var = trim(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_ip(ipField))", - "error": [], + "query": "row var = trim(true)", + "error": [ + "Argument of [trim] must be [string], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_string(booleanField))", - "error": [], + "query": "from a_index | where length(trim(booleanField)) > 0", + "error": [ + "Argument of [trim] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = to_string(versionField)", + "query": "from a_index | eval var = trim(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(versionField)", - "error": [], + "query": "from a_index | eval trim(booleanField)", + "error": [ + "Argument of [trim] must be [string], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | eval var = to_str(versionField)", + "query": "from a_index | eval trim(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_string(to_version(stringField))", + "query": "row nullVar = null | eval trim(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(booleanField, extraArg)", - "error": [ - "Error: [to_string] function expects exactly one argument, got 2." - ], - "warning": [] - }, - { - "query": "from a_index | sort to_string(booleanField)", + "query": "from a_index | stats var = avg(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_string(null)", + "query": "from a_index | stats avg(numberField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_string(nullVar)", + "query": "from a_index | stats var = round(avg(numberField))", "error": [], "warning": [] }, { - "query": "row var = to_unsigned_long(\"a\")", + "query": "from a_index | stats round(avg(numberField))", "error": [], "warning": [] }, { - "query": "row to_unsigned_long(\"a\")", + "query": "from a_index | stats var = round(avg(numberField)) + avg(numberField)", "error": [], "warning": [] }, { - "query": "row var = to_ul(\"a\")", + "query": "from a_index | stats round(avg(numberField)) + avg(numberField)", "error": [], "warning": [] }, { - "query": "row var = to_ulong(\"a\")", + "query": "from a_index | stats avg(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_unsigned_long(stringField)", + "query": "from a_index | stats var0 = avg(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_unsigned_long(stringField)", + "query": "from a_index | stats avg(numberField), avg(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ul(stringField)", + "query": "from a_index | stats avg(numberField), var0 = avg(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ulong(stringField)", + "query": "from a_index | stats var0 = avg(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_unsigned_long(*)", - "error": [ - "Using wildcards (*) in to_unsigned_long is not allowed" - ], - "warning": [] - }, - { - "query": "from a_index | sort to_unsigned_long(stringField)", + "query": "from a_index | stats avg(numberField), avg(numberField)", "error": [], "warning": [] }, { - "query": "row var = to_unsigned_long(true)", + "query": "from a_index | stats avg(numberField), var0 = avg(numberField)", "error": [], "warning": [] }, { - "query": "row to_unsigned_long(true)", + "query": "from a_index | stats avg(numberField) by round(numberField / 2)", "error": [], "warning": [] }, { - "query": "row var = to_ul(true)", + "query": "from a_index | stats var0 = avg(numberField) by var1 = round(numberField / 2)", "error": [], "warning": [] }, { - "query": "row var = to_ulong(true)", + "query": "from a_index | stats avg(numberField), avg(numberField) by round(numberField / 2), ipField", "error": [], "warning": [] }, { - "query": "row var = to_unsigned_long(to_boolean(true))", + "query": "from a_index | stats avg(numberField), var0 = avg(numberField) by var1 = round(numberField / 2), ipField", "error": [], "warning": [] }, { - "query": "row var = to_unsigned_long(now())", + "query": "from a_index | stats avg(numberField), avg(numberField) by round(numberField / 2), numberField / 2", "error": [], "warning": [] }, { - "query": "row to_unsigned_long(now())", + "query": "from a_index | stats avg(numberField), var0 = avg(numberField) by var1 = round(numberField / 2), numberField / 2", "error": [], "warning": [] }, { - "query": "row var = to_ul(now())", - "error": [], + "query": "from a_index | stats var = avg(avg(numberField))", + "error": [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" + ], "warning": [] }, { - "query": "row var = to_ulong(now())", - "error": [], + "query": "from a_index | stats avg(avg(numberField))", + "error": [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" + ], "warning": [] }, { - "query": "row var = to_unsigned_long(to_datetime(now()))", - "error": [], + "query": "from a_index | stats avg(stringField)", + "error": [ + "Argument of [avg] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "row var = to_unsigned_long(5)", - "error": [], + "query": "from a_index | stats var = avg(*)", + "error": [ + "Using wildcards (*) in avg is not allowed" + ], "warning": [] }, { - "query": "row to_unsigned_long(5)", - "error": [], + "query": "from a_index | sort avg(numberField)", + "error": [ + "SORT does not support function avg" + ], "warning": [] }, { - "query": "row var = to_ul(5)", - "error": [], + "query": "from a_index | where avg(numberField)", + "error": [ + "WHERE does not support function avg" + ], "warning": [] }, { - "query": "row var = to_ulong(5)", - "error": [], + "query": "from a_index | where avg(numberField) > 0", + "error": [ + "WHERE does not support function avg" + ], "warning": [] }, { - "query": "row var = to_unsigned_long(to_integer(true))", - "error": [], + "query": "from a_index | eval var = avg(numberField)", + "error": [ + "EVAL does not support function avg" + ], "warning": [] }, { - "query": "row var = to_unsigned_long(to_string(true))", - "error": [], + "query": "from a_index | eval var = avg(numberField) > 0", + "error": [ + "EVAL does not support function avg" + ], "warning": [] }, { - "query": "row var = to_unsigned_long(to_cartesianpoint(\"POINT (30 10)\"))", + "query": "from a_index | eval avg(numberField)", "error": [ - "Argument of [to_unsigned_long] must be [boolean], found value [to_cartesianpoint(\"POINT (30 10)\")] type [cartesian_point]" + "EVAL does not support function avg" ], "warning": [] }, { - "query": "from a_index | where to_unsigned_long(booleanField) > 0", - "error": [], + "query": "from a_index | eval avg(numberField) > 0", + "error": [ + "EVAL does not support function avg" + ], "warning": [] }, { - "query": "from a_index | where to_unsigned_long(cartesianPointField) > 0", + "query": "from a_index | stats avg(booleanField)", "error": [ - "Argument of [to_unsigned_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]" + "Argument of [avg] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where to_unsigned_long(dateField) > 0", + "query": "from a_index | stats avg(null)", "error": [], "warning": [] }, { - "query": "from a_index | where to_unsigned_long(numberField) > 0", + "query": "row nullVar = null | stats avg(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | where to_unsigned_long(stringField) > 0", + "query": "from a_index | stats var = sum(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_unsigned_long(booleanField)", + "query": "from a_index | stats sum(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_unsigned_long(booleanField)", + "query": "from a_index | stats var = round(sum(numberField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ul(booleanField)", + "query": "from a_index | stats round(sum(numberField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ulong(booleanField)", + "query": "from a_index | stats var = round(sum(numberField)) + sum(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_unsigned_long(to_boolean(booleanField))", + "query": "from a_index | stats round(sum(numberField)) + sum(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_unsigned_long(cartesianPointField)", - "error": [ - "Argument of [to_unsigned_long] must be [boolean], found value [cartesianPointField] type [cartesian_point]" - ], + "query": "from a_index | stats sum(numberField / 2)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_unsigned_long(dateField)", + "query": "from a_index | stats var0 = sum(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_unsigned_long(dateField)", + "query": "from a_index | stats avg(numberField), sum(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ul(dateField)", + "query": "from a_index | stats avg(numberField), var0 = sum(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ulong(dateField)", + "query": "from a_index | stats var0 = sum(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_unsigned_long(to_datetime(dateField))", + "query": "from a_index | stats avg(numberField), sum(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_unsigned_long(numberField)", + "query": "from a_index | stats avg(numberField), var0 = sum(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_unsigned_long(numberField)", + "query": "from a_index | stats sum(numberField) by round(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ul(numberField)", + "query": "from a_index | stats var0 = sum(numberField) by var1 = round(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ulong(numberField)", + "query": "from a_index | stats avg(numberField), sum(numberField) by round(numberField / 2), ipField", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_unsigned_long(to_integer(booleanField))", + "query": "from a_index | stats avg(numberField), var0 = sum(numberField) by var1 = round(numberField / 2), ipField", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_unsigned_long(to_string(booleanField))", + "query": "from a_index | stats avg(numberField), sum(numberField) by round(numberField / 2), numberField / 2", "error": [], "warning": [] }, { - "query": "from a_index | eval to_unsigned_long(booleanField, extraArg)", - "error": [ - "Error: [to_unsigned_long] function expects exactly one argument, got 2." - ], + "query": "from a_index | stats avg(numberField), var0 = sum(numberField) by var1 = round(numberField / 2), numberField / 2", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_unsigned_long(booleanField)", - "error": [], + "query": "from a_index | stats var = sum(avg(numberField))", + "error": [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" + ], "warning": [] }, { - "query": "from a_index | eval to_unsigned_long(null)", - "error": [], + "query": "from a_index | stats sum(avg(numberField))", + "error": [ + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" + ], "warning": [] }, { - "query": "row nullVar = null | eval to_unsigned_long(nullVar)", - "error": [], + "query": "from a_index | stats sum(stringField)", + "error": [ + "Argument of [sum] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "row var = to_upper(\"a\")", - "error": [], + "query": "from a_index | stats var = sum(*)", + "error": [ + "Using wildcards (*) in sum is not allowed" + ], "warning": [] }, { - "query": "row to_upper(\"a\")", - "error": [], + "query": "from a_index | sort sum(numberField)", + "error": [ + "SORT does not support function sum" + ], "warning": [] }, { - "query": "row var = to_upper(to_string(\"a\"))", - "error": [], + "query": "from a_index | where sum(numberField)", + "error": [ + "WHERE does not support function sum" + ], "warning": [] }, { - "query": "row var = to_upper(5)", + "query": "from a_index | where sum(numberField) > 0", "error": [ - "Argument of [to_upper] must be [string], found value [5] type [number]" + "WHERE does not support function sum" ], "warning": [] }, { - "query": "from a_index | where length(to_upper(stringField)) > 0", - "error": [], + "query": "from a_index | eval var = sum(numberField)", + "error": [ + "EVAL does not support function sum" + ], "warning": [] }, { - "query": "from a_index | where length(to_upper(numberField)) > 0", + "query": "from a_index | eval var = sum(numberField) > 0", "error": [ - "Argument of [to_upper] must be [string], found value [numberField] type [number]" + "EVAL does not support function sum" ], "warning": [] }, { - "query": "from a_index | eval var = to_upper(stringField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval to_upper(stringField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval var = to_upper(to_string(stringField))", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval to_upper(numberField)", - "error": [ - "Argument of [to_upper] must be [string], found value [numberField] type [number]" - ], - "warning": [] - }, - { - "query": "from a_index | eval to_upper(stringField, extraArg)", - "error": [ - "Error: [to_upper] function expects exactly one argument, got 2." - ], - "warning": [] - }, - { - "query": "from a_index | eval var = to_upper(*)", + "query": "from a_index | eval sum(numberField)", "error": [ - "Using wildcards (*) in to_upper is not allowed" + "EVAL does not support function sum" ], "warning": [] }, { - "query": "from a_index | sort to_upper(stringField)", - "error": [], - "warning": [] - }, - { - "query": "row var = to_upper(to_string(true))", - "error": [], - "warning": [] - }, - { - "query": "row var = to_upper(true)", + "query": "from a_index | eval sum(numberField) > 0", "error": [ - "Argument of [to_upper] must be [string], found value [true] type [boolean]" + "EVAL does not support function sum" ], "warning": [] }, { - "query": "from a_index | where length(to_upper(booleanField)) > 0", + "query": "from a_index | stats sum(booleanField)", "error": [ - "Argument of [to_upper] must be [string], found value [booleanField] type [boolean]" + "Argument of [sum] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = to_upper(to_string(booleanField))", + "query": "from a_index | stats sum(null)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_upper(booleanField)", - "error": [ - "Argument of [to_upper] must be [string], found value [booleanField] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | eval to_upper(null)", + "query": "row nullVar = null | stats sum(nullVar)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_upper(nullVar)", + "query": "from a_index | stats var = median(numberField)", "error": [], "warning": [] }, { - "query": "row var = to_version(\"a\")", + "query": "from a_index | stats median(numberField)", "error": [], "warning": [] }, { - "query": "row to_version(\"a\")", + "query": "from a_index | stats var = round(median(numberField))", "error": [], "warning": [] }, { - "query": "row var = to_ver(\"a\")", + "query": "from a_index | stats round(median(numberField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_version(stringField)", + "query": "from a_index | stats var = round(median(numberField)) + median(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_version(stringField)", + "query": "from a_index | stats round(median(numberField)) + median(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ver(stringField)", + "query": "from a_index | stats median(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_version(*)", - "error": [ - "Using wildcards (*) in to_version is not allowed" - ], - "warning": [] - }, - { - "query": "from a_index | sort to_version(stringField)", + "query": "from a_index | stats var0 = median(numberField / 2)", "error": [], "warning": [] }, { - "query": "row var = to_version(to_version(\"1.0.0\"))", + "query": "from a_index | stats avg(numberField), median(numberField / 2)", "error": [], "warning": [] }, { - "query": "row to_version(to_version(\"1.0.0\"))", + "query": "from a_index | stats avg(numberField), var0 = median(numberField / 2)", "error": [], "warning": [] }, { - "query": "row var = to_ver(to_version(\"1.0.0\"))", + "query": "from a_index | stats var0 = median(numberField)", "error": [], "warning": [] }, { - "query": "row var = to_version(true)", - "error": [ - "Argument of [to_version] must be [string], found value [true] type [boolean]" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = to_version(versionField)", + "query": "from a_index | stats avg(numberField), median(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_version(versionField)", + "query": "from a_index | stats avg(numberField), var0 = median(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_ver(versionField)", + "query": "from a_index | stats median(numberField) by round(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_version(stringField, extraArg)", - "error": [ - "Error: [to_version] function expects exactly one argument, got 2." - ], - "warning": [] - }, - { - "query": "from a_index | eval to_version(null)", + "query": "from a_index | stats var0 = median(numberField) by var1 = round(numberField / 2)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_version(nullVar)", + "query": "from a_index | stats avg(numberField), median(numberField) by round(numberField / 2), ipField", "error": [], "warning": [] }, { - "query": "row var = trim(\"a\")", + "query": "from a_index | stats avg(numberField), var0 = median(numberField) by var1 = round(numberField / 2), ipField", "error": [], "warning": [] }, { - "query": "row trim(\"a\")", + "query": "from a_index | stats avg(numberField), median(numberField) by round(numberField / 2), numberField / 2", "error": [], "warning": [] }, { - "query": "row var = trim(to_string(\"a\"))", + "query": "from a_index | stats avg(numberField), var0 = median(numberField) by var1 = round(numberField / 2), numberField / 2", "error": [], "warning": [] }, { - "query": "row var = trim(5)", + "query": "from a_index | stats var = median(avg(numberField))", "error": [ - "Argument of [trim] must be [string], found value [5] type [number]" + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" ], "warning": [] }, { - "query": "from a_index | where length(trim(stringField)) > 0", - "error": [], - "warning": [] - }, - { - "query": "from a_index | where length(trim(numberField)) > 0", + "query": "from a_index | stats median(avg(numberField))", "error": [ - "Argument of [trim] must be [string], found value [numberField] type [number]" + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" ], "warning": [] }, { - "query": "from a_index | eval var = trim(stringField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval trim(stringField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval var = trim(to_string(stringField))", - "error": [], + "query": "from a_index | stats median(stringField)", + "error": [ + "Argument of [median] must be [number], found value [stringField] type [string]" + ], "warning": [] }, { - "query": "from a_index | eval trim(numberField)", + "query": "from a_index | stats var = median(*)", "error": [ - "Argument of [trim] must be [string], found value [numberField] type [number]" + "Using wildcards (*) in median is not allowed" ], "warning": [] }, { - "query": "from a_index | eval trim(stringField, extraArg)", + "query": "from a_index | sort median(numberField)", "error": [ - "Error: [trim] function expects exactly one argument, got 2." + "SORT does not support function median" ], "warning": [] }, { - "query": "from a_index | eval var = trim(*)", + "query": "from a_index | where median(numberField)", "error": [ - "Using wildcards (*) in trim is not allowed" + "WHERE does not support function median" ], "warning": [] }, { - "query": "from a_index | sort trim(stringField)", - "error": [], + "query": "from a_index | where median(numberField) > 0", + "error": [ + "WHERE does not support function median" + ], "warning": [] }, { - "query": "row var = trim(to_string(true))", - "error": [], + "query": "from a_index | eval var = median(numberField)", + "error": [ + "EVAL does not support function median" + ], "warning": [] }, { - "query": "row var = trim(true)", + "query": "from a_index | eval var = median(numberField) > 0", "error": [ - "Argument of [trim] must be [string], found value [true] type [boolean]" + "EVAL does not support function median" ], "warning": [] }, { - "query": "from a_index | where length(trim(booleanField)) > 0", + "query": "from a_index | eval median(numberField)", "error": [ - "Argument of [trim] must be [string], found value [booleanField] type [boolean]" + "EVAL does not support function median" ], "warning": [] }, { - "query": "from a_index | eval var = trim(to_string(booleanField))", - "error": [], + "query": "from a_index | eval median(numberField) > 0", + "error": [ + "EVAL does not support function median" + ], "warning": [] }, { - "query": "from a_index | eval trim(booleanField)", + "query": "from a_index | stats median(booleanField)", "error": [ - "Argument of [trim] must be [string], found value [booleanField] type [boolean]" + "Argument of [median] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval trim(null)", + "query": "from a_index | stats median(null)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval trim(nullVar)", + "query": "row nullVar = null | stats median(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = avg(numberField)", + "query": "from a_index | stats var = median_absolute_deviation(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField)", + "query": "from a_index | stats median_absolute_deviation(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(avg(numberField))", + "query": "from a_index | stats var = round(median_absolute_deviation(numberField))", "error": [], "warning": [] }, { - "query": "from a_index | stats round(avg(numberField))", + "query": "from a_index | stats round(median_absolute_deviation(numberField))", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(avg(numberField)) + avg(numberField)", + "query": "from a_index | stats var = round(median_absolute_deviation(numberField)) + median_absolute_deviation(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(avg(numberField)) + avg(numberField)", + "query": "from a_index | stats round(median_absolute_deviation(numberField)) + median_absolute_deviation(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField / 2)", + "query": "from a_index | stats median_absolute_deviation(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = avg(numberField / 2)", + "query": "from a_index | stats var0 = median_absolute_deviation(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), avg(numberField / 2)", + "query": "from a_index | stats avg(numberField), median_absolute_deviation(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = avg(numberField / 2)", + "query": "from a_index | stats avg(numberField), var0 = median_absolute_deviation(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = avg(numberField)", + "query": "from a_index | stats var0 = median_absolute_deviation(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), avg(numberField)", + "query": "from a_index | stats avg(numberField), median_absolute_deviation(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = avg(numberField)", + "query": "from a_index | stats avg(numberField), var0 = median_absolute_deviation(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField) by round(numberField / 2)", + "query": "from a_index | stats median_absolute_deviation(numberField) by round(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = avg(numberField) by var1 = round(numberField / 2)", + "query": "from a_index | stats var0 = median_absolute_deviation(numberField) by var1 = round(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), avg(numberField) by round(numberField / 2), ipField", + "query": "from a_index | stats avg(numberField), median_absolute_deviation(numberField) by round(numberField / 2), ipField", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = avg(numberField) by var1 = round(numberField / 2), ipField", + "query": "from a_index | stats avg(numberField), var0 = median_absolute_deviation(numberField) by var1 = round(numberField / 2), ipField", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), avg(numberField) by round(numberField / 2), numberField / 2", + "query": "from a_index | stats avg(numberField), median_absolute_deviation(numberField) by round(numberField / 2), numberField / 2", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = avg(numberField) by var1 = round(numberField / 2), numberField / 2", + "query": "from a_index | stats avg(numberField), var0 = median_absolute_deviation(numberField) by var1 = round(numberField / 2), numberField / 2", "error": [], "warning": [] }, { - "query": "from a_index | stats var = avg(avg(numberField))", + "query": "from a_index | stats var = median_absolute_deviation(avg(numberField))", "error": [ "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" ], "warning": [] }, { - "query": "from a_index | stats avg(avg(numberField))", + "query": "from a_index | stats median_absolute_deviation(avg(numberField))", "error": [ "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" ], "warning": [] }, { - "query": "from a_index | stats avg(stringField)", + "query": "from a_index | stats median_absolute_deviation(stringField)", "error": [ - "Argument of [avg] must be [number], found value [stringField] type [string]" + "Argument of [median_absolute_deviation] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | stats var = avg(*)", + "query": "from a_index | stats var = median_absolute_deviation(*)", "error": [ - "Using wildcards (*) in avg is not allowed" + "Using wildcards (*) in median_absolute_deviation is not allowed" ], "warning": [] }, { - "query": "from a_index | sort avg(numberField)", + "query": "from a_index | sort median_absolute_deviation(numberField)", "error": [ - "SORT does not support function avg" + "SORT does not support function median_absolute_deviation" ], "warning": [] }, { - "query": "from a_index | where avg(numberField)", + "query": "from a_index | where median_absolute_deviation(numberField)", "error": [ - "WHERE does not support function avg" + "WHERE does not support function median_absolute_deviation" ], "warning": [] }, { - "query": "from a_index | where avg(numberField) > 0", + "query": "from a_index | where median_absolute_deviation(numberField) > 0", "error": [ - "WHERE does not support function avg" + "WHERE does not support function median_absolute_deviation" ], "warning": [] }, { - "query": "from a_index | eval var = avg(numberField)", + "query": "from a_index | eval var = median_absolute_deviation(numberField)", "error": [ - "EVAL does not support function avg" + "EVAL does not support function median_absolute_deviation" ], "warning": [] }, { - "query": "from a_index | eval var = avg(numberField) > 0", + "query": "from a_index | eval var = median_absolute_deviation(numberField) > 0", "error": [ - "EVAL does not support function avg" + "EVAL does not support function median_absolute_deviation" ], "warning": [] }, { - "query": "from a_index | eval avg(numberField)", + "query": "from a_index | eval median_absolute_deviation(numberField)", "error": [ - "EVAL does not support function avg" + "EVAL does not support function median_absolute_deviation" ], "warning": [] }, { - "query": "from a_index | eval avg(numberField) > 0", + "query": "from a_index | eval median_absolute_deviation(numberField) > 0", "error": [ - "EVAL does not support function avg" + "EVAL does not support function median_absolute_deviation" ], "warning": [] }, { - "query": "from a_index | stats avg(booleanField)", + "query": "from a_index | stats median_absolute_deviation(booleanField)", "error": [ - "Argument of [avg] must be [number], found value [booleanField] type [boolean]" + "Argument of [median_absolute_deviation] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | stats avg(null)", + "query": "from a_index | stats median_absolute_deviation(null)", "error": [], "warning": [] }, { - "query": "row nullVar = null | stats avg(nullVar)", + "query": "row nullVar = null | stats median_absolute_deviation(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = sum(numberField)", + "query": "from a_index | stats var = percentile(numberField, 5)", "error": [], "warning": [] }, { - "query": "from a_index | stats sum(numberField)", + "query": "from a_index | stats percentile(numberField, 5)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(sum(numberField))", + "query": "from a_index | stats var = round(percentile(numberField, 5))", "error": [], "warning": [] }, { - "query": "from a_index | stats round(sum(numberField))", + "query": "from a_index | stats round(percentile(numberField, 5))", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(sum(numberField)) + sum(numberField)", + "query": "from a_index | stats var = round(percentile(numberField, 5)) + percentile(numberField, 5)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(sum(numberField)) + sum(numberField)", + "query": "from a_index | stats round(percentile(numberField, 5)) + percentile(numberField, 5)", "error": [], "warning": [] }, { - "query": "from a_index | stats sum(numberField / 2)", - "error": [], + "query": "from a_index | stats percentile(numberField, numberField)", + "error": [ + "Argument of [percentile] must be a constant, received [numberField]" + ], "warning": [] }, { - "query": "from a_index | stats var0 = sum(numberField / 2)", + "query": "from a_index | stats percentile(numberField / 2, 5)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), sum(numberField / 2)", + "query": "from a_index | stats var0 = percentile(numberField / 2, 5)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = sum(numberField / 2)", + "query": "from a_index | stats avg(numberField), percentile(numberField / 2, 5)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = sum(numberField)", + "query": "from a_index | stats avg(numberField), var0 = percentile(numberField / 2, 5)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), sum(numberField)", + "query": "from a_index | stats var0 = percentile(numberField, 5)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = sum(numberField)", + "query": "from a_index | stats avg(numberField), percentile(numberField, 5)", "error": [], "warning": [] }, { - "query": "from a_index | stats sum(numberField) by round(numberField / 2)", + "query": "from a_index | stats avg(numberField), var0 = percentile(numberField, 5)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = sum(numberField) by var1 = round(numberField / 2)", + "query": "from a_index | stats percentile(numberField, 5) by round(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), sum(numberField) by round(numberField / 2), ipField", + "query": "from a_index | stats var0 = percentile(numberField, 5) by var1 = round(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = sum(numberField) by var1 = round(numberField / 2), ipField", + "query": "from a_index | stats avg(numberField), percentile(numberField, 5) by round(numberField / 2), ipField", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), sum(numberField) by round(numberField / 2), numberField / 2", + "query": "from a_index | stats avg(numberField), var0 = percentile(numberField, 5) by var1 = round(numberField / 2), ipField", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = sum(numberField) by var1 = round(numberField / 2), numberField / 2", + "query": "from a_index | stats avg(numberField), percentile(numberField, 5) by round(numberField / 2), numberField / 2", "error": [], "warning": [] }, { - "query": "from a_index | stats var = sum(avg(numberField))", - "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" - ], + "query": "from a_index | stats avg(numberField), var0 = percentile(numberField, 5) by var1 = round(numberField / 2), numberField / 2", + "error": [], "warning": [] }, { - "query": "from a_index | stats sum(avg(numberField))", + "query": "from a_index | stats var = percentile(avg(numberField), 5)", "error": [ "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" ], "warning": [] }, { - "query": "from a_index | stats sum(stringField)", + "query": "from a_index | stats percentile(avg(numberField), 5)", "error": [ - "Argument of [sum] must be [number], found value [stringField] type [string]" + "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" ], "warning": [] }, { - "query": "from a_index | stats var = sum(*)", + "query": "from a_index | stats percentile(stringField, 5)", "error": [ - "Using wildcards (*) in sum is not allowed" + "Argument of [percentile] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | sort sum(numberField)", + "query": "from a_index | sort percentile(numberField, 5)", "error": [ - "SORT does not support function sum" + "SORT does not support function percentile" ], "warning": [] }, { - "query": "from a_index | where sum(numberField)", + "query": "from a_index | where percentile(numberField, 5)", "error": [ - "WHERE does not support function sum" + "WHERE does not support function percentile" ], "warning": [] }, { - "query": "from a_index | where sum(numberField) > 0", + "query": "from a_index | where percentile(numberField, 5) > 0", "error": [ - "WHERE does not support function sum" + "WHERE does not support function percentile" ], "warning": [] }, { - "query": "from a_index | eval var = sum(numberField)", + "query": "from a_index | eval var = percentile(numberField, 5)", "error": [ - "EVAL does not support function sum" + "EVAL does not support function percentile" ], "warning": [] }, { - "query": "from a_index | eval var = sum(numberField) > 0", + "query": "from a_index | eval var = percentile(numberField, 5) > 0", "error": [ - "EVAL does not support function sum" + "EVAL does not support function percentile" ], "warning": [] }, { - "query": "from a_index | eval sum(numberField)", + "query": "from a_index | eval percentile(numberField, 5)", "error": [ - "EVAL does not support function sum" + "EVAL does not support function percentile" ], "warning": [] }, { - "query": "from a_index | eval sum(numberField) > 0", + "query": "from a_index | eval percentile(numberField, 5) > 0", "error": [ - "EVAL does not support function sum" + "EVAL does not support function percentile" ], "warning": [] }, { - "query": "from a_index | stats sum(booleanField)", + "query": "from a_index | stats percentile(booleanField, 5)", "error": [ - "Argument of [sum] must be [number], found value [booleanField] type [boolean]" + "Argument of [percentile] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | stats sum(null)", + "query": "from a_index | stats percentile(null, null)", "error": [], "warning": [] }, { - "query": "row nullVar = null | stats sum(nullVar)", - "error": [], + "query": "row nullVar = null | stats percentile(nullVar, nullVar)", + "error": [ + "Argument of [percentile] must be a constant, received [nullVar]" + ], "warning": [] }, { - "query": "from a_index | stats var = median(numberField)", + "query": "from a_index | stats var = max(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats median(numberField)", + "query": "from a_index | stats max(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(median(numberField))", + "query": "from a_index | stats var = round(max(numberField))", "error": [], "warning": [] }, { - "query": "from a_index | stats round(median(numberField))", + "query": "from a_index | stats round(max(numberField))", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(median(numberField)) + median(numberField)", + "query": "from a_index | stats var = round(max(numberField)) + max(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(median(numberField)) + median(numberField)", + "query": "from a_index | stats round(max(numberField)) + max(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats median(numberField / 2)", + "query": "from a_index | stats max(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = median(numberField / 2)", + "query": "from a_index | stats var0 = max(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), median(numberField / 2)", + "query": "from a_index | stats avg(numberField), max(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = median(numberField / 2)", + "query": "from a_index | stats avg(numberField), var0 = max(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = median(numberField)", + "query": "from a_index | stats var0 = max(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), median(numberField)", + "query": "from a_index | stats avg(numberField), max(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = median(numberField)", + "query": "from a_index | stats avg(numberField), var0 = max(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats median(numberField) by round(numberField / 2)", + "query": "from a_index | stats max(numberField) by round(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = median(numberField) by var1 = round(numberField / 2)", + "query": "from a_index | stats var0 = max(numberField) by var1 = round(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), median(numberField) by round(numberField / 2), ipField", + "query": "from a_index | stats avg(numberField), max(numberField) by round(numberField / 2), ipField", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = median(numberField) by var1 = round(numberField / 2), ipField", + "query": "from a_index | stats avg(numberField), var0 = max(numberField) by var1 = round(numberField / 2), ipField", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), median(numberField) by round(numberField / 2), numberField / 2", + "query": "from a_index | stats avg(numberField), max(numberField) by round(numberField / 2), numberField / 2", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = median(numberField) by var1 = round(numberField / 2), numberField / 2", + "query": "from a_index | stats avg(numberField), var0 = max(numberField) by var1 = round(numberField / 2), numberField / 2", "error": [], "warning": [] }, { - "query": "from a_index | stats var = median(avg(numberField))", + "query": "from a_index | stats var = max(avg(numberField))", "error": [ "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" ], "warning": [] }, { - "query": "from a_index | stats median(avg(numberField))", + "query": "from a_index | stats max(avg(numberField))", "error": [ "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" ], "warning": [] }, { - "query": "from a_index | stats median(stringField)", + "query": "from a_index | stats max(stringField)", "error": [ - "Argument of [median] must be [number], found value [stringField] type [string]" + "Argument of [max] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | stats var = median(*)", + "query": "from a_index | stats var = max(*)", "error": [ - "Using wildcards (*) in median is not allowed" + "Using wildcards (*) in max is not allowed" ], "warning": [] }, { - "query": "from a_index | sort median(numberField)", - "error": [ - "SORT does not support function median" - ], + "query": "from a_index | stats var = max(dateField)", + "error": [], "warning": [] }, { - "query": "from a_index | where median(numberField)", + "query": "from a_index | stats max(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(max(dateField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(max(dateField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = round(max(dateField)) + max(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats round(max(dateField)) + max(dateField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | sort max(numberField)", "error": [ - "WHERE does not support function median" + "SORT does not support function max" ], "warning": [] }, { - "query": "from a_index | where median(numberField) > 0", + "query": "from a_index | where max(numberField)", "error": [ - "WHERE does not support function median" + "WHERE does not support function max" ], "warning": [] }, { - "query": "from a_index | eval var = median(numberField)", + "query": "from a_index | where max(numberField) > 0", "error": [ - "EVAL does not support function median" + "WHERE does not support function max" ], "warning": [] }, { - "query": "from a_index | eval var = median(numberField) > 0", + "query": "from a_index | where max(dateField)", "error": [ - "EVAL does not support function median" + "WHERE does not support function max" ], "warning": [] }, { - "query": "from a_index | eval median(numberField)", + "query": "from a_index | where max(dateField) > 0", "error": [ - "EVAL does not support function median" + "WHERE does not support function max" ], "warning": [] }, { - "query": "from a_index | eval median(numberField) > 0", + "query": "from a_index | eval var = max(numberField)", "error": [ - "EVAL does not support function median" + "EVAL does not support function max" ], "warning": [] }, { - "query": "from a_index | stats median(booleanField)", + "query": "from a_index | eval var = max(numberField) > 0", "error": [ - "Argument of [median] must be [number], found value [booleanField] type [boolean]" + "EVAL does not support function max" ], "warning": [] }, { - "query": "from a_index | stats median(null)", - "error": [], + "query": "from a_index | eval max(numberField)", + "error": [ + "EVAL does not support function max" + ], "warning": [] }, { - "query": "row nullVar = null | stats median(nullVar)", - "error": [], + "query": "from a_index | eval max(numberField) > 0", + "error": [ + "EVAL does not support function max" + ], "warning": [] }, { - "query": "from a_index | stats var = median_absolute_deviation(numberField)", - "error": [], + "query": "from a_index | eval var = max(dateField)", + "error": [ + "EVAL does not support function max" + ], "warning": [] }, { - "query": "from a_index | stats median_absolute_deviation(numberField)", - "error": [], + "query": "from a_index | eval var = max(dateField) > 0", + "error": [ + "EVAL does not support function max" + ], "warning": [] }, { - "query": "from a_index | stats var = round(median_absolute_deviation(numberField))", + "query": "from a_index | eval max(dateField)", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | eval max(dateField) > 0", + "error": [ + "EVAL does not support function max" + ], + "warning": [] + }, + { + "query": "from a_index | stats max(booleanField)", + "error": [ + "Argument of [max] must be [number], found value [booleanField] type [boolean]" + ], + "warning": [] + }, + { + "query": "from a_index | stats max(null)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(median_absolute_deviation(numberField))", + "query": "row nullVar = null | stats max(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(median_absolute_deviation(numberField)) + median_absolute_deviation(numberField)", + "query": "from a_index | stats max(\"2022\")", "error": [], "warning": [] }, { - "query": "from a_index | stats round(median_absolute_deviation(numberField)) + median_absolute_deviation(numberField)", + "query": "from a_index | stats max(concat(\"20\", \"22\"))", + "error": [ + "Argument of [max] must be [number], found value [concat(\"20\", \"22\")] type [string]" + ], + "warning": [] + }, + { + "query": "from a_index | stats var = min(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats median_absolute_deviation(numberField / 2)", + "query": "from a_index | stats min(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = median_absolute_deviation(numberField / 2)", + "query": "from a_index | stats var = round(min(numberField))", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), median_absolute_deviation(numberField / 2)", + "query": "from a_index | stats round(min(numberField))", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = median_absolute_deviation(numberField / 2)", + "query": "from a_index | stats var = round(min(numberField)) + min(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = median_absolute_deviation(numberField)", + "query": "from a_index | stats round(min(numberField)) + min(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), median_absolute_deviation(numberField)", + "query": "from a_index | stats min(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = median_absolute_deviation(numberField)", + "query": "from a_index | stats var0 = min(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats median_absolute_deviation(numberField) by round(numberField / 2)", + "query": "from a_index | stats avg(numberField), min(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = median_absolute_deviation(numberField) by var1 = round(numberField / 2)", + "query": "from a_index | stats avg(numberField), var0 = min(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), median_absolute_deviation(numberField) by round(numberField / 2), ipField", + "query": "from a_index | stats var0 = min(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = median_absolute_deviation(numberField) by var1 = round(numberField / 2), ipField", + "query": "from a_index | stats avg(numberField), min(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), median_absolute_deviation(numberField) by round(numberField / 2), numberField / 2", + "query": "from a_index | stats avg(numberField), var0 = min(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = median_absolute_deviation(numberField) by var1 = round(numberField / 2), numberField / 2", + "query": "from a_index | stats min(numberField) by round(numberField / 2)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = median_absolute_deviation(avg(numberField))", + "query": "from a_index | stats var0 = min(numberField) by var1 = round(numberField / 2)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(numberField), min(numberField) by round(numberField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(numberField), var0 = min(numberField) by var1 = round(numberField / 2), ipField", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(numberField), min(numberField) by round(numberField / 2), numberField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats avg(numberField), var0 = min(numberField) by var1 = round(numberField / 2), numberField / 2", + "error": [], + "warning": [] + }, + { + "query": "from a_index | stats var = min(avg(numberField))", "error": [ "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" ], "warning": [] }, { - "query": "from a_index | stats median_absolute_deviation(avg(numberField))", + "query": "from a_index | stats min(avg(numberField))", "error": [ "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" ], "warning": [] }, { - "query": "from a_index | stats median_absolute_deviation(stringField)", + "query": "from a_index | stats min(stringField)", "error": [ - "Argument of [median_absolute_deviation] must be [number], found value [stringField] type [string]" + "Argument of [min] must be [number], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | stats var = median_absolute_deviation(*)", + "query": "from a_index | stats var = min(*)", "error": [ - "Using wildcards (*) in median_absolute_deviation is not allowed" + "Using wildcards (*) in min is not allowed" ], "warning": [] }, { - "query": "from a_index | sort median_absolute_deviation(numberField)", - "error": [ - "SORT does not support function median_absolute_deviation" - ], + "query": "from a_index | stats var = min(dateField)", + "error": [], "warning": [] }, { - "query": "from a_index | where median_absolute_deviation(numberField)", - "error": [ - "WHERE does not support function median_absolute_deviation" - ], + "query": "from a_index | stats min(dateField)", + "error": [], "warning": [] }, { - "query": "from a_index | where median_absolute_deviation(numberField) > 0", - "error": [ - "WHERE does not support function median_absolute_deviation" - ], + "query": "from a_index | stats var = round(min(dateField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = median_absolute_deviation(numberField)", - "error": [ - "EVAL does not support function median_absolute_deviation" - ], + "query": "from a_index | stats round(min(dateField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = median_absolute_deviation(numberField) > 0", - "error": [ - "EVAL does not support function median_absolute_deviation" - ], + "query": "from a_index | stats var = round(min(dateField)) + min(dateField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval median_absolute_deviation(numberField)", - "error": [ - "EVAL does not support function median_absolute_deviation" - ], + "query": "from a_index | stats round(min(dateField)) + min(dateField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval median_absolute_deviation(numberField) > 0", + "query": "from a_index | sort min(numberField)", "error": [ - "EVAL does not support function median_absolute_deviation" + "SORT does not support function min" ], "warning": [] }, { - "query": "from a_index | stats median_absolute_deviation(booleanField)", + "query": "from a_index | where min(numberField)", "error": [ - "Argument of [median_absolute_deviation] must be [number], found value [booleanField] type [boolean]" + "WHERE does not support function min" ], "warning": [] }, { - "query": "from a_index | stats median_absolute_deviation(null)", - "error": [], + "query": "from a_index | where min(numberField) > 0", + "error": [ + "WHERE does not support function min" + ], "warning": [] }, { - "query": "row nullVar = null | stats median_absolute_deviation(nullVar)", - "error": [], + "query": "from a_index | where min(dateField)", + "error": [ + "WHERE does not support function min" + ], "warning": [] }, { - "query": "from a_index | stats var = percentile(numberField, 5)", - "error": [], + "query": "from a_index | where min(dateField) > 0", + "error": [ + "WHERE does not support function min" + ], "warning": [] }, { - "query": "from a_index | stats percentile(numberField, 5)", - "error": [], + "query": "from a_index | eval var = min(numberField)", + "error": [ + "EVAL does not support function min" + ], "warning": [] }, { - "query": "from a_index | stats var = round(percentile(numberField, 5))", - "error": [], + "query": "from a_index | eval var = min(numberField) > 0", + "error": [ + "EVAL does not support function min" + ], "warning": [] }, { - "query": "from a_index | stats round(percentile(numberField, 5))", - "error": [], + "query": "from a_index | eval min(numberField)", + "error": [ + "EVAL does not support function min" + ], "warning": [] }, { - "query": "from a_index | stats var = round(percentile(numberField, 5)) + percentile(numberField, 5)", - "error": [], + "query": "from a_index | eval min(numberField) > 0", + "error": [ + "EVAL does not support function min" + ], "warning": [] }, { - "query": "from a_index | stats round(percentile(numberField, 5)) + percentile(numberField, 5)", - "error": [], + "query": "from a_index | eval var = min(dateField)", + "error": [ + "EVAL does not support function min" + ], "warning": [] }, { - "query": "from a_index | stats percentile(numberField, numberField)", + "query": "from a_index | eval var = min(dateField) > 0", "error": [ - "Argument of [percentile] must be a constant, received [numberField]" + "EVAL does not support function min" ], "warning": [] }, { - "query": "from a_index | stats percentile(numberField / 2, 5)", - "error": [], + "query": "from a_index | eval min(dateField)", + "error": [ + "EVAL does not support function min" + ], "warning": [] }, { - "query": "from a_index | stats var0 = percentile(numberField / 2, 5)", - "error": [], + "query": "from a_index | eval min(dateField) > 0", + "error": [ + "EVAL does not support function min" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), percentile(numberField / 2, 5)", - "error": [], + "query": "from a_index | stats min(booleanField)", + "error": [ + "Argument of [min] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = percentile(numberField / 2, 5)", + "query": "from a_index | stats min(null)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = percentile(numberField, 5)", + "query": "row nullVar = null | stats min(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), percentile(numberField, 5)", + "query": "from a_index | stats min(\"2022\")", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = percentile(numberField, 5)", - "error": [], + "query": "from a_index | stats min(concat(\"20\", \"22\"))", + "error": [ + "Argument of [min] must be [number], found value [concat(\"20\", \"22\")] type [string]" + ], "warning": [] }, { - "query": "from a_index | stats percentile(numberField, 5) by round(numberField / 2)", + "query": "from a_index | stats var = count(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = percentile(numberField, 5) by var1 = round(numberField / 2)", + "query": "from a_index | stats count(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), percentile(numberField, 5) by round(numberField / 2), ipField", + "query": "from a_index | stats var = round(count(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = percentile(numberField, 5) by var1 = round(numberField / 2), ipField", + "query": "from a_index | stats round(count(stringField))", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), percentile(numberField, 5) by round(numberField / 2), numberField / 2", + "query": "from a_index | stats var = round(count(stringField)) + count(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = percentile(numberField, 5) by var1 = round(numberField / 2), numberField / 2", + "query": "from a_index | stats round(count(stringField)) + count(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = percentile(avg(numberField), 5)", + "query": "from a_index | sort count(stringField)", "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" + "SORT does not support function count" ], "warning": [] }, { - "query": "from a_index | stats percentile(avg(numberField), 5)", + "query": "from a_index | where count(stringField)", "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" + "WHERE does not support function count" ], "warning": [] }, { - "query": "from a_index | stats percentile(stringField, 5)", + "query": "from a_index | where count(stringField) > 0", "error": [ - "Argument of [percentile] must be [number], found value [stringField] type [string]" + "WHERE does not support function count" ], "warning": [] }, { - "query": "from a_index | sort percentile(numberField, 5)", + "query": "from a_index | eval var = count(stringField)", "error": [ - "SORT does not support function percentile" + "EVAL does not support function count" ], "warning": [] }, { - "query": "from a_index | where percentile(numberField, 5)", + "query": "from a_index | eval var = count(stringField) > 0", "error": [ - "WHERE does not support function percentile" + "EVAL does not support function count" ], "warning": [] }, { - "query": "from a_index | where percentile(numberField, 5) > 0", - "error": [ - "WHERE does not support function percentile" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = percentile(numberField, 5)", - "error": [ - "EVAL does not support function percentile" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = percentile(numberField, 5) > 0", - "error": [ - "EVAL does not support function percentile" - ], - "warning": [] - }, - { - "query": "from a_index | eval percentile(numberField, 5)", - "error": [ - "EVAL does not support function percentile" - ], - "warning": [] - }, - { - "query": "from a_index | eval percentile(numberField, 5) > 0", - "error": [ - "EVAL does not support function percentile" - ], - "warning": [] - }, - { - "query": "from a_index | stats percentile(booleanField, 5)", + "query": "from a_index | eval count(stringField)", "error": [ - "Argument of [percentile] must be [number], found value [booleanField] type [boolean]" + "EVAL does not support function count" ], "warning": [] }, { - "query": "from a_index | stats percentile(null, null)", - "error": [], - "warning": [] - }, - { - "query": "row nullVar = null | stats percentile(nullVar, nullVar)", + "query": "from a_index | eval count(stringField) > 0", "error": [ - "Argument of [percentile] must be a constant, received [nullVar]" + "EVAL does not support function count" ], "warning": [] }, { - "query": "from a_index | stats var = max(numberField)", + "query": "from a_index | stats count(null)", "error": [], "warning": [] }, { - "query": "from a_index | stats max(numberField)", + "query": "row nullVar = null | stats count(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(max(numberField))", + "query": "from a_index | stats var = count_distinct(stringField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(max(numberField))", + "query": "from a_index | stats count_distinct(stringField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(max(numberField)) + max(numberField)", + "query": "from a_index | stats var = round(count_distinct(stringField, numberField))", "error": [], "warning": [] }, { - "query": "from a_index | stats round(max(numberField)) + max(numberField)", + "query": "from a_index | stats round(count_distinct(stringField, numberField))", "error": [], "warning": [] }, { - "query": "from a_index | stats max(numberField / 2)", + "query": "from a_index | stats var = round(count_distinct(stringField, numberField)) + count_distinct(stringField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var0 = max(numberField / 2)", + "query": "from a_index | stats round(count_distinct(stringField, numberField)) + count_distinct(stringField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), max(numberField / 2)", - "error": [], + "query": "from a_index | sort count_distinct(stringField, numberField)", + "error": [ + "SORT does not support function count_distinct" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = max(numberField / 2)", - "error": [], + "query": "from a_index | where count_distinct(stringField, numberField)", + "error": [ + "WHERE does not support function count_distinct" + ], "warning": [] }, { - "query": "from a_index | stats var0 = max(numberField)", - "error": [], + "query": "from a_index | where count_distinct(stringField, numberField) > 0", + "error": [ + "WHERE does not support function count_distinct" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), max(numberField)", - "error": [], + "query": "from a_index | eval var = count_distinct(stringField, numberField)", + "error": [ + "EVAL does not support function count_distinct" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = max(numberField)", - "error": [], + "query": "from a_index | eval var = count_distinct(stringField, numberField) > 0", + "error": [ + "EVAL does not support function count_distinct" + ], "warning": [] }, { - "query": "from a_index | stats max(numberField) by round(numberField / 2)", - "error": [], + "query": "from a_index | eval count_distinct(stringField, numberField)", + "error": [ + "EVAL does not support function count_distinct" + ], "warning": [] }, { - "query": "from a_index | stats var0 = max(numberField) by var1 = round(numberField / 2)", - "error": [], + "query": "from a_index | eval count_distinct(stringField, numberField) > 0", + "error": [ + "EVAL does not support function count_distinct" + ], "warning": [] }, { - "query": "from a_index | stats avg(numberField), max(numberField) by round(numberField / 2), ipField", + "query": "from a_index | stats count_distinct(null, null)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = max(numberField) by var1 = round(numberField / 2), ipField", + "query": "row nullVar = null | stats count_distinct(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), max(numberField) by round(numberField / 2), numberField / 2", + "query": "from a_index | stats var = st_centroid_agg(cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = max(numberField) by var1 = round(numberField / 2), numberField / 2", + "query": "from a_index | stats st_centroid_agg(cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = max(avg(numberField))", + "query": "from a_index | stats var = st_centroid_agg(avg(numberField))", "error": [ "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" ], "warning": [] }, { - "query": "from a_index | stats max(avg(numberField))", + "query": "from a_index | stats st_centroid_agg(avg(numberField))", "error": [ "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" ], "warning": [] }, { - "query": "from a_index | stats max(stringField)", + "query": "from a_index | stats st_centroid_agg(stringField)", "error": [ - "Argument of [max] must be [number], found value [stringField] type [string]" + "Argument of [st_centroid_agg] must be [cartesian_point], found value [stringField] type [string]" ], "warning": [] }, { - "query": "from a_index | stats var = max(*)", + "query": "from a_index | stats var = st_centroid_agg(*)", "error": [ - "Using wildcards (*) in max is not allowed" + "Using wildcards (*) in st_centroid_agg is not allowed" ], "warning": [] }, { - "query": "from a_index | stats var = max(dateField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats max(dateField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats var = round(max(dateField))", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats round(max(dateField))", + "query": "from a_index | stats var = st_centroid_agg(geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(max(dateField)) + max(dateField)", + "query": "from a_index | stats st_centroid_agg(geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(max(dateField)) + max(dateField)", - "error": [], + "query": "from a_index | sort st_centroid_agg(cartesianPointField)", + "error": [ + "SORT does not support function st_centroid_agg" + ], "warning": [] }, { - "query": "from a_index | sort max(numberField)", + "query": "from a_index | where st_centroid_agg(cartesianPointField)", "error": [ - "SORT does not support function max" + "WHERE does not support function st_centroid_agg" ], "warning": [] }, { - "query": "from a_index | where max(numberField)", + "query": "from a_index | where st_centroid_agg(cartesianPointField) > 0", "error": [ - "WHERE does not support function max" + "WHERE does not support function st_centroid_agg" ], "warning": [] }, { - "query": "from a_index | where max(numberField) > 0", + "query": "from a_index | where st_centroid_agg(geoPointField)", "error": [ - "WHERE does not support function max" + "WHERE does not support function st_centroid_agg" ], "warning": [] }, { - "query": "from a_index | where max(dateField)", + "query": "from a_index | where st_centroid_agg(geoPointField) > 0", "error": [ - "WHERE does not support function max" + "WHERE does not support function st_centroid_agg" ], "warning": [] }, { - "query": "from a_index | where max(dateField) > 0", + "query": "from a_index | eval var = st_centroid_agg(cartesianPointField)", "error": [ - "WHERE does not support function max" + "EVAL does not support function st_centroid_agg" ], "warning": [] }, { - "query": "from a_index | eval var = max(numberField)", + "query": "from a_index | eval var = st_centroid_agg(cartesianPointField) > 0", "error": [ - "EVAL does not support function max" + "EVAL does not support function st_centroid_agg" ], "warning": [] }, { - "query": "from a_index | eval var = max(numberField) > 0", + "query": "from a_index | eval st_centroid_agg(cartesianPointField)", "error": [ - "EVAL does not support function max" + "EVAL does not support function st_centroid_agg" ], "warning": [] }, { - "query": "from a_index | eval max(numberField)", + "query": "from a_index | eval st_centroid_agg(cartesianPointField) > 0", "error": [ - "EVAL does not support function max" + "EVAL does not support function st_centroid_agg" ], "warning": [] }, { - "query": "from a_index | eval max(numberField) > 0", + "query": "from a_index | eval var = st_centroid_agg(geoPointField)", "error": [ - "EVAL does not support function max" + "EVAL does not support function st_centroid_agg" ], "warning": [] }, { - "query": "from a_index | eval var = max(dateField)", + "query": "from a_index | eval var = st_centroid_agg(geoPointField) > 0", "error": [ - "EVAL does not support function max" + "EVAL does not support function st_centroid_agg" ], "warning": [] }, { - "query": "from a_index | eval var = max(dateField) > 0", + "query": "from a_index | eval st_centroid_agg(geoPointField)", "error": [ - "EVAL does not support function max" + "EVAL does not support function st_centroid_agg" ], "warning": [] }, { - "query": "from a_index | eval max(dateField)", + "query": "from a_index | eval st_centroid_agg(geoPointField) > 0", "error": [ - "EVAL does not support function max" + "EVAL does not support function st_centroid_agg" ], "warning": [] }, { - "query": "from a_index | eval max(dateField) > 0", + "query": "from a_index | stats st_centroid_agg(booleanField)", "error": [ - "EVAL does not support function max" + "Argument of [st_centroid_agg] must be [cartesian_point], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | stats max(booleanField)", - "error": [ - "Argument of [max] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | stats st_centroid_agg(null)", + "error": [], "warning": [] }, { - "query": "from a_index | stats max(null)", + "query": "row nullVar = null | stats st_centroid_agg(nullVar)", "error": [], "warning": [] }, { - "query": "row nullVar = null | stats max(nullVar)", + "query": "from a_index | stats var = values(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | stats max(\"2022\")", + "query": "from a_index | stats values(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | stats max(concat(\"20\", \"22\"))", + "query": "from a_index | sort values(stringField)", "error": [ - "Argument of [max] must be [number], found value [concat(\"20\", \"22\")] type [string]" + "SORT does not support function values" ], "warning": [] }, { - "query": "from a_index | stats var = min(numberField)", - "error": [], + "query": "from a_index | where values(stringField)", + "error": [ + "WHERE does not support function values" + ], "warning": [] }, { - "query": "from a_index | stats min(numberField)", - "error": [], + "query": "from a_index | where values(stringField) > 0", + "error": [ + "WHERE does not support function values" + ], "warning": [] }, { - "query": "from a_index | stats var = round(min(numberField))", - "error": [], + "query": "from a_index | eval var = values(stringField)", + "error": [ + "EVAL does not support function values" + ], "warning": [] }, { - "query": "from a_index | stats round(min(numberField))", - "error": [], + "query": "from a_index | eval var = values(stringField) > 0", + "error": [ + "EVAL does not support function values" + ], "warning": [] }, { - "query": "from a_index | stats var = round(min(numberField)) + min(numberField)", - "error": [], + "query": "from a_index | eval values(stringField)", + "error": [ + "EVAL does not support function values" + ], "warning": [] }, { - "query": "from a_index | stats round(min(numberField)) + min(numberField)", - "error": [], + "query": "from a_index | eval values(stringField) > 0", + "error": [ + "EVAL does not support function values" + ], "warning": [] }, { - "query": "from a_index | stats min(numberField / 2)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats var0 = min(numberField / 2)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats avg(numberField), min(numberField / 2)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats avg(numberField), var0 = min(numberField / 2)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats var0 = min(numberField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats avg(numberField), min(numberField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats avg(numberField), var0 = min(numberField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats min(numberField) by round(numberField / 2)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats var0 = min(numberField) by var1 = round(numberField / 2)", + "query": "from a_index | stats values(null)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), min(numberField) by round(numberField / 2), ipField", + "query": "row nullVar = null | stats values(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = min(numberField) by var1 = round(numberField / 2), ipField", + "query": "from a_index | stats by bucket(dateField, 1 year)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), min(numberField) by round(numberField / 2), numberField / 2", + "query": "from a_index | stats by bin(dateField, 1 year)", "error": [], "warning": [] }, { - "query": "from a_index | stats avg(numberField), var0 = min(numberField) by var1 = round(numberField / 2), numberField / 2", + "query": "from a_index | stats by bucket(numberField, 5)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = min(avg(numberField))", - "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" - ], - "warning": [] - }, - { - "query": "from a_index | stats min(avg(numberField))", - "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" - ], - "warning": [] - }, - { - "query": "from a_index | stats min(stringField)", - "error": [ - "Argument of [min] must be [number], found value [stringField] type [string]" - ], - "warning": [] - }, - { - "query": "from a_index | stats var = min(*)", + "query": "from a_index | stats by bucket(numberField, numberField)", "error": [ - "Using wildcards (*) in min is not allowed" + "Argument of [bucket] must be a constant, received [numberField]" ], "warning": [] }, { - "query": "from a_index | stats var = min(dateField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats min(dateField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats var = round(min(dateField))", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats round(min(dateField))", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats var = round(min(dateField)) + min(dateField)", + "query": "from a_index | stats by bin(numberField, 5)", "error": [], "warning": [] }, { - "query": "from a_index | stats round(min(dateField)) + min(dateField)", + "query": "from a_index | stats by bucket(dateField, 5, \"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | sort min(numberField)", - "error": [ - "SORT does not support function min" - ], - "warning": [] - }, - { - "query": "from a_index | where min(numberField)", - "error": [ - "WHERE does not support function min" - ], - "warning": [] - }, - { - "query": "from a_index | where min(numberField) > 0", - "error": [ - "WHERE does not support function min" - ], - "warning": [] - }, - { - "query": "from a_index | where min(dateField)", - "error": [ - "WHERE does not support function min" - ], - "warning": [] - }, - { - "query": "from a_index | where min(dateField) > 0", - "error": [ - "WHERE does not support function min" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = min(numberField)", - "error": [ - "EVAL does not support function min" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = min(numberField) > 0", - "error": [ - "EVAL does not support function min" - ], - "warning": [] - }, - { - "query": "from a_index | eval min(numberField)", + "query": "from a_index | stats by bucket(dateField, numberField, stringField, stringField)", "error": [ - "EVAL does not support function min" + "Argument of [bucket] must be a constant, received [numberField]", + "Argument of [bucket] must be a constant, received [stringField]", + "Argument of [bucket] must be a constant, received [stringField]" ], "warning": [] }, { - "query": "from a_index | eval min(numberField) > 0", - "error": [ - "EVAL does not support function min" - ], + "query": "from a_index | stats by bin(dateField, 5, \"a\", \"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = min(dateField)", - "error": [ - "EVAL does not support function min" - ], + "query": "from a_index | stats by bucket(dateField, 5, now(), now())", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = min(dateField) > 0", + "query": "from a_index | stats by bucket(dateField, numberField, dateField, dateField)", "error": [ - "EVAL does not support function min" + "Argument of [bucket] must be a constant, received [numberField]", + "Argument of [bucket] must be a constant, received [dateField]", + "Argument of [bucket] must be a constant, received [dateField]" ], "warning": [] }, { - "query": "from a_index | eval min(dateField)", - "error": [ - "EVAL does not support function min" - ], + "query": "from a_index | stats by bin(dateField, 5, now(), now())", + "error": [], "warning": [] }, { - "query": "from a_index | eval min(dateField) > 0", - "error": [ - "EVAL does not support function min" - ], + "query": "from a_index | stats by bucket(dateField, 5, \"a\", now())", + "error": [], "warning": [] }, { - "query": "from a_index | stats min(booleanField)", + "query": "from a_index | stats by bucket(dateField, numberField, stringField, dateField)", "error": [ - "Argument of [min] must be [number], found value [booleanField] type [boolean]" + "Argument of [bucket] must be a constant, received [numberField]", + "Argument of [bucket] must be a constant, received [stringField]", + "Argument of [bucket] must be a constant, received [dateField]" ], "warning": [] }, { - "query": "from a_index | stats min(null)", - "error": [], - "warning": [] - }, - { - "query": "row nullVar = null | stats min(nullVar)", + "query": "from a_index | stats by bin(dateField, 5, \"a\", now())", "error": [], "warning": [] }, { - "query": "from a_index | stats min(\"2022\")", + "query": "from a_index | stats by bucket(dateField, 5, now(), \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | stats min(concat(\"20\", \"22\"))", + "query": "from a_index | stats by bucket(dateField, numberField, dateField, stringField)", "error": [ - "Argument of [min] must be [number], found value [concat(\"20\", \"22\")] type [string]" + "Argument of [bucket] must be a constant, received [numberField]", + "Argument of [bucket] must be a constant, received [dateField]", + "Argument of [bucket] must be a constant, received [stringField]" ], "warning": [] }, { - "query": "from a_index | stats var = count(stringField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats count(stringField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats var = round(count(stringField))", + "query": "from a_index | stats by bin(dateField, 5, now(), \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | stats round(count(stringField))", + "query": "from a_index | stats by bucket(numberField, 5, 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = round(count(stringField)) + count(stringField)", - "error": [], + "query": "from a_index | stats by bucket(numberField, numberField, numberField, numberField)", + "error": [ + "Argument of [bucket] must be a constant, received [numberField]", + "Argument of [bucket] must be a constant, received [numberField]", + "Argument of [bucket] must be a constant, received [numberField]" + ], "warning": [] }, { - "query": "from a_index | stats round(count(stringField)) + count(stringField)", + "query": "from a_index | stats by bin(numberField, 5, 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | sort count(stringField)", - "error": [ - "SORT does not support function count" - ], - "warning": [] - }, - { - "query": "from a_index | where count(stringField)", + "query": "from a_index | sort bucket(dateField, 1 year)", "error": [ - "WHERE does not support function count" + "SORT does not support function bucket" ], "warning": [] }, { - "query": "from a_index | where count(stringField) > 0", - "error": [ - "WHERE does not support function count" - ], + "query": "from a_index | stats bucket(null, null, null, null)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = count(stringField)", + "query": "row nullVar = null | stats bucket(nullVar, nullVar, nullVar, nullVar)", "error": [ - "EVAL does not support function count" + "Argument of [bucket] must be a constant, received [nullVar]", + "Argument of [bucket] must be a constant, received [nullVar]", + "Argument of [bucket] must be a constant, received [nullVar]" ], "warning": [] }, { - "query": "from a_index | eval var = count(stringField) > 0", - "error": [ - "EVAL does not support function count" - ], + "query": "from a_index | stats bucket(\"2022\", 1 year)", + "error": [], "warning": [] }, { - "query": "from a_index | eval count(stringField)", + "query": "from a_index | stats bucket(concat(\"20\", \"22\"), 1 year)", "error": [ - "EVAL does not support function count" + "Argument of [bucket] must be [date], found value [concat(\"20\", \"22\")] type [string]" ], "warning": [] }, { - "query": "from a_index | eval count(stringField) > 0", + "query": "from a_index | stats by bucket(concat(\"20\", \"22\"), 1 year)", "error": [ - "EVAL does not support function count" + "Argument of [bucket] must be [date], found value [concat(\"20\", \"22\")] type [string]" ], "warning": [] }, { - "query": "from a_index | stats count(null)", - "error": [], - "warning": [] - }, - { - "query": "row nullVar = null | stats count(nullVar)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats var = count_distinct(stringField, numberField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats count_distinct(stringField, numberField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats var = round(count_distinct(stringField, numberField))", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats round(count_distinct(stringField, numberField))", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats var = round(count_distinct(stringField, numberField)) + count_distinct(stringField, numberField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | stats round(count_distinct(stringField, numberField)) + count_distinct(stringField, numberField)", + "query": "from a_index | stats bucket(\"2022\", 5, \"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | sort count_distinct(stringField, numberField)", + "query": "from a_index | stats bucket(concat(\"20\", \"22\"), 5, \"a\", \"a\")", "error": [ - "SORT does not support function count_distinct" + "Argument of [bucket] must be [date], found value [concat(\"20\", \"22\")] type [string]" ], "warning": [] }, { - "query": "from a_index | where count_distinct(stringField, numberField)", - "error": [ - "WHERE does not support function count_distinct" - ], + "query": "from a_index | stats bucket(\"2022\", 5, \"2022\", \"2022\")", + "error": [], "warning": [] }, { - "query": "from a_index | where count_distinct(stringField, numberField) > 0", + "query": "from a_index | stats bucket(concat(\"20\", \"22\"), 5, concat(\"20\", \"22\"), concat(\"20\", \"22\"))", "error": [ - "WHERE does not support function count_distinct" + "Argument of [bucket] must be [date], found value [concat(\"20\", \"22\")] type [string]" ], "warning": [] }, { - "query": "from a_index | eval var = count_distinct(stringField, numberField)", - "error": [ - "EVAL does not support function count_distinct" - ], + "query": "from a_index | stats bucket(\"2022\", 5, \"a\", \"2022\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = count_distinct(stringField, numberField) > 0", + "query": "from a_index | stats bucket(concat(\"20\", \"22\"), 5, \"a\", concat(\"20\", \"22\"))", "error": [ - "EVAL does not support function count_distinct" + "Argument of [bucket] must be [date], found value [concat(\"20\", \"22\")] type [string]" ], "warning": [] }, { - "query": "from a_index | eval count_distinct(stringField, numberField)", - "error": [ - "EVAL does not support function count_distinct" - ], + "query": "from a_index | stats bucket(\"2022\", 5, \"2022\", \"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | eval count_distinct(stringField, numberField) > 0", + "query": "from a_index | stats bucket(concat(\"20\", \"22\"), 5, concat(\"20\", \"22\"), \"a\")", "error": [ - "EVAL does not support function count_distinct" + "Argument of [bucket] must be [date], found value [concat(\"20\", \"22\")] type [string]" ], "warning": [] }, { - "query": "from a_index | stats count_distinct(null, null)", - "error": [], - "warning": [] - }, - { - "query": "row nullVar = null | stats count_distinct(nullVar, nullVar)", + "query": "row var = cbrt(5)", "error": [], "warning": [] }, { - "query": "from a_index | stats var = st_centroid_agg(cartesianPointField)", + "query": "row cbrt(5)", "error": [], "warning": [] }, { - "query": "from a_index | stats st_centroid_agg(cartesianPointField)", + "query": "row var = cbrt(to_integer(true))", "error": [], "warning": [] }, { - "query": "from a_index | stats var = st_centroid_agg(avg(numberField))", - "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" - ], - "warning": [] - }, - { - "query": "from a_index | stats st_centroid_agg(avg(numberField))", + "query": "row var = cbrt(true)", "error": [ - "Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [avg(numberField)] of type [number]" + "Argument of [cbrt] must be [number], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | stats st_centroid_agg(stringField)", - "error": [ - "Argument of [st_centroid_agg] must be [cartesian_point], found value [stringField] type [string]" - ], + "query": "from a_index | where cbrt(numberField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | stats var = st_centroid_agg(*)", + "query": "from a_index | where cbrt(booleanField) > 0", "error": [ - "Using wildcards (*) in st_centroid_agg is not allowed" + "Argument of [cbrt] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | stats var = st_centroid_agg(geoPointField)", + "query": "from a_index | eval var = cbrt(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats st_centroid_agg(geoPointField)", + "query": "from a_index | eval cbrt(numberField)", "error": [], "warning": [] }, { - "query": "from a_index | sort st_centroid_agg(cartesianPointField)", - "error": [ - "SORT does not support function st_centroid_agg" - ], - "warning": [] - }, - { - "query": "from a_index | where st_centroid_agg(cartesianPointField)", - "error": [ - "WHERE does not support function st_centroid_agg" - ], - "warning": [] - }, - { - "query": "from a_index | where st_centroid_agg(cartesianPointField) > 0", - "error": [ - "WHERE does not support function st_centroid_agg" - ], - "warning": [] - }, - { - "query": "from a_index | where st_centroid_agg(geoPointField)", - "error": [ - "WHERE does not support function st_centroid_agg" - ], - "warning": [] - }, - { - "query": "from a_index | where st_centroid_agg(geoPointField) > 0", - "error": [ - "WHERE does not support function st_centroid_agg" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = st_centroid_agg(cartesianPointField)", - "error": [ - "EVAL does not support function st_centroid_agg" - ], - "warning": [] - }, - { - "query": "from a_index | eval var = st_centroid_agg(cartesianPointField) > 0", - "error": [ - "EVAL does not support function st_centroid_agg" - ], - "warning": [] - }, - { - "query": "from a_index | eval st_centroid_agg(cartesianPointField)", - "error": [ - "EVAL does not support function st_centroid_agg" - ], - "warning": [] - }, - { - "query": "from a_index | eval st_centroid_agg(cartesianPointField) > 0", - "error": [ - "EVAL does not support function st_centroid_agg" - ], + "query": "from a_index | eval var = cbrt(to_integer(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = st_centroid_agg(geoPointField)", + "query": "from a_index | eval cbrt(booleanField)", "error": [ - "EVAL does not support function st_centroid_agg" + "Argument of [cbrt] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = st_centroid_agg(geoPointField) > 0", + "query": "from a_index | eval var = cbrt(*)", "error": [ - "EVAL does not support function st_centroid_agg" + "Using wildcards (*) in cbrt is not allowed" ], "warning": [] }, { - "query": "from a_index | eval st_centroid_agg(geoPointField)", + "query": "from a_index | eval cbrt(numberField, extraArg)", "error": [ - "EVAL does not support function st_centroid_agg" + "Error: [cbrt] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | eval st_centroid_agg(geoPointField) > 0", - "error": [ - "EVAL does not support function st_centroid_agg" - ], + "query": "from a_index | sort cbrt(numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats st_centroid_agg(booleanField)", - "error": [ - "Argument of [st_centroid_agg] must be [cartesian_point], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval cbrt(null)", + "error": [], "warning": [] }, { - "query": "from a_index | stats st_centroid_agg(null)", + "query": "row nullVar = null | eval cbrt(nullVar)", "error": [], "warning": [] }, { - "query": "row nullVar = null | stats st_centroid_agg(nullVar)", + "query": "row var = from_base64(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | stats var = values(stringField)", + "query": "row from_base64(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | stats values(stringField)", + "query": "row var = from_base64(to_string(true))", "error": [], "warning": [] }, { - "query": "from a_index | sort values(stringField)", + "query": "row var = from_base64(true)", "error": [ - "SORT does not support function values" + "Argument of [from_base64] must be [string], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | where values(stringField)", - "error": [ - "WHERE does not support function values" - ], + "query": "from a_index | where length(from_base64(stringField)) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | where values(stringField) > 0", + "query": "from a_index | where length(from_base64(booleanField)) > 0", "error": [ - "WHERE does not support function values" + "Argument of [from_base64] must be [string], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = values(stringField)", - "error": [ - "EVAL does not support function values" - ], + "query": "from a_index | eval var = from_base64(stringField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = values(stringField) > 0", + "query": "from a_index | eval from_base64(stringField)", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval var = from_base64(to_string(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval from_base64(booleanField)", "error": [ - "EVAL does not support function values" + "Argument of [from_base64] must be [string], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval values(stringField)", + "query": "from a_index | eval var = from_base64(*)", "error": [ - "EVAL does not support function values" + "Using wildcards (*) in from_base64 is not allowed" ], "warning": [] }, { - "query": "from a_index | eval values(stringField) > 0", + "query": "from a_index | eval from_base64(stringField, extraArg)", "error": [ - "EVAL does not support function values" + "Error: [from_base64] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "from a_index | stats values(null)", + "query": "from a_index | sort from_base64(stringField)", "error": [], "warning": [] }, { - "query": "row nullVar = null | stats values(nullVar)", + "query": "from a_index | eval from_base64(null)", "error": [], "warning": [] }, { - "query": "from a_index | stats by bucket(dateField, 1 year)", + "query": "row nullVar = null | eval from_base64(nullVar)", "error": [], "warning": [] }, { - "query": "from a_index | stats by bin(dateField, 1 year)", + "query": "row var = locate(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | stats by bucket(numberField, 5)", + "query": "row locate(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | stats by bucket(numberField, numberField)", - "error": [ - "Argument of [bucket] must be a constant, received [numberField]" - ], + "query": "row var = locate(to_string(true), to_string(true))", + "error": [], "warning": [] }, { - "query": "from a_index | stats by bin(numberField, 5)", + "query": "row var = locate(\"a\", \"a\", 5)", "error": [], "warning": [] }, { - "query": "from a_index | stats by bucket(dateField, 5, \"a\", \"a\")", + "query": "row locate(\"a\", \"a\", 5)", "error": [], "warning": [] }, { - "query": "from a_index | stats by bucket(dateField, numberField, stringField, stringField)", - "error": [ - "Argument of [bucket] must be a constant, received [numberField]", - "Argument of [bucket] must be a constant, received [stringField]", - "Argument of [bucket] must be a constant, received [stringField]" - ], + "query": "row var = locate(to_string(true), to_string(true), to_integer(true))", + "error": [], "warning": [] }, { - "query": "from a_index | stats by bin(dateField, 5, \"a\", \"a\")", - "error": [], + "query": "row var = locate(true, true, true)", + "error": [ + "Argument of [locate] must be [string], found value [true] type [boolean]", + "Argument of [locate] must be [string], found value [true] type [boolean]", + "Argument of [locate] must be [number], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | stats by bucket(dateField, 5, now(), now())", + "query": "from a_index | where locate(stringField, stringField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | stats by bucket(dateField, numberField, dateField, dateField)", + "query": "from a_index | where locate(booleanField, booleanField) > 0", "error": [ - "Argument of [bucket] must be a constant, received [numberField]", - "Argument of [bucket] must be a constant, received [dateField]", - "Argument of [bucket] must be a constant, received [dateField]" + "Argument of [locate] must be [string], found value [booleanField] type [boolean]", + "Argument of [locate] must be [string], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | stats by bin(dateField, 5, now(), now())", + "query": "from a_index | where locate(stringField, stringField, numberField) > 0", "error": [], "warning": [] }, { - "query": "from a_index | stats by bucket(dateField, 5, \"a\", now())", - "error": [], + "query": "from a_index | where locate(booleanField, booleanField, booleanField) > 0", + "error": [ + "Argument of [locate] must be [string], found value [booleanField] type [boolean]", + "Argument of [locate] must be [string], found value [booleanField] type [boolean]", + "Argument of [locate] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "from a_index | stats by bucket(dateField, numberField, stringField, dateField)", - "error": [ - "Argument of [bucket] must be a constant, received [numberField]", - "Argument of [bucket] must be a constant, received [stringField]", - "Argument of [bucket] must be a constant, received [dateField]" - ], + "query": "from a_index | eval var = locate(stringField, stringField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats by bin(dateField, 5, \"a\", now())", + "query": "from a_index | eval locate(stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | stats by bucket(dateField, 5, now(), \"a\")", + "query": "from a_index | eval var = locate(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | stats by bucket(dateField, numberField, dateField, stringField)", + "query": "from a_index | eval locate(booleanField, booleanField)", "error": [ - "Argument of [bucket] must be a constant, received [numberField]", - "Argument of [bucket] must be a constant, received [dateField]", - "Argument of [bucket] must be a constant, received [stringField]" + "Argument of [locate] must be [string], found value [booleanField] type [boolean]", + "Argument of [locate] must be [string], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | stats by bin(dateField, 5, now(), \"a\")", + "query": "from a_index | eval var = locate(stringField, stringField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats by bucket(numberField, 5, 5, 5)", + "query": "from a_index | eval locate(stringField, stringField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | stats by bucket(numberField, numberField, numberField, numberField)", + "query": "from a_index | eval var = locate(to_string(booleanField), to_string(booleanField), to_integer(booleanField))", + "error": [], + "warning": [] + }, + { + "query": "from a_index | eval locate(booleanField, booleanField, booleanField)", "error": [ - "Argument of [bucket] must be a constant, received [numberField]", - "Argument of [bucket] must be a constant, received [numberField]", - "Argument of [bucket] must be a constant, received [numberField]" + "Argument of [locate] must be [string], found value [booleanField] type [boolean]", + "Argument of [locate] must be [string], found value [booleanField] type [boolean]", + "Argument of [locate] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | stats by bin(numberField, 5, 5, 5)", + "query": "from a_index | eval locate(stringField, stringField, numberField, extraArg)", + "error": [ + "Error: [locate] function expects no more than 3 arguments, got 4." + ], + "warning": [] + }, + { + "query": "from a_index | sort locate(stringField, stringField)", "error": [], "warning": [] }, { - "query": "from a_index | sort bucket(dateField, 1 year)", - "error": [ - "SORT does not support function bucket" - ], + "query": "from a_index | eval locate(null, null, null)", + "error": [], "warning": [] }, { - "query": "from a_index | stats bucket(null, null, null, null)", + "query": "row nullVar = null | eval locate(nullVar, nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row nullVar = null | stats bucket(nullVar, nullVar, nullVar, nullVar)", - "error": [ - "Argument of [bucket] must be a constant, received [nullVar]", - "Argument of [bucket] must be a constant, received [nullVar]", - "Argument of [bucket] must be a constant, received [nullVar]" - ], + "query": "row var = to_base64(\"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | stats bucket(\"2022\", 1 year)", + "query": "row to_base64(\"a\")", "error": [], "warning": [] }, { - "query": "from a_index | stats bucket(concat(\"20\", \"22\"), 1 year)", - "error": [ - "Argument of [bucket] must be [date], found value [concat(\"20\", \"22\")] type [string]" - ], + "query": "row var = to_base64(to_string(true))", + "error": [], "warning": [] }, { - "query": "from a_index | stats by bucket(concat(\"20\", \"22\"), 1 year)", + "query": "row var = to_base64(true)", "error": [ - "Argument of [bucket] must be [date], found value [concat(\"20\", \"22\")] type [string]" + "Argument of [to_base64] must be [string], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | stats bucket(\"2022\", 5, \"a\", \"a\")", + "query": "from a_index | where length(to_base64(stringField)) > 0", "error": [], "warning": [] }, { - "query": "from a_index | stats bucket(concat(\"20\", \"22\"), 5, \"a\", \"a\")", + "query": "from a_index | where length(to_base64(booleanField)) > 0", "error": [ - "Argument of [bucket] must be [date], found value [concat(\"20\", \"22\")] type [string]" + "Argument of [to_base64] must be [string], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | stats bucket(\"2022\", 5, \"2022\", \"2022\")", + "query": "from a_index | eval var = to_base64(stringField)", "error": [], "warning": [] }, { - "query": "from a_index | stats bucket(concat(\"20\", \"22\"), 5, concat(\"20\", \"22\"), concat(\"20\", \"22\"))", - "error": [ - "Argument of [bucket] must be [date], found value [concat(\"20\", \"22\")] type [string]" - ], + "query": "from a_index | eval to_base64(stringField)", + "error": [], "warning": [] }, { - "query": "from a_index | stats bucket(\"2022\", 5, \"a\", \"2022\")", + "query": "from a_index | eval var = to_base64(to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | stats bucket(concat(\"20\", \"22\"), 5, \"a\", concat(\"20\", \"22\"))", + "query": "from a_index | eval to_base64(booleanField)", "error": [ - "Argument of [bucket] must be [date], found value [concat(\"20\", \"22\")] type [string]" + "Argument of [to_base64] must be [string], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | stats bucket(\"2022\", 5, \"2022\", \"a\")", - "error": [], + "query": "from a_index | eval var = to_base64(*)", + "error": [ + "Using wildcards (*) in to_base64 is not allowed" + ], "warning": [] }, { - "query": "from a_index | stats bucket(concat(\"20\", \"22\"), 5, concat(\"20\", \"22\"), \"a\")", + "query": "from a_index | eval to_base64(stringField, extraArg)", "error": [ - "Argument of [bucket] must be [date], found value [concat(\"20\", \"22\")] type [string]" + "Error: [to_base64] function expects exactly one argument, got 2." ], "warning": [] }, { - "query": "row var = cbrt(5)", + "query": "from a_index | sort to_base64(stringField)", "error": [], "warning": [] }, { - "query": "row cbrt(5)", + "query": "from a_index | eval to_base64(null)", "error": [], "warning": [] }, { - "query": "row var = cbrt(to_integer(true))", + "query": "row nullVar = null | eval to_base64(nullVar)", "error": [], "warning": [] }, { - "query": "row var = cbrt(true)", - "error": [ - "Argument of [cbrt] must be [number], found value [true] type [boolean]" - ], + "query": "row var = ip_prefix(to_ip(\"127.0.0.1\"), 5, 5)", + "error": [], "warning": [] }, { - "query": "from a_index | where cbrt(numberField) > 0", + "query": "row ip_prefix(to_ip(\"127.0.0.1\"), 5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | where cbrt(booleanField) > 0", + "query": "row var = ip_prefix(to_ip(to_ip(\"127.0.0.1\")), to_integer(true), to_integer(true))", + "error": [], + "warning": [] + }, + { + "query": "row var = ip_prefix(true, true, true)", "error": [ - "Argument of [cbrt] must be [number], found value [booleanField] type [boolean]" + "Argument of [ip_prefix] must be [ip], found value [true] type [boolean]", + "Argument of [ip_prefix] must be [number], found value [true] type [boolean]", + "Argument of [ip_prefix] must be [number], found value [true] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = cbrt(numberField)", + "query": "from a_index | eval var = ip_prefix(ipField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval cbrt(numberField)", + "query": "from a_index | eval ip_prefix(ipField, numberField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = cbrt(to_integer(booleanField))", + "query": "from a_index | eval var = ip_prefix(to_ip(ipField), to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval cbrt(booleanField)", + "query": "from a_index | eval ip_prefix(booleanField, booleanField, booleanField)", "error": [ - "Argument of [cbrt] must be [number], found value [booleanField] type [boolean]" + "Argument of [ip_prefix] must be [ip], found value [booleanField] type [boolean]", + "Argument of [ip_prefix] must be [number], found value [booleanField] type [boolean]", + "Argument of [ip_prefix] must be [number], found value [booleanField] type [boolean]" ], "warning": [] }, { - "query": "from a_index | eval var = cbrt(*)", + "query": "from a_index | eval ip_prefix(ipField, numberField, numberField, extraArg)", "error": [ - "Using wildcards (*) in cbrt is not allowed" + "Error: [ip_prefix] function expects exactly 3 arguments, got 4." ], "warning": [] }, { - "query": "from a_index | eval cbrt(numberField, extraArg)", - "error": [ - "Error: [cbrt] function expects exactly one argument, got 2." - ], + "query": "from a_index | sort ip_prefix(ipField, numberField, numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort cbrt(numberField)", + "query": "from a_index | eval ip_prefix(null, null, null)", "error": [], "warning": [] }, { - "query": "from a_index | eval cbrt(null)", + "query": "row nullVar = null | eval ip_prefix(nullVar, nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval cbrt(nullVar)", + "query": "row var = mv_append(true, true)", "error": [], "warning": [] }, { - "query": "row var = from_base64(\"a\")", + "query": "row mv_append(true, true)", "error": [], "warning": [] }, { - "query": "row from_base64(\"a\")", + "query": "row var = mv_append(to_boolean(true), to_boolean(true))", "error": [], "warning": [] }, { - "query": "row var = from_base64(to_string(true))", + "query": "row var = mv_append(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = from_base64(true)", - "error": [ - "Argument of [from_base64] must be [string], found value [true] type [boolean]" - ], + "query": "row mv_append(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where length(from_base64(stringField)) > 0", + "query": "row var = mv_append(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | where length(from_base64(booleanField)) > 0", - "error": [ - "Argument of [from_base64] must be [string], found value [booleanField] type [boolean]" - ], + "query": "row var = mv_append(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = from_base64(stringField)", + "query": "row mv_append(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval from_base64(stringField)", + "query": "row var = mv_append(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = from_base64(to_string(booleanField))", + "query": "row var = mv_append(now(), now())", "error": [], "warning": [] }, { - "query": "from a_index | eval from_base64(booleanField)", - "error": [ - "Argument of [from_base64] must be [string], found value [booleanField] type [boolean]" - ], + "query": "row mv_append(now(), now())", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = from_base64(*)", - "error": [ - "Using wildcards (*) in from_base64 is not allowed" - ], + "query": "row var = mv_append(to_datetime(now()), to_datetime(now()))", + "error": [], "warning": [] }, { - "query": "from a_index | eval from_base64(stringField, extraArg)", - "error": [ - "Error: [from_base64] function expects exactly one argument, got 2." - ], + "query": "row var = mv_append(5, 5)", + "error": [], "warning": [] }, { - "query": "from a_index | sort from_base64(stringField)", + "query": "row mv_append(5, 5)", "error": [], "warning": [] }, { - "query": "from a_index | eval from_base64(null)", + "query": "row var = mv_append(to_integer(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval from_base64(nullVar)", + "query": "row var = mv_append(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = locate(\"a\", \"a\")", + "query": "row mv_append(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row locate(\"a\", \"a\")", + "query": "row var = mv_append(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = locate(to_string(true), to_string(true))", + "query": "row var = mv_append(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row var = locate(\"a\", \"a\", 5)", + "query": "row mv_append(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", "error": [], "warning": [] }, { - "query": "row locate(\"a\", \"a\", 5)", + "query": "row var = mv_append(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", "error": [], "warning": [] }, { - "query": "row var = locate(to_string(true), to_string(true), to_integer(true))", + "query": "row var = mv_append(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", "error": [], "warning": [] }, { - "query": "row var = locate(true, true, true)", - "error": [ - "Argument of [locate] must be [string], found value [true] type [boolean]", - "Argument of [locate] must be [string], found value [true] type [boolean]", - "Argument of [locate] must be [number], found value [true] type [boolean]" - ], + "query": "row mv_append(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", + "error": [], "warning": [] }, { - "query": "from a_index | where locate(stringField, stringField) > 0", + "query": "row var = mv_append(to_ip(to_ip(\"127.0.0.1\")), to_ip(to_ip(\"127.0.0.1\")))", "error": [], "warning": [] }, { - "query": "from a_index | where locate(booleanField, booleanField) > 0", - "error": [ - "Argument of [locate] must be [string], found value [booleanField] type [boolean]", - "Argument of [locate] must be [string], found value [booleanField] type [boolean]" - ], + "query": "row var = mv_append(\"a\", \"a\")", + "error": [], "warning": [] }, { - "query": "from a_index | where locate(stringField, stringField, numberField) > 0", + "query": "row mv_append(\"a\", \"a\")", "error": [], "warning": [] }, { - "query": "from a_index | where locate(booleanField, booleanField, booleanField) > 0", - "error": [ - "Argument of [locate] must be [string], found value [booleanField] type [boolean]", - "Argument of [locate] must be [string], found value [booleanField] type [boolean]", - "Argument of [locate] must be [number], found value [booleanField] type [boolean]" - ], + "query": "row var = mv_append(to_string(true), to_string(true))", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = locate(stringField, stringField)", + "query": "row var = mv_append(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval locate(stringField, stringField)", + "query": "row mv_append(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = locate(to_string(booleanField), to_string(booleanField))", + "query": "row var = mv_append(to_version(\"a\"), to_version(\"a\"))", "error": [], "warning": [] }, { - "query": "from a_index | eval locate(booleanField, booleanField)", - "error": [ - "Argument of [locate] must be [string], found value [booleanField] type [boolean]", - "Argument of [locate] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | where mv_append(numberField, numberField) > 0", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = locate(stringField, stringField, numberField)", + "query": "from a_index | where length(mv_append(stringField, stringField)) > 0", "error": [], "warning": [] }, { - "query": "from a_index | eval locate(stringField, stringField, numberField)", + "query": "from a_index | eval var = mv_append(booleanField, booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = locate(to_string(booleanField), to_string(booleanField), to_integer(booleanField))", + "query": "from a_index | eval mv_append(booleanField, booleanField)", "error": [], "warning": [] }, { - "query": "from a_index | eval locate(booleanField, booleanField, booleanField)", - "error": [ - "Argument of [locate] must be [string], found value [booleanField] type [boolean]", - "Argument of [locate] must be [string], found value [booleanField] type [boolean]", - "Argument of [locate] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_append(to_boolean(booleanField), to_boolean(booleanField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval locate(stringField, stringField, numberField, extraArg)", - "error": [ - "Error: [locate] function expects no more than 3 arguments, got 4." - ], + "query": "from a_index | eval var = mv_append(cartesianPointField, cartesianPointField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort locate(stringField, stringField)", + "query": "from a_index | eval mv_append(cartesianPointField, cartesianPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval locate(null, null, null)", + "query": "from a_index | eval var = mv_append(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval locate(nullVar, nullVar, nullVar)", + "query": "from a_index | eval var = mv_append(cartesianShapeField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row var = to_base64(\"a\")", + "query": "from a_index | eval mv_append(cartesianShapeField, cartesianShapeField)", "error": [], "warning": [] }, { - "query": "row to_base64(\"a\")", + "query": "from a_index | eval var = mv_append(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", "error": [], "warning": [] }, { - "query": "row var = to_base64(to_string(true))", + "query": "from a_index | eval var = mv_append(dateField, dateField)", "error": [], "warning": [] }, { - "query": "row var = to_base64(true)", - "error": [ - "Argument of [to_base64] must be [string], found value [true] type [boolean]" - ], + "query": "from a_index | eval mv_append(dateField, dateField)", + "error": [], "warning": [] }, { - "query": "from a_index | where length(to_base64(stringField)) > 0", + "query": "from a_index | eval var = mv_append(to_datetime(dateField), to_datetime(dateField))", "error": [], "warning": [] }, { - "query": "from a_index | where length(to_base64(booleanField)) > 0", - "error": [ - "Argument of [to_base64] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_append(numberField, numberField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_base64(stringField)", + "query": "from a_index | eval mv_append(numberField, numberField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_base64(stringField)", + "query": "from a_index | eval var = mv_append(to_integer(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_base64(to_string(booleanField))", + "query": "from a_index | eval var = mv_append(geoPointField, geoPointField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_base64(booleanField)", - "error": [ - "Argument of [to_base64] must be [string], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval mv_append(geoPointField, geoPointField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = to_base64(*)", - "error": [ - "Using wildcards (*) in to_base64 is not allowed" - ], + "query": "from a_index | eval var = mv_append(to_geopoint(geoPointField), to_geopoint(geoPointField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval to_base64(stringField, extraArg)", - "error": [ - "Error: [to_base64] function expects exactly one argument, got 2." - ], + "query": "from a_index | eval var = mv_append(geoShapeField, geoShapeField)", + "error": [], "warning": [] }, { - "query": "from a_index | sort to_base64(stringField)", + "query": "from a_index | eval mv_append(geoShapeField, geoShapeField)", "error": [], "warning": [] }, { - "query": "from a_index | eval to_base64(null)", + "query": "from a_index | eval var = mv_append(to_geoshape(geoPointField), to_geoshape(geoPointField))", "error": [], "warning": [] }, { - "query": "row nullVar = null | eval to_base64(nullVar)", + "query": "from a_index | eval var = mv_append(ipField, ipField)", "error": [], "warning": [] }, { - "query": "row var = ip_prefix(to_ip(\"127.0.0.1\"), 5, 5)", + "query": "from a_index | eval mv_append(ipField, ipField)", "error": [], "warning": [] }, { - "query": "row ip_prefix(to_ip(\"127.0.0.1\"), 5, 5)", + "query": "from a_index | eval var = mv_append(to_ip(ipField), to_ip(ipField))", "error": [], "warning": [] }, { - "query": "row var = ip_prefix(to_ip(to_ip(\"127.0.0.1\")), to_integer(true), to_integer(true))", + "query": "from a_index | eval var = mv_append(stringField, stringField)", "error": [], "warning": [] }, { - "query": "row var = ip_prefix(true, true, true)", - "error": [ - "Argument of [ip_prefix] must be [ip], found value [true] type [boolean]", - "Argument of [ip_prefix] must be [number], found value [true] type [boolean]", - "Argument of [ip_prefix] must be [number], found value [true] type [boolean]" - ], + "query": "from a_index | eval mv_append(stringField, stringField)", + "error": [], "warning": [] }, { - "query": "from a_index | eval var = ip_prefix(ipField, numberField, numberField)", + "query": "from a_index | eval var = mv_append(to_string(booleanField), to_string(booleanField))", "error": [], "warning": [] }, { - "query": "from a_index | eval ip_prefix(ipField, numberField, numberField)", + "query": "from a_index | eval var = mv_append(versionField, versionField)", "error": [], "warning": [] }, { - "query": "from a_index | eval var = ip_prefix(to_ip(ipField), to_integer(booleanField), to_integer(booleanField))", + "query": "from a_index | eval mv_append(versionField, versionField)", "error": [], "warning": [] }, { - "query": "from a_index | eval ip_prefix(booleanField, booleanField, booleanField)", - "error": [ - "Argument of [ip_prefix] must be [ip], found value [booleanField] type [boolean]", - "Argument of [ip_prefix] must be [number], found value [booleanField] type [boolean]", - "Argument of [ip_prefix] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from a_index | eval var = mv_append(to_version(stringField), to_version(stringField))", + "error": [], "warning": [] }, { - "query": "from a_index | eval ip_prefix(ipField, numberField, numberField, extraArg)", + "query": "from a_index | eval mv_append(booleanField, booleanField, extraArg)", "error": [ - "Error: [ip_prefix] function expects exactly 3 arguments, got 4." + "Error: [mv_append] function expects exactly 2 arguments, got 3." ], "warning": [] }, { - "query": "from a_index | sort ip_prefix(ipField, numberField, numberField)", - "error": [], - "warning": [] - }, - { - "query": "from a_index | eval ip_prefix(null, null, null)", - "error": [], - "warning": [] - }, - { - "query": "row nullVar = null | eval ip_prefix(nullVar, nullVar, nullVar)", + "query": "from a_index | sort mv_append(booleanField, booleanField)", "error": [], "warning": [] }, { - "query": "row var = mv_append(true, true)", + "query": "from a_index | eval mv_append(null, null)", "error": [], "warning": [] }, { - "query": "row mv_append(true, true)", + "query": "row nullVar = null | eval mv_append(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row var = mv_append(to_boolean(true), to_boolean(true))", + "query": "row var = repeat(\"a\", 5)", "error": [], "warning": [] }, { - "query": "row var = mv_append(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row repeat(\"a\", 5)", "error": [], "warning": [] }, { - "query": "row mv_append(to_cartesianpoint(\"POINT (30 10)\"), to_cartesianpoint(\"POINT (30 10)\"))", + "query": "row var = repeat(to_string(true), to_integer(true))", "error": [], "warning": [] }, { - "query": "row var = mv_append(to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianpoint(to_cartesianpoint(\"POINT (30 10)\")))", - "error": [], + "query": "row var = repeat(true, true)", + "error": [ + "Argument of [repeat] must be [string], found value [true] type [boolean]", + "Argument of [repeat] must be [number], found value [true] type [boolean]" + ], "warning": [] }, { - "query": "row var = mv_append(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", + "query": "from a_index | where length(repeat(stringField, numberField)) > 0", "error": [], "warning": [] }, { - "query": "row mv_append(to_cartesianshape(\"POINT (30 10)\"), to_cartesianshape(\"POINT (30 10)\"))", - "error": [], + "query": "from a_index | where length(repeat(booleanField, booleanField)) > 0", + "error": [ + "Argument of [repeat] must be [string], found value [booleanField] type [boolean]", + "Argument of [repeat] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = mv_append(to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")), to_cartesianshape(to_cartesianpoint(\"POINT (30 10)\")))", + "query": "from a_index | eval var = repeat(stringField, numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_append(now(), now())", + "query": "from a_index | eval repeat(stringField, numberField)", "error": [], "warning": [] }, { - "query": "row mv_append(now(), now())", + "query": "from a_index | eval var = repeat(to_string(booleanField), to_integer(booleanField))", "error": [], "warning": [] }, { - "query": "row var = mv_append(to_datetime(now()), to_datetime(now()))", - "error": [], + "query": "from a_index | eval repeat(booleanField, booleanField)", + "error": [ + "Argument of [repeat] must be [string], found value [booleanField] type [boolean]", + "Argument of [repeat] must be [number], found value [booleanField] type [boolean]" + ], "warning": [] }, { - "query": "row var = mv_append(5, 5)", - "error": [], + "query": "from a_index | eval repeat(stringField, numberField, extraArg)", + "error": [ + "Error: [repeat] function expects exactly 2 arguments, got 3." + ], "warning": [] }, { - "query": "row mv_append(5, 5)", + "query": "from a_index | sort repeat(stringField, numberField)", "error": [], "warning": [] }, { - "query": "row var = mv_append(to_integer(true), to_integer(true))", + "query": "from a_index | eval repeat(null, null)", "error": [], "warning": [] }, { - "query": "row var = mv_append(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", + "query": "row nullVar = null | eval repeat(nullVar, nullVar)", "error": [], "warning": [] }, { - "query": "row mv_append(to_geopoint(\"POINT (30 10)\"), to_geopoint(\"POINT (30 10)\"))", - "error": [], + "query": "f", + "error": [ + "SyntaxError: mismatched input 'f' expecting {'explain', 'from', 'meta', 'metrics', 'row', 'show'}" + ], "warning": [] }, { - "query": "row var = mv_append(to_geopoint(to_geopoint(\"POINT (30 10)\")), to_geopoint(to_geopoint(\"POINT (30 10)\")))", - "error": [], + "query": "from ", + "error": [ + "SyntaxError: missing INDEX_UNQUOTED_IDENTIFIER at ''" + ], "warning": [] }, { - "query": "row var = mv_append(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "from index", "error": [], "warning": [] }, { - "query": "row mv_append(to_geoshape(\"POINT (30 10)\"), to_geoshape(\"POINT (30 10)\"))", + "query": "FROM index", "error": [], "warning": [] }, { - "query": "row var = mv_append(to_geoshape(to_geopoint(\"POINT (30 10)\")), to_geoshape(to_geopoint(\"POINT (30 10)\")))", + "query": "FrOm index", "error": [], "warning": [] }, { - "query": "row var = mv_append(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", + "query": "from index, other_index", "error": [], "warning": [] }, { - "query": "row mv_append(to_ip(\"127.0.0.1\"), to_ip(\"127.0.0.1\"))", + "query": "from index, other_index,.secret_index", "error": [], "warning": [] }, { - "query": "row var = mv_append(to_ip(to_ip(\"127.0.0.1\")), to_ip(to_ip(\"127.0.0.1\")))", + "query": "from .secret_index", "error": [], "warning": [] }, { - "query": "row var = mv_append(\"a\", \"a\")", + "query": "from .secret_index", "error": [], "warning": [] }, { - "query": "row mv_append(\"a\", \"a\")", + "query": "from .secret_index", "error": [], "warning": [] }, { - "query": "row var = mv_append(to_string(true), to_string(true))", + "query": "from ind*, other*", "error": [], "warning": [] }, { - "query": "row var = mv_append(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", + "query": "from index*", "error": [], "warning": [] }, { - "query": "row mv_append(to_version(\"1.0.0\"), to_version(\"1.0.0\"))", + "query": "FROM *a_i*dex*", "error": [], "warning": [] }, { - "query": "row var = mv_append(to_version(\"a\"), to_version(\"a\"))", + "query": "FROM in*ex*", "error": [], "warning": [] }, { - "query": "from a_index | where mv_append(numberField, numberField) > 0", + "query": "FROM *n*ex", "error": [], "warning": [] }, { - "query": "from a_index | where length(mv_append(stringField, stringField)) > 0", + "query": "FROM *n*ex*", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(booleanField, booleanField)", + "query": "FROM i*d*x*", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_append(booleanField, booleanField)", + "query": "FROM i*d*x", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_boolean(booleanField), to_boolean(booleanField))", + "query": "FROM i***x*", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(cartesianPointField, cartesianPointField)", + "query": "FROM i****", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_append(cartesianPointField, cartesianPointField)", + "query": "FROM i**", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_cartesianpoint(cartesianPointField), to_cartesianpoint(cartesianPointField))", + "query": "fRoM index**", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(cartesianShapeField, cartesianShapeField)", + "query": "fRoM *ex", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_append(cartesianShapeField, cartesianShapeField)", + "query": "fRoM *ex*", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_cartesianshape(cartesianPointField), to_cartesianshape(cartesianPointField))", + "query": "fRoM in*ex", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(dateField, dateField)", + "query": "fRoM ind*ex", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_append(dateField, dateField)", + "query": "fRoM *,-.*", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_datetime(dateField), to_datetime(dateField))", + "query": "fRoM remote-*:indexes*", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(numberField, numberField)", + "query": "fRoM remote-*:indexes", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_append(numberField, numberField)", + "query": "fRoM remote-ccs:indexes", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_integer(booleanField), to_integer(booleanField))", + "query": "fRoM a_index, remote-ccs:indexes", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(geoPointField, geoPointField)", + "query": "fRoM .secret_index", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_append(geoPointField, geoPointField)", + "query": "from my-index", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_geopoint(geoPointField), to_geopoint(geoPointField))", - "error": [], + "query": "from index,", + "error": [ + "SyntaxError: missing INDEX_UNQUOTED_IDENTIFIER at ''" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_append(geoShapeField, geoShapeField)", - "error": [], + "query": "FROM index\n, \tother_index\t,\n \t ", + "error": [ + "SyntaxError: missing INDEX_UNQUOTED_IDENTIFIER at ''" + ], "warning": [] }, { - "query": "from a_index | eval mv_append(geoShapeField, geoShapeField)", - "error": [], + "query": "from assignment = 1", + "error": [ + "SyntaxError: mismatched input '=' expecting ", + "Unknown index [assignment]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_geoshape(geoPointField), to_geoshape(geoPointField))", - "error": [], + "query": "FROM `index`", + "error": [ + "SyntaxError: token recognition error at: '`'", + "SyntaxError: token recognition error at: '`'" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_append(ipField, ipField)", - "error": [], + "query": "from assignment = 1", + "error": [ + "SyntaxError: mismatched input '=' expecting ", + "Unknown index [assignment]" + ], "warning": [] }, { - "query": "from a_index | eval mv_append(ipField, ipField)", - "error": [], + "query": "FROM index, missingIndex", + "error": [ + "Unknown index [missingIndex]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_ip(ipField), to_ip(ipField))", - "error": [], + "query": "from average()", + "error": [ + "Unknown index [average()]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_append(stringField, stringField)", - "error": [], + "query": "fRom custom_function()", + "error": [ + "Unknown index [custom_function()]" + ], "warning": [] }, { - "query": "from a_index | eval mv_append(stringField, stringField)", - "error": [], + "query": "FROM indexes*", + "error": [ + "Unknown index [indexes*]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_string(booleanField), to_string(booleanField))", - "error": [], + "query": "from numberField", + "error": [ + "Unknown index [numberField]" + ], "warning": [] }, { - "query": "from a_index | eval var = mv_append(versionField, versionField)", - "error": [], + "query": "FROM policy", + "error": [ + "Unknown index [policy]" + ], "warning": [] }, { - "query": "from a_index | eval mv_append(versionField, versionField)", + "query": "from index metadata _id", "error": [], "warning": [] }, { - "query": "from a_index | eval var = mv_append(to_version(stringField), to_version(stringField))", + "query": "from index metadata _id, \t\n _index\n ", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_append(booleanField, booleanField, extraArg)", + "query": "from index (metadata _id)", "error": [ - "Error: [mv_append] function expects exactly 2 arguments, got 3." + "SyntaxError: mismatched input '(metadata' expecting " ], "warning": [] }, { - "query": "from a_index | sort mv_append(booleanField, booleanField)", + "query": "from index [METADATA _id]", "error": [], "warning": [] }, { - "query": "from a_index | eval mv_append(null, null)", + "query": "from index [METADATA _id]", "error": [], - "warning": [] + "warning": [ + "Square brackets '[]' need to be removed from FROM METADATA declaration" + ] }, { - "query": "row nullVar = null | eval mv_append(nullVar, nullVar)", + "query": "from index [metadata _id]", "error": [], - "warning": [] + "warning": [ + "Square brackets '[]' need to be removed from FROM METADATA declaration" + ] }, { - "query": "row var = repeat(\"a\", 5)", + "query": "from index [METADATA _id, _source]", "error": [], - "warning": [] + "warning": [ + "Square brackets '[]' need to be removed from FROM METADATA declaration" + ] }, { - "query": "row repeat(\"a\", 5)", + "query": "from remote-ccs:indexes [METADATA _id]", "error": [], - "warning": [] + "warning": [ + "Square brackets '[]' need to be removed from FROM METADATA declaration" + ] }, { - "query": "row var = repeat(to_string(true), to_integer(true))", + "query": "from *:indexes [METADATA _id]", "error": [], - "warning": [] + "warning": [ + "Square brackets '[]' need to be removed from FROM METADATA declaration" + ] }, { - "query": "row var = repeat(true, true)", + "query": "from index [METADATA _id, _source2]", "error": [ - "Argument of [repeat] must be [string], found value [true] type [boolean]", - "Argument of [repeat] must be [number], found value [true] type [boolean]" + "Metadata field [_source2] is not available. Available metadata fields are: [_version, _id, _index, _source, _ignored]" ], - "warning": [] - }, - { - "query": "from a_index | where length(repeat(stringField, numberField)) > 0", - "error": [], - "warning": [] + "warning": [ + "Square brackets '[]' need to be removed from FROM METADATA declaration" + ] }, { - "query": "from a_index | where length(repeat(booleanField, booleanField)) > 0", + "query": "from index [metadata _id, _source] [METADATA _id2]", "error": [ - "Argument of [repeat] must be [string], found value [booleanField] type [boolean]", - "Argument of [repeat] must be [number], found value [booleanField] type [boolean]" + "SyntaxError: mismatched input '[' expecting " ], - "warning": [] + "warning": [ + "Square brackets '[]' need to be removed from FROM METADATA declaration" + ] }, { - "query": "from a_index | eval var = repeat(stringField, numberField)", + "query": "from index METADATA _id", "error": [], "warning": [] }, { - "query": "from a_index | eval repeat(stringField, numberField)", + "query": "from index METADATA _id", "error": [], "warning": [] }, { - "query": "from a_index | eval var = repeat(to_string(booleanField), to_integer(booleanField))", + "query": "from index metadata _id", "error": [], "warning": [] }, { - "query": "from a_index | eval repeat(booleanField, booleanField)", - "error": [ - "Argument of [repeat] must be [string], found value [booleanField] type [boolean]", - "Argument of [repeat] must be [number], found value [booleanField] type [boolean]" - ], + "query": "from index METADATA _id, _source", + "error": [], "warning": [] }, { - "query": "from a_index | eval repeat(stringField, numberField, extraArg)", - "error": [ - "Error: [repeat] function expects exactly 2 arguments, got 3." - ], + "query": "from remote-ccs:indexes METADATA _id", + "error": [], "warning": [] }, { - "query": "from a_index | sort repeat(stringField, numberField)", + "query": "from *:indexes METADATA _id", "error": [], "warning": [] }, { - "query": "from a_index | eval repeat(null, null)", - "error": [], + "query": "from index METADATA _id, _source2", + "error": [ + "Metadata field [_source2] is not available. Available metadata fields are: [_version, _id, _index, _source, _ignored]" + ], "warning": [] }, { - "query": "row nullVar = null | eval repeat(nullVar, nullVar)", - "error": [], + "query": "from index metadata _id, _source METADATA _id2", + "error": [ + "SyntaxError: mismatched input 'METADATA' expecting " + ], "warning": [] } ] diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/helpers.ts b/packages/kbn-esql-validation-autocomplete/src/validation/helpers.ts index 20ec8990e1830..11cf5fd2d8f49 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/helpers.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/helpers.ts @@ -6,7 +6,13 @@ * Side Public License, v 1. */ -import type { ESQLAst, ESQLAstItem, ESQLMessage, ESQLSingleAstItem } from '@kbn/esql-ast'; +import type { + ESQLAst, + ESQLAstItem, + ESQLAstMetricsCommand, + ESQLMessage, + ESQLSingleAstItem, +} from '@kbn/esql-ast'; import { FunctionDefinition } from '../definitions/types'; import { getAllArrayTypes, getAllArrayValues } from '../shared/helpers'; import { getMessageFromId } from './errors'; @@ -14,8 +20,10 @@ import type { ESQLPolicy, ReferenceMaps } from './types'; export function buildQueryForFieldsFromSource(queryString: string, ast: ESQLAst) { const firstCommand = ast[0]; - if (firstCommand == null) { - return ''; + if (!firstCommand) return ''; + if (firstCommand.name === 'metrics') { + const metrics = firstCommand as ESQLAstMetricsCommand; + return `FROM ${metrics.sources.map((source) => source.name).join(', ')}`; } return queryString.substring(0, firstCommand.location.max + 1); } diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/types.ts b/packages/kbn-esql-validation-autocomplete/src/validation/types.ts index fbfb35818cac1..4d9732c0b5b22 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/types.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/types.ts @@ -167,6 +167,26 @@ export interface ValidationErrors { message: string; type: { value: string | number }; }; + noAggFunction: { + message: string; + type: { + commandName: string; + expression: string; + }; + }; + expressionNotAggClosed: { + message: string; + type: { + commandName: string; + expression: string; + }; + }; + aggInAggFunction: { + message: string; + type: { + nestedAgg: string; + }; + }; } export type ErrorTypes = keyof ValidationErrors; diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.from.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.from.test.ts deleted file mode 100644 index 1639800c446d8..0000000000000 --- a/packages/kbn-esql-validation-autocomplete/src/validation/validation.from.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { getAstAndSyntaxErrors } from '@kbn/esql-ast'; -import { ESQLCallbacks } from '../shared/types'; -import { ValidationOptions } from './types'; -import { validateQuery } from './validation'; -import { getCallbackMocks } from '../__tests__/helpers'; - -const setup = async () => { - const callbacks = getCallbackMocks(); - const validate = async ( - query: string, - opts: ValidationOptions = {}, - cb: ESQLCallbacks = callbacks - ) => { - return await validateQuery(query, getAstAndSyntaxErrors, opts, cb); - }; - - return { - callbacks, - validate, - }; -}; - -test('does not load fields when validating only a single FROM, SHOW, ROW command', async () => { - const { validate, callbacks } = await setup(); - - await validate('FROM kib'); - await validate('FROM kibana_ecommerce METADATA _i'); - await validate('FROM kibana_ecommerce METADATA _id | '); - await validate('SHOW'); - await validate('ROW \t'); - - expect(callbacks.getFieldsFor.mock.calls.length).toBe(0); -}); - -test('loads fields with FROM source when commands after pipe present', async () => { - const { validate, callbacks } = await setup(); - - await validate('FROM kibana_ecommerce METADATA _id | eval'); - - expect(callbacks.getFieldsFor.mock.calls.length).toBe(1); -}); diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts index 35a5ab9742561..86e8c216946f6 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts @@ -18,7 +18,6 @@ import capitalize from 'lodash/capitalize'; import { camelCase } from 'lodash'; import { getAstAndSyntaxErrors } from '@kbn/esql-ast'; import { nonNullable } from '../shared/helpers'; -import { METADATA_FIELDS } from '../shared/constants'; import { FUNCTION_DESCRIBE_BLOCK_NAME } from './function_describe_block_name'; import { fields, @@ -28,6 +27,8 @@ import { policies, unsupported_field, } from '../__tests__/helpers'; +import { validationFromCommandTestSuite as runFromTestSuite } from './__tests__/test_suites/validation.command.from'; +import { Setup, setup } from './__tests__/helpers'; const NESTING_LEVELS = 4; const NESTED_DEPTHS = Array(NESTING_LEVELS) @@ -262,120 +263,32 @@ describe('validation logic', () => { ); }); - describe('from', () => { - testErrorsAndWarnings('f', [ - `SyntaxError: mismatched input 'f' expecting {'explain', 'from', 'meta', 'metrics', 'row', 'show'}`, - ]); - testErrorsAndWarnings(`from `, ["SyntaxError: missing INDEX_UNQUOTED_IDENTIFIER at ''"]); - testErrorsAndWarnings(`from index,`, [ - "SyntaxError: missing INDEX_UNQUOTED_IDENTIFIER at ''", - ]); - testErrorsAndWarnings(`from assignment = 1`, [ - "SyntaxError: mismatched input '=' expecting ", - 'Unknown index [assignment]', - ]); - testErrorsAndWarnings(`from index`, []); - testErrorsAndWarnings(`FROM index`, []); - testErrorsAndWarnings(`FrOm index`, []); - testErrorsAndWarnings('from `index`', [ - "SyntaxError: token recognition error at: '`'", - "SyntaxError: token recognition error at: '`'", - ]); - - testErrorsAndWarnings(`from index, other_index`, []); - testErrorsAndWarnings(`from index, missingIndex`, ['Unknown index [missingIndex]']); - testErrorsAndWarnings(`from fn()`, ['Unknown index [fn()]']); - testErrorsAndWarnings(`from average()`, ['Unknown index [average()]']); - for (const isWrapped of [true, false]) { - function setWrapping(option: string) { - return isWrapped ? `[${option}]` : option; - } - function addBracketsWarning() { - return isWrapped - ? ["Square brackets '[]' need to be removed from FROM METADATA declaration"] - : []; - } - testErrorsAndWarnings( - `from index ${setWrapping('METADATA _id')}`, - [], - addBracketsWarning() - ); - testErrorsAndWarnings( - `from index ${setWrapping('metadata _id')}`, - [], - addBracketsWarning() - ); - - testErrorsAndWarnings( - `from index ${setWrapping('METADATA _id, _source')}`, - [], - addBracketsWarning() - ); - testErrorsAndWarnings( - `from index ${setWrapping('METADATA _id, _source2')}`, - [ - `Metadata field [_source2] is not available. Available metadata fields are: [${METADATA_FIELDS.join( - ', ' - )}]`, - ], - addBracketsWarning() - ); - testErrorsAndWarnings( - `from index ${setWrapping('metadata _id, _source')} ${setWrapping('METADATA _id2')}`, - [ - isWrapped - ? "SyntaxError: mismatched input '[' expecting " - : "SyntaxError: mismatched input 'METADATA' expecting ", - ], - addBracketsWarning() - ); + const collectFixturesSetup: Setup = async (...args) => { + const api = await setup(...args); + type ExpectErrors = Awaited>['expectErrors']; + return { + ...api, + expectErrors: async (...params: Parameters) => { + const [query, error = [], warning = []] = params; + const allStrings = + error.every((e) => typeof e === 'string') && + warning.every((w) => typeof w === 'string'); + if (allStrings) { + testCases.push({ + query, + error, + warning, + }); + } + }, + }; + }; - testErrorsAndWarnings( - `from remote-ccs:indexes ${setWrapping('METADATA _id')}`, - [], - addBracketsWarning() - ); - testErrorsAndWarnings( - `from *:indexes ${setWrapping('METADATA _id')}`, - [], - addBracketsWarning() - ); - } - testErrorsAndWarnings(`from index (metadata _id)`, [ - "SyntaxError: mismatched input '(metadata' expecting ", - ]); - testErrorsAndWarnings(`from ind*, other*`, []); - testErrorsAndWarnings(`from index*`, []); - testErrorsAndWarnings(`from *a_i*dex*`, []); - testErrorsAndWarnings(`from in*ex*`, []); - testErrorsAndWarnings(`from *n*ex`, []); - testErrorsAndWarnings(`from *n*ex*`, []); - testErrorsAndWarnings(`from i*d*x*`, []); - testErrorsAndWarnings(`from i*d*x`, []); - testErrorsAndWarnings(`from i***x*`, []); - testErrorsAndWarnings(`from i****`, []); - testErrorsAndWarnings(`from i**`, []); - testErrorsAndWarnings(`from index**`, []); - testErrorsAndWarnings(`from *ex`, []); - testErrorsAndWarnings(`from *ex*`, []); - testErrorsAndWarnings(`from in*ex`, []); - testErrorsAndWarnings(`from ind*ex`, []); - testErrorsAndWarnings(`from *,-.*`, []); - testErrorsAndWarnings(`from indexes*`, ['Unknown index [indexes*]']); - - testErrorsAndWarnings(`from remote-*:indexes*`, []); - testErrorsAndWarnings(`from remote-*:indexes`, []); - testErrorsAndWarnings(`from remote-ccs:indexes`, []); - testErrorsAndWarnings(`from a_index, remote-ccs:indexes`, []); - testErrorsAndWarnings('from .secret_index', []); - testErrorsAndWarnings('from my-index', []); - testErrorsAndWarnings('from numberField', ['Unknown index [numberField]']); - testErrorsAndWarnings('from policy', ['Unknown index [policy]']); - }); + runFromTestSuite(collectFixturesSetup); describe('row', () => { testErrorsAndWarnings('row', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); testErrorsAndWarnings('row missing_column', ['Unknown column [missing_column]']); testErrorsAndWarnings('row fn()', ['Unknown function [fn]']); @@ -404,7 +317,7 @@ describe('validation logic', () => { "SyntaxError: mismatched input '' expecting '('", ]); testErrorsAndWarnings('row var = 1 in (', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", 'Error: [in] function expects exactly 2 arguments, got 1.', ]); testErrorsAndWarnings('row var = 1 not in ', [ @@ -782,7 +695,7 @@ describe('validation logic', () => { describe('dissect', () => { testErrorsAndWarnings('from a_index | dissect', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); testErrorsAndWarnings('from a_index | dissect stringField', [ "SyntaxError: missing QUOTED_STRING at ''", @@ -807,7 +720,7 @@ describe('validation logic', () => { "SyntaxError: mismatched input '' expecting '='", ]); testErrorsAndWarnings('from a_index | dissect stringField "%{firstWord}" option = ', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', 'null', '?', 'true', '+', '-', OPENING_BRACKET}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET}", 'Invalid option for DISSECT: [option]', ]); testErrorsAndWarnings('from a_index | dissect stringField "%{firstWord}" option = 1', [ @@ -836,7 +749,7 @@ describe('validation logic', () => { describe('grok', () => { testErrorsAndWarnings('from a_index | grok', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); testErrorsAndWarnings('from a_index | grok stringField', [ "SyntaxError: missing QUOTED_STRING at ''", @@ -921,7 +834,7 @@ describe('validation logic', () => { } for (const wrongOp of ['*', '/', '%']) { testErrorsAndWarnings(`from a_index | where ${wrongOp}+ numberField`, [ - `SyntaxError: extraneous input '${wrongOp}' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}`, + `SyntaxError: extraneous input '${wrongOp}' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}`, ]); } @@ -994,7 +907,7 @@ describe('validation logic', () => { describe('eval', () => { testErrorsAndWarnings('from a_index | eval ', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); testErrorsAndWarnings('from a_index | eval stringField ', []); testErrorsAndWarnings('from a_index | eval b = stringField', []); @@ -1007,7 +920,7 @@ describe('validation logic', () => { ]); testErrorsAndWarnings('from a_index | eval a=b', ['Unknown column [b]']); testErrorsAndWarnings('from a_index | eval a=b, ', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", 'Unknown column [b]', ]); testErrorsAndWarnings('from a_index | eval a=round', ['Unknown column [round]']); @@ -1016,7 +929,7 @@ describe('validation logic', () => { ]); testErrorsAndWarnings('from a_index | eval a=round(numberField) ', []); testErrorsAndWarnings('from a_index | eval a=round(numberField), ', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); testErrorsAndWarnings('from a_index | eval a=round(numberField) + round(numberField) ', []); testErrorsAndWarnings('from a_index | eval a=round(numberField) + round(stringField) ', [ @@ -1079,7 +992,7 @@ describe('validation logic', () => { for (const wrongOp of ['*', '/', '%']) { testErrorsAndWarnings(`from a_index | eval ${wrongOp}+ numberField`, [ - `SyntaxError: extraneous input '${wrongOp}' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}`, + `SyntaxError: extraneous input '${wrongOp}' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}`, ]); } testErrorsAndWarnings( @@ -1283,11 +1196,11 @@ describe('validation logic', () => { [] ); testErrorsAndWarnings('from a_index | eval not', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", 'Error: [not] function expects exactly one argument, got 0.', ]); testErrorsAndWarnings('from a_index | eval in', [ - "SyntaxError: mismatched input 'in' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input 'in' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); testErrorsAndWarnings('from a_index | eval stringField in stringField', [ @@ -1370,238 +1283,14 @@ describe('validation logic', () => { }); }); - describe('stats', () => { - testErrorsAndWarnings('from a_index | stats ', [ - 'At least one aggregation or grouping expression required in [STATS]', - ]); - testErrorsAndWarnings('from a_index | stats by stringField', []); - testErrorsAndWarnings('from a_index | stats by ', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", - ]); - testErrorsAndWarnings('from a_index | stats numberField ', [ - 'Expected an aggregate function or group but got [numberField] of type [FieldAttribute]', - ]); - testErrorsAndWarnings('from a_index | stats numberField=', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", - ]); - testErrorsAndWarnings('from a_index | stats numberField=5 by ', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", - ]); - testErrorsAndWarnings('from a_index | stats avg(numberField) by wrongField', [ - 'Unknown column [wrongField]', - ]); - testErrorsAndWarnings('from a_index | stats avg(numberField) by wrongField + 1', [ - 'Unknown column [wrongField]', - ]); - testErrorsAndWarnings('from a_index | stats avg(numberField) by var0 = wrongField + 1', [ - 'Unknown column [wrongField]', - ]); - testErrorsAndWarnings('from a_index | stats avg(numberField) by 1', []); - testErrorsAndWarnings('from a_index | stats avg(numberField) by percentile(numberField)', [ - 'STATS BY does not support function percentile', - ]); - testErrorsAndWarnings('from a_index | stats count(`numberField`)', []); - - // this is a scenario that was failing because "or" didn't accept "null" - testErrorsAndWarnings('from a_index | stats count(stringField == "a" or null)', []); - - for (const subCommand of ['keep', 'drop', 'eval']) { - testErrorsAndWarnings( - `from a_index | stats count(\`numberField\`) | ${subCommand} \`count(\`\`numberField\`\`)\` `, - [] - ); - } - - testErrorsAndWarnings( - 'from a_index | stats avg(numberField) by stringField, percentile(numberField) by ipField', - [ - "SyntaxError: mismatched input 'by' expecting ", - 'STATS BY does not support function percentile', - ] - ); - - testErrorsAndWarnings( - 'from a_index | stats avg(numberField), percentile(numberField, 50) by ipField', - [] - ); - - testErrorsAndWarnings( - 'from a_index | stats avg(numberField), percentile(numberField, 50) BY ipField', - [] - ); - for (const op of ['+', '-', '*', '/', '%']) { - testErrorsAndWarnings( - `from a_index | stats avg(numberField) ${op} percentile(numberField, 50) BY ipField`, - [] - ); - } - testErrorsAndWarnings('from a_index | stats count(* + 1) BY ipField', [ - "SyntaxError: no viable alternative at input 'count(* +'", - ]); - testErrorsAndWarnings('from a_index | stats count(* + round(numberField)) BY ipField', [ - "SyntaxError: no viable alternative at input 'count(* +'", - ]); - testErrorsAndWarnings('from a_index | stats count(round(*)) BY ipField', [ - 'Using wildcards (*) in round is not allowed', - ]); - testErrorsAndWarnings('from a_index | stats count(count(*)) BY ipField', [ - `Aggregate function's parameters must be an attribute, literal or a non-aggregation function; found [count(*)] of type [number]`, - ]); - testErrorsAndWarnings('from a_index | stats numberField + 1', [ - 'At least one aggregation function required in [STATS], found [numberField+1]', - ]); - - for (const nesting of NESTED_DEPTHS) { - const moreBuiltinWrapping = Array(nesting).fill('+1').join(''); - testErrorsAndWarnings( - `from a_index | stats 5 + avg(numberField) ${moreBuiltinWrapping}`, - [] - ); - testErrorsAndWarnings( - `from a_index | stats 5 ${moreBuiltinWrapping} + avg(numberField)`, - [] - ); - testErrorsAndWarnings(`from a_index | stats 5 ${moreBuiltinWrapping} + numberField`, [ - `At least one aggregation function required in [STATS], found [5${moreBuiltinWrapping}+numberField]`, - ]); - testErrorsAndWarnings(`from a_index | stats 5 + numberField ${moreBuiltinWrapping}`, [ - `At least one aggregation function required in [STATS], found [5+numberField${moreBuiltinWrapping}]`, - ]); - testErrorsAndWarnings( - `from a_index | stats 5 + numberField ${moreBuiltinWrapping}, var0 = sum(numberField)`, - [ - `At least one aggregation function required in [STATS], found [5+numberField${moreBuiltinWrapping}]`, - ] - ); - const evalFnWrapping = Array(nesting).fill('round(').join(''); - const closingWrapping = Array(nesting).fill(')').join(''); - // stress test the validation of the nesting check here - testErrorsAndWarnings( - `from a_index | stats ${evalFnWrapping} sum(numberField) ${closingWrapping}`, - [] - ); - testErrorsAndWarnings( - `from a_index | stats ${evalFnWrapping} sum(numberField) ${closingWrapping} + ${evalFnWrapping} sum(numberField) ${closingWrapping}`, - [] - ); - testErrorsAndWarnings( - `from a_index | stats ${evalFnWrapping} numberField + sum(numberField) ${closingWrapping}`, - [ - `Cannot combine aggregation and non-aggregation values in [STATS], found [${evalFnWrapping}numberField+sum(numberField)${closingWrapping}]`, - ] - ); - testErrorsAndWarnings( - `from a_index | stats ${evalFnWrapping} numberField + sum(numberField) ${closingWrapping}, var0 = sum(numberField)`, - [ - `Cannot combine aggregation and non-aggregation values in [STATS], found [${evalFnWrapping}numberField+sum(numberField)${closingWrapping}]`, - ] - ); - testErrorsAndWarnings( - `from a_index | stats var0 = ${evalFnWrapping} numberField + sum(numberField) ${closingWrapping}, var1 = sum(numberField)`, - [ - `Cannot combine aggregation and non-aggregation values in [STATS], found [${evalFnWrapping}numberField+sum(numberField)${closingWrapping}]`, - ] - ); - testErrorsAndWarnings( - `from a_index | stats ${evalFnWrapping} sum(numberField + numberField) ${closingWrapping}`, - [] - ); - testErrorsAndWarnings( - `from a_index | stats ${evalFnWrapping} sum(numberField + round(numberField)) ${closingWrapping}`, - [] - ); - testErrorsAndWarnings( - `from a_index | stats ${evalFnWrapping} sum(numberField + round(numberField)) ${closingWrapping} + ${evalFnWrapping} sum(numberField + round(numberField)) ${closingWrapping}`, - [] - ); - testErrorsAndWarnings( - `from a_index | stats sum(${evalFnWrapping} numberField ${closingWrapping} )`, - [] - ); - testErrorsAndWarnings( - `from a_index | stats sum(${evalFnWrapping} numberField ${closingWrapping} ) + sum(${evalFnWrapping} numberField ${closingWrapping} )`, - [] - ); - } - - testErrorsAndWarnings('from a_index | stats 5 + numberField + 1', [ - 'At least one aggregation function required in [STATS], found [5+numberField+1]', - ]); - - testErrorsAndWarnings('from a_index | stats numberField + 1 by ipField', [ - 'At least one aggregation function required in [STATS], found [numberField+1]', - ]); - - testErrorsAndWarnings( - 'from a_index | stats avg(numberField), percentile(numberField, 50) + 1 by ipField', - [] - ); - - testErrorsAndWarnings('from a_index | stats count(*)', []); - testErrorsAndWarnings('from a_index | stats count()', []); - testErrorsAndWarnings('from a_index | stats var0 = count(*)', []); - testErrorsAndWarnings('from a_index | stats var0 = count()', []); - testErrorsAndWarnings('from a_index | stats var0 = avg(numberField), count(*)', []); - testErrorsAndWarnings('from a_index | stats var0 = avg(fn(number)), count(*)', [ - 'Unknown function [fn]', - ]); - - // test all not allowed combinations - testErrorsAndWarnings('from a_index | STATS sum( numberField ) + abs( numberField ) ', [ - 'Cannot combine aggregation and non-aggregation values in [STATS], found [sum(numberField)+abs(numberField)]', - ]); - testErrorsAndWarnings('from a_index | STATS abs( numberField + sum( numberField )) ', [ - 'Cannot combine aggregation and non-aggregation values in [STATS], found [abs(numberField+sum(numberField))]', - ]); - - testErrorsAndWarnings( - `FROM index - | EVAL numberField * 3.281 - | STATS avg_numberField = AVG(\`numberField * 3.281\`)`, - [] - ); - - testErrorsAndWarnings( - `FROM index | STATS AVG(numberField) by round(numberField) + 1 | EVAL \`round(numberField) + 1\` / 2`, - [] - ); - - testErrorsAndWarnings(`from a_index | stats sum(case(false, 0, 1))`, []); - testErrorsAndWarnings(`from a_index | stats var0 = sum( case(false, 0, 1))`, []); - - describe('constant-only parameters', () => { - testErrorsAndWarnings('from index | stats by bucket(dateField, abs(numberField), "", "")', [ - 'Argument of [bucket] must be a constant, received [abs(numberField)]', - ]); - testErrorsAndWarnings( - 'from index | stats by bucket(dateField, abs(length(numberField)), "", "")', - ['Argument of [bucket] must be a constant, received [abs(length(numberField))]'] - ); - testErrorsAndWarnings('from index | stats by bucket(dateField, pi(), "", "")', []); - testErrorsAndWarnings('from index | stats by bucket(dateField, 1 + 30 / 10, "", "")', []); - testErrorsAndWarnings( - 'from index | stats by bucket(dateField, 1 + 30 / 10, concat("", ""), "")', - [] - ); - testErrorsAndWarnings( - 'from index | stats by bucket(dateField, numberField, stringField, stringField)', - [ - 'Argument of [bucket] must be a constant, received [numberField]', - 'Argument of [bucket] must be a constant, received [stringField]', - 'Argument of [bucket] must be a constant, received [stringField]', - ] - ); - }); - }); - describe('sort', () => { testErrorsAndWarnings('from a_index | sort ', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); testErrorsAndWarnings('from a_index | sort "field" ', []); testErrorsAndWarnings('from a_index | sort wrongField ', ['Unknown column [wrongField]']); testErrorsAndWarnings('from a_index | sort numberField, ', [ - "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", + "SyntaxError: mismatched input '' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}", ]); testErrorsAndWarnings('from a_index | sort numberField, stringField', []); for (const dir of ['desc', 'asc']) { @@ -1945,6 +1634,69 @@ describe('validation logic', () => { }); }); + describe('inline casting', () => { + // accepts casting + testErrorsAndWarnings('from a_index | eval 1::string', []); + + // errors if the cast type is invalid + // testErrorsAndWarnings('from a_index | eval 1::foo', ['Invalid type [foo] for casting']); + + // accepts casting with multiple types + testErrorsAndWarnings('from a_index | eval 1::string::long::double', []); + + // takes into account casting in function arguments + testErrorsAndWarnings('from a_index | eval trim("23"::double)', [ + 'Argument of [trim] must be [string], found value ["23"::double] type [double]', + ]); + testErrorsAndWarnings('from a_index | eval trim(23::string)', []); + testErrorsAndWarnings('from a_index | eval 1 + "2"::long', []); + testErrorsAndWarnings('from a_index | eval 1 + "2"', [ + // just a counter-case to make sure the previous test is meaningful + 'Argument of [+] must be [number], found value ["2"] type [string]', + ]); + testErrorsAndWarnings( + 'from a_index | eval trim(to_double("23")::string::double::long::string::double)', + [ + 'Argument of [trim] must be [string], found value [to_double("23")::string::double::long::string::double] type [double]', + ] + ); + + // accepts elasticsearch subtypes and type aliases like int and keyword + // (once https://github.com/elastic/kibana/issues/174710 is done this won't be a special case anymore) + testErrorsAndWarnings('from a_index | eval CEIL(23::long)', []); + testErrorsAndWarnings('from a_index | eval CEIL(23::unsigned_long)', []); + testErrorsAndWarnings('from a_index | eval CEIL(23::int)', []); + testErrorsAndWarnings('from a_index | eval CEIL(23::integer)', []); + testErrorsAndWarnings('from a_index | eval CEIL(23::double)', []); + + testErrorsAndWarnings('from a_index | eval TRIM(23::string)', []); + testErrorsAndWarnings('from a_index | eval TRIM(23::text)', []); + testErrorsAndWarnings('from a_index | eval TRIM(23::keyword)', []); + + testErrorsAndWarnings('from a_index | eval true AND "false"::boolean', []); + testErrorsAndWarnings('from a_index | eval true AND "false"::bool', []); + testErrorsAndWarnings('from a_index | eval true AND "false"', [ + // just a counter-case to make sure the previous tests are meaningful + 'Argument of [and] must be [boolean], found value ["false"] type [string]', + ]); + + // enforces strings for cartesian_point conversion + // testErrorsAndWarnings('from a_index | eval 23::cartesian_point', ['wrong type!']); + + // still validates nested functions when they are casted + testErrorsAndWarnings('from a_index | eval to_lower(trim(numberField)::string)', [ + 'Argument of [trim] must be [string], found value [numberField] type [number]', + ]); + testErrorsAndWarnings( + 'from a_index | eval to_upper(trim(numberField)::string::string::string::string)', + ['Argument of [trim] must be [string], found value [numberField] type [number]'] + ); + testErrorsAndWarnings( + 'from a_index | eval to_lower(to_upper(trim(numberField)::string)::string)', + ['Argument of [trim] must be [string], found value [numberField] type [number]'] + ); + }); + describe(FUNCTION_DESCRIBE_BLOCK_NAME, () => { describe('date_diff', () => { testErrorsAndWarnings( diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.ts index 1bc4121d729a2..fcd17d5451825 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/validation.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/validation.ts @@ -7,34 +7,35 @@ */ import uniqBy from 'lodash/uniqBy'; -import type { +import { AstProviderFn, ESQLAstItem, + ESQLAstMetricsCommand, ESQLColumn, ESQLCommand, ESQLCommandMode, ESQLCommandOption, ESQLFunction, ESQLMessage, - ESQLSingleAstItem, ESQLSource, + walk, } from '@kbn/esql-ast'; +import type { ESQLAstField } from '@kbn/esql-ast/src/types'; import { CommandModeDefinition, CommandOptionsDefinition, - FunctionArgSignature, + FunctionParameter, FunctionDefinition, - SignatureArgType, } from '../definitions/types'; import { areFieldAndVariableTypesCompatible, extractSingularType, - getColumnHit, + lookupColumn, getCommandDefinition, getFunctionDefinition, isArrayType, isColumnItem, - isEqualType, + checkFunctionArgMatchesDefinition, isFunctionItem, isLiteralItem, isOptionItem, @@ -44,16 +45,19 @@ import { inKnownTimeInterval, printFunctionSignature, sourceExists, - columnExists, + getColumnExists, hasWildcard, hasCCSSource, isSettingItem, isAssignment, isVariable, isValidLiteralOption, + isAggFunction, + getQuotedColumnName, + isInlineCastItem, } from '../shared/helpers'; import { collectVariables } from '../shared/variables'; -import { getMessageFromId, getUnknownTypeLabel } from './errors'; +import { getMessageFromId, errors } from './errors'; import type { ErrorTypes, ESQLRealField, @@ -77,7 +81,7 @@ import { METADATA_FIELDS } from '../shared/constants'; function validateFunctionLiteralArg( astFunction: ESQLFunction, actualArg: ESQLAstItem, - argDef: FunctionArgSignature, + argDef: FunctionParameter, references: ReferenceMaps, parentCommand: string ) { @@ -101,14 +105,14 @@ function validateFunctionLiteralArg( ); } - if (!isEqualType(actualArg, argDef, references, parentCommand)) { + if (!checkFunctionArgMatchesDefinition(actualArg, argDef, references, parentCommand)) { messages.push( getMessageFromId({ messageId: 'wrongArgumentType', values: { name: astFunction.name, argType: argDef.type, - value: actualArg.value, + value: typeof actualArg.value === 'number' ? actualArg.value : String(actualArg.value), givenType: actualArg.literalType, }, locations: actualArg.location, @@ -129,7 +133,7 @@ function validateFunctionLiteralArg( }) ); } else { - if (!isEqualType(actualArg, argDef, references, parentCommand)) { + if (!checkFunctionArgMatchesDefinition(actualArg, argDef, references, parentCommand)) { messages.push( getMessageFromId({ messageId: 'wrongArgumentType', @@ -148,10 +152,39 @@ function validateFunctionLiteralArg( return messages; } +function validateInlineCastArg( + astFunction: ESQLFunction, + arg: ESQLAstItem, + parameterDefinition: FunctionParameter, + references: ReferenceMaps, + parentCommand: string +) { + if (!isInlineCastItem(arg)) { + return []; + } + + if (!checkFunctionArgMatchesDefinition(arg, parameterDefinition, references, parentCommand)) { + return [ + getMessageFromId({ + messageId: 'wrongArgumentType', + values: { + name: astFunction.name, + argType: parameterDefinition.type, + value: arg.text, + givenType: arg.castType, + }, + locations: arg.location, + }), + ]; + } + + return []; +} + function validateNestedFunctionArg( astFunction: ESQLFunction, actualArg: ESQLAstItem, - argDef: SignatureArgType, + parameterDefinition: FunctionParameter, references: ReferenceMaps, parentCommand: string ) { @@ -165,7 +198,11 @@ function validateNestedFunctionArg( const argFn = getFunctionDefinition(actualArg.name)!; const fnDef = getFunctionDefinition(astFunction.name)!; // no nestying criteria should be enforced only for same type function - if ('noNestingFunctions' in argDef && argDef.noNestingFunctions && fnDef.type === argFn.type) { + if ( + 'noNestingFunctions' in parameterDefinition && + parameterDefinition.noNestingFunctions && + fnDef.type === argFn.type + ) { messages.push( getMessageFromId({ messageId: 'noNestedArgumentSupport', @@ -174,13 +211,15 @@ function validateNestedFunctionArg( }) ); } - if (!isEqualType(actualArg, argDef, references, parentCommand)) { + if ( + !checkFunctionArgMatchesDefinition(actualArg, parameterDefinition, references, parentCommand) + ) { messages.push( getMessageFromId({ messageId: 'wrongArgumentType', values: { name: astFunction.name, - argType: argDef.type, + argType: parameterDefinition.type, value: printFunctionSignature(actualArg) || actualArg.name, givenType: argFn.signatures[0].returnType, }, @@ -195,85 +234,82 @@ function validateNestedFunctionArg( function validateFunctionColumnArg( astFunction: ESQLFunction, actualArg: ESQLAstItem, - argDef: SignatureArgType, + parameterDefinition: FunctionParameter, references: ReferenceMaps, parentCommand: string ) { const messages: ESQLMessage[] = []; - if (isColumnItem(actualArg)) { - if (actualArg.name) { - const { hit: columnCheck, nameHit } = columnExists(actualArg, references); - if (!columnCheck) { - if (argDef.constantOnly) { - messages.push( - getMessageFromId({ - messageId: 'expectedConstant', - values: { - fn: astFunction.name, - given: getUnknownTypeLabel(), - }, - locations: actualArg.location, - }) - ); - } else { - messages.push( - getMessageFromId({ - messageId: 'unknownColumn', - values: { - name: actualArg.name, - }, - locations: actualArg.location, - }) - ); - } - } else { - if (argDef.constantOnly) { - messages.push( - getMessageFromId({ - messageId: 'expectedConstant', - values: { - fn: astFunction.name, - given: actualArg.name, - }, - locations: actualArg.location, - }) - ); - } - if (actualArg.name === '*') { - // if function does not support wildcards return a specific error - if (!('supportsWildcard' in argDef) || !argDef.supportsWildcard) { - messages.push( - getMessageFromId({ - messageId: 'noWildcardSupportAsArg', - values: { - name: astFunction.name, - }, - locations: actualArg.location, - }) - ); - } - // do not validate any further for now, only count() accepts wildcard as args... - } else { - if (!isEqualType(actualArg, argDef, references, parentCommand, nameHit)) { - // guaranteed by the check above - const columnHit = getColumnHit(nameHit!, references); - messages.push( - getMessageFromId({ - messageId: 'wrongArgumentType', - values: { - name: astFunction.name, - argType: argDef.type, - value: actualArg.name, - givenType: columnHit!.type, - }, - locations: actualArg.location, - }) - ); - } - } - } + if (!isColumnItem(actualArg)) { + return messages; + } + + const columnName = getQuotedColumnName(actualArg); + const columnExists = getColumnExists(actualArg, references); + + if (parameterDefinition.constantOnly) { + messages.push( + getMessageFromId({ + messageId: 'expectedConstant', + values: { + fn: astFunction.name, + given: columnName, + }, + locations: actualArg.location, + }) + ); + + return messages; + } + + if (!columnExists) { + messages.push( + getMessageFromId({ + messageId: 'unknownColumn', + values: { + name: actualArg.name, + }, + locations: actualArg.location, + }) + ); + + return messages; + } + + if (actualArg.name === '*') { + // if function does not support wildcards return a specific error + if (!('supportsWildcard' in parameterDefinition) || !parameterDefinition.supportsWildcard) { + messages.push( + getMessageFromId({ + messageId: 'noWildcardSupportAsArg', + values: { + name: astFunction.name, + }, + locations: actualArg.location, + }) + ); } + + return messages; + } + + if ( + !checkFunctionArgMatchesDefinition(actualArg, parameterDefinition, references, parentCommand) + ) { + const columnHit = lookupColumn(actualArg, references); + messages.push( + getMessageFromId({ + messageId: 'wrongArgumentType', + values: { + name: astFunction.name, + argType: parameterDefinition.type, + value: actualArg.name, + givenType: columnHit!.type, + }, + locations: actualArg.location, + }) + ); } + return messages; } @@ -292,6 +328,13 @@ function extractCompatibleSignaturesForFunction( }); } +function removeInlineCasts(arg: ESQLAstItem): ESQLAstItem { + if (isInlineCastItem(arg)) { + return removeInlineCasts(arg.value); + } + return arg; +} + function validateFunction( astFunction: ESQLFunction, parentCommand: string, @@ -311,15 +354,7 @@ function validateFunction( if (!isFnSupported.supported) { if (isFnSupported.reason === 'unknownFunction') { - messages.push( - getMessageFromId({ - messageId: 'unknownFunction', - values: { - name: astFunction.name, - }, - locations: astFunction.location, - }) - ); + messages.push(errors.unknownFunction(astFunction)); } // for nested functions skip this check and make the nested check fail later on if (isFnSupported.reason === 'unsupportedFunction' && !isNested) { @@ -396,7 +431,15 @@ function validateFunction( return signature.params[i]?.constantOnly; }); const wrappedArray = Array.isArray(arg) ? arg : [arg]; - for (const subArg of wrappedArray) { + for (const _subArg of wrappedArray) { + /** + * we need to remove the inline casts + * to see if there's a function under there + * + * e.g. for ABS(CEIL(numberField)::int), we need to validate CEIL(numberField) + */ + const subArg = removeInlineCasts(_subArg); + if (isFunctionItem(subArg)) { const messagesFromArg = validateFunction( subArg, @@ -419,9 +462,11 @@ function validateFunction( * and each should be validated as if each were constantOnly. */ allMatchingArgDefinitionsAreConstantOnly || forceConstantOnly, - // use the nesting flag for now just for stats + // use the nesting flag for now just for stats and metrics // TODO: revisit this part later on to make it more generic - parentCommand === 'stats' ? isNested || !isAssignment(astFunction) : false + parentCommand === 'stats' || parentCommand === 'metrics' + ? isNested || !isAssignment(astFunction) + : false ); if (messagesFromArg.some(({ code }) => code === 'expectedConstant')) { @@ -473,6 +518,7 @@ function validateFunction( validateFunctionLiteralArg, validateNestedFunctionArg, validateFunctionColumnArg, + validateInlineCastArg, ].flatMap((validateFn) => { return validateFn( astFunction, @@ -567,6 +613,153 @@ function validateSetting( return messages; } +/** + * Validate that a function is an aggregate function or that all children + * recursively terminate at either a literal or an aggregate function. + */ +const isFunctionAggClosed = (fn: ESQLFunction): boolean => + isAggFunction(fn) || areFunctionArgsAggClosed(fn); + +const areFunctionArgsAggClosed = (fn: ESQLFunction): boolean => + fn.args.every((arg) => isLiteralItem(arg) || (isFunctionItem(arg) && isFunctionAggClosed(arg))); + +/** + * Looks for first nested aggregate function in an aggregate function, recursively. + */ +const findNestedAggFunctionInAggFunction = (agg: ESQLFunction): ESQLFunction | undefined => { + for (const arg of agg.args) { + if (isFunctionItem(arg)) { + return isAggFunction(arg) ? arg : findNestedAggFunctionInAggFunction(arg); + } + } +}; + +/** + * Looks for first nested aggregate function in another aggregate a function, + * recursively. + * + * @param fn Function to check for nested aggregate functions. + * @param parentIsAgg Whether the parent function of `fn` is an aggregate function. + * @returns The first nested aggregate function in `fn`, or `undefined` if none is found. + */ +const findNestedAggFunction = ( + fn: ESQLFunction, + parentIsAgg: boolean = false +): ESQLFunction | undefined => { + if (isAggFunction(fn)) { + return parentIsAgg ? fn : findNestedAggFunctionInAggFunction(fn); + } + + for (const arg of fn.args) { + if (isFunctionItem(arg)) { + const nestedAgg = findNestedAggFunction(arg, parentIsAgg || isAggFunction(fn)); + if (nestedAgg) return nestedAgg; + } + } +}; + +/** + * Validates aggregates fields: `... ...`. + */ +const validateAggregates = ( + command: ESQLCommand, + aggregates: ESQLAstField[], + references: ReferenceMaps +) => { + const messages: ESQLMessage[] = []; + + // Should never happen. + if (!aggregates.length) { + messages.push(errors.unexpected(command.location)); + return messages; + } + + let hasMissingAggregationFunctionError = false; + + for (const aggregate of aggregates) { + if (isFunctionItem(aggregate)) { + messages.push(...validateFunction(aggregate, command.name, undefined, references)); + + let hasAggregationFunction = false; + + walk(aggregate, { + visitFunction: (fn) => { + const definition = getFunctionDefinition(fn.name); + if (!definition) return; + if (definition.type === 'agg') hasAggregationFunction = true; + }, + }); + + if (!hasAggregationFunction) { + hasMissingAggregationFunctionError = true; + messages.push(errors.noAggFunction(command, aggregate)); + } + } else if (isColumnItem(aggregate)) { + messages.push(errors.unknownAggFunction(aggregate)); + } else { + // Should never happen. + } + } + + if (hasMissingAggregationFunctionError) { + return messages; + } + + for (const aggregate of aggregates) { + if (isFunctionItem(aggregate)) { + const fn = isAssignment(aggregate) ? aggregate.args[1] : aggregate; + if (isFunctionItem(fn) && !isFunctionAggClosed(fn)) { + messages.push(errors.expressionNotAggClosed(command, fn)); + } + } + } + + if (messages.length) { + return messages; + } + + for (const aggregate of aggregates) { + if (isFunctionItem(aggregate)) { + const aggInAggFunction = findNestedAggFunction(aggregate); + if (aggInAggFunction) { + messages.push(errors.aggInAggFunction(aggInAggFunction)); + break; + } + } + } + + return messages; +}; + +/** + * Validates grouping fields of the BY clause: `... BY `. + */ +const validateByGrouping = ( + fields: ESQLAstItem[], + commandName: string, + referenceMaps: ReferenceMaps, + multipleParams: boolean +): ESQLMessage[] => { + const messages: ESQLMessage[] = []; + for (const field of fields) { + if (!Array.isArray(field)) { + if (!multipleParams) { + if (isColumnItem(field)) { + messages.push(...validateColumnForCommand(field, commandName, referenceMaps)); + } + } else { + if (isColumnItem(field)) { + messages.push(...validateColumnForCommand(field, commandName, referenceMaps)); + } + if (isFunctionItem(field)) { + messages.push(...validateFunction(field, commandName, 'by', referenceMaps)); + } + } + } + } + return messages; +}; + function validateOption( option: ESQLCommandOption, optionDef: CommandOptionsDefinition | undefined, @@ -624,38 +817,40 @@ function validateSource( if (source.incomplete) { return messages; } - const commandDef = getCommandDefinition(commandName); - // give up on validate if CCS for now + const hasCCS = hasCCSSource(source.name); - if (!hasCCS) { - const isWildcardAndNotSupported = - hasWildcard(source.name) && !commandDef.signature.params.some(({ wildcards }) => wildcards); - if (isWildcardAndNotSupported) { + if (hasCCS) { + return messages; + } + + const commandDef = getCommandDefinition(commandName); + const isWildcardAndNotSupported = + hasWildcard(source.name) && !commandDef.signature.params.some(({ wildcards }) => wildcards); + if (isWildcardAndNotSupported) { + messages.push( + getMessageFromId({ + messageId: 'wildcardNotSupportedForCommand', + values: { command: commandName.toUpperCase(), value: source.name }, + locations: source.location, + }) + ); + } else { + if (source.sourceType === 'index' && !sourceExists(source.name, sources)) { messages.push( getMessageFromId({ - messageId: 'wildcardNotSupportedForCommand', - values: { command: commandName.toUpperCase(), value: source.name }, + messageId: 'unknownIndex', + values: { name: source.name }, + locations: source.location, + }) + ); + } else if (source.sourceType === 'policy' && !policies.has(source.name)) { + messages.push( + getMessageFromId({ + messageId: 'unknownPolicy', + values: { name: source.name }, locations: source.location, }) ); - } else { - if (source.sourceType === 'index' && !sourceExists(source.name, sources)) { - messages.push( - getMessageFromId({ - messageId: 'unknownIndex', - values: { name: source.name }, - locations: source.location, - }) - ); - } else if (source.sourceType === 'policy' && !policies.has(source.name)) { - messages.push( - getMessageFromId({ - messageId: 'unknownPolicy', - values: { name: source.name }, - locations: source.location, - }) - ); - } } } @@ -671,25 +866,17 @@ function validateColumnForCommand( if (commandName === 'row') { if (!references.variables.has(column.name)) { - messages.push( - getMessageFromId({ - messageId: 'unknownColumn', - values: { - name: column.name, - }, - locations: column.location, - }) - ); + messages.push(errors.unknownColumn(column)); } } else { - const { hit: columnCheck, nameHit } = columnExists(column, references); - if (columnCheck && nameHit) { + const columnName = getQuotedColumnName(column); + if (getColumnExists(column, references)) { const commandDef = getCommandDefinition(commandName); const columnParamsWithInnerTypes = commandDef.signature.params.filter( ({ type, innerType }) => type === 'column' && innerType ); // this should be guaranteed by the columnCheck above - const columnRef = getColumnHit(nameHit, references)!; + const columnRef = lookupColumn(column, references)!; if (columnParamsWithInnerTypes.length) { const hasSomeWrongInnerTypes = columnParamsWithInnerTypes.every(({ innerType }) => { @@ -706,7 +893,7 @@ function validateColumnForCommand( type: supportedTypes.join(', '), typeCount: supportedTypes.length, givenType: columnRef.type, - column: nameHit, + column: columnName, }, locations: column.location, }) @@ -714,7 +901,7 @@ function validateColumnForCommand( } } if ( - hasWildcard(nameHit) && + hasWildcard(columnName) && !isVariable(columnRef) && !commandDef.signature.params.some(({ type, wildcards }) => type === 'column' && wildcards) ) { @@ -723,7 +910,7 @@ function validateColumnForCommand( messageId: 'wildcardNotSupportedForCommand', values: { command: commandName.toUpperCase(), - value: nameHit, + value: columnName, }, locations: column.location, }) @@ -731,21 +918,55 @@ function validateColumnForCommand( } } else { if (column.name) { - messages.push( - getMessageFromId({ - messageId: 'unknownColumn', - values: { - name: column.name, - }, - locations: column.location, - }) - ); + messages.push(errors.unknownColumn(column)); } } } return messages; } +export function validateSources( + command: ESQLCommand, + sources: ESQLSource[], + references: ReferenceMaps +): ESQLMessage[] { + const messages: ESQLMessage[] = []; + + for (const source of sources) { + messages.push(...validateSource(source, command.name, references)); + } + + return messages; +} + +/** + * Validates the METRICS source command: + * + * METRICS [ [ BY ]] + */ +const validateMetricsCommand = ( + command: ESQLAstMetricsCommand, + references: ReferenceMaps +): ESQLMessage[] => { + const messages: ESQLMessage[] = []; + const { sources, aggregates, grouping } = command; + + // METRICS ... + messages.push(...validateSources(command, sources, references)); + + // ... ... + if (aggregates && aggregates.length) { + messages.push(...validateAggregates(command, aggregates, references)); + + // ... BY + if (grouping && grouping.length) { + messages.push(...validateByGrouping(grouping, 'metrics', references, true)); + } + } + + return messages; +}; + function validateCommand(command: ESQLCommand, references: ReferenceMaps): ESQLMessage[] { const messages: ESQLMessage[] = []; if (command.incomplete) { @@ -754,66 +975,67 @@ function validateCommand(command: ESQLCommand, references: ReferenceMaps): ESQLM // do not check the command exists, the grammar is already picking that up const commandDef = getCommandDefinition(command.name); - if (commandDef.validate) { + if (commandDef?.validate) { messages.push(...commandDef.validate(command)); } - // Now validate arguments - for (const commandArg of command.args) { - const wrappedArg = Array.isArray(commandArg) ? commandArg : [commandArg]; - for (const arg of wrappedArg) { - if (isFunctionItem(arg)) { - messages.push(...validateFunction(arg, command.name, undefined, references)); - } + switch (commandDef.name) { + case 'metrics': { + const metrics = command as ESQLAstMetricsCommand; + messages.push(...validateMetricsCommand(metrics, references)); + break; + } + default: { + // Now validate arguments + for (const commandArg of command.args) { + const wrappedArg = Array.isArray(commandArg) ? commandArg : [commandArg]; + for (const arg of wrappedArg) { + if (isFunctionItem(arg)) { + messages.push(...validateFunction(arg, command.name, undefined, references)); + } - if (isSettingItem(arg)) { - messages.push(...validateSetting(arg, commandDef.modes[0], command, references)); - } + if (isSettingItem(arg)) { + messages.push(...validateSetting(arg, commandDef.modes[0], command, references)); + } - if (isOptionItem(arg)) { - messages.push( - ...validateOption( - arg, - commandDef.options.find(({ name }) => name === arg.name), - command, - references - ) - ); - } - if (isColumnItem(arg)) { - if (command.name === 'stats') { - messages.push( - getMessageFromId({ - messageId: 'unknownAggregateFunction', - values: { - value: (arg as ESQLSingleAstItem).name, - type: 'FieldAttribute', - }, - locations: (arg as ESQLSingleAstItem).location, - }) - ); - } else { - messages.push(...validateColumnForCommand(arg, command.name, references)); + if (isOptionItem(arg)) { + messages.push( + ...validateOption( + arg, + commandDef.options.find(({ name }) => name === arg.name), + command, + references + ) + ); + } + if (isColumnItem(arg)) { + if (command.name === 'stats') { + messages.push(errors.unknownAggFunction(arg)); + } else { + messages.push(...validateColumnForCommand(arg, command.name, references)); + } + } + if (isTimeIntervalItem(arg)) { + messages.push( + getMessageFromId({ + messageId: 'unsupportedTypeForCommand', + values: { + command: command.name.toUpperCase(), + type: 'date_period', + value: arg.name, + }, + locations: arg.location, + }) + ); + } + if (isSourceItem(arg)) { + messages.push(...validateSource(arg, command.name, references)); + } } } - if (isTimeIntervalItem(arg)) { - messages.push( - getMessageFromId({ - messageId: 'unsupportedTypeForCommand', - values: { - command: command.name.toUpperCase(), - type: 'date_period', - value: arg.name, - }, - locations: arg.location, - }) - ); - } - if (isSourceItem(arg)) { - messages.push(...validateSource(arg, command.name, references)); - } } } + // no need to check for mandatory options passed // as they are already validated at syntax level return messages; @@ -891,7 +1113,6 @@ export async function validateQuery( if (!options.ignoreOnMissingCallbacks) { return result; } - const { errors, warnings } = result; const finalCallbacks = callbacks || {}; const errorTypoesToIgnore = Object.entries(ignoreErrorsMap).reduce((acc, [key, errorCodes]) => { if ( @@ -904,7 +1125,7 @@ export async function validateQuery( } return acc; }, {} as Partial>); - const filteredErrors = errors + const filteredErrors = result.errors .filter((error) => { if ('severity' in error) { return true; @@ -921,7 +1142,7 @@ export async function validateQuery( } : error ); - return { errors: filteredErrors, warnings }; + return { errors: filteredErrors, warnings: result.warnings }; } /** @@ -937,7 +1158,8 @@ async function validateAst( ): Promise { const messages: ESQLMessage[] = []; - const { ast, errors } = await astProvider(queryString); + const parsingResult = await astProvider(queryString); + const { ast } = parsingResult; const [sources, availableFields, availablePolicies] = await Promise.all([ // retrieve the list of available sources @@ -974,18 +1196,19 @@ async function validateAst( messages.push(...validateUnsupportedTypeFields(availableFields)); for (const command of ast) { - const commandMessages = validateCommand(command, { + const references: ReferenceMaps = { sources, fields: availableFields, policies: availablePolicies, variables, query: queryString, - }); + }; + const commandMessages = validateCommand(command, references); messages.push(...commandMessages); } return { - errors: [...errors, ...messages.filter(({ type }) => type === 'error')], + errors: [...parsingResult.errors, ...messages.filter(({ type }) => type === 'error')], warnings: messages.filter(({ type }) => type === 'warning'), }; } diff --git a/packages/kbn-expandable-flyout/src/index.stories.tsx b/packages/kbn-expandable-flyout/src/index.stories.tsx index e02e3de8b791a..1c10c5454b727 100644 --- a/packages/kbn-expandable-flyout/src/index.stories.tsx +++ b/packages/kbn-expandable-flyout/src/index.stories.tsx @@ -101,13 +101,17 @@ const registeredPanels = [ ]; export const Right: Story = () => { - const state = { - right: { - id: 'right', + const state: State = { + byId: { + memory: { + right: { + id: 'right', + }, + left: undefined, + preview: undefined, + }, }, - left: {}, - preview: [], - } as unknown as State; + }; return ( @@ -117,15 +121,19 @@ export const Right: Story = () => { }; export const Left: Story = () => { - const state = { - right: { - id: 'right', - }, - left: { - id: 'left', + const state: State = { + byId: { + memory: { + right: { + id: 'right', + }, + left: { + id: 'left', + }, + preview: undefined, + }, }, - preview: [], - } as unknown as State; + }; return ( @@ -135,19 +143,23 @@ export const Left: Story = () => { }; export const Preview: Story = () => { - const state = { - right: { - id: 'right', - }, - left: { - id: 'left', - }, - preview: [ - { - id: 'preview1', + const state: State = { + byId: { + memory: { + right: { + id: 'right', + }, + left: { + id: 'left', + }, + preview: [ + { + id: 'preview1', + }, + ], }, - ], - } as unknown as State; + }, + }; return ( @@ -157,22 +169,26 @@ export const Preview: Story = () => { }; export const MultiplePreviews: Story = () => { - const state = { - right: { - id: 'right', - }, - left: { - id: 'left', - }, - preview: [ - { - id: 'preview1', + const state: State = { + byId: { + memory: { + right: { + id: 'right', + }, + left: { + id: 'left', + }, + preview: [ + { + id: 'preview1', + }, + { + id: 'preview2', + }, + ], }, - { - id: 'preview2', - }, - ], - } as unknown as State; + }, + }; return ( diff --git a/packages/kbn-expandable-flyout/src/test/provider.tsx b/packages/kbn-expandable-flyout/src/test/provider.tsx index 896b563056206..281225161b27a 100644 --- a/packages/kbn-expandable-flyout/src/test/provider.tsx +++ b/packages/kbn-expandable-flyout/src/test/provider.tsx @@ -9,6 +9,7 @@ import { Provider as ReduxProvider } from 'react-redux'; import { configureStore } from '@reduxjs/toolkit'; import React, { FC, PropsWithChildren } from 'react'; +import { I18nProvider } from '@kbn/i18n-react'; import { ExpandableFlyoutContextProvider } from '../context'; import { reducer } from '../reducer'; import { Context } from '../redux'; @@ -32,10 +33,12 @@ export const TestProvider: FC> = ({ }); return ( - - - {children} - - + + + + {children} + + + ); }; diff --git a/packages/kbn-expandable-flyout/tsconfig.json b/packages/kbn-expandable-flyout/tsconfig.json index 2b6e518016871..5a0cf87cf61e2 100644 --- a/packages/kbn-expandable-flyout/tsconfig.json +++ b/packages/kbn-expandable-flyout/tsconfig.json @@ -20,6 +20,7 @@ ], "kbn_references": [ "@kbn/i18n", - "@kbn/kibana-utils-plugin" + "@kbn/kibana-utils-plugin", + "@kbn/i18n-react" ] } diff --git a/packages/kbn-field-utils/index.ts b/packages/kbn-field-utils/index.ts index d631c51c757a4..3adb267938940 100644 --- a/packages/kbn-field-utils/index.ts +++ b/packages/kbn-field-utils/index.ts @@ -27,7 +27,3 @@ export { export { FieldIcon, type FieldIconProps, getFieldIconProps } from './src/components/field_icon'; export { FieldDescription, type FieldDescriptionProps } from './src/components/field_description'; -export { - FieldDescriptionIconButton, - type FieldDescriptionIconButtonProps, -} from './src/components/field_description_icon_button'; diff --git a/packages/kbn-field-utils/src/components/field_description_icon_button/field_description_icon_button.test.tsx b/packages/kbn-field-utils/src/components/field_description_icon_button/field_description_icon_button.test.tsx deleted file mode 100644 index c677415cd4ace..0000000000000 --- a/packages/kbn-field-utils/src/components/field_description_icon_button/field_description_icon_button.test.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { FieldDescriptionIconButton } from './field_description_icon_button'; -import { render, screen } from '@testing-library/react'; - -describe('FieldDescriptionIconButton', () => { - it('should render correctly when no custom description', async () => { - const { container } = render(); - expect(container).toBeEmptyDOMElement(); - }); - - it('should render correctly with a short custom description', async () => { - const customDescription = 'test this desc'; - render(); - expect(screen.queryByTestId('fieldDescription-bytes')).toBeNull(); - screen.queryByTestId('fieldDescriptionPopoverButton-bytes')?.click(); - expect(screen.queryByTestId('fieldDescription-bytes')).toHaveTextContent(customDescription); - }); - - it('should render correctly with a long custom description', async () => { - const customDescription = 'test this long desc '.repeat(8).trim(); - render(); - expect(screen.queryByTestId('fieldDescription-bytes')).toBeNull(); - screen.queryByTestId('fieldDescriptionPopoverButton-bytes')?.click(); - expect(screen.queryByTestId('fieldDescription-bytes')).toHaveTextContent(customDescription); - }); -}); diff --git a/packages/kbn-field-utils/src/components/field_description_icon_button/field_description_icon_button.tsx b/packages/kbn-field-utils/src/components/field_description_icon_button/field_description_icon_button.tsx deleted file mode 100644 index c0358c53cd27f..0000000000000 --- a/packages/kbn-field-utils/src/components/field_description_icon_button/field_description_icon_button.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { useState } from 'react'; -import { i18n } from '@kbn/i18n'; -import { css } from '@emotion/react'; -import { EuiButtonIcon, EuiPopover, EuiPopoverProps, useEuiTheme } from '@elastic/eui'; -import { FieldDescription, FieldDescriptionProps } from '../field_description'; - -export type FieldDescriptionIconButtonProps = Pick & { - field: FieldDescriptionProps['field']; -}; - -export const FieldDescriptionIconButton: React.FC = ({ - field, - ...otherProps -}) => { - const { euiTheme } = useEuiTheme(); - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - - if (!field?.customDescription) { - return null; - } - - const buttonTitle = i18n.translate('fieldUtils.fieldDescriptionIconButtonTitle', { - defaultMessage: 'View field description', - }); - - return ( - - setIsPopoverOpen(false)} - panelProps={{ - css: css` - max-width: ${euiTheme.base * 20}px; - `, - }} - button={ - setIsPopoverOpen(!isPopoverOpen)} - /> - } - > - - - - ); -}; diff --git a/packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/xy_chart.ts b/packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/xy_chart.ts index a992a97a23e4b..60891ea0b1c10 100644 --- a/packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/xy_chart.ts +++ b/packages/kbn-lens-embeddable-utils/attribute_builder/visualization_types/xy_chart.ts @@ -17,7 +17,7 @@ import type { import type { DataView } from '@kbn/data-views-plugin/public'; import type { SavedObjectReference } from '@kbn/core/server'; import { AxesSettingsConfig } from '@kbn/visualizations-plugin/common'; -import type { XYLegendValue } from '@kbn/visualizations-plugin/common/constants'; +import { LegendValue } from '@elastic/charts'; import type { Chart, ChartConfig, ChartLayer } from '../types'; import { DEFAULT_LAYER_ID } from '../utils'; import { XY_ID } from './constants'; @@ -131,7 +131,7 @@ export const getXYVisualizationState = ( isVisible: false, position: 'right', showSingleSeries: false, - legendStats: ['currentAndLastValue' as XYLegendValue.CurrentAndLastValue], + legendStats: [LegendValue.CurrentAndLastValue], }, valueLabels: 'show', yLeftScale: 'linear', diff --git a/packages/kbn-monaco/src/console/types.ts b/packages/kbn-monaco/src/console/types.ts index 986ffd6704508..346bd0e6beeeb 100644 --- a/packages/kbn-monaco/src/console/types.ts +++ b/packages/kbn-monaco/src/console/types.ts @@ -15,7 +15,7 @@ export interface ParsedRequest { startOffset: number; endOffset?: number; method: string; - url: string; + url?: string; data?: Array>; } export interface ConsoleParserResult { diff --git a/packages/kbn-monaco/src/esql/lib/esql_theme.ts b/packages/kbn-monaco/src/esql/lib/esql_theme.ts index f9ed8c6849acc..80f9fc5400989 100644 --- a/packages/kbn-monaco/src/esql/lib/esql_theme.ts +++ b/packages/kbn-monaco/src/esql/lib/esql_theme.ts @@ -12,15 +12,6 @@ import { ESQL_TOKEN_POSTFIX } from './constants'; import { monaco } from '../../monaco_imports'; const buildRuleGroup = themeRuleGroupBuilderFactory(ESQL_TOKEN_POSTFIX); -const COMMANDS_COLORS = { - dark: '#a68ac5', - light: '#765b96', -}; - -const FUNCTIONS_COLORS = { - dark: '#d97797', - light: '#a34a68', -}; export const buildESQlTheme = (): monaco.editor.IStandaloneThemeData => ({ base: darkMode ? 'vs-dark' : 'vs', @@ -55,10 +46,16 @@ export const buildESQlTheme = (): monaco.editor.IStandaloneThemeData => ({ euiThemeVars.euiTextColor ), + // source commands + ...buildRuleGroup( + ['from', 'row', 'show', 'meta'], + euiThemeVars.euiColorPrimaryText, + true // isBold + ), + // commands ...buildRuleGroup( [ - 'from', 'metrics', 'metadata', 'mv_expand', @@ -80,9 +77,6 @@ export const buildESQlTheme = (): monaco.editor.IStandaloneThemeData => ({ 'in', 'as', 'expr_ws', - 'row', - 'show', - 'meta', 'limit', 'nulls_ordering_direction', 'nulls_ordering', @@ -90,17 +84,37 @@ export const buildESQlTheme = (): monaco.editor.IStandaloneThemeData => ({ 'enrich', 'on', 'with', + 'asc', + 'desc', ], - darkMode ? COMMANDS_COLORS.dark : COMMANDS_COLORS.light + euiThemeVars.euiColorAccentText, + true // isBold ), // functions - ...buildRuleGroup(['functions'], darkMode ? FUNCTIONS_COLORS.dark : FUNCTIONS_COLORS.light), + ...buildRuleGroup(['functions'], euiThemeVars.euiColorPrimaryText), // operators ...buildRuleGroup( - ['or', 'and', 'rp', 'lp', 'plus', 'minus', 'asterisk', 'slash'], - euiThemeVars.euiTextSubduedColor + [ + 'or', + 'and', + 'rp', // ')' + 'lp', // '(' + 'eq', // '==' + 'cieq', // '=~' + 'neq', // '!=' + 'lt', // '<' + 'lte', // '<=' + 'gt', // '>' + 'gte', // '>=' + 'plus', // '+' + 'minus', // '-' + 'asterisk', // '*' + 'slash', // '/' + 'percent', // '%' + ], + euiThemeVars.euiColorPrimaryText ), // comments @@ -113,8 +127,28 @@ export const buildESQlTheme = (): monaco.editor.IStandaloneThemeData => ({ 'src_line_comment', 'src_multiline_comment', ], - darkMode ? euiThemeVars.euiColorDarkestShade : euiThemeVars.euiColorMediumShade + euiThemeVars.euiColorDisabledText + ), + + // values + ...buildRuleGroup( + ['quoted_string', 'integer_literal', 'decimal_literal'], + euiThemeVars.euiColorSuccessText ), ], - colors: {}, + colors: { + 'editor.foreground': euiThemeVars.euiTextColor, + 'editor.background': euiThemeVars.euiColorEmptyShade, + 'editor.lineHighlightBackground': euiThemeVars.euiColorLightestShade, + 'editor.lineHighlightBorder': euiThemeVars.euiColorLightestShade, + 'editor.selectionHighlightBackground': euiThemeVars.euiColorLightestShade, + 'editor.selectionHighlightBorder': euiThemeVars.euiColorLightShade, + 'editorSuggestWidget.background': euiThemeVars.euiColorEmptyShade, + 'editorSuggestWidget.border': euiThemeVars.euiColorEmptyShade, + 'editorSuggestWidget.focusHighlightForeground': euiThemeVars.euiColorEmptyShade, + 'editorSuggestWidget.foreground': euiThemeVars.euiTextColor, + 'editorSuggestWidget.highlightForeground': euiThemeVars.euiColorPrimary, + 'editorSuggestWidget.selectedBackground': euiThemeVars.euiColorPrimary, + 'editorSuggestWidget.selectedForeground': euiThemeVars.euiColorEmptyShade, + }, }); diff --git a/packages/kbn-openapi-common/README.md b/packages/kbn-openapi-common/README.md new file mode 100644 index 0000000000000..7199904b486be --- /dev/null +++ b/packages/kbn-openapi-common/README.md @@ -0,0 +1,3 @@ +# OpenAPI Common Schemas + +This package contains common reusable schemas like `NonEmptyString` to be reused by any OpenAPI specification defined inside Kibana. diff --git a/packages/kbn-openapi-common/kibana.jsonc b/packages/kbn-openapi-common/kibana.jsonc new file mode 100644 index 0000000000000..4254feb1b8a73 --- /dev/null +++ b/packages/kbn-openapi-common/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/openapi-common", + "owner": "@elastic/security-detection-rule-management" +} diff --git a/packages/kbn-openapi-common/package.json b/packages/kbn-openapi-common/package.json new file mode 100644 index 0000000000000..c90099eacadf0 --- /dev/null +++ b/packages/kbn-openapi-common/package.json @@ -0,0 +1,11 @@ +{ + "description": "OpenAPI common schemas for Kibana", + "license": "SSPL-1.0 OR Elastic License 2.0", + "name": "@kbn/openapi-common", + "private": true, + "version": "1.0.0", + "scripts": { + "openapi:generate": "node scripts/openapi_generate" + } +} + diff --git a/packages/kbn-openapi-common/schemas/error_responses.gen.ts b/packages/kbn-openapi-common/schemas/error_responses.gen.ts new file mode 100644 index 0000000000000..1555dcbb612e1 --- /dev/null +++ b/packages/kbn-openapi-common/schemas/error_responses.gen.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Error response schemas + * version: not applicable + */ + +import { z } from 'zod'; + +export type PlatformErrorResponse = z.infer; +export const PlatformErrorResponse = z.object({ + statusCode: z.number().int(), + error: z.string(), + message: z.string(), +}); + +export type SiemErrorResponse = z.infer; +export const SiemErrorResponse = z.object({ + status_code: z.number().int(), + message: z.string(), +}); diff --git a/packages/kbn-openapi-common/schemas/error_responses.schema.yaml b/packages/kbn-openapi-common/schemas/error_responses.schema.yaml new file mode 100644 index 0000000000000..9574103b9411e --- /dev/null +++ b/packages/kbn-openapi-common/schemas/error_responses.schema.yaml @@ -0,0 +1,32 @@ +openapi: 3.0.0 +info: + title: Error response schemas + version: 'not applicable' +paths: {} +components: + x-codegen-enabled: true + schemas: + PlatformErrorResponse: + type: object + properties: + statusCode: + type: integer + error: + type: string + message: + type: string + required: + - statusCode + - error + - message + + SiemErrorResponse: + type: object + properties: + status_code: + type: integer + message: + type: string + required: + - status_code + - message diff --git a/packages/kbn-openapi-common/schemas/primitives.gen.ts b/packages/kbn-openapi-common/schemas/primitives.gen.ts new file mode 100644 index 0000000000000..a9027448d7e77 --- /dev/null +++ b/packages/kbn-openapi-common/schemas/primitives.gen.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Shared Primitives Schema + * version: not applicable + */ + +import { z } from 'zod'; + +/** + * A string that is not empty and does not contain only whitespace + */ +export type NonEmptyString = z.infer; +export const NonEmptyString = z + .string() + .min(1) + .regex(/^(?! *$).+$/); + +/** + * A universally unique identifier + */ +export type UUID = z.infer; +export const UUID = z.string().uuid(); diff --git a/packages/kbn-openapi-common/schemas/primitives.schema.yaml b/packages/kbn-openapi-common/schemas/primitives.schema.yaml new file mode 100644 index 0000000000000..177ad2ed30ecc --- /dev/null +++ b/packages/kbn-openapi-common/schemas/primitives.schema.yaml @@ -0,0 +1,18 @@ +openapi: 3.0.0 +info: + title: Shared Primitives Schema + version: 'not applicable' +paths: {} +components: + x-codegen-enabled: true + schemas: + NonEmptyString: + type: string + pattern: ^(?! *$).+$ + minLength: 1 + description: A string that is not empty and does not contain only whitespace + + UUID: + type: string + format: uuid + description: A universally unique identifier diff --git a/packages/kbn-openapi-common/scripts/openapi_generate.js b/packages/kbn-openapi-common/scripts/openapi_generate.js new file mode 100644 index 0000000000000..e8e7ffca22ede --- /dev/null +++ b/packages/kbn-openapi-common/scripts/openapi_generate.js @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +require('../../../src/setup_node_env'); +const { resolve } = require('path'); +const { generate } = require('@kbn/openapi-generator'); + +const ROOT = resolve(__dirname, '..'); + +(async () => { + await generate({ + title: 'OpenAPI Common Schemas (kbn-openapi-common)', + rootDir: ROOT, + sourceGlob: './schemas/**/*.schema.yaml', + templateName: 'zod_operation_schema', + }); +})(); diff --git a/packages/kbn-openapi-common/tsconfig.json b/packages/kbn-openapi-common/tsconfig.json new file mode 100644 index 0000000000000..168274f98f058 --- /dev/null +++ b/packages/kbn-openapi-common/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "outDir": "target/types", + "types": ["jest", "node"] + }, + "exclude": ["target/**/*"], + "extends": "../../tsconfig.base.json", + "include": ["**/*.ts"], + "kbn_references": [] +} diff --git a/packages/kbn-openapi-generator/src/openapi_generator.ts b/packages/kbn-openapi-generator/src/openapi_generator.ts index bdbc8ecec3c97..b5e73613139aa 100644 --- a/packages/kbn-openapi-generator/src/openapi_generator.ts +++ b/packages/kbn-openapi-generator/src/openapi_generator.ts @@ -100,6 +100,7 @@ export const generate = async (config: GeneratorConfig) => { version: 'Bundle (no version)', }, imports: {}, + circularRefs: new Set(), }); await fs.writeFile(bundle.outFile, result); diff --git a/packages/kbn-openapi-generator/src/parser/get_generation_context.ts b/packages/kbn-openapi-generator/src/parser/get_generation_context.ts index 7f161c0bc7645..2813ff85201e0 100644 --- a/packages/kbn-openapi-generator/src/parser/get_generation_context.ts +++ b/packages/kbn-openapi-generator/src/parser/get_generation_context.ts @@ -13,12 +13,14 @@ import { getImportsMap, ImportsMap } from './lib/get_imports_map'; import { normalizeSchema } from './lib/normalize_schema'; import { NormalizedOperation, OpenApiDocument } from './openapi_types'; import { getInfo } from './lib/get_info'; +import { getCircularRefs } from './lib/get_circular_refs'; export interface GenerationContext { components: OpenAPIV3.ComponentsObject | undefined; operations: NormalizedOperation[]; info: OpenAPIV3.InfoObject; imports: ImportsMap; + circularRefs: Set; } export function getGenerationContext(document: OpenApiDocument): GenerationContext { @@ -28,11 +30,13 @@ export function getGenerationContext(document: OpenApiDocument): GenerationConte const operations = getApiOperationsList(normalizedDocument); const info = getInfo(normalizedDocument); const imports = getImportsMap(normalizedDocument); + const circularRefs = getCircularRefs(normalizedDocument); return { components, operations, info, imports, + circularRefs, }; } diff --git a/packages/kbn-openapi-generator/src/parser/lib/find_refs.ts b/packages/kbn-openapi-generator/src/parser/lib/find_refs.ts new file mode 100644 index 0000000000000..1829c0a5e7ee2 --- /dev/null +++ b/packages/kbn-openapi-generator/src/parser/lib/find_refs.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 { hasRef } from './helpers/has_ref'; +import { traverseObject } from './helpers/traverse_object'; + +/** + * Traverse the OpenAPI document recursively and find all references + * + * @param obj Any object + * @returns A list of external references + */ +export function findRefs(obj: unknown): string[] { + const refs: string[] = []; + + traverseObject(obj, (element) => { + if (hasRef(element)) { + refs.push(element.$ref); + } + }); + + return refs; +} diff --git a/packages/kbn-openapi-generator/src/parser/lib/get_circular_refs.ts b/packages/kbn-openapi-generator/src/parser/lib/get_circular_refs.ts new file mode 100644 index 0000000000000..da9649d5f0c6d --- /dev/null +++ b/packages/kbn-openapi-generator/src/parser/lib/get_circular_refs.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not 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 { OpenApiDocument } from '../openapi_types'; +import type { PlainObject } from './helpers/plain_object'; +import { extractByJsonPointer } from './helpers/extract_by_json_pointer'; +import { findRefs } from './find_refs'; + +/** + * Extracts circular references from a provided document. + * Currently only local references are supported. + */ +export function getCircularRefs(document: OpenApiDocument): Set { + const localRefs = findLocalRefs(document); + const circularRefs = new Set(); + const resolveLocalRef = (localRef: string): PlainObject => + extractByJsonPointer(document, extractJsonPointer(localRef)); + + // In general references represent a disconnected graph. To find + // all references cycles we need to check each reference. + for (const startRef of new Set(localRefs)) { + const cycleHeadRef = findCycleHeadRef(startRef, resolveLocalRef); + + if (cycleHeadRef) { + circularRefs.add(cycleHeadRef); + } + } + + return circularRefs; +} + +/** + * Searches for a cycle head. A search starts from `startRef` reference. + * + * A cycle head is a first ref in a cycle. If `startRef` inside a cycle + * a cycle head is the starting ref. It can be illustrated as + * + * c1 - c2 - c3 + * / | + * r1 -> r2 -> r3 -> head c4 + * \ | + * c7 - c6 - c5 + * + * On the schema above references `r1`, `r2` and `r3` depend on the cycle but + * aren't part of the cycle. When search is started from them `head` is + * returned. If a search starts from `c3` then `c3` will be returned. + * + * @param startRef A starting point to find a cycle + * @param resolveRef A callback function to resolve an encountered reference. + * It should return a document node the provided ref resolves to. + * @returns a Set representing a cycle or an empty set if a cycle is not found + */ +function findCycleHeadRef( + startRef: string, + resolveRef: (ref: string) => PlainObject +): string | undefined { + let result: string | undefined; + + const visitedRefs = new Set(); + const search = (ref: string): void => { + if (visitedRefs.has(ref)) { + result = ref; + return; + } + + const refNode = resolveRef(ref); + const nextRefs = findLocalRefs(refNode); + + visitedRefs.add(ref); + nextRefs.forEach(search); + visitedRefs.delete(ref); + }; + + search(startRef); + + return result; +} + +/** + * Finds local references + */ +function findLocalRefs(obj: unknown): string[] { + return findRefs(obj).filter((ref) => isLocalRef(ref)); +} + +/** + * Checks whether the provided ref is local. + * Local references start with `#/` + */ +function isLocalRef(ref: string): boolean { + return ref.startsWith('#/'); +} + +/** + * Extracts a JSON Pointer from a local reference + * by getting rid of the leading slash + */ +function extractJsonPointer(ref: string): string { + return ref.substring(1); +} diff --git a/packages/kbn-openapi-generator/src/parser/lib/get_imports_map.ts b/packages/kbn-openapi-generator/src/parser/lib/get_imports_map.ts index c2259052e0cb6..634ac82aaae8c 100644 --- a/packages/kbn-openapi-generator/src/parser/lib/get_imports_map.ts +++ b/packages/kbn-openapi-generator/src/parser/lib/get_imports_map.ts @@ -8,7 +8,7 @@ import { uniq } from 'lodash'; import type { OpenApiDocument } from '../openapi_types'; -import { traverseObject } from './traverse_object'; +import { findRefs } from './find_refs'; export interface ImportsMap { [importPath: string]: string[]; @@ -37,31 +37,3 @@ export const getImportsMap = (parsedSchema: OpenApiDocument): ImportsMap => { return importMap; }; - -/** - * Check if an object has a $ref property - * - * @param obj Any object - * @returns True if the object has a $ref property - */ -const hasRef = (obj: unknown): obj is { $ref: string } => { - return typeof obj === 'object' && obj !== null && '$ref' in obj; -}; - -/** - * Traverse the OpenAPI document recursively and find all references - * - * @param obj Any object - * @returns A list of external references - */ -function findRefs(obj: unknown): string[] { - const refs: string[] = []; - - traverseObject(obj, (element) => { - if (hasRef(element)) { - refs.push(element.$ref); - } - }); - - return refs; -} diff --git a/packages/kbn-openapi-generator/src/parser/lib/helpers/extract_by_json_pointer.ts b/packages/kbn-openapi-generator/src/parser/lib/helpers/extract_by_json_pointer.ts new file mode 100644 index 0000000000000..bdfe59965b576 --- /dev/null +++ b/packages/kbn-openapi-generator/src/parser/lib/helpers/extract_by_json_pointer.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { PlainObject } from './plain_object'; +import { isPlainObjectType } from './is_plain_object_type'; + +/** + * Extract a node from a document using a provided [JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901). + * + * JSON Pointer is the second part in [JSON Reference](https://datatracker.ietf.org/doc/html/draft-pbryan-zyp-json-ref-03). + * For example an object `{ $ref: "./some-file.yaml#/components/schemas/MySchema"}` is a reference node. + * Where `/components/schemas/MySchema` is a JSON pointer. `./some-file.yaml` is a document reference. + * Yaml shares the same JSON reference standard and basically can be considered just as a different + * JS Object serialization format. See OpenAPI [Using $ref](https://swagger.io/docs/specification/using-ref/) for more information. + * + * @param document a document containing node to resolve by using the pointer + * @param pointer a JSON Pointer + * @returns resolved document node (it's always a JS object) + */ +export function extractByJsonPointer(document: unknown, pointer: string): PlainObject { + if (!pointer.startsWith('/')) { + throw new Error('$ref pointer must start with a leading slash'); + } + + if (!isPlainObjectType(document)) { + throw new Error('document must be an object'); + } + + let target = document; + + for (const segment of pointer.slice(1).split('/')) { + const nextTarget = target[segment]; + + if (!isPlainObjectType(nextTarget)) { + throw new Error(`JSON Pointer "${pointer}" is not found in "${JSON.stringify(document)}"`); + } + + target = nextTarget; + } + + return target; +} diff --git a/packages/kbn-openapi-generator/src/parser/lib/helpers/has_ref.ts b/packages/kbn-openapi-generator/src/parser/lib/helpers/has_ref.ts new file mode 100644 index 0000000000000..4457665070f7b --- /dev/null +++ b/packages/kbn-openapi-generator/src/parser/lib/helpers/has_ref.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { NormalizedReferenceObject } from '../../openapi_types'; + +/** + * Check if an object has a $ref property + * + * @param obj Any object + * @returns True if the object has a $ref property + */ +export function hasRef(obj: unknown): obj is NormalizedReferenceObject { + return typeof obj === 'object' && obj !== null && '$ref' in obj; +} diff --git a/packages/kbn-openapi-generator/src/parser/lib/helpers/is_plain_object_type.ts b/packages/kbn-openapi-generator/src/parser/lib/helpers/is_plain_object_type.ts new file mode 100644 index 0000000000000..c3612443800e9 --- /dev/null +++ b/packages/kbn-openapi-generator/src/parser/lib/helpers/is_plain_object_type.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { isPlainObject } from 'lodash'; +import type { PlainObject } from './plain_object'; + +export function isPlainObjectType(maybeObj: unknown): maybeObj is PlainObject { + return isPlainObject(maybeObj); +} diff --git a/packages/kbn-openapi-generator/src/parser/lib/helpers/plain_object.ts b/packages/kbn-openapi-generator/src/parser/lib/helpers/plain_object.ts new file mode 100644 index 0000000000000..8a3fead0f8b80 --- /dev/null +++ b/packages/kbn-openapi-generator/src/parser/lib/helpers/plain_object.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type PlainObject = Record; diff --git a/packages/kbn-openapi-generator/src/parser/lib/traverse_object.ts b/packages/kbn-openapi-generator/src/parser/lib/helpers/traverse_object.ts similarity index 100% rename from packages/kbn-openapi-generator/src/parser/lib/traverse_object.ts rename to packages/kbn-openapi-generator/src/parser/lib/helpers/traverse_object.ts diff --git a/packages/kbn-openapi-generator/src/parser/lib/normalize_schema.ts b/packages/kbn-openapi-generator/src/parser/lib/normalize_schema.ts index 0ed02a28585eb..d082b9a96dc44 100644 --- a/packages/kbn-openapi-generator/src/parser/lib/normalize_schema.ts +++ b/packages/kbn-openapi-generator/src/parser/lib/normalize_schema.ts @@ -7,41 +7,31 @@ */ import { OpenAPIV3 } from 'openapi-types'; -import { NormalizedReferenceObject } from '../openapi_types'; -import { traverseObject } from './traverse_object'; - -/** - * Check if an object has a $ref property - * - * @param obj Any object - * @returns True if the object has a $ref property - */ -const hasRef = (obj: unknown): obj is NormalizedReferenceObject => { - return typeof obj === 'object' && obj !== null && '$ref' in obj; -}; - -const stringIsUrl = (str: string) => { - try { - new URL(str); - return true; - } catch { - return false; - } -}; - -export function normalizeSchema(schema: OpenAPIV3.Document) { +import { URL } from 'node:url'; +import { traverseObject } from './helpers/traverse_object'; +import { hasRef } from './helpers/has_ref'; + +function isUrl(maybeUrl: string): boolean { + return URL.canParse(maybeUrl); +} + +export function normalizeSchema(schema: OpenAPIV3.Document): OpenAPIV3.Document { traverseObject(schema, (element) => { - if (hasRef(element)) { - if (stringIsUrl(element.$ref)) { - throw new Error(`URL references are not supported: ${element.$ref}`); - } - const referenceName = element.$ref.split('/').pop(); - if (!referenceName) { - throw new Error(`Cannot parse reference name: ${element.$ref}`); - } - - element.referenceName = referenceName; + if (!hasRef(element)) { + return; + } + + if (isUrl(element.$ref)) { + throw new Error(`URL references are not supported: ${element.$ref}`); } + + const referenceName = element.$ref.split('/').pop(); + + if (!referenceName) { + throw new Error(`Cannot parse reference name: ${element.$ref}`); + } + + element.referenceName = referenceName; }); return schema; diff --git a/packages/kbn-openapi-generator/src/template_service/register_helpers.ts b/packages/kbn-openapi-generator/src/template_service/register_helpers.ts index c676ae869c7ab..55f2d9d60f37a 100644 --- a/packages/kbn-openapi-generator/src/template_service/register_helpers.ts +++ b/packages/kbn-openapi-generator/src/template_service/register_helpers.ts @@ -7,6 +7,7 @@ */ import type Handlebars from '@kbn/handlebars'; +import { HelperOptions } from 'handlebars'; import { snakeCase, camelCase } from 'lodash'; export function registerHelpers(handlebarsInstance: typeof Handlebars) { @@ -47,13 +48,43 @@ export function registerHelpers(handlebarsInstance: typeof Handlebars) { handlebarsInstance.registerHelper('isUnknown', (val: object) => { return !('type' in val || '$ref' in val || 'anyOf' in val || 'oneOf' in val || 'allOf' in val); }); - handlebarsInstance.registerHelper('startsWithSpecialChar', (val: string) => { - return /^[^a-zA-Z0-9]/.test(val); - }); handlebarsInstance.registerHelper( 'replace', (val: string, searchValue: string, replaceValue: string) => { return val.replace(searchValue, replaceValue); } ); + + /** + * Checks whether provided reference is a known circular reference or a part of circular chain. + * + * It's expected that `context.recursiveRefs` has been filled by the parser. + */ + handlebarsInstance.registerHelper('isCircularRef', (ref: string, options: HelperOptions) => { + if (!options.data?.root?.circularRefs) { + return false; + } + + const circularRefs: Set = options.data.root.circularRefs; + + return circularRefs.has(ref); + }); + + /** + * Checks whether provided schema is circular or a part of the circular chain. + * + * It's expected that `context.circularRefs` has been filled by the parser. + */ + handlebarsInstance.registerHelper( + 'isCircularSchema', + (schemaName: string, options: HelperOptions) => { + if (!options.data?.root?.circularRefs) { + return false; + } + + const circularRefs: Set = options.data.root.circularRefs; + + return circularRefs.has(`#/components/schemas/${schemaName}`); + } + ); } diff --git a/packages/kbn-openapi-generator/src/template_service/templates/ts_input_type.handlebars b/packages/kbn-openapi-generator/src/template_service/templates/ts_input_type.handlebars new file mode 100644 index 0000000000000..453e4cdf452d5 --- /dev/null +++ b/packages/kbn-openapi-generator/src/template_service/templates/ts_input_type.handlebars @@ -0,0 +1,84 @@ +{{~#if type~}} + {{~> (concat "type_" type)~}} +{{~/if~}} + +{{~#if $ref~}} + {{referenceName}}Input + {{~#if nullable}} | null {{/if~}} +{{~/if~}} + +{{~#if allOf~}} + {{~#each allOf~}} + {{~> ts_input_type ~}} + {{~#unless @last~}}&{{~/unless~}} + {{~/each~}} +{{~/if~}} + +{{~#if anyOf~}} + {{~#each anyOf~}} + {{~> ts_input_type ~}} + {{~#unless @last~}}|{{~/unless~}} + {{~/each~}} +{{~/if~}} + +{{~#if oneOf~}} + {{~#each oneOf~}} + {{~> ts_input_type ~}} + {{~#unless @last~}}|{{~/unless~}} + {{~/each~}} +{{~/if~}} + +{{#if (isUnknown .)}} +unknown +{{/if}} + +{{~#*inline "type_array"~}} + ({{~> ts_input_type items ~}})[] +{{~/inline~}} + +{{~#*inline "type_boolean"~}} + boolean +{{~/inline~}} + +{{~#*inline "type_integer"~}} + number +{{~/inline~}} + +{{~#*inline "type_number"~}} + number +{{~/inline~}} + +{{~#*inline "type_object"~}} + {{~#if (eq x-modify "required")}} Required< {{/if~}} + {{~#if (eq x-modify "partial")}} Partial< {{/if~}} + { + {{#each properties}} + {{#if description}} + /** + * {{{description}}} + */ + {{/if}} + '{{@key}}'{{~#unless (includes ../required @key)}}?{{/unless~}}: {{> ts_input_type }}; + {{/each}} + {{~#if additionalProperties}} + {{~#if (eq additionalProperties true)~}} + [key: string]: unknown; + {{~else~}} + [key: string]: {{> ts_input_type additionalProperties}}; + {{~/if~}} + {{~/if~}} + } + {{~#if (eq x-modify "partial")}} > {{/if~}} + {{~#if (eq x-modify "required")}} > {{/if~}} +{{~/inline~}} + +{{~#*inline "type_string"~}} + {{~#if enum~}} + {{~#each enum~}} + '{{.}}' + {{~#unless @last~}}|{{~/unless~}} + {{~/each~}} + {{~else~}} + string + {{~/if~}} +{{~/inline~}} diff --git a/packages/kbn-openapi-generator/src/template_service/templates/ts_type.handlebars b/packages/kbn-openapi-generator/src/template_service/templates/ts_type.handlebars new file mode 100644 index 0000000000000..5017af3aa5df0 --- /dev/null +++ b/packages/kbn-openapi-generator/src/template_service/templates/ts_type.handlebars @@ -0,0 +1,84 @@ +{{~#if type~}} + {{~> (concat "type_" type)~}} +{{~/if~}} + +{{~#if $ref~}} + {{referenceName}} + {{~#if nullable}} | null {{/if~}} +{{~/if~}} + +{{~#if allOf~}} + {{~#each allOf~}} + {{~> ts_type ~}} + {{~#unless @last~}}&{{~/unless~}} + {{~/each~}} +{{~/if~}} + +{{~#if anyOf~}} + {{~#each anyOf~}} + {{~> ts_type ~}} + {{~#unless @last~}}|{{~/unless~}} + {{~/each~}} +{{~/if~}} + +{{~#if oneOf~}} + {{~#each oneOf~}} + {{~> ts_type ~}} + {{~#unless @last~}}|{{~/unless~}} + {{~/each~}} +{{~/if~}} + +{{#if (isUnknown .)}} +unknown +{{/if}} + +{{~#*inline "type_array"~}} + ({{~> ts_type items ~}})[] +{{~/inline~}} + +{{~#*inline "type_boolean"~}} + boolean +{{~/inline~}} + +{{~#*inline "type_integer"~}} + number +{{~/inline~}} + +{{~#*inline "type_number"~}} + number +{{~/inline~}} + +{{~#*inline "type_object"~}} + {{~#if (eq x-modify "required")}} Required< {{/if~}} + {{~#if (eq x-modify "partial")}} Partial< {{/if~}} + { + {{#each properties}} + {{#if description}} + /** + * {{{description}}} + */ + {{/if}} + '{{@key}}'{{~#unless (or (includes ../required @key) (defined default))}}?{{/unless~}}: {{> ts_type }}; + {{/each}} + {{~#if additionalProperties}} + {{~#if (eq additionalProperties true)~}} + [key: string]: unknown; + {{~else~}} + [key: string]: {{> ts_type additionalProperties}}; + {{~/if~}} + {{~/if~}} + } + {{~#if (eq x-modify "partial")}} > {{/if~}} + {{~#if (eq x-modify "required")}} > {{/if~}} +{{~/inline~}} + +{{~#*inline "type_string"~}} + {{~#if enum~}} + {{~#each enum~}} + '{{.}}' + {{~#unless @last~}}|{{~/unless~}} + {{~/each~}} + {{~else~}} + string + {{~/if~}} +{{~/inline~}} 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 30bc647b1fb25..6e5d21363a5ae 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 @@ -7,8 +7,9 @@ {{> disclaimer}} -import { z } from "zod"; -import { requiredOptional, isValidDateMath, ArrayFromString, BooleanFromString } from "@kbn/zod-helpers" +import type { ZodTypeDef } from 'zod'; +import { z } from 'zod'; +import { requiredOptional, isValidDateMath, ArrayFromString, BooleanFromString } from '@kbn/zod-helpers'; {{#each imports}} import { @@ -25,8 +26,14 @@ import { {{/if}} */ {{/if}} +{{#if (isCircularSchema @key)}} +export type {{@key}} = {{> ts_type}}; +export type {{@key}}Input = {{> ts_input_type }}; +export const {{@key}}: z.ZodType<{{@key}}, ZodTypeDef, {{@key}}Input> = {{> zod_schema_item }}; +{{else}} export type {{@key}} = z.infer; export const {{@key}} = {{> zod_schema_item}}; +{{/if}} {{#if enum}} {{#unless (isSingle enum)}} export type {{@key}}Enum = typeof {{@key}}.enum; diff --git a/packages/kbn-openapi-generator/src/template_service/templates/zod_schema_item.handlebars b/packages/kbn-openapi-generator/src/template_service/templates/zod_schema_item.handlebars index 29d4453da4d7b..7ad0abaf1cad8 100644 --- a/packages/kbn-openapi-generator/src/template_service/templates/zod_schema_item.handlebars +++ b/packages/kbn-openapi-generator/src/template_service/templates/zod_schema_item.handlebars @@ -6,7 +6,11 @@ {{~/if~}} {{~#if $ref~}} - {{referenceName}} + {{~#if (isCircularRef $ref)~}} + z.lazy(() => {{referenceName}}) + {{~else~}} + {{referenceName}} + {{~/if~}} {{~#if nullable}}.nullable(){{/if~}} {{~#if (eq requiredBool false)}}.optional(){{/if~}} {{~#if (defined default)}}.default({{{toJSON default}}}){{/if~}} @@ -87,11 +91,7 @@ z.unknown() * {{{description}}} */ {{/if}} - {{#if (startsWithSpecialChar @key)}} - '{{@key}}': {{> zod_schema_item requiredBool=(includes ../required @key)}}, - {{else}} - {{@key}}: {{> zod_schema_item requiredBool=(includes ../required @key)}}, - {{/if}} + '{{@key}}':{{~> zod_schema_item requiredBool=(includes ../required @key)~}}, {{/each}} }) {{~#if (eq additionalProperties false)}}.strict(){{/if~}} @@ -127,4 +127,3 @@ z.unknown() {{~#if pattern}}.regex(/{{pattern}}/){{/if~}} {{~/if~}} {{~/inline~}} - diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index a7ec0e03f3dda..c3ffc507387b8 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -31,7 +31,7 @@ pageLoadAssetSize: dataQuality: 19384 datasetQuality: 50624 dataViewEditor: 28082 - dataViewFieldEditor: 27000 + dataViewFieldEditor: 42021 dataViewManagement: 5300 dataViews: 65000 dataVisualizer: 27530 @@ -41,8 +41,8 @@ pageLoadAssetSize: discoverShared: 17111 embeddable: 87309 embeddableEnhanced: 22107 - enterpriseSearch: 50858 - esqlDataGrid: 24598 + enterpriseSearch: 66810 + esqlDataGrid: 24582 esUiShared: 326654 eventAnnotation: 30000 eventAnnotationListing: 25841 @@ -132,6 +132,8 @@ pageLoadAssetSize: screenshotMode: 17856 screenshotting: 22870 searchConnectors: 30000 + searchHomepage: 19831 + searchInferenceEndpoints: 20470 searchNotebooks: 18942 searchPlayground: 19325 searchprofiler: 67080 diff --git a/packages/kbn-reporting/public/share/share_context_menu/register_csv_modal_reporting.tsx b/packages/kbn-reporting/public/share/share_context_menu/register_csv_modal_reporting.tsx index 6877ed1c2b384..f9e4286333cf7 100644 --- a/packages/kbn-reporting/public/share/share_context_menu/register_csv_modal_reporting.tsx +++ b/packages/kbn-reporting/public/share/share_context_menu/register_csv_modal_reporting.tsx @@ -142,7 +142,7 @@ export const reportingCsvShareProvider = ({ const relativePath = apiClient.getReportingPublicJobPath( reportType, - apiClient.getDecoratedJobParams(getJobParams()) + apiClient.getDecoratedJobParams(getJobParams(true)) ); const absoluteUrl = new URL(relativePath, window.location.href).toString(); diff --git a/packages/kbn-router-to-openapispec/src/__snapshots__/generate_oas.test.ts.snap b/packages/kbn-router-to-openapispec/src/__snapshots__/generate_oas.test.ts.snap index fee1a8534a7ec..be1b698f5cd9a 100644 --- a/packages/kbn-router-to-openapispec/src/__snapshots__/generate_oas.test.ts.snap +++ b/packages/kbn-router-to-openapispec/src/__snapshots__/generate_oas.test.ts.snap @@ -26,7 +26,6 @@ Object { "paths": Object { "/foo/{id}": Object { "get": Object { - "description": undefined, "operationId": "/foo/{id}#0", "parameters": Array [ Object { @@ -133,7 +132,7 @@ Object { "paths": Object { "/bar": Object { "get": Object { - "description": undefined, + "deprecated": true, "operationId": "/bar#0", "parameters": Array [ Object { @@ -577,7 +576,6 @@ Object { "paths": Object { "/recursive": Object { "get": Object { - "description": undefined, "operationId": "/recursive#0", "parameters": Array [ Object { @@ -660,7 +658,6 @@ Object { "paths": Object { "/foo/{id}": Object { "get": Object { - "description": undefined, "operationId": "/foo/{id}#0", "parameters": Array [ Object { @@ -698,7 +695,6 @@ Object { }, "/test": Object { "get": Object { - "description": undefined, "operationId": "/test#0", "parameters": Array [ Object { diff --git a/packages/kbn-router-to-openapispec/src/generate_oas.test.util.ts b/packages/kbn-router-to-openapispec/src/generate_oas.test.util.ts index bed7d10c51d8e..2fb821018dcee 100644 --- a/packages/kbn-router-to-openapispec/src/generate_oas.test.util.ts +++ b/packages/kbn-router-to-openapispec/src/generate_oas.test.util.ts @@ -85,6 +85,7 @@ export const getVersionedRouterDefaults = () => ({ options: { summary: 'versioned route', access: 'public', + deprecated: true, options: { tags: ['ignore-me', 'oas-tag:versioned'], }, diff --git a/packages/kbn-router-to-openapispec/src/process_router.ts b/packages/kbn-router-to-openapispec/src/process_router.ts index 393b745b6aab3..9437612211a92 100644 --- a/packages/kbn-router-to-openapispec/src/process_router.ts +++ b/packages/kbn-router-to-openapispec/src/process_router.ts @@ -61,8 +61,9 @@ export const processRouter = ( const operation: OpenAPIV3.OperationObject = { summary: route.options.summary ?? '', - description: route.options.description, tags: route.options.tags ? extractTags(route.options.tags) : [], + ...(route.options.description ? { description: route.options.description } : {}), + ...(route.options.deprecated ? { deprecated: route.options.deprecated } : {}), requestBody: !!validationSchemas?.body ? { content: { diff --git a/packages/kbn-router-to-openapispec/src/process_versioned_router.ts b/packages/kbn-router-to-openapispec/src/process_versioned_router.ts index cc873b26835cf..19b41f4812a30 100644 --- a/packages/kbn-router-to-openapispec/src/process_versioned_router.ts +++ b/packages/kbn-router-to-openapispec/src/process_versioned_router.ts @@ -85,8 +85,9 @@ export const processVersionedRouter = ( const hasVersionFilter = Boolean(filters?.version); const operation: OpenAPIV3.OperationObject = { summary: route.options.summary ?? '', - description: route.options.description, tags: route.options.options?.tags ? extractTags(route.options.options.tags) : [], + ...(route.options.description ? { description: route.options.description } : {}), + ...(route.options.deprecated ? { deprecated: route.options.deprecated } : {}), requestBody: hasBody ? { content: hasVersionFilter diff --git a/packages/kbn-search-connectors/types/native_connectors.ts b/packages/kbn-search-connectors/types/native_connectors.ts index e8976b0f09f7b..5cd8ee5311a1d 100644 --- a/packages/kbn-search-connectors/types/native_connectors.ts +++ b/packages/kbn-search-connectors/types/native_connectors.ts @@ -886,8 +886,56 @@ export const NATIVE_CONNECTOR_DEFINITIONS: Record; +export const CreateListRequestBody = z.object({ + id: ListId.optional(), + name: ListName, + description: ListDescription, + type: ListType, + serializer: z.string().optional(), + deserializer: z.string().optional(), + meta: ListMetadata.optional(), + version: z.number().int().min(1).optional().default(1), +}); +export type CreateListRequestBodyInput = z.input; + +export type CreateListResponse = z.infer; +export const CreateListResponse = List; diff --git a/packages/kbn-securitysolution-lists-common/api/create_list/create_list.schema.yaml b/packages/kbn-securitysolution-lists-common/api/create_list/create_list.schema.yaml new file mode 100644 index 0000000000000..0cd635c46ff7f --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/create_list/create_list.schema.yaml @@ -0,0 +1,82 @@ +openapi: 3.0.0 +info: + title: Create list API endpoint + version: '2023-10-31' +paths: + /api/lists: + post: + x-labels: [serverless, ess] + operationId: CreateList + x-codegen-enabled: true + summary: Creates a list + tags: + - List API + requestBody: + description: List's properties + required: true + content: + application/json: + schema: + type: object + properties: + id: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListId' + name: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListName' + description: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListDescription' + type: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListType' + serializer: + type: string + deserializer: + type: string + meta: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListMetadata' + version: + type: integer + minimum: 1 + default: 1 + required: + - name + - description + - type + responses: + 200: + description: Successful response + content: + application/json: + schema: + $ref: '../model/list_schemas.schema.yaml#/components/schemas/List' + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 409: + description: List already exists response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-lists-common/api/create_list_index/create_list_index.gen.ts b/packages/kbn-securitysolution-lists-common/api/create_list_index/create_list_index.gen.ts new file mode 100644 index 0000000000000..b2217f2016422 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/create_list_index/create_list_index.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Create list DS API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +export type CreateListIndexResponse = z.infer; +export const CreateListIndexResponse = z.object({ + acknowledged: z.boolean(), +}); diff --git a/packages/kbn-securitysolution-lists-common/api/create_list_index/create_list_index.schema.yaml b/packages/kbn-securitysolution-lists-common/api/create_list_index/create_list_index.schema.yaml new file mode 100644 index 0000000000000..f42937a8885d2 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/create_list_index/create_list_index.schema.yaml @@ -0,0 +1,56 @@ +openapi: 3.0.0 +info: + title: Create list DS API endpoint + version: '2023-10-31' +paths: + /api/lists/index: + post: + x-labels: [serverless, ess] + operationId: CreateListIndex + x-codegen-enabled: true + summary: Creates necessary list data streams + tags: + - List API + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + properties: + acknowledged: + type: boolean + required: [acknowledged] + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 409: + description: List data stream exists response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-lists-common/api/create_list_item/create_list_item.gen.ts b/packages/kbn-securitysolution-lists-common/api/create_list_item/create_list_item.gen.ts new file mode 100644 index 0000000000000..94e1be2171921 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/create_list_item/create_list_item.gen.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Create list item API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { ListItemId, ListId, ListItemValue, ListItemMetadata } from '../model/list_common.gen'; +import { ListItem } from '../model/list_schemas.gen'; + +export type CreateListItemRequestBody = z.infer; +export const CreateListItemRequestBody = z.object({ + id: ListItemId.optional(), + list_id: ListId, + value: ListItemValue, + meta: ListItemMetadata.optional(), + /** + * Determines when changes made by the request are made visible to search + */ + refresh: z.enum(['true', 'false', 'wait_for']).optional(), +}); +export type CreateListItemRequestBodyInput = z.input; + +export type CreateListItemResponse = z.infer; +export const CreateListItemResponse = ListItem; diff --git a/packages/kbn-securitysolution-lists-common/api/create_list_item/create_list_item.schema.yaml b/packages/kbn-securitysolution-lists-common/api/create_list_item/create_list_item.schema.yaml new file mode 100644 index 0000000000000..ca75b8555b9c4 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/create_list_item/create_list_item.schema.yaml @@ -0,0 +1,78 @@ +openapi: 3.0.0 +info: + title: Create list item API endpoint + version: '2023-10-31' +paths: + /api/lists/items: + post: + x-labels: [serverless, ess] + operationId: CreateListItem + x-codegen-enabled: true + summary: Creates a list item + tags: + - List item API + requestBody: + description: List item's properties + required: true + content: + application/json: + schema: + type: object + properties: + id: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListItemId' + list_id: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListId' + value: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListItemValue' + meta: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListItemMetadata' + refresh: + type: string + enum: + - 'true' + - 'false' + - wait_for + description: Determines when changes made by the request are made visible to search + required: + - list_id + - value + responses: + 200: + description: Successful response + content: + application/json: + schema: + $ref: '../model/list_schemas.schema.yaml#/components/schemas/ListItem' + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 409: + description: List item already exists response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-lists-common/api/delete_list/delete_list.gen.ts b/packages/kbn-securitysolution-lists-common/api/delete_list/delete_list.gen.ts new file mode 100644 index 0000000000000..fe058928eca0b --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/delete_list/delete_list.gen.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Delete list API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; +import { BooleanFromString } from '@kbn/zod-helpers'; + +import { ListId } from '../model/list_common.gen'; +import { List } from '../model/list_schemas.gen'; + +export type DeleteListRequestQuery = z.infer; +export const DeleteListRequestQuery = z.object({ + /** + * List's `id` value + */ + id: ListId, + deleteReferences: BooleanFromString.optional().default(false), + ignoreReferences: BooleanFromString.optional().default(false), +}); +export type DeleteListRequestQueryInput = z.input; + +export type DeleteListResponse = z.infer; +export const DeleteListResponse = List; diff --git a/packages/kbn-securitysolution-lists-common/api/delete_list/delete_list.schema.yaml b/packages/kbn-securitysolution-lists-common/api/delete_list/delete_list.schema.yaml new file mode 100644 index 0000000000000..c116d43edd93a --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/delete_list/delete_list.schema.yaml @@ -0,0 +1,71 @@ +openapi: 3.0.0 +info: + title: Delete list API endpoint + version: '2023-10-31' +paths: + /api/lists: + delete: + x-labels: [serverless, ess] + operationId: DeleteList + x-codegen-enabled: true + summary: Deletes a list + tags: + - List API + parameters: + - name: id + in: query + required: true + description: List's `id` value + schema: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListId' + - name: deleteReferences + in: query + required: false + schema: + type: boolean + default: false + - name: ignoreReferences + in: query + required: false + schema: + type: boolean + default: false + responses: + 200: + description: Successful response + content: + application/json: + schema: + $ref: '../model/list_schemas.schema.yaml#/components/schemas/List' + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 404: + description: List not found response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-lists-common/api/delete_list_index/delete_list_index.gen.ts b/packages/kbn-securitysolution-lists-common/api/delete_list_index/delete_list_index.gen.ts new file mode 100644 index 0000000000000..3be609d0b8a92 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/delete_list_index/delete_list_index.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Delete list DS API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +export type DeleteListIndexResponse = z.infer; +export const DeleteListIndexResponse = z.object({ + acknowledged: z.boolean(), +}); diff --git a/packages/kbn-securitysolution-lists-common/api/delete_list_index/delete_list_index.schema.yaml b/packages/kbn-securitysolution-lists-common/api/delete_list_index/delete_list_index.schema.yaml new file mode 100644 index 0000000000000..c3b4969aa3283 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/delete_list_index/delete_list_index.schema.yaml @@ -0,0 +1,56 @@ +openapi: 3.0.0 +info: + title: Delete list DS API endpoint + version: '2023-10-31' +paths: + /api/lists/index: + delete: + x-labels: [serverless, ess] + operationId: DeleteListIndex + x-codegen-enabled: true + summary: Deletes list data streams + tags: + - List API + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + properties: + acknowledged: + type: boolean + required: [acknowledged] + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 404: + description: List data stream not found response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-lists-common/api/delete_list_item/delete_list_item.gen.ts b/packages/kbn-securitysolution-lists-common/api/delete_list_item/delete_list_item.gen.ts new file mode 100644 index 0000000000000..d94b2f18d6158 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/delete_list_item/delete_list_item.gen.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Delete list item API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { ListId } from '../model/list_common.gen'; +import { ListItem } from '../model/list_schemas.gen'; + +export type DeleteListItemRequestQuery = z.infer; +export const DeleteListItemRequestQuery = z.object({ + /** + * Required if `list_id` and `value` are not specified + */ + id: ListId.optional(), + /** + * Required if `id` is not specified + */ + list_id: ListId.optional(), + /** + * Required if `id` is not specified + */ + value: z.string().optional(), + /** + * Determines when changes made by the request are made visible to search + */ + refresh: z.enum(['true', 'false', 'wait_for']).optional().default('false'), +}); +export type DeleteListItemRequestQueryInput = z.input; + +export type DeleteListItemResponse = z.infer; +export const DeleteListItemResponse = z.union([ListItem, z.array(ListItem)]); diff --git a/packages/kbn-securitysolution-lists-common/api/delete_list_item/delete_list_item.schema.yaml b/packages/kbn-securitysolution-lists-common/api/delete_list_item/delete_list_item.schema.yaml new file mode 100644 index 0000000000000..63938d313aea2 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/delete_list_item/delete_list_item.schema.yaml @@ -0,0 +1,83 @@ +openapi: 3.0.0 +info: + title: Delete list item API endpoint + version: '2023-10-31' +paths: + /api/lists/items: + delete: + x-labels: [serverless, ess] + operationId: DeleteListItem + x-codegen-enabled: true + summary: Deletes a list item + tags: + - List item API + parameters: + - name: id + in: query + required: false + description: Required if `list_id` and `value` are not specified + schema: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListId' + - name: list_id + in: query + required: false + description: Required if `id` is not specified + schema: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListId' + - name: value + in: query + required: false + description: Required if `id` is not specified + schema: + type: string + - name: refresh + in: query + required: false + description: Determines when changes made by the request are made visible to search + schema: + type: string + enum: ['true', 'false', 'wait_for'] + default: 'false' + responses: + 200: + description: Successful response + content: + application/json: + schema: + oneOf: + - $ref: '../model/list_schemas.schema.yaml#/components/schemas/ListItem' + - type: array + items: + $ref: '../model/list_schemas.schema.yaml#/components/schemas/ListItem' + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 404: + description: List item not found response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-lists-common/api/export_list_item/export_list_item.gen.ts b/packages/kbn-securitysolution-lists-common/api/export_list_item/export_list_item.gen.ts new file mode 100644 index 0000000000000..0e831e0887bd0 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/export_list_item/export_list_item.gen.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Export list items API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { ListId } from '../model/list_common.gen'; + +export type ExportListItemsRequestQuery = z.infer; +export const ExportListItemsRequestQuery = z.object({ + /** + * List's id to export + */ + list_id: ListId, +}); +export type ExportListItemsRequestQueryInput = z.input; diff --git a/packages/kbn-securitysolution-lists-common/api/export_list_item/export_list_item.schema.yaml b/packages/kbn-securitysolution-lists-common/api/export_list_item/export_list_item.schema.yaml new file mode 100644 index 0000000000000..26708a18a899a --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/export_list_item/export_list_item.schema.yaml @@ -0,0 +1,61 @@ +openapi: 3.0.0 +info: + title: Export list items API endpoint + version: '2023-10-31' +paths: + /api/lists/items/_export: + post: + operationId: ExportListItems + x-codegen-enabled: true + summary: Exports list items + description: Exports list item values from the specified list + tags: + - List items Import/Export API + parameters: + - name: list_id + in: query + required: true + description: List's id to export + schema: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListId' + responses: + 200: + description: Successful response + content: + application/ndjson: + schema: + type: string + format: binary + description: A `.txt` file containing list items from the specified list + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 404: + description: List not found response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-lists-common/api/find_list/find_list.gen.ts b/packages/kbn-securitysolution-lists-common/api/find_list/find_list.gen.ts new file mode 100644 index 0000000000000..22bd50fcf0f35 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/find_list/find_list.gen.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Find lists API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { NonEmptyString } from '@kbn/openapi-common/schemas/primitives.gen'; +import { List } from '../model/list_schemas.gen'; + +export type FindListsCursor = z.infer; +export const FindListsCursor = NonEmptyString; + +export type FindListsFilter = z.infer; +export const FindListsFilter = NonEmptyString; + +export type FindListsRequestQuery = z.infer; +export const FindListsRequestQuery = z.object({ + /** + * The page number to return + */ + page: z.coerce.number().int().optional(), + /** + * The number of lists to return per page + */ + per_page: z.coerce.number().int().optional(), + /** + * Determines which field is used to sort the results + */ + sort_field: NonEmptyString.optional(), + /** + * Determines the sort order, which can be `desc` or `asc` + */ + sort_order: z.enum(['desc', 'asc']).optional(), + /** + * Returns the list that come after the last list returned in the previous call +(use the cursor value returned in the previous call). This parameter uses +the `tie_breaker_id` field to ensure all lists are sorted and returned correctly. + + */ + cursor: FindListsCursor.optional(), + /** + * Filters the returned results according to the value of the specified field, +using the : syntax. + + */ + filter: FindListsFilter.optional(), +}); +export type FindListsRequestQueryInput = z.input; + +export type FindListsResponse = z.infer; +export const FindListsResponse = z.object({ + data: z.array(List), + page: z.number().int().min(0), + per_page: z.number().int().min(0), + total: z.number().int().min(0), + cursor: FindListsCursor, +}); diff --git a/packages/kbn-securitysolution-lists-common/api/find_list/find_list.schema.yaml b/packages/kbn-securitysolution-lists-common/api/find_list/find_list.schema.yaml new file mode 100644 index 0000000000000..7fa5f1ac581ac --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/find_list/find_list.schema.yaml @@ -0,0 +1,119 @@ +openapi: 3.0.0 +info: + title: Find lists API endpoint + version: '2023-10-31' +paths: + /api/lists/_find: + get: + x-labels: [serverless, ess] + operationId: FindLists + x-codegen-enabled: true + summary: Finds lists + tags: + - List API + parameters: + - name: page + in: query + required: false + description: The page number to return + schema: + type: integer + - name: per_page + in: query + required: false + description: The number of lists to return per page + schema: + type: integer + - name: sort_field + in: query + required: false + description: Determines which field is used to sort the results + schema: + $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' + - name: sort_order + in: query + required: false + description: Determines the sort order, which can be `desc` or `asc` + schema: + type: string + enum: [desc, asc] + - name: cursor + in: query + required: false + description: | + Returns the list that come after the last list returned in the previous call + (use the cursor value returned in the previous call). This parameter uses + the `tie_breaker_id` field to ensure all lists are sorted and returned correctly. + schema: + $ref: '#/components/schemas/FindListsCursor' + - name: filter + in: query + required: false + description: | + Filters the returned results according to the value of the specified field, + using the : syntax. + schema: + $ref: '#/components/schemas/FindListsFilter' + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '../model/list_schemas.schema.yaml#/components/schemas/List' + page: + type: integer + minimum: 0 + per_page: + type: integer + minimum: 0 + total: + type: integer + minimum: 0 + cursor: + $ref: '#/components/schemas/FindListsCursor' + required: + - data + - page + - per_page + - total + - cursor + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + +components: + schemas: + FindListsCursor: + $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' + + FindListsFilter: + $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' diff --git a/packages/kbn-securitysolution-lists-common/api/find_list_item/find_list_item.gen.ts b/packages/kbn-securitysolution-lists-common/api/find_list_item/find_list_item.gen.ts new file mode 100644 index 0000000000000..ef23adf7a7dcd --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/find_list_item/find_list_item.gen.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Find list items API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { NonEmptyString } from '@kbn/openapi-common/schemas/primitives.gen'; +import { ListId } from '../model/list_common.gen'; +import { ListItem } from '../model/list_schemas.gen'; + +export type FindListItemsCursor = z.infer; +export const FindListItemsCursor = NonEmptyString; + +export type FindListItemsFilter = z.infer; +export const FindListItemsFilter = NonEmptyString; + +export type FindListItemsRequestQuery = z.infer; +export const FindListItemsRequestQuery = z.object({ + /** + * List's ide + */ + list_id: ListId, + /** + * The page number to return + */ + page: z.coerce.number().int().optional(), + /** + * The number of list items to return per page + */ + per_page: z.coerce.number().int().optional(), + /** + * Determines which field is used to sort the results + */ + sort_field: NonEmptyString.optional(), + /** + * Determines the sort order, which can be `desc` or `asc` + */ + sort_order: z.enum(['desc', 'asc']).optional(), + /** + * Returns the list that come after the last list returned in the previous call +(use the cursor value returned in the previous call). This parameter uses +the `tie_breaker_id` field to ensure all lists are sorted and returned correctly. + + */ + cursor: FindListItemsCursor.optional(), + /** + * Filters the returned results according to the value of the specified field, +using the : syntax. + + */ + filter: FindListItemsFilter.optional(), +}); +export type FindListItemsRequestQueryInput = z.input; + +export type FindListItemsResponse = z.infer; +export const FindListItemsResponse = z.object({ + data: z.array(ListItem), + page: z.number().int().min(0), + per_page: z.number().int().min(0), + total: z.number().int().min(0), + cursor: FindListItemsCursor, +}); diff --git a/packages/kbn-securitysolution-lists-common/api/find_list_item/find_list_item.schema.yaml b/packages/kbn-securitysolution-lists-common/api/find_list_item/find_list_item.schema.yaml new file mode 100644 index 0000000000000..92dbc361b7ad2 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/find_list_item/find_list_item.schema.yaml @@ -0,0 +1,125 @@ +openapi: 3.0.0 +info: + title: Find list items API endpoint + version: '2023-10-31' +paths: + /api/lists/_find: + get: + x-labels: [serverless, ess] + operationId: FindListItems + x-codegen-enabled: true + summary: Finds list items + tags: + - List API + parameters: + - name: list_id + in: query + required: true + description: List's ide + schema: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListId' + - name: page + in: query + required: false + description: The page number to return + schema: + type: integer + - name: per_page + in: query + required: false + description: The number of list items to return per page + schema: + type: integer + - name: sort_field + in: query + required: false + description: Determines which field is used to sort the results + schema: + $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' + - name: sort_order + in: query + required: false + description: Determines the sort order, which can be `desc` or `asc` + schema: + type: string + enum: [desc, asc] + - name: cursor + in: query + required: false + description: | + Returns the list that come after the last list returned in the previous call + (use the cursor value returned in the previous call). This parameter uses + the `tie_breaker_id` field to ensure all lists are sorted and returned correctly. + schema: + $ref: '#/components/schemas/FindListItemsCursor' + - name: filter + in: query + required: false + description: | + Filters the returned results according to the value of the specified field, + using the : syntax. + schema: + $ref: '#/components/schemas/FindListItemsFilter' + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '../model/list_schemas.schema.yaml#/components/schemas/ListItem' + page: + type: integer + minimum: 0 + per_page: + type: integer + minimum: 0 + total: + type: integer + minimum: 0 + cursor: + $ref: '#/components/schemas/FindListItemsCursor' + required: + - data + - page + - per_page + - total + - cursor + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + +components: + schemas: + FindListItemsCursor: + $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' + + FindListItemsFilter: + $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' diff --git a/packages/kbn-securitysolution-lists-common/api/get_list_privileges/get_list_privileges.gen.ts b/packages/kbn-securitysolution-lists-common/api/get_list_privileges/get_list_privileges.gen.ts new file mode 100644 index 0000000000000..3fe6348242822 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/get_list_privileges/get_list_privileges.gen.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Get list privileges API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +export type ListPrivileges = z.infer; +export const ListPrivileges = z.object({ + username: z.string(), + has_all_requested: z.boolean(), + cluster: z.object({}).catchall(z.boolean()), + index: z.object({}).catchall(z.object({}).catchall(z.boolean())), + application: z.object({}).catchall(z.boolean()), +}); + +export type ListItemPrivileges = z.infer; +export const ListItemPrivileges = z.object({ + username: z.string(), + has_all_requested: z.boolean(), + cluster: z.object({}).catchall(z.boolean()), + index: z.object({}).catchall(z.object({}).catchall(z.boolean())), + application: z.object({}).catchall(z.boolean()), +}); + +export type GetListPrivilegesResponse = z.infer; +export const GetListPrivilegesResponse = z.object({ + lists: ListPrivileges, + listItems: ListItemPrivileges, + is_authenticated: z.boolean(), +}); diff --git a/packages/kbn-securitysolution-lists-common/api/get_list_privileges/get_list_privileges.schema.yaml b/packages/kbn-securitysolution-lists-common/api/get_list_privileges/get_list_privileges.schema.yaml new file mode 100644 index 0000000000000..729da9b8f62a8 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/get_list_privileges/get_list_privileges.schema.yaml @@ -0,0 +1,115 @@ +openapi: 3.0.0 +info: + title: Get list privileges API endpoint + version: '2023-10-31' +paths: + /api/lists/privileges: + get: + x-labels: [serverless, ess] + operationId: GetListPrivileges + x-codegen-enabled: true + summary: Gets list privileges + tags: + - List API + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + properties: + lists: + $ref: '#/components/schemas/ListPrivileges' + listItems: + $ref: '#/components/schemas/ListItemPrivileges' + is_authenticated: + type: boolean + required: + - lists + - listItems + - is_authenticated + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + +components: + schemas: + ListPrivileges: + type: object + properties: + username: + type: string + has_all_requested: + type: boolean + cluster: + type: object + additionalProperties: + type: boolean + index: + type: object + additionalProperties: + type: object + additionalProperties: + type: boolean + application: + type: object + additionalProperties: + type: boolean + required: + - username + - has_all_requested + - cluster + - index + - application + + ListItemPrivileges: + type: object + properties: + username: + type: string + has_all_requested: + type: boolean + cluster: + type: object + additionalProperties: + type: boolean + index: + type: object + additionalProperties: + type: object + additionalProperties: + type: boolean + application: + type: object + additionalProperties: + type: boolean + required: + - username + - has_all_requested + - cluster + - index + - application diff --git a/packages/kbn-securitysolution-lists-common/api/import_list_item/import_list_item.gen.ts b/packages/kbn-securitysolution-lists-common/api/import_list_item/import_list_item.gen.ts new file mode 100644 index 0000000000000..5748e6a78e2c0 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/import_list_item/import_list_item.gen.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Import list items API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { ListId, ListType } from '../model/list_common.gen'; +import { List } from '../model/list_schemas.gen'; + +export type ImportListItemsRequestQuery = z.infer; +export const ImportListItemsRequestQuery = z.object({ + /** + * List's id. + +Required when importing to an existing list. + + */ + list_id: ListId.optional(), + /** + * Type of the importing list. + +Required when importing a new list that is `list_id` is not specified. + + */ + type: ListType.optional(), + serializer: z.string().optional(), + deserializer: z.string().optional(), + /** + * Determines when changes made by the request are made visible to search + */ + refresh: z.enum(['true', 'false', 'wait_for']).optional(), +}); +export type ImportListItemsRequestQueryInput = z.input; + +export type ImportListItemsResponse = z.infer; +export const ImportListItemsResponse = List; diff --git a/packages/kbn-securitysolution-lists-common/api/import_list_item/import_list_item.schema.yaml b/packages/kbn-securitysolution-lists-common/api/import_list_item/import_list_item.schema.yaml new file mode 100644 index 0000000000000..561d52587aad2 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/import_list_item/import_list_item.schema.yaml @@ -0,0 +1,102 @@ +openapi: 3.0.0 +info: + title: Import list items API endpoint + version: '2023-10-31' +paths: + /api/lists/items/_import: + post: + operationId: ImportListItems + x-codegen-enabled: true + summary: Imports list items + description: | + Imports a list of items from a `.txt` or `.csv` file. The maximum file size is 9 million bytes. + + You can import items to a new or existing list. + tags: + - List items Import/Export API + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + description: A `.txt` or `.csv` file containing newline separated list items + parameters: + - name: list_id + in: query + required: false + description: | + List's id. + + Required when importing to an existing list. + schema: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListId' + - name: type + in: query + required: false + description: | + Type of the importing list. + + Required when importing a new list that is `list_id` is not specified. + schema: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListType' + - name: serializer + in: query + required: false + schema: + type: string + - name: deserializer + in: query + required: false + schema: + type: string + - name: refresh + in: query + required: false + description: Determines when changes made by the request are made visible to search + schema: + type: string + enum: ['true', 'false', 'wait_for'] + responses: + 200: + description: Successful response + content: + application/json: + schema: + $ref: '../model/list_schemas.schema.yaml#/components/schemas/List' + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 409: + description: List with specified list_id does not exist response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-lists-common/api/index.ts b/packages/kbn-securitysolution-lists-common/api/index.ts new file mode 100644 index 0000000000000..7b1fb1508653d --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/index.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +export * from './model/list_common.gen'; +export * from './model/list_schemas.gen'; +export * from './create_list_index/create_list_index.gen'; +export * from './create_list_item/create_list_item.gen'; +export * from './create_list/create_list.gen'; +export * from './delete_list_index/delete_list_index.gen'; +export * from './delete_list_item/delete_list_item.gen'; +export * from './delete_list/delete_list.gen'; +export * from './find_list_item/find_list_item.gen'; +export * from './find_list/find_list.gen'; +export * from './export_list_item/export_list_item.gen'; +export * from './import_list_item/import_list_item.gen'; +export * from './patch_list_item/patch_list_item.gen'; +export * from './patch_list/patch_list.gen'; +export * from './read_list_index/read_list_index.gen'; +export * from './read_list_item/read_list_item.gen'; +export * from './read_list/read_list.gen'; +export * from './update_list_item/update_list_item.gen'; +export * from './update_list/update_list.gen'; diff --git a/packages/kbn-securitysolution-lists-common/api/model/list_common.gen.ts b/packages/kbn-securitysolution-lists-common/api/model/list_common.gen.ts new file mode 100644 index 0000000000000..f4a6a007fed9b --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/model/list_common.gen.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Common List Attributes + * version: not applicable + */ + +import { z } from 'zod'; + +import { NonEmptyString } from '@kbn/openapi-common/schemas/primitives.gen'; + +export type ListId = z.infer; +export const ListId = NonEmptyString; + +export type ListType = z.infer; +export const ListType = z.enum([ + 'binary', + 'boolean', + 'byte', + 'date', + 'date_nanos', + 'date_range', + 'double', + 'double_range', + 'float', + 'float_range', + 'geo_point', + 'geo_shape', + 'half_float', + 'integer', + 'integer_range', + 'ip', + 'ip_range', + 'keyword', + 'long', + 'long_range', + 'shape', + 'short', + 'text', +]); +export type ListTypeEnum = typeof ListType.enum; +export const ListTypeEnum = ListType.enum; + +export type ListName = z.infer; +export const ListName = NonEmptyString; + +export type ListDescription = z.infer; +export const ListDescription = NonEmptyString; + +export type ListMetadata = z.infer; +export const ListMetadata = z.object({}).catchall(z.unknown()); + +export type ListItemId = z.infer; +export const ListItemId = NonEmptyString; + +export type ListItemValue = z.infer; +export const ListItemValue = NonEmptyString; + +export type ListItemDescription = z.infer; +export const ListItemDescription = NonEmptyString; + +export type ListItemMetadata = z.infer; +export const ListItemMetadata = z.object({}).catchall(z.unknown()); diff --git a/packages/kbn-securitysolution-lists-common/api/model/list_common.schema.yaml b/packages/kbn-securitysolution-lists-common/api/model/list_common.schema.yaml new file mode 100644 index 0000000000000..6fb160105bb5a --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/model/list_common.schema.yaml @@ -0,0 +1,59 @@ +openapi: 3.0.0 +info: + title: Common List Attributes + version: 'not applicable' +paths: {} +components: + schemas: + ListId: + $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' + + ListType: + type: string + enum: + - binary + - boolean + - byte + - date + - date_nanos + - date_range + - double + - double_range + - float + - float_range + - geo_point + - geo_shape + - half_float + - integer + - integer_range + - ip + - ip_range + - keyword + - long + - long_range + - shape + - short + - text + + ListName: + $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' + + ListDescription: + $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' + + ListMetadata: + type: object + additionalProperties: true + + ListItemId: + $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' + + ListItemValue: + $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' + + ListItemDescription: + $ref: '../../../kbn-openapi-common/schemas/primitives.schema.yaml#/components/schemas/NonEmptyString' + + ListItemMetadata: + type: object + additionalProperties: true diff --git a/packages/kbn-securitysolution-lists-common/api/model/list_schemas.gen.ts b/packages/kbn-securitysolution-lists-common/api/model/list_schemas.gen.ts new file mode 100644 index 0000000000000..c70278df938ce --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/model/list_schemas.gen.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: List Schemas + * version: not applicable + */ + +import { z } from 'zod'; + +import { + ListId, + ListType, + ListName, + ListDescription, + ListMetadata, + ListItemId, + ListItemValue, + ListItemMetadata, +} from './list_common.gen'; + +export type List = z.infer; +export const List = z.object({ + id: ListId, + type: ListType, + name: ListName, + description: ListDescription, + serializer: z.string().optional(), + deserializer: z.string().optional(), + immutable: z.boolean(), + meta: ListMetadata.optional(), + '@timestamp': z.string().datetime().optional(), + version: z.number().int().min(1), + _version: z.string().optional(), + tie_breaker_id: z.string(), + created_at: z.string().datetime(), + created_by: z.string(), + updated_at: z.string().datetime(), + updated_by: z.string(), +}); + +export type ListItem = z.infer; +export const ListItem = z.object({ + id: ListItemId, + type: ListType, + list_id: ListId, + value: ListItemValue, + serializer: z.string().optional(), + deserializer: z.string().optional(), + meta: ListItemMetadata.optional(), + '@timestamp': z.string().datetime().optional(), + _version: z.string().optional(), + tie_breaker_id: z.string(), + created_at: z.string().datetime(), + created_by: z.string(), + updated_at: z.string().datetime(), + updated_by: z.string(), +}); diff --git a/packages/kbn-securitysolution-lists-common/api/model/list_schemas.schema.yaml b/packages/kbn-securitysolution-lists-common/api/model/list_schemas.schema.yaml new file mode 100644 index 0000000000000..838dc5e4edea0 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/model/list_schemas.schema.yaml @@ -0,0 +1,103 @@ +openapi: 3.0.0 +info: + title: List Schemas + version: 'not applicable' +paths: {} +components: + schemas: + List: + type: object + properties: + id: + $ref: './list_common.schema.yaml#/components/schemas/ListId' + type: + $ref: './list_common.schema.yaml#/components/schemas/ListType' + name: + $ref: './list_common.schema.yaml#/components/schemas/ListName' + description: + $ref: './list_common.schema.yaml#/components/schemas/ListDescription' + serializer: + type: string + deserializer: + type: string + immutable: + type: boolean + meta: + $ref: './list_common.schema.yaml#/components/schemas/ListMetadata' + '@timestamp': + type: string + format: date-time + version: + type: integer + minimum: 1 + _version: + type: string + tie_breaker_id: + type: string + created_at: + type: string + format: date-time + created_by: + type: string + updated_at: + type: string + format: date-time + updated_by: + type: string + required: + - id + - type + - name + - description + - immutable + - version + - tie_breaker_id + - created_at + - created_by + - updated_at + - updated_by + + ListItem: + type: object + properties: + id: + $ref: './list_common.schema.yaml#/components/schemas/ListItemId' + type: + $ref: './list_common.schema.yaml#/components/schemas/ListType' + list_id: + $ref: './list_common.schema.yaml#/components/schemas/ListId' + value: + $ref: './list_common.schema.yaml#/components/schemas/ListItemValue' + serializer: + type: string + deserializer: + type: string + meta: + $ref: './list_common.schema.yaml#/components/schemas/ListItemMetadata' + '@timestamp': + type: string + format: date-time + _version: + type: string + tie_breaker_id: + type: string + created_at: + type: string + format: date-time + created_by: + type: string + updated_at: + type: string + format: date-time + updated_by: + type: string + required: + - id + - type + - list_id + - value + - tie_breaker_id + - created_at + - created_by + - updated_at + - updated_by diff --git a/packages/kbn-securitysolution-lists-common/api/patch_list/patch_list.gen.ts b/packages/kbn-securitysolution-lists-common/api/patch_list/patch_list.gen.ts new file mode 100644 index 0000000000000..47eeb87d0f5d4 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/patch_list/patch_list.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Patch list API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { ListId, ListName, ListDescription, ListMetadata } from '../model/list_common.gen'; +import { List } from '../model/list_schemas.gen'; + +export type PatchListRequestBody = z.infer; +export const PatchListRequestBody = z.object({ + id: ListId, + name: ListName.optional(), + description: ListDescription.optional(), + meta: ListMetadata.optional(), + version: z.number().int().min(1).optional(), + _version: z.string().optional(), +}); +export type PatchListRequestBodyInput = z.input; + +export type PatchListResponse = z.infer; +export const PatchListResponse = List; diff --git a/packages/kbn-securitysolution-lists-common/api/patch_list/patch_list.schema.yaml b/packages/kbn-securitysolution-lists-common/api/patch_list/patch_list.schema.yaml new file mode 100644 index 0000000000000..cae09887db312 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/patch_list/patch_list.schema.yaml @@ -0,0 +1,75 @@ +openapi: 3.0.0 +info: + title: Patch list API endpoint + version: '2023-10-31' +paths: + /api/lists: + patch: + x-labels: [serverless, ess] + operationId: PatchList + x-codegen-enabled: true + summary: Patches a list + tags: + - List API + requestBody: + description: List's properties + required: true + content: + application/json: + schema: + type: object + properties: + id: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListId' + name: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListName' + description: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListDescription' + meta: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListMetadata' + version: + type: integer + minimum: 1 + _version: + type: string + required: + - id + responses: + 200: + description: Successful response + content: + application/json: + schema: + $ref: '../model/list_schemas.schema.yaml#/components/schemas/List' + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 404: + description: List not found response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-lists-common/api/patch_list_item/patch_list_item.gen.ts b/packages/kbn-securitysolution-lists-common/api/patch_list_item/patch_list_item.gen.ts new file mode 100644 index 0000000000000..ce29d68bed7a7 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/patch_list_item/patch_list_item.gen.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Patch list item API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { ListItemId, ListItemValue, ListItemMetadata } from '../model/list_common.gen'; +import { ListItem } from '../model/list_schemas.gen'; + +export type PatchListItemRequestBody = z.infer; +export const PatchListItemRequestBody = z.object({ + id: ListItemId, + value: ListItemValue.optional(), + meta: ListItemMetadata.optional(), + _version: z.string().optional(), + /** + * Determines when changes made by the request are made visible to search + */ + refresh: z.enum(['true', 'false', 'wait_for']).optional(), +}); +export type PatchListItemRequestBodyInput = z.input; + +export type PatchListItemResponse = z.infer; +export const PatchListItemResponse = ListItem; diff --git a/packages/kbn-securitysolution-lists-common/api/patch_list_item/patch_list_item.schema.yaml b/packages/kbn-securitysolution-lists-common/api/patch_list_item/patch_list_item.schema.yaml new file mode 100644 index 0000000000000..36ca55c7ae065 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/patch_list_item/patch_list_item.schema.yaml @@ -0,0 +1,77 @@ +openapi: 3.0.0 +info: + title: Patch list item API endpoint + version: '2023-10-31' +paths: + /api/lists/items: + patch: + x-labels: [serverless, ess] + operationId: PatchListItem + x-codegen-enabled: true + summary: Patches a list item + tags: + - List item API + requestBody: + description: List item's properties + required: true + content: + application/json: + schema: + type: object + properties: + id: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListItemId' + value: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListItemValue' + meta: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListItemMetadata' + _version: + type: string + refresh: + type: string + enum: + - 'true' + - 'false' + - wait_for + description: Determines when changes made by the request are made visible to search + required: + - id + responses: + 200: + description: Successful response + content: + application/json: + schema: + $ref: '../model/list_schemas.schema.yaml#/components/schemas/ListItem' + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 404: + description: List item not found response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-lists-common/api/read_list/read_list.gen.ts b/packages/kbn-securitysolution-lists-common/api/read_list/read_list.gen.ts new file mode 100644 index 0000000000000..2ae0eb6f6c91b --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/read_list/read_list.gen.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Get list API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { ListId } from '../model/list_common.gen'; +import { List } from '../model/list_schemas.gen'; + +export type GetListRequestQuery = z.infer; +export const GetListRequestQuery = z.object({ + /** + * List's `id` value + */ + id: ListId, +}); +export type GetListRequestQueryInput = z.input; + +export type GetListResponse = z.infer; +export const GetListResponse = List; diff --git a/packages/kbn-securitysolution-lists-common/api/read_list/read_list.schema.yaml b/packages/kbn-securitysolution-lists-common/api/read_list/read_list.schema.yaml new file mode 100644 index 0000000000000..4a2ae5d2cd42c --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/read_list/read_list.schema.yaml @@ -0,0 +1,59 @@ +openapi: 3.0.0 +info: + title: Get list API endpoint + version: '2023-10-31' +paths: + /api/lists: + get: + x-labels: [serverless, ess] + operationId: GetList + x-codegen-enabled: true + summary: Retrieves a list using its id field + tags: + - List API + parameters: + - name: id + in: query + required: true + description: List's `id` value + schema: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListId' + responses: + 200: + description: Successful response + content: + application/json: + schema: + $ref: '../model/list_schemas.schema.yaml#/components/schemas/List' + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 404: + description: List not found response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-lists-common/api/read_list_index/read_list_index.gen.ts b/packages/kbn-securitysolution-lists-common/api/read_list_index/read_list_index.gen.ts new file mode 100644 index 0000000000000..c25105f038636 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/read_list_index/read_list_index.gen.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Get list DS existence status API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +export type GetListIndexResponse = z.infer; +export const GetListIndexResponse = z.object({ + list_index: z.boolean(), + list_item_index: z.boolean(), +}); diff --git a/packages/kbn-securitysolution-lists-common/api/read_list_index/read_list_index.schema.yaml b/packages/kbn-securitysolution-lists-common/api/read_list_index/read_list_index.schema.yaml new file mode 100644 index 0000000000000..accef8b58411e --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/read_list_index/read_list_index.schema.yaml @@ -0,0 +1,58 @@ +openapi: 3.0.0 +info: + title: Get list DS existence status API endpoint + version: '2023-10-31' +paths: + /api/lists/index: + get: + x-labels: [serverless, ess] + operationId: GetListIndex + x-codegen-enabled: true + summary: Get list data stream existence status + tags: + - List API + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + properties: + list_index: + type: boolean + list_item_index: + type: boolean + required: [list_index, list_item_index] + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 404: + description: List data stream(s) not found response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-lists-common/api/read_list_item/read_list_item.gen.ts b/packages/kbn-securitysolution-lists-common/api/read_list_item/read_list_item.gen.ts new file mode 100644 index 0000000000000..e9f5b9eef9cf8 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/read_list_item/read_list_item.gen.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Get list item API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { ListId } from '../model/list_common.gen'; +import { ListItem } from '../model/list_schemas.gen'; + +export type GetListItemRequestQuery = z.infer; +export const GetListItemRequestQuery = z.object({ + /** + * Required if `list_id` and `value` are not specified + */ + id: ListId.optional(), + /** + * Required if `id` is not specified + */ + list_id: ListId.optional(), + /** + * Required if `id` is not specified + */ + value: z.string().optional(), +}); +export type GetListItemRequestQueryInput = z.input; + +export type GetListItemResponse = z.infer; +export const GetListItemResponse = z.union([ListItem, z.array(ListItem)]); diff --git a/packages/kbn-securitysolution-lists-common/api/read_list_item/read_list_item.schema.yaml b/packages/kbn-securitysolution-lists-common/api/read_list_item/read_list_item.schema.yaml new file mode 100644 index 0000000000000..3a651617163aa --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/read_list_item/read_list_item.schema.yaml @@ -0,0 +1,75 @@ +openapi: 3.0.0 +info: + title: Get list item API endpoint + version: '2023-10-31' +paths: + /api/lists/items: + get: + x-labels: [serverless, ess] + operationId: GetListItem + x-codegen-enabled: true + summary: Gets a list item + tags: + - List item API + parameters: + - name: id + in: query + required: false + description: Required if `list_id` and `value` are not specified + schema: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListId' + - name: list_id + in: query + required: false + description: Required if `id` is not specified + schema: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListId' + - name: value + in: query + required: false + description: Required if `id` is not specified + schema: + type: string + responses: + 200: + description: Successful response + content: + application/json: + schema: + oneOf: + - $ref: '../model/list_schemas.schema.yaml#/components/schemas/ListItem' + - type: array + items: + $ref: '../model/list_schemas.schema.yaml#/components/schemas/ListItem' + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 404: + description: List item not found response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-lists-common/api/update_list/update_list.gen.ts b/packages/kbn-securitysolution-lists-common/api/update_list/update_list.gen.ts new file mode 100644 index 0000000000000..833239a7eb82b --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/update_list/update_list.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Update list API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { ListId, ListName, ListDescription, ListMetadata } from '../model/list_common.gen'; +import { List } from '../model/list_schemas.gen'; + +export type UpdateListRequestBody = z.infer; +export const UpdateListRequestBody = z.object({ + id: ListId, + name: ListName, + description: ListDescription, + meta: ListMetadata.optional(), + version: z.number().int().min(1).optional(), + _version: z.string().optional(), +}); +export type UpdateListRequestBodyInput = z.input; + +export type UpdateListResponse = z.infer; +export const UpdateListResponse = List; diff --git a/packages/kbn-securitysolution-lists-common/api/update_list/update_list.schema.yaml b/packages/kbn-securitysolution-lists-common/api/update_list/update_list.schema.yaml new file mode 100644 index 0000000000000..d25b21157c725 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/update_list/update_list.schema.yaml @@ -0,0 +1,77 @@ +openapi: 3.0.0 +info: + title: Update list API endpoint + version: '2023-10-31' +paths: + /api/lists: + put: + x-labels: [serverless, ess] + operationId: UpdateList + x-codegen-enabled: true + summary: Updates a list + tags: + - List API + requestBody: + description: List's properties + required: true + content: + application/json: + schema: + type: object + properties: + id: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListId' + name: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListName' + description: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListDescription' + meta: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListMetadata' + version: + type: integer + minimum: 1 + _version: + type: string + required: + - id + - name + - description + responses: + 200: + description: Successful response + content: + application/json: + schema: + $ref: '../model/list_schemas.schema.yaml#/components/schemas/List' + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 404: + description: List not found response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-lists-common/api/update_list_item/update_list_item.gen.ts b/packages/kbn-securitysolution-lists-common/api/update_list_item/update_list_item.gen.ts new file mode 100644 index 0000000000000..069c101beaaf4 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/update_list_item/update_list_item.gen.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Update list item API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { ListItemId, ListItemValue, ListItemMetadata } from '../model/list_common.gen'; +import { ListItem } from '../model/list_schemas.gen'; + +export type UpdateListItemRequestBody = z.infer; +export const UpdateListItemRequestBody = z.object({ + id: ListItemId, + value: ListItemValue, + meta: ListItemMetadata.optional(), + _version: z.string().optional(), +}); +export type UpdateListItemRequestBodyInput = z.input; + +export type UpdateListItemResponse = z.infer; +export const UpdateListItemResponse = ListItem; diff --git a/packages/kbn-securitysolution-lists-common/api/update_list_item/update_list_item.schema.yaml b/packages/kbn-securitysolution-lists-common/api/update_list_item/update_list_item.schema.yaml new file mode 100644 index 0000000000000..21df82f4ba40a --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/api/update_list_item/update_list_item.schema.yaml @@ -0,0 +1,71 @@ +openapi: 3.0.0 +info: + title: Update list item API endpoint + version: '2023-10-31' +paths: + /api/lists/items: + put: + x-labels: [serverless, ess] + operationId: UpdateListItem + x-codegen-enabled: true + summary: Updates a list item + tags: + - List item API + requestBody: + description: List item's properties + required: true + content: + application/json: + schema: + type: object + properties: + id: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListItemId' + value: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListItemValue' + meta: + $ref: '../model/list_common.schema.yaml#/components/schemas/ListItemMetadata' + _version: + type: string + required: + - id + - value + responses: + 200: + description: Successful response + content: + application/json: + schema: + $ref: '../model/list_schemas.schema.yaml#/components/schemas/ListItem' + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 403: + description: Not enough privileges response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 404: + description: List item not found response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../kbn-openapi-common/schemas/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' diff --git a/packages/kbn-securitysolution-lists-common/kibana.jsonc b/packages/kbn-securitysolution-lists-common/kibana.jsonc new file mode 100644 index 0000000000000..614314f10e8b4 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/securitysolution-lists-common", + "owner": "@elastic/security-detection-engine" +} diff --git a/packages/kbn-securitysolution-lists-common/package.json b/packages/kbn-securitysolution-lists-common/package.json new file mode 100644 index 0000000000000..298f0e47bb10f --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/package.json @@ -0,0 +1,10 @@ +{ + "description": "Security Solution Lists common package", + "license": "SSPL-1.0 OR Elastic License 2.0", + "name": "@kbn/securitysolution-lists-common", + "private": true, + "version": "1.0.0", + "scripts": { + "openapi:generate": "node scripts/openapi_generate" + } +} diff --git a/packages/kbn-securitysolution-lists-common/scripts/openapi_generate.js b/packages/kbn-securitysolution-lists-common/scripts/openapi_generate.js new file mode 100644 index 0000000000000..8580994b16859 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/scripts/openapi_generate.js @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +require('../../../src/setup_node_env'); +const { resolve } = require('path'); +const { generate } = require('@kbn/openapi-generator'); + +const ROOT = resolve(__dirname, '..'); + +(async () => { + await generate({ + title: 'OpenAPI Lists API Schemas', + rootDir: ROOT, + sourceGlob: './**/*.schema.yaml', + templateName: 'zod_operation_schema', + }); +})(); diff --git a/packages/kbn-securitysolution-lists-common/tsconfig.json b/packages/kbn-securitysolution-lists-common/tsconfig.json new file mode 100644 index 0000000000000..b6a14b40363c7 --- /dev/null +++ b/packages/kbn-securitysolution-lists-common/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "outDir": "target/types", + "types": ["jest", "node"] + }, + "exclude": ["target/**/*"], + "extends": "../../tsconfig.base.json", + "include": ["**/*.ts"], + "kbn_references": ["@kbn/zod-helpers", "@kbn/openapi-common"] +} diff --git a/packages/kbn-test/src/auth/saml_auth.ts b/packages/kbn-test/src/auth/saml_auth.ts index a88d11c6a5bc9..99343c8c6912b 100644 --- a/packages/kbn-test/src/auth/saml_auth.ts +++ b/packages/kbn-test/src/auth/saml_auth.ts @@ -24,11 +24,9 @@ import { export class Session { readonly cookie; readonly email; - readonly fullname; - constructor(cookie: Cookie, email: string, fullname: string) { + constructor(cookie: Cookie, email: string) { this.cookie = cookie; this.email = email; - this.fullname = fullname; } getCookieValue() { @@ -239,7 +237,7 @@ export const finishSAMLHandshake = async ({ ); }; -const getSecurityProfile = async ({ +export const getSecurityProfile = async ({ kbnHost, cookie, log, @@ -274,8 +272,7 @@ export const createCloudSAMLSession = async (params: CloudSamlSessionParams) => const { location, sid } = await createSAMLRequest(kbnHost, kbnVersion, log); const samlResponse = await createSAMLResponse({ location, ecSession, email, kbnHost, log }); const cookie = await finishSAMLHandshake({ kbnHost, samlResponse, sid, log }); - const userProfile = await getSecurityProfile({ kbnHost, cookie, log }); - return new Session(cookie, email, userProfile.full_name); + return new Session(cookie, email); }; export const createLocalSAMLSession = async (params: LocalSamlSessionParams) => { @@ -288,5 +285,5 @@ export const createLocalSAMLSession = async (params: LocalSamlSessionParams) => roles: [role], }); const cookie = await finishSAMLHandshake({ kbnHost, samlResponse, log }); - return new Session(cookie, email, fullname); + return new Session(cookie, email); }; diff --git a/packages/kbn-test/src/auth/session_manager.ts b/packages/kbn-test/src/auth/session_manager.ts index a8edca981c1d4..1783c3f389aa1 100644 --- a/packages/kbn-test/src/auth/session_manager.ts +++ b/packages/kbn-test/src/auth/session_manager.ts @@ -13,7 +13,12 @@ import { resolve } from 'path'; import Url from 'url'; import { KbnClient } from '../kbn_client'; import { readCloudUsersFromFile } from './helper'; -import { createCloudSAMLSession, createLocalSAMLSession, Session } from './saml_auth'; +import { + createCloudSAMLSession, + createLocalSAMLSession, + getSecurityProfile, + Session, +} from './saml_auth'; import { Role, User } from './types'; export interface HostOptions { @@ -146,8 +151,14 @@ export class SamlSessionManager { return session.getCookieValue(); } + async getEmail(role: string) { + const session = await this.getSessionByRole(role); + return session.email; + } + async getUserData(role: string) { - const { email, fullname } = await this.getSessionByRole(role); - return { email, fullname }; + const { cookie } = await this.getSessionByRole(role); + const profileData = await getSecurityProfile({ kbnHost: this.kbnHost, cookie, log: this.log }); + return profileData; } } diff --git a/packages/kbn-test/src/auth/sesson_manager.test.ts b/packages/kbn-test/src/auth/sesson_manager.test.ts index f0679917654a0..df037ebdf04e4 100644 --- a/packages/kbn-test/src/auth/sesson_manager.test.ts +++ b/packages/kbn-test/src/auth/sesson_manager.test.ts @@ -12,7 +12,7 @@ import { Session } from './saml_auth'; import { SamlSessionManager } from './session_manager'; import * as samlAuth from './saml_auth'; import * as helper from './helper'; -import { Role, User } from './types'; +import { Role, User, UserProfile } from './types'; import { SERVERLESS_ROLES_ROOT_PATH } from '@kbn/es'; const log = new ToolingLog(); @@ -23,6 +23,7 @@ const roleEditor = 'editor'; const createLocalSAMLSessionMock = jest.spyOn(samlAuth, 'createLocalSAMLSession'); const createCloudSAMLSessionMock = jest.spyOn(samlAuth, 'createCloudSAMLSession'); +const getSecurityProfileMock = jest.spyOn(samlAuth, 'getSecurityProfile'); const readCloudUsersFromFileMock = jest.spyOn(helper, 'readCloudUsersFromFile'); const isValidHostnameMock = jest.spyOn(helper, 'isValidHostname'); @@ -42,7 +43,7 @@ describe('SamlSessionManager', () => { .KbnClient.mockImplementation(() => ({ version: { get } })); get.mockImplementation(() => Promise.resolve('8.12.0')); - createLocalSAMLSessionMock.mockResolvedValue(new Session(cookieInstance, email, fullname)); + createLocalSAMLSessionMock.mockResolvedValue(new Session(cookieInstance, testEmail)); }); const hostOptions = { @@ -59,8 +60,8 @@ describe('SamlSessionManager', () => { log, supportedRoles, }; - const email = 'testuser@elastic.com'; - const fullname = 'Test User'; + const testEmail = 'testuser@elastic.com'; + const testFullname = 'Test User'; const cookieInstance = Cookie.parse( 'sid=kbn_cookie_value; Path=/; Expires=Wed, 01 Oct 2023 07:00:00 GMT' )!; @@ -91,10 +92,26 @@ describe('SamlSessionManager', () => { expect(createCloudSAMLSessionMock.mock.calls).toHaveLength(0); }); - test(`'getUserData' should return the correct email & fullname`, async () => { + test(`'getEmail' return the correct email`, async () => { const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); - const data = await samlSessionManager.getUserData(roleViewer); - expect(data).toEqual({ email, fullname }); + const email = await samlSessionManager.getEmail(roleEditor); + expect(email).toBe(testEmail); + }); + + test(`'getUserData' should call security API and return user profile data`, async () => { + const testData: UserProfile = { + username: '6ta90xc', + roles: [roleEditor], + full_name: testFullname, + email: testEmail, + enabled: true, + elastic_cloud_user: false, + }; + getSecurityProfileMock.mockResolvedValueOnce(testData); + const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); + const userData = await samlSessionManager.getUserData(roleViewer); + + expect(userData).toEqual(testData); }); test(`throws error when role is not in 'supportedRoles'`, async () => { @@ -117,6 +134,15 @@ describe('SamlSessionManager', () => { test(`doesn't throw error when supportedRoles is not defined`, async () => { const nonExistingRole = 'tester'; + const testData: UserProfile = { + username: '6ta90xc', + roles: [nonExistingRole], + full_name: testFullname, + email: testEmail, + enabled: true, + elastic_cloud_user: false, + }; + getSecurityProfileMock.mockResolvedValueOnce(testData); const samlSessionManager = new SamlSessionManager({ hostOptions, log, @@ -127,6 +153,7 @@ describe('SamlSessionManager', () => { await samlSessionManager.getUserData(nonExistingRole); expect(createLocalSAMLSessionMock.mock.calls).toHaveLength(1); expect(createCloudSAMLSessionMock.mock.calls).toHaveLength(0); + expect(getSecurityProfileMock.mock.calls).toHaveLength(1); }); }); @@ -184,9 +211,7 @@ describe('SamlSessionManager', () => { .KbnClient.mockImplementation(() => ({ version: { get } })); get.mockImplementationOnce(() => Promise.resolve('8.12.0')); - createCloudSAMLSessionMock.mockResolvedValue( - new Session(cloudCookieInstance, cloudEmail, cloudFullname) - ); + createCloudSAMLSessionMock.mockResolvedValue(new Session(cloudCookieInstance, cloudEmail)); readCloudUsersFromFileMock.mockReturnValue(cloudUsers); }); @@ -201,9 +226,7 @@ describe('SamlSessionManager', () => { test(`'getSessionCookieForRole' should return the actual cookie value`, async () => { const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); - createCloudSAMLSessionMock.mockResolvedValue( - new Session(cloudCookieInstance, cloudEmail, cloudFullname) - ); + createCloudSAMLSessionMock.mockResolvedValue(new Session(cloudCookieInstance, cloudEmail)); const cookie = await samlSessionManager.getSessionCookieForRole(roleViewer); expect(cookie).toBe(cloudCookieInstance.value); }); @@ -223,10 +246,26 @@ describe('SamlSessionManager', () => { expect(createCloudSAMLSessionMock.mock.calls).toHaveLength(2); }); - test(`'getUserData' should return the correct email & fullname`, async () => { + test(`'getEmail' return the correct email`, async () => { const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); - const data = await samlSessionManager.getUserData(roleViewer); - expect(data).toEqual({ email: cloudEmail, fullname: cloudFullname }); + const email = await samlSessionManager.getEmail(roleViewer); + expect(email).toBe(cloudEmail); + }); + + test(`'getUserData' should call security API and return user profile data`, async () => { + const testData: UserProfile = { + username: '92qab123', + roles: [roleViewer], + full_name: cloudFullname, + email: cloudEmail, + enabled: true, + elastic_cloud_user: true, + }; + getSecurityProfileMock.mockResolvedValueOnce(testData); + const samlSessionManager = new SamlSessionManager(samlSessionManagerOptions); + const userData = await samlSessionManager.getUserData(roleViewer); + + expect(userData).toEqual(testData); }); test(`throws error for non-existing role when 'supportedRoles' is defined`, async () => { diff --git a/packages/kbn-test/src/jest/mocks/apm_agent_mock.ts b/packages/kbn-test/src/jest/mocks/apm_agent_mock.ts index d44770d2f2096..d0a39b5c7fbfd 100644 --- a/packages/kbn-test/src/jest/mocks/apm_agent_mock.ts +++ b/packages/kbn-test/src/jest/mocks/apm_agent_mock.ts @@ -10,6 +10,8 @@ import type { Agent, Transaction } from 'elastic-apm-node'; const transaction: jest.Mocked = { addLabels: jest.fn().mockReturnValue(true), + addLink: jest.fn(), + addLinks: jest.fn(), ensureParentId: jest.fn().mockReturnValue(''), setLabel: jest.fn().mockReturnValue(true), setOutcome: jest.fn(), diff --git a/packages/kbn-text-based-editor/package.json b/packages/kbn-text-based-editor/package.json index 93c1c949a10d1..c585b638268ce 100644 --- a/packages/kbn-text-based-editor/package.json +++ b/packages/kbn-text-based-editor/package.json @@ -8,8 +8,7 @@ ], "scripts": { "make:docs": "ts-node --transpileOnly scripts/generate_esql_docs.ts", - "postmake:docs": "yarn run lint:fix && yarn run i18n:fix", - "lint:fix": "cd ../.. && node ./scripts/eslint --fix ./packages/kbn-text-based-editor/src/esql_documentation_sections.tsx", - "i18n:fix": "cd ../.. && node ./scripts/i18n_check.js --fix" + "postmake:docs": "yarn run lint:fix", + "lint:fix": "cd ../.. && node ./scripts/eslint --fix ./packages/kbn-text-based-editor/src/esql_documentation_sections.tsx" } } diff --git a/packages/kbn-text-based-editor/src/editor_footer.tsx b/packages/kbn-text-based-editor/src/editor_footer.tsx index 697563ebc404c..27a6ffbeabbdb 100644 --- a/packages/kbn-text-based-editor/src/editor_footer.tsx +++ b/packages/kbn-text-based-editor/src/editor_footer.tsx @@ -145,7 +145,6 @@ export const EditorFooter = memo(function EditorFooter({ queryHasChanged, measuredContainerWidth, }: EditorFooterProps) { - const { euiTheme } = useEuiTheme(); const [isErrorPopoverOpen, setIsErrorPopoverOpen] = useState(false); const [isWarningPopoverOpen, setIsWarningPopoverOpen] = useState(false); const onUpdateAndSubmit = useCallback( @@ -162,10 +161,6 @@ export const EditorFooter = memo(function EditorFooter({ [runQuery, updateQuery] ); - const shadowStyle = isInCompactMode - ? `inset 0 0px 0, inset 0 -1px 0 ${euiTheme.border.color}` - : 'none'; - return ( diff --git a/packages/kbn-text-based-editor/src/helpers.ts b/packages/kbn-text-based-editor/src/helpers.ts index 8d4943800f750..59dffbd29e07f 100644 --- a/packages/kbn-text-based-editor/src/helpers.ts +++ b/packages/kbn-text-based-editor/src/helpers.ts @@ -9,12 +9,27 @@ import { useRef } from 'react'; import useDebounce from 'react-use/lib/useDebounce'; import { monaco } from '@kbn/monaco'; +import type { CoreStart } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { MapCache } from 'lodash'; export type MonacoMessage = monaco.editor.IMarkerData; +interface IntegrationsResponse { + items: Array<{ + name: string; + title?: string; + dataStreams: Array<{ + name: string; + title?: string; + }>; + }>; +} + +const INTEGRATIONS_API = '/api/fleet/epm/packages/installed'; +const API_VERSION = '2023-10-31'; + export const useDebounceWithOptions = ( fn: Function, { skipFirstRender }: { skipFirstRender: boolean } = { skipFirstRender: false }, @@ -227,10 +242,38 @@ export const clearCacheWhenOld = (cache: MapCache, esqlQuery: string) => { } }; -export const getESQLSources = async (dataViews: DataViewsPublicPluginStart) => { - const [remoteIndices, localIndices] = await Promise.all([ +const getIntegrations = async (core: CoreStart) => { + const fleetCapabilities = core.application.capabilities.fleet; + if (!fleetCapabilities?.read) { + return []; + } + // Ideally we should use the Fleet plugin constants to fetch the integrations + // import { EPM_API_ROUTES, API_VERSIONS } from '@kbn/fleet-plugin/common'; + // but it complicates things as we need to use an x-pack plugin as dependency to get 2 constants + // and this needs to be done in various places in the codebase which use the editor + // https://github.com/elastic/kibana/issues/186061 + const response = (await core.http + .get(INTEGRATIONS_API, { query: undefined, version: API_VERSION }) + .catch((error) => { + // eslint-disable-next-line no-console + console.error('Failed to fetch integrations', error); + })) as IntegrationsResponse; + + return ( + response?.items?.map((source) => ({ + name: source.name, + hidden: false, + title: source.title, + dataStreams: source.dataStreams, + })) ?? [] + ); +}; + +export const getESQLSources = async (dataViews: DataViewsPublicPluginStart, core: CoreStart) => { + const [remoteIndices, localIndices, integrations] = await Promise.all([ getRemoteIndicesList(dataViews), getIndicesList(dataViews), + getIntegrations(core), ]); - return [...localIndices, ...remoteIndices]; + return [...localIndices, ...remoteIndices, ...integrations]; }; diff --git a/packages/kbn-text-based-editor/src/overwrite.scss b/packages/kbn-text-based-editor/src/overwrite.scss index e24fd604d7268..7c66fe96e36cb 100644 --- a/packages/kbn-text-based-editor/src/overwrite.scss +++ b/packages/kbn-text-based-editor/src/overwrite.scss @@ -1,45 +1,63 @@ -.TextBasedLangEditor .monaco-editor { - border-top-left-radius: 6px; - border-bottom-left-radius: 6px; +/* Editor styles for any layout mode */ +/* NOTE: Much of this is overriding Monaco styles so the specificity is intentional */ + +// Radius for both the main container and the margin (container for line numbers) +.TextBasedLangEditor .monaco-editor, .TextBasedLangEditor .monaco-editor .margin, .TextBasedLangEditor .monaco-editor .overflow-guard { + border-top-left-radius: $euiBorderRadius; + border-bottom-left-radius: $euiBorderRadius; } .TextBasedLangEditor .monaco-editor .monaco-hover { display: none !important; } -.TextBasedLangEditor--expanded .monaco-editor .monaco-hover { - display: block !important; +.TextBasedLangEditor .monaco-editor .margin-view-overlays .line-numbers { + color: $euiColorDisabledText; } -.TextBasedLangEditor .monaco-editor .margin { - border-top-left-radius: 6px; - border-bottom-left-radius: 6px; +// Currently focused line(s) +.TextBasedLangEditor .monaco-editor .current-line ~ .line-numbers { + color: $euiTextSubduedColor; } -.TextBasedLangEditor--compact .monaco-editor { - border-top-left-radius: 0; - border-bottom-left-radius: 0; +// Suggest (autocomplete) menu +.TextBasedLangEditor .monaco-editor .suggest-widget, .TextBasedLangEditor .monaco-editor .suggest-details-container { + @include euiBottomShadow; + border-radius: $euiBorderRadius; } -.TextBasedLangEditor--compact .monaco-editor .margin { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - background-color: $euiColorLightestShade; - color: $euiColorDisabledText; +.TextBasedLangEditor .monaco-editor .suggest-details-container { + background-color: $euiColorEmptyShade; + line-height: 1.5rem; } -.TextBasedLangEditor .monaco-editor .margin-view-overlays .line-numbers { - color: $euiColorDisabledText; +.TextBasedLangEditor_errorMessage { + @include euiTextBreakWord; } -.TextBasedLangEditor .monaco-editor .current-line ~ .line-numbers { - color: $euiTextSubduedColor; -} +/* For compact mode */ +// All scrollable containers (e.g. main container and suggest menu) .TextBasedLangEditor--compact .monaco-editor .monaco-scrollable-element { - margin-left: 4px; + margin-left: $euiSizeS; } -.TextBasedLangEditor_errorMessage { - @include euiTextBreakWord; +// Suggest menu in compact mode +.TextBasedLangEditor--compact .monaco-editor .monaco-list .monaco-scrollable-element { + margin-left: 0; + + .monaco-list-row.focused { + border-radius: $euiBorderRadius; + } } + +/* For expanded mode */ + +.TextBasedLangEditor--expanded .monaco-editor .monaco-hover { + display: block !important; +} + +.TextBasedLangEditor--expanded .monaco-editor, .TextBasedLangEditor--expanded .monaco-editor .margin, .TextBasedLangEditor--expanded .monaco-editor .overflow-guard { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} \ No newline at end of file diff --git a/packages/kbn-text-based-editor/src/query_history.tsx b/packages/kbn-text-based-editor/src/query_history.tsx index d7272e8d59e12..9f0f82deb3db7 100644 --- a/packages/kbn-text-based-editor/src/query_history.tsx +++ b/packages/kbn-text-based-editor/src/query_history.tsx @@ -364,6 +364,8 @@ export function QueryHistory({ .euiTable th[data-test-subj='tableHeaderCell_duration_3'] span { justify-content: flex-end; } + border-bottom-left-radius: ${euiTheme.border.radius.medium}; + border-top-left-radius: ${euiTheme.border.radius.medium}; max-height: ${isInCompactMode ? CONTAINER_MAX_HEIGHT_COMPACT : CONTAINER_MAX_HEIGHT_EXPANDED}px; overflow-y: auto; ${scrollBarStyles} diff --git a/packages/kbn-text-based-editor/src/text_based_languages_editor.styles.ts b/packages/kbn-text-based-editor/src/text_based_languages_editor.styles.ts index 39a02a35516ab..130d0ca69fc4a 100644 --- a/packages/kbn-text-based-editor/src/text_based_languages_editor.styles.ts +++ b/packages/kbn-text-based-editor/src/text_based_languages_editor.styles.ts @@ -102,7 +102,7 @@ export const textBasedLanguageEditorStyles = ( }, historyContainer: { border: euiTheme.border.thin, - borderTop: `2px solid ${euiTheme.colors.lightShade}`, + borderTop: 'none', borderLeft: editorIsInline ? 'none' : euiTheme.border.thin, borderRight: editorIsInline ? 'none' : euiTheme.border.thin, backgroundColor: euiTheme.colors.lightestShade, @@ -119,8 +119,8 @@ export const textBasedLanguageEditorStyles = ( borderTopLeftRadius: editorIsInline ? 0 : euiTheme.border.radius.medium, borderTopRightRadius: editorIsInline ? 0 : euiTheme.border.radius.medium, backgroundColor: euiTheme.colors.lightestShade, - paddingLeft: euiTheme.size.xs, - paddingRight: euiTheme.size.xs, + paddingLeft: euiTheme.size.s, + paddingRight: euiTheme.size.s, paddingTop: showHeader ? euiTheme.size.s : euiTheme.size.xs, paddingBottom: showHeader ? euiTheme.size.s : euiTheme.size.xs, width: '100%', diff --git a/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx b/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx index dac1f609430b6..b33d8125bfd25 100644 --- a/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx +++ b/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx @@ -391,7 +391,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ const { cache: dataSourcesCache, memoizedSources } = useMemo(() => { const fn = memoize( - (...args: [DataViewsPublicPluginStart]) => ({ + (...args: [DataViewsPublicPluginStart, CoreStart]) => ({ timestamp: Date.now(), result: getESQLSources(...args), }), @@ -405,7 +405,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ const callbacks: ESQLCallbacks = { getSources: async () => { clearCacheWhenOld(dataSourcesCache, queryString); - const sources = await memoizedSources(dataViews).result; + const sources = await memoizedSources(dataViews, core).result; return sources; }, getFieldsFor: async ({ query: queryToExecute }: { query?: string } | undefined = {}) => { @@ -445,6 +445,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ memoizedSources, dataSourcesCache, dataViews, + core, esqlFieldsCache, memoizedFieldsFromESQL, expressions, @@ -666,40 +667,43 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ }, [language, documentationSections]); const codeEditorOptions: CodeEditorProps['options'] = { - automaticLayout: true, accessibilitySupport: 'off', + autoIndent: 'none', + automaticLayout: true, + fixedOverflowWidgets: true, folding: false, fontSize: 14, - padding: { - top: 8, - bottom: 8, - }, - scrollBeyondLastLine: false, - quickSuggestions: true, - minimap: { enabled: false }, - wordWrap: 'on', - lineNumbers: showLineNumbers ? 'on' : 'off', - theme: language === 'esql' ? ESQL_THEME_ID : isDark ? 'vs-dark' : 'vs', - lineDecorationsWidth: 12, - autoIndent: 'none', - wrappingIndent: 'none', - lineNumbersMinChars: 3, - overviewRulerLanes: 0, hideCursorInOverviewRuler: true, - scrollbar: { - horizontal: 'hidden', - vertical: 'auto', - }, - overviewRulerBorder: false, // this becomes confusing with multiple markers, so quick fixes // will be proposed only within the tooltip lightbulb: { enabled: false, }, + lineDecorationsWidth: 12, + lineNumbers: showLineNumbers ? 'on' : 'off', + lineNumbersMinChars: 3, + minimap: { enabled: false }, + overviewRulerLanes: 0, + overviewRulerBorder: false, + padding: { + top: 8, + bottom: 8, + }, + quickSuggestions: true, readOnly: isLoading || isDisabled || Boolean(!isCompactFocused && codeOneLiner && codeOneLiner.includes('...')), + renderLineHighlight: !isCodeEditorExpanded ? 'none' : 'line', + renderLineHighlightOnlyWhenFocus: true, + scrollbar: { + horizontal: 'hidden', + vertical: 'auto', + }, + scrollBeyondLastLine: false, + theme: language === 'esql' ? ESQL_THEME_ID : isDark ? 'vs-dark' : 'vs', + wordWrap: 'on', + wrappingIndent: 'none', }; if (isCompactFocused) { @@ -741,7 +745,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ {!Boolean(hideMinimizeButton) && ( - + { expandCodeEditor(false); updateLinesFromModel = false; @@ -1013,6 +1017,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ borderRadius: 0, backgroundColor: isDark ? euiTheme.colors.lightestShade : '#e9edf3', border: '1px solid rgb(17 43 134 / 10%) !important', + transform: 'none !important', }, }} /> @@ -1042,6 +1047,7 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ backgroundColor: isDark ? euiTheme.colors.lightestShade : '#e9edf3', border: '1px solid rgb(17 43 134 / 10%) !important', borderLeft: 'transparent !important', + transform: 'none !important', }} /> diff --git a/packages/kbn-ui-shared-deps-npm/webpack.config.js b/packages/kbn-ui-shared-deps-npm/webpack.config.js index 79502207aea00..24085109d0d56 100644 --- a/packages/kbn-ui-shared-deps-npm/webpack.config.js +++ b/packages/kbn-ui-shared-deps-npm/webpack.config.js @@ -63,7 +63,6 @@ module.exports = (_, argv) => { '@elastic/eui/optimize/es/components/provider/nested', '@elastic/eui/optimize/es/services', '@elastic/eui/optimize/es/services/format', - '@elastic/eui/dist/eui_charts_theme', '@elastic/eui/dist/eui_theme_light.json', '@elastic/eui/dist/eui_theme_dark.json', '@elastic/numeral', diff --git a/packages/kbn-ui-shared-deps-src/src/definitions.js b/packages/kbn-ui-shared-deps-src/src/definitions.js index 78ee1229da4e9..b2b38e131fb80 100644 --- a/packages/kbn-ui-shared-deps-src/src/definitions.js +++ b/packages/kbn-ui-shared-deps-src/src/definitions.js @@ -75,7 +75,6 @@ const externals = { '__kbnSharedDeps__.ElasticEuiLibComponentsUseIsNestedEuiProvider', '@elastic/eui/lib/services': '__kbnSharedDeps__.ElasticEuiLibServices', '@elastic/eui/lib/services/format': '__kbnSharedDeps__.ElasticEuiLibServicesFormat', - '@elastic/eui/dist/eui_charts_theme': '__kbnSharedDeps__.ElasticEuiChartsTheme', // transient dep of eui '@hello-pangea/dnd': '__kbnSharedDeps__.HelloPangeaDnd', diff --git a/packages/kbn-ui-shared-deps-src/src/entry.js b/packages/kbn-ui-shared-deps-src/src/entry.js index b012cb5660113..1d0e88ef0efd8 100644 --- a/packages/kbn-ui-shared-deps-src/src/entry.js +++ b/packages/kbn-ui-shared-deps-src/src/entry.js @@ -42,7 +42,6 @@ export const ElasticEui = require('@elastic/eui'); export const ElasticEuiLibComponentsUseIsNestedEuiProvider = require('@elastic/eui/optimize/es/components/provider/nested'); export const ElasticEuiLibServices = require('@elastic/eui/optimize/es/services'); export const ElasticEuiLibServicesFormat = require('@elastic/eui/optimize/es/services/format'); -export const ElasticEuiChartsTheme = require('@elastic/eui/dist/eui_charts_theme'); export const KbnDatemath = require('@kbn/datemath'); export const HelloPangeaDnd = require('@hello-pangea/dnd/dist/dnd'); export const ReduxjsToolkit = require('@reduxjs/toolkit'); diff --git a/packages/kbn-unified-data-table/src/components/data_table_additional_display_settings.test.tsx b/packages/kbn-unified-data-table/src/components/data_table_additional_display_settings.test.tsx index ccb53ff49b15b..5afe08db07476 100644 --- a/packages/kbn-unified-data-table/src/components/data_table_additional_display_settings.test.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_additional_display_settings.test.tsx @@ -140,6 +140,42 @@ describe('UnifiedDataTableAdditionalDisplaySettings', function () { expect(onChangeSampleSizeMock).not.toHaveBeenCalled(); }); + + it('should only render integers when a decimal value is provided', async () => { + const invalidDecimalValue = 6.11; + const validIntegerValue = 6; + + const onChangeSampleSizeMock = jest.fn(); + + const component = mountWithIntl( + + ); + const input = findTestSubject(component, 'unifiedDataTableSampleSizeInput').last(); + expect(input.prop('value')).toBe(50); + + await act(async () => { + input.simulate('change', { + target: { + value: invalidDecimalValue, + }, + }); + }); + + await new Promise((resolve) => setTimeout(resolve, 0)); + component.update(); + + expect( + findTestSubject(component, 'unifiedDataTableSampleSizeInput').last().prop('value') + ).toBe(validIntegerValue); + }); }); describe('rowHeight', () => { diff --git a/packages/kbn-unified-data-table/src/components/data_table_additional_display_settings.tsx b/packages/kbn-unified-data-table/src/components/data_table_additional_display_settings.tsx index f57127d185a94..cbead22b39997 100644 --- a/packages/kbn-unified-data-table/src/components/data_table_additional_display_settings.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_additional_display_settings.tsx @@ -70,7 +70,7 @@ export const UnifiedDataTableAdditionalDisplaySettings: React.FC< return; } - const newSampleSize = Number(event.target.value); + const newSampleSize = parseInt(event.target.value, 10) ?? RANGE_MIN_SAMPLE_SIZE; if (newSampleSize >= MIN_ALLOWED_SAMPLE_SIZE) { setActiveSampleSize(newSampleSize); diff --git a/packages/kbn-unified-data-table/src/utils/get_render_cell_popover.tsx b/packages/kbn-unified-data-table/src/utils/get_render_cell_popover.tsx index ea6e2a54a90de..7d8d8335cffff 100644 --- a/packages/kbn-unified-data-table/src/utils/get_render_cell_popover.tsx +++ b/packages/kbn-unified-data-table/src/utils/get_render_cell_popover.tsx @@ -19,7 +19,9 @@ import React, { memo, useEffect } from 'react'; * * */ export const getCustomCellPopoverRenderer = () => { - return memo(function RenderCustomCellPopover(props: EuiDataGridCellPopoverElementProps) { + const RenderCustomCellPopoverMemoized = memo(function RenderCustomCellPopoverMemoized( + props: EuiDataGridCellPopoverElementProps + ) { const { setCellPopoverProps, DefaultCellPopover } = props; useEffect(() => { @@ -30,4 +32,10 @@ export const getCustomCellPopoverRenderer = () => { return ; }); + + // Components passed to EUI DataGrid cannot be memoized components + // otherwise EUI throws an error `typeof Component !== 'function'` + return (props: EuiDataGridCellPopoverElementProps) => ( + + ); }; diff --git a/packages/kbn-unified-data-table/src/utils/get_render_cell_value.tsx b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.tsx index a56336fd53de7..35234bdb58d8f 100644 --- a/packages/kbn-unified-data-table/src/utils/get_render_cell_value.tsx +++ b/packages/kbn-unified-data-table/src/utils/get_render_cell_value.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { memo, useEffect, useContext } from 'react'; +import React, { useEffect, useContext } from 'react'; import { i18n } from '@kbn/i18n'; import type { DataView, DataViewField } from '@kbn/data-views-plugin/public'; import { @@ -47,10 +47,7 @@ export const getRenderCellValueFn = ({ externalCustomRenderers?: CustomCellRenderer; isPlainRecord?: boolean; }) => { - /** - * memo is imperative here otherwise the cell will re-render on every hover on every cell - */ - return memo(function UnifiedDataTableRenderCellValue({ + return function UnifiedDataTableRenderCellValue({ rowIndex, columnId, isDetails, @@ -149,7 +146,7 @@ export const getRenderCellValueFn = ({ }} /> ); - }); + }; }; /** diff --git a/packages/kbn-unified-doc-viewer/src/components/doc_viewer/doc_viewer_tab.tsx b/packages/kbn-unified-doc-viewer/src/components/doc_viewer/doc_viewer_tab.tsx index ad0f7d9461f58..95a4db9528ad2 100644 --- a/packages/kbn-unified-doc-viewer/src/components/doc_viewer/doc_viewer_tab.tsx +++ b/packages/kbn-unified-doc-viewer/src/components/doc_viewer/doc_viewer_tab.tsx @@ -46,6 +46,8 @@ export class DocViewerTab extends React.Component { !isEqual(nextProps.renderProps.hit.raw.highlight, this.props.renderProps.hit.raw.highlight) || nextProps.id !== this.props.id || !isEqual(nextProps.renderProps.columns, this.props.renderProps.columns) || + nextProps.renderProps.decreaseAvailableHeightBy !== + this.props.renderProps.decreaseAvailableHeightBy || nextState.hasError ); } diff --git a/packages/kbn-unified-doc-viewer/src/components/field_name/__snapshots__/field_name.test.tsx.snap b/packages/kbn-unified-doc-viewer/src/components/field_name/__snapshots__/field_name.test.tsx.snap index c63b1b5bc0d11..cf9ddb38bbe69 100644 --- a/packages/kbn-unified-doc-viewer/src/components/field_name/__snapshots__/field_name.test.tsx.snap +++ b/packages/kbn-unified-doc-viewer/src/components/field_name/__snapshots__/field_name.test.tsx.snap @@ -1,268 +1,349 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`FieldName renders a custom description icon 1`] = ` -Array [ +

+
- -
+ - -
-
+ + test + + +
- , -] + + `; exports[`FieldName renders a geo field 1`] = ` -Array [ +
- - - Geo point - - -
, + + + Geo point + + +
+ +
- - - test.test.test + + + test.test.test + - +
- , -] + + `; exports[`FieldName renders a number field by providing a field record 1`] = ` -Array [ +
- - - Number - - -
, + + + Number + + +
+ +
- - - test.test.test + + + test.test.test + - +
- , -] + + `; exports[`FieldName renders a string field by providing fieldType and fieldName 1`] = ` -Array [ +
- - - String - - -
, + + + String + + +
+ +
- - - test + + + test + - +
- , -] + + `; exports[`FieldName renders unknown field 1`] = ` -Array [ +
- - - Unknown field - - -
, + + + Unknown field + + +
+ +
- - - test.test.test + + + test.test.test + - +
- , -] + + `; exports[`FieldName renders when mapping is provided 1`] = ` -Array [ +
- - - Number - - -
, + + + Number + + +
+ +
- - - bytes + + + bytes + - +
- , -] + + `; exports[`FieldName renders with a search highlight 1`] = ` -Array [ +
- - - Number - - -
, + + + Number + + +
+ +
- - - - te - - st.test.test + + + + te + + st.test.test + - +
- , -] + + `; diff --git a/packages/kbn-unified-doc-viewer/src/components/field_name/field_name.scss b/packages/kbn-unified-doc-viewer/src/components/field_name/field_name.scss index edc4ea270d755..fce22a86b8c73 100644 --- a/packages/kbn-unified-doc-viewer/src/components/field_name/field_name.scss +++ b/packages/kbn-unified-doc-viewer/src/components/field_name/field_name.scss @@ -1,12 +1,20 @@ -.kbnDocViewer__fieldIcon { +.kbnDocViewer__fieldIconContainer { padding-top: $euiSizeXS * 1.5; + line-height: $euiSize; } .kbnDocViewer__fieldName { - line-height: $euiLineHeight; padding: $euiSizeXS; + padding-left: 0; + line-height: $euiLineHeight; + + .euiDataGridRowCell__popover & { + font-size: $euiFontSizeS; + line-height: $euiLineHeight; + } } .kbnDocViewer__multiFieldBadge { @include euiFont; + margin: $euiSizeXS 0; } diff --git a/packages/kbn-unified-doc-viewer/src/components/field_name/field_name.tsx b/packages/kbn-unified-doc-viewer/src/components/field_name/field_name.tsx index 9494cc4d03ae0..d7f380195947a 100644 --- a/packages/kbn-unified-doc-viewer/src/components/field_name/field_name.tsx +++ b/packages/kbn-unified-doc-viewer/src/components/field_name/field_name.tsx @@ -6,15 +6,22 @@ * Side Public License, v 1. */ -import React, { Fragment } from 'react'; +import React from 'react'; import './field_name.scss'; -import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiToolTip, EuiHighlight } from '@elastic/eui'; +import { + EuiBadge, + EuiFlexGroup, + EuiFlexItem, + EuiToolTip, + EuiHighlight, + EuiIcon, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { FieldIcon, FieldIconProps } from '@kbn/react-field'; import type { DataViewField } from '@kbn/data-views-plugin/public'; import { getDataViewFieldSubtypeMulti } from '@kbn/es-query'; -import { FieldDescriptionIconButton, getFieldTypeName } from '@kbn/field-utils'; +import { getFieldTypeName } from '@kbn/field-utils'; interface Props { fieldName: string; @@ -23,6 +30,7 @@ interface Props { fieldIconProps?: Omit; scripted?: boolean; highlight?: string; + isPinned?: boolean; } export function FieldName({ @@ -32,6 +40,7 @@ export function FieldName({ fieldIconProps, scripted = false, highlight = '', + isPinned = false, }: Props) { const typeName = getFieldTypeName(fieldType); const displayName = @@ -41,54 +50,76 @@ export function FieldName({ const isMultiField = !!subTypeMulti?.multi; return ( - - - + + + + + + + {isPinned && ( + + + + )} + - - - + + - {displayName} - - - - {fieldMapping?.customDescription ? ( - - + + {displayName} + - ) : null} - {isMultiField && ( - - - - - - )} - - + + + +
+ )} + + + ); } diff --git a/packages/kbn-unified-doc-viewer/src/services/types.ts b/packages/kbn-unified-doc-viewer/src/services/types.ts index 115583c738bb0..c6e10d09cea00 100644 --- a/packages/kbn-unified-doc-viewer/src/services/types.ts +++ b/packages/kbn-unified-doc-viewer/src/services/types.ts @@ -49,6 +49,7 @@ export interface DocViewRenderProps { onAddColumn?: (columnName: string) => void; onRemoveColumn?: (columnName: string) => void; docViewsRegistry?: DocViewsRegistry | ((prevRegistry: DocViewsRegistry) => DocViewsRegistry); + decreaseAvailableHeightBy?: number; } export type DocViewerComponent = React.FC; export type DocViewRenderFn = ( @@ -83,7 +84,7 @@ export interface FieldRecordLegacy { action: { isActive: boolean; onFilter?: DocViewFilterFn; - onToggleColumn: (field: string) => void; + onToggleColumn: ((field: string) => void) | undefined; flattenedField: unknown; }; field: { diff --git a/packages/kbn-zod-helpers/index.ts b/packages/kbn-zod-helpers/index.ts index d8a62f58686b2..e3b0f4e35a19d 100644 --- a/packages/kbn-zod-helpers/index.ts +++ b/packages/kbn-zod-helpers/index.ts @@ -14,3 +14,4 @@ export * from './src/is_valid_date_math'; export * from './src/required_optional'; export * from './src/safe_parse_result'; export * from './src/stringify_zod_error'; +export * from './src/build_route_validation_with_zod'; diff --git a/packages/kbn-zod-helpers/src/build_route_validation_with_zod.ts b/packages/kbn-zod-helpers/src/build_route_validation_with_zod.ts new file mode 100644 index 0000000000000..a72eb96d3b968 --- /dev/null +++ b/packages/kbn-zod-helpers/src/build_route_validation_with_zod.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { TypeOf, ZodType } from 'zod'; +import type { RouteValidationFunction, RouteValidationResultFactory } from '@kbn/core/server'; +import { stringifyZodError } from './stringify_zod_error'; + +/** + * Zod validation factory for Kibana route's request validation. + * It allows to pass a Zod schema for parameters, query and/or body validation. + * + * Example: + * + * ```ts + * router.versioned + * .post({ + * access: 'public', + * path: MY_URL, + * }) + * .addVersion( + * { + * version: 'my-version', + * validate: { + * request: { + * params: buildRouteValidationWithZod(MyRequestParamsZodSchema), + * query: buildRouteValidationWithZod(MyRequestQueryZodSchema), + * body: buildRouteValidationWithZod(MyRequestBodyZodSchema), + * }, + * }, + * }, + * ``` + */ +export function buildRouteValidationWithZod>( + schema: ZodSchema +): RouteValidationFunction { + return (inputValue: unknown, validationResult: RouteValidationResultFactory) => { + const decoded = schema.safeParse(inputValue); + + return decoded.success + ? validationResult.ok(decoded.data) + : validationResult.badRequest(stringifyZodError(decoded.error)); + }; +} diff --git a/packages/kbn-zod-helpers/tsconfig.json b/packages/kbn-zod-helpers/tsconfig.json index 0b3850da21158..fbe3b91a89493 100644 --- a/packages/kbn-zod-helpers/tsconfig.json +++ b/packages/kbn-zod-helpers/tsconfig.json @@ -6,7 +6,5 @@ "exclude": ["target/**/*"], "extends": "../../tsconfig.base.json", "include": ["**/*.ts"], - "kbn_references": [ - "@kbn/datemath", - ] + "kbn_references": ["@kbn/datemath", "@kbn/core"] } diff --git a/packages/presentation/presentation_containers/interfaces/child_state.ts b/packages/presentation/presentation_containers/interfaces/child_state.ts index c197974c67add..3a399d8a3c913 100644 --- a/packages/presentation/presentation_containers/interfaces/child_state.ts +++ b/packages/presentation/presentation_containers/interfaces/child_state.ts @@ -9,7 +9,9 @@ import { SerializedPanelState } from './serialized_state'; export interface HasSerializedChildState { - getSerializedStateForChild: (childId: string) => SerializedPanelState; + getSerializedStateForChild: ( + childId: string + ) => SerializedPanelState | undefined; } export interface HasRuntimeChildState { diff --git a/packages/presentation/presentation_containers/interfaces/serialized_state.ts b/packages/presentation/presentation_containers/interfaces/serialized_state.ts index 593362ab6a7e1..be9e9d4236c33 100644 --- a/packages/presentation/presentation_containers/interfaces/serialized_state.ts +++ b/packages/presentation/presentation_containers/interfaces/serialized_state.ts @@ -32,8 +32,8 @@ export const apiHasSerializableState = (api: unknown | null): api is HasSerializ export interface HasSnapshottableState { /** - * Serializes all runtime state exactly as it appears. This could be used - * to rehydrate a component's state without needing to deserialize it. + * Serializes all runtime state exactly as it appears. This can be used + * to rehydrate a component's state without needing to serialize then deserialize it. */ snapshotRuntimeState: () => RuntimeState; } diff --git a/packages/presentation/presentation_publishing/index.ts b/packages/presentation/presentation_publishing/index.ts index e3136215f3d40..d56cd53b67a54 100644 --- a/packages/presentation/presentation_publishing/index.ts +++ b/packages/presentation/presentation_publishing/index.ts @@ -16,8 +16,8 @@ export interface EmbeddableApiContext { export { getInitialValuesFromComparators, - runComparators, getUnchangingComparator, + runComparators, type ComparatorDefinition, type ComparatorFunction, type StateComparators, @@ -35,22 +35,22 @@ export { type SerializedTimeRange, } from './interfaces/fetch/initialize_time_range'; export { - apiPublishesPartialUnifiedSearch, apiPublishesFilters, + apiPublishesPartialUnifiedSearch, apiPublishesTimeRange, apiPublishesUnifiedSearch, apiPublishesWritableUnifiedSearch, useSearchApi, - type PublishesTimeRange, type PublishesFilters, + type PublishesTimeRange, + type PublishesTimeslice, type PublishesUnifiedSearch, type PublishesWritableUnifiedSearch, - type PublishesTimeslice, } from './interfaces/fetch/publishes_unified_search'; export { apiHasAppContext, - type HasAppContext, type EmbeddableAppContext, + type HasAppContext, } from './interfaces/has_app_context'; export { apiHasDisableTriggers, @@ -63,9 +63,9 @@ export { type HasExecutionContext, } from './interfaces/has_execution_context'; export { + apiHasInPlaceLibraryTransforms, apiHasLegacyLibraryTransforms, apiHasLibraryTransforms, - apiHasInPlaceLibraryTransforms, type HasInPlaceLibraryTransforms, type HasLegacyLibraryTransforms, type HasLibraryTransforms, @@ -130,7 +130,11 @@ export { type PublishesPanelTitle, type PublishesWritablePanelTitle, } from './interfaces/titles/publishes_panel_title'; -export { initializeTitles, type SerializedTitles } from './interfaces/titles/titles_api'; +export { + initializeTitles, + stateHasTitles, + type SerializedTitles, +} from './interfaces/titles/titles_api'; export { useBatchedOptionalPublishingSubjects, useBatchedPublishingSubjects, diff --git a/packages/presentation/presentation_publishing/interfaces/has_library_transforms.ts b/packages/presentation/presentation_publishing/interfaces/has_library_transforms.ts index 17d48eca51be7..778748f0e1f2c 100644 --- a/packages/presentation/presentation_publishing/interfaces/has_library_transforms.ts +++ b/packages/presentation/presentation_publishing/interfaces/has_library_transforms.ts @@ -34,7 +34,7 @@ interface LibraryTransformGuards { * APIs that inherit this interface can be linked to and unlinked from the library in place without * re-initialization. */ -export interface HasInPlaceLibraryTransforms +export interface HasInPlaceLibraryTransforms extends Partial, DuplicateTitleCheck { /** @@ -49,6 +49,11 @@ export interface HasInPlaceLibraryTransforms */ saveToLibrary: (title: string) => Promise; + /** + * gets a snapshot of this embeddable's runtime state without any state that links it to a library item. + */ + getByValueRuntimeSnapshot: () => RuntimeState; + /** * Un-links this embeddable from the library. This method is optional, and only needed if the Embeddable * is not meant to be re-initialized as part of the unlink operation. If the embeddable needs to be re-initialized @@ -69,6 +74,7 @@ export const apiHasInPlaceLibraryTransforms = ( }; /** + * @deprecated use HasInPlaceLibraryTransforms instead * APIs that inherit this interface can be linked to and unlinked from the library. After the save or unlink * operation, the embeddable will be reinitialized. */ diff --git a/packages/presentation/presentation_publishing/interfaces/titles/titles_api.ts b/packages/presentation/presentation_publishing/interfaces/titles/titles_api.ts index 026ceb1cc84fc..772e7fe6b113a 100644 --- a/packages/presentation/presentation_publishing/interfaces/titles/titles_api.ts +++ b/packages/presentation/presentation_publishing/interfaces/titles/titles_api.ts @@ -17,6 +17,14 @@ export interface SerializedTitles { hidePanelTitles?: boolean; } +export const stateHasTitles = (state: unknown): state is SerializedTitles => { + return ( + (state as SerializedTitles)?.title !== undefined || + (state as SerializedTitles)?.description !== undefined || + (state as SerializedTitles)?.hidePanelTitles !== undefined + ); +}; + export interface TitlesApi extends PublishesWritablePanelTitle, PublishesWritablePanelDescription {} export const initializeTitles = ( diff --git a/packages/react/kibana_context/root/eui_provider.test.tsx b/packages/react/kibana_context/root/eui_provider.test.tsx index b860b37662f6a..5292c2f7a6b8b 100644 --- a/packages/react/kibana_context/root/eui_provider.test.tsx +++ b/packages/react/kibana_context/root/eui_provider.test.tsx @@ -56,7 +56,7 @@ describe('KibanaEuiProvider', () => { const coreTheme: KibanaTheme = { darkMode: true }; const wrapper = mountWithIntl( - + ); @@ -64,6 +64,7 @@ describe('KibanaEuiProvider', () => { await refresh(wrapper); expect(euiTheme!.colorMode).toEqual('DARK'); + expect(euiTheme!.euiTheme.breakpoint.xxl).toEqual(1600); expect(consoleWarnMock).not.toBeCalled(); }); diff --git a/packages/react/kibana_context/root/eui_provider.tsx b/packages/react/kibana_context/root/eui_provider.tsx index 29745997b80bd..15467f2b91517 100644 --- a/packages/react/kibana_context/root/eui_provider.tsx +++ b/packages/react/kibana_context/root/eui_provider.tsx @@ -60,6 +60,7 @@ export const KibanaEuiProvider: FC> = theme: { theme$ }, globalStyles: globalStylesProp, colorMode: colorModeProp, + modify, children, }) => { const theme = useObservable(theme$, defaultTheme); @@ -74,7 +75,7 @@ export const KibanaEuiProvider: FC> = const globalStyles = globalStylesProp === false ? false : undefined; return ( - + {children} ); diff --git a/packages/response-ops/feature_flag_service/README.md b/packages/response-ops/feature_flag_service/README.md new file mode 100644 index 0000000000000..e1f2cc2d9e387 --- /dev/null +++ b/packages/response-ops/feature_flag_service/README.md @@ -0,0 +1,27 @@ +# @kbn/response-ops-feature-flags + +This packages exposes a feature flag service that is used in the ResponseOps plugins and packages to handle feature flags. + +## Usage + +### Create feature flag service + +``` +const featureFlagService = createFeatureFlagService(['test.myFeature', 'test.myFeature.subFeature']); // TS will infer the types automatically +``` + +or + +``` +type FeatureFlagValues = 'test.myFeature' | 'test.myOtherFeature' +const featureFlagService = createFeatureFlagService(['test.myFeature']); +``` + +### Checking the existence of a feature flag +``` +const featureFlagService = createFeatureFlagService(['test.myFeature', 'test.myFeature.subFeature']); + +if (featureFlagService.isFeatureFlagSet('test.myFeature')) { + // my feature code +} +``` \ No newline at end of file diff --git a/packages/response-ops/feature_flag_service/feature_flag_service.test.ts b/packages/response-ops/feature_flag_service/feature_flag_service.test.ts new file mode 100644 index 0000000000000..55ccc64ffdd9e --- /dev/null +++ b/packages/response-ops/feature_flag_service/feature_flag_service.test.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createFeatureFlagService } from './feature_flag_service'; + +type FeatureFlagValues = 'test.myFeature' | 'test.myOtherFeature'; + +describe('FeatureFlagService', () => { + it('returns true if the feature exists', () => { + const featureFlagService = createFeatureFlagService(['test.myFeature']); + expect(featureFlagService.isFeatureFlagSet('test.myFeature')).toBe(true); + }); + + it('returns false if the feature does not exist', () => { + const featureFlagService = createFeatureFlagService(['test.myFeature']); + // @ts-expect-error: foo is not part of the valid feature flags + expect(featureFlagService.isFeatureFlagSet('foo')).toBe(false); + }); + + it('returns true if the feature exists (as typed)', () => { + const featureFlagService = createFeatureFlagService(['test.myFeature']); + expect(featureFlagService.isFeatureFlagSet('test.myFeature')).toBe(true); + }); +}); diff --git a/packages/response-ops/feature_flag_service/feature_flag_service.ts b/packages/response-ops/feature_flag_service/feature_flag_service.ts new file mode 100644 index 0000000000000..857378d75341f --- /dev/null +++ b/packages/response-ops/feature_flag_service/feature_flag_service.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +class FeatureFlag { + private readonly featureFlags = new Set(); + + constructor(featureFlags: T[]) { + featureFlags.forEach((featureFlag) => this.featureFlags.add(featureFlag)); + } + + public isFeatureFlagSet(featureFlag: T): boolean { + return this.featureFlags.has(featureFlag); + } +} + +export const createFeatureFlagService = (featureFlags: T[]) => { + return new FeatureFlag(featureFlags); +}; + +export type FeatureFlagService = InstanceType>; diff --git a/packages/response-ops/feature_flag_service/index.ts b/packages/response-ops/feature_flag_service/index.ts new file mode 100644 index 0000000000000..8677b72deb045 --- /dev/null +++ b/packages/response-ops/feature_flag_service/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { createFeatureFlagService } from './feature_flag_service'; +export type { FeatureFlagService } from './feature_flag_service'; diff --git a/packages/analytics/client/jest.config.js b/packages/response-ops/feature_flag_service/jest.config.js similarity index 82% rename from packages/analytics/client/jest.config.js rename to packages/response-ops/feature_flag_service/jest.config.js index aa0d319f9d1e7..c85570b29dff2 100644 --- a/packages/analytics/client/jest.config.js +++ b/packages/response-ops/feature_flag_service/jest.config.js @@ -8,6 +8,6 @@ module.exports = { preset: '@kbn/test/jest_node', - rootDir: '../../../', - roots: ['/packages/analytics/client'], + rootDir: '../../..', + roots: ['/packages/response-ops/feature_flag_service'], }; diff --git a/packages/response-ops/feature_flag_service/kibana.jsonc b/packages/response-ops/feature_flag_service/kibana.jsonc new file mode 100644 index 0000000000000..9d52b84f64ec2 --- /dev/null +++ b/packages/response-ops/feature_flag_service/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/response-ops-feature-flag-service", + "owner": "@elastic/response-ops" +} diff --git a/packages/analytics/shippers/elastic_v3/common/package.json b/packages/response-ops/feature_flag_service/package.json similarity index 52% rename from packages/analytics/shippers/elastic_v3/common/package.json rename to packages/response-ops/feature_flag_service/package.json index 4e1caaf0d6a2f..6ad0848efbdca 100644 --- a/packages/analytics/shippers/elastic_v3/common/package.json +++ b/packages/response-ops/feature_flag_service/package.json @@ -1,7 +1,6 @@ { - "name": "@kbn/analytics-shippers-elastic-v3-common", + "name": "@kbn/response-ops-feature-flag-service", "private": true, "version": "1.0.0", - "author": "Kibana Core", "license": "SSPL-1.0 OR Elastic License 2.0" -} \ No newline at end of file +} diff --git a/packages/analytics/client/tsconfig.json b/packages/response-ops/feature_flag_service/tsconfig.json similarity index 65% rename from packages/analytics/client/tsconfig.json rename to packages/response-ops/feature_flag_service/tsconfig.json index b5bb1c1f7c010..6d27b06d5f8ba 100644 --- a/packages/analytics/client/tsconfig.json +++ b/packages/response-ops/feature_flag_service/tsconfig.json @@ -8,13 +8,10 @@ ] }, "include": [ - "**/*.ts" - ], - "kbn_references": [ - "@kbn/logging", - "@kbn/logging-mocks" + "**/*.ts", ], "exclude": [ - "target/**/*", - ] + "target/**/*" + ], + "kbn_references": [] } diff --git a/packages/shared-ux/code_editor/impl/__snapshots__/code_editor.test.tsx.snap b/packages/shared-ux/code_editor/impl/__snapshots__/code_editor.test.tsx.snap index a538b4845231a..e0fe7d3ea2386 100644 --- a/packages/shared-ux/code_editor/impl/__snapshots__/code_editor.test.tsx.snap +++ b/packages/shared-ux/code_editor/impl/__snapshots__/code_editor.test.tsx.snap @@ -169,6 +169,7 @@ exports[` is rendered 1`] = `

} + data-test-subj="codeEditorAccessibilityOverlay" delay="regular" display="block" position="top" diff --git a/packages/shared-ux/code_editor/impl/code_editor.tsx b/packages/shared-ux/code_editor/impl/code_editor.tsx index 2e8d2c2732ff7..35dcb275b52f9 100644 --- a/packages/shared-ux/code_editor/impl/code_editor.tsx +++ b/packages/shared-ux/code_editor/impl/code_editor.tsx @@ -155,6 +155,8 @@ export interface CodeEditorProps { * Enables the editor to get disabled when pressing ESC to resolve focus trapping for accessibility. */ accessibilityOverlayEnabled?: boolean; + + dataTestSubj?: string; } export const CodeEditor: React.FC = ({ @@ -186,6 +188,7 @@ export const CodeEditor: React.FC = ({ }), fitToContent, accessibilityOverlayEnabled = true, + dataTestSubj, }) => { const { colorMode, euiTheme } = useEuiTheme(); const useDarkTheme = useDarkThemeProp ?? colorMode === 'DARK'; @@ -280,6 +283,7 @@ export const CodeEditor: React.FC = ({ return ( @@ -485,7 +489,7 @@ export const CodeEditor: React.FC = ({
{accessibilityOverlayEnabled && renderPrompt()} diff --git a/packages/shared-ux/markdown/impl/markdown.tsx b/packages/shared-ux/markdown/impl/markdown.tsx index 954a69c5c67e6..1df0aff10648f 100644 --- a/packages/shared-ux/markdown/impl/markdown.tsx +++ b/packages/shared-ux/markdown/impl/markdown.tsx @@ -84,9 +84,6 @@ export const Markdown = ({ // Render EuiMarkdownFormat when readOnly set to true if (readOnly) { - if (!children && !markdownContent) { - throw new Error('Markdown content is required in [readOnly] mode'); - } return ( component style={restProps.style} > - {children ?? markdownContent!} + {children ?? markdownContent ?? ''} ); } diff --git a/renovate.json b/renovate.json index 2cb7a55cfb4ae..f0995502bb1bc 100644 --- a/renovate.json +++ b/renovate.json @@ -427,6 +427,14 @@ "prCreation": "not-pending", "minimumReleaseAge": "7 days", "enabled": true + }, + { + "groupName": "MSW", + "matchPackageNames": ["msw"], + "reviewers": ["team:kibana-cloud-security-posture"], + "matchBaseBranches": ["main"], + "labels": ["Team: Cloud Security", "release_note:skip", "backport:skip"], + "enabled": true } ] } diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 2260b78f90d9a..372eabdac9c95 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -96,20 +96,46 @@ export type { UiSettingsType, } from '@kbn/core-ui-settings-common'; -export type { AnalyticsServiceSetup, AnalyticsServiceStart } from '@kbn/core-analytics-browser'; export type { AnalyticsClient, - Event, - EventContext, - EventType, - EventTypeOpts, - IShipper, + AnalyticsClientInitContext, + AnalyticsServiceSetup, + AnalyticsServiceStart, + KbnAnalyticsWindowApi, + // Types for the registerShipper API ShipperClassConstructor, + RegisterShipperOpts, + // Types for the optIn API OptInConfig, + OptInConfigPerType, + ShipperName, + // Types for the registerContextProvider API ContextProviderOpts, + ContextProviderName, + // Types for the registerEventType API + EventTypeOpts, + // Events + Event, + EventContext, + EventType, TelemetryCounter, TelemetryCounterType, -} from '@kbn/analytics-client'; + // Schema + RootSchema, + SchemaObject, + SchemaArray, + SchemaChildValue, + SchemaMeta, + SchemaValue, + SchemaMetaOptional, + PossibleSchemaTypes, + AllowedSchemaBooleanTypes, + AllowedSchemaNumberTypes, + AllowedSchemaStringTypes, + AllowedSchemaTypes, + // Shippers + IShipper, +} from '@kbn/core-analytics-browser'; export { AppStatus } from '@kbn/core-application-browser'; export type { diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 901e0e3153651..1e89c9ed5b24e 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -482,21 +482,43 @@ export type { DocLinksServiceStart, DocLinksServiceSetup } from '@kbn/core-doc-l export type { AnalyticsClient, + AnalyticsClientInitContext, + AnalyticsServiceSetup, + AnalyticsServicePreboot, + AnalyticsServiceStart, + // Types for the registerShipper API + ShipperClassConstructor, + RegisterShipperOpts, + // Types for the optIn API + OptInConfig, + OptInConfigPerType, + ShipperName, + // Types for the registerContextProvider API + ContextProviderOpts, + ContextProviderName, + // Types for the registerEventType API + EventTypeOpts, + // Events Event, EventContext, EventType, - EventTypeOpts, - IShipper, - ContextProviderOpts, - OptInConfig, - ShipperClassConstructor, TelemetryCounter, TelemetryCounterType, -} from '@kbn/analytics-client'; -export type { - AnalyticsServiceSetup, - AnalyticsServicePreboot, - AnalyticsServiceStart, + // Schema + RootSchema, + SchemaObject, + SchemaArray, + SchemaChildValue, + SchemaMeta, + SchemaValue, + SchemaMetaOptional, + PossibleSchemaTypes, + AllowedSchemaBooleanTypes, + AllowedSchemaNumberTypes, + AllowedSchemaStringTypes, + AllowedSchemaTypes, + // Shippers + IShipper, } from '@kbn/core-analytics-server'; export type { RequestHandlerContext, 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 c89ca689dbfc8..2337bbfaaa89e 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 @@ -55,8 +55,8 @@ describe('checking migration metadata changes on all registered SO types', () => expect(hashMap).toMatchInlineSnapshot(` Object { - "action": "cc93fe2c0c76e57c2568c63170e05daea897c136", - "action_task_params": "96e27e7f4e8273ffcd87060221e2b75e81912dd5", + "action": "0e6fc0b74c7312a8c11ff6b14437b93a997358b8", + "action_task_params": "b50cb5c8a493881474918e8d4985e61374ca4c30", "ad_hoc_run_params": "d4e3c5c794151d0a4f5c71e886b2aa638da73ad2", "alert": "3a67d3f1db80af36bd57aaea47ecfef87e43c58f", "api_key_pending_invalidation": "1399e87ca37b3d3a65d269c924eda70726cfe886", diff --git a/src/core/server/integration_tests/elasticsearch/max_response_size_logging.test.ts b/src/core/server/integration_tests/elasticsearch/max_response_size_logging.test.ts new file mode 100644 index 0000000000000..d9d6cda70c74c --- /dev/null +++ b/src/core/server/integration_tests/elasticsearch/max_response_size_logging.test.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + createTestServers, + type TestElasticsearchUtils, + type TestKibanaUtils, +} from '@kbn/core-test-helpers-kbn-server'; +import { unsafeConsole } from '@kbn/security-hardening'; + +describe('Elasticsearch max response size', () => { + let mockConsoleLog: jest.SpyInstance; + let esServer: TestElasticsearchUtils; + let kibanaServer: TestKibanaUtils; + + beforeAll(async () => { + mockConsoleLog = jest.spyOn(unsafeConsole, 'log'); + + const { startES, startKibana } = createTestServers({ + adjustTimeout: jest.setTimeout, + settings: { + kbn: { + logging: { + appenders: { + 'test-appender': { + type: 'console', + layout: { + type: 'pattern', + }, + }, + }, + loggers: [ + { name: 'elasticsearch.warnings', appenders: ['test-appender'], level: 'info' }, + ], + }, + }, + }, + }); + + esServer = await startES(); + kibanaServer = await startKibana(); + }); + + beforeEach(() => { + mockConsoleLog.mockClear(); + }); + + afterAll(async () => { + mockConsoleLog.mockRestore(); + await kibanaServer.stop(); + await esServer.stop(); + }); + + it('rejects the response when the response size is larger than the requested limit', async () => { + const esClient = kibanaServer.coreStart.elasticsearch.client.asInternalUser; + + try { + await esClient.cluster.stats({}, { maxResponseSize: 200 }); + expect('should have thrown').toEqual('but it did not'); + } catch (e) { + expect(e.name).toEqual('RequestAbortedError'); + expect(e.message).toContain('is bigger than the maximum allowed string (200)'); + } + }); + + it('logs a warning with the expected message', async () => { + const esClient = kibanaServer.coreStart.elasticsearch.client.asInternalUser; + + try { + await esClient.cluster.stats({}, { maxResponseSize: 200 }); + expect('should have thrown').toEqual('but it did not'); + } catch (e) { + const calls = mockConsoleLog.mock.calls; + + const warningCall = calls + .map((call) => call[0]) + .find((call) => call.includes('elasticsearch.warnings')); + expect(warningCall).toContain( + 'Request against GET /_cluster/stats was aborted: The content length' + ); + expect(warningCall).toContain('is bigger than the maximum allowed string (200)'); + } + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts index 2b6c7136519ff..64342b3b63d1f 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions.test.ts @@ -39,11 +39,12 @@ import { removeWriteBlock, transformDocs, waitForIndexStatus, - initAction, + fetchIndices, cloneIndex, type DocumentsTransformFailed, type DocumentsTransformSuccess, createBulkIndexOperationTuple, + checkClusterRoutingAllocationEnabled, } from '@kbn/core-saved-objects-migration-server-internal'; const { startES } = createTestServers({ @@ -132,7 +133,7 @@ describe('migration actions', () => { await esServer.stop(); }); - describe('initAction', () => { + describe('fetchIndices', () => { afterAll(async () => { await client.cluster.putSettings({ body: { @@ -145,7 +146,7 @@ describe('migration actions', () => { }); it('resolves right empty record if no indices were found', async () => { expect.assertions(1); - const task = initAction({ client, indices: ['no_such_index'] }); + const task = fetchIndices({ client, indices: ['no_such_index'] }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", @@ -155,7 +156,7 @@ describe('migration actions', () => { }); it('resolves right record with found indices', async () => { expect.assertions(1); - const res = (await initAction({ + const res = (await fetchIndices({ client, indices: ['no_such_index', 'existing_index_with_docs'], })()) as Either.Right; @@ -174,7 +175,7 @@ describe('migration actions', () => { }); it('includes the _meta data of the indices in the response', async () => { expect.assertions(1); - const res = (await initAction({ + const res = (await fetchIndices({ client, indices: ['existing_index_with_docs'], })()) as Either.Right; @@ -200,6 +201,9 @@ describe('migration actions', () => { }) ); }); + }); + + describe('checkClusterRoutingAllocation', () => { it('resolves left when cluster.routing.allocation.enabled is incompatible', async () => { expect.assertions(3); await client.cluster.putSettings({ @@ -210,10 +214,7 @@ describe('migration actions', () => { }, }, }); - const task = initAction({ - client, - indices: ['existing_index_with_docs'], - }); + const task = checkClusterRoutingAllocationEnabled(client); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", @@ -230,10 +231,7 @@ describe('migration actions', () => { }, }, }); - const task2 = initAction({ - client, - indices: ['existing_index_with_docs'], - }); + const task2 = checkClusterRoutingAllocationEnabled(client); await expect(task2()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", @@ -250,10 +248,7 @@ describe('migration actions', () => { }, }, }); - const task3 = initAction({ - client, - indices: ['existing_index_with_docs'], - }); + const task3 = checkClusterRoutingAllocationEnabled(client); await expect(task3()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", @@ -272,10 +267,7 @@ describe('migration actions', () => { }, }, }); - const task = initAction({ - client, - indices: ['existing_index_with_docs'], - }); + const task = checkClusterRoutingAllocationEnabled(client); const result = await task(); expect(Either.isRight(result)).toBe(true); }); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts index c114293823cb7..92b470288d327 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts @@ -39,11 +39,12 @@ import { removeWriteBlock, transformDocs, waitForIndexStatus, - initAction, + fetchIndices, cloneIndex, type DocumentsTransformFailed, type DocumentsTransformSuccess, createBulkIndexOperationTuple, + checkClusterRoutingAllocationEnabled, } from '@kbn/core-saved-objects-migration-server-internal'; interface EsServer { @@ -169,7 +170,7 @@ export const runActionTestSuite = ({ await esServer.stop(); }); - describe('initAction', () => { + describe('fetchIndices', () => { afterAll(async () => { await client.cluster.putSettings({ body: { @@ -182,7 +183,7 @@ export const runActionTestSuite = ({ }); it('resolves right empty record if no indices were found', async () => { expect.assertions(1); - const task = initAction({ client, indices: ['no_such_index'] }); + const task = fetchIndices({ client, indices: ['no_such_index'] }); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Right", @@ -192,7 +193,7 @@ export const runActionTestSuite = ({ }); it('resolves right record with found indices', async () => { expect.assertions(1); - const res = (await initAction({ + const res = (await fetchIndices({ client, indices: ['no_such_index', 'existing_index_with_docs'], })()) as Either.Right; @@ -211,7 +212,7 @@ export const runActionTestSuite = ({ }); it('includes the _meta data of the indices in the response', async () => { expect.assertions(1); - const res = (await initAction({ + const res = (await fetchIndices({ client, indices: ['existing_index_with_docs'], })()) as Either.Right; @@ -237,6 +238,9 @@ export const runActionTestSuite = ({ }) ); }); + }); + + describe('checkClusterRoutingAllocation', () => { it('resolves left when cluster.routing.allocation.enabled is incompatible', async () => { expect.assertions(3); await client.cluster.putSettings({ @@ -247,10 +251,7 @@ export const runActionTestSuite = ({ }, }, }); - const task = initAction({ - client, - indices: ['existing_index_with_docs'], - }); + const task = checkClusterRoutingAllocationEnabled(client); await expect(task()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", @@ -267,10 +268,7 @@ export const runActionTestSuite = ({ }, }, }); - const task2 = initAction({ - client, - indices: ['existing_index_with_docs'], - }); + const task2 = checkClusterRoutingAllocationEnabled(client); await expect(task2()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", @@ -287,10 +285,7 @@ export const runActionTestSuite = ({ }, }, }); - const task3 = initAction({ - client, - indices: ['existing_index_with_docs'], - }); + const task3 = checkClusterRoutingAllocationEnabled(client); await expect(task3()).resolves.toMatchInlineSnapshot(` Object { "_tag": "Left", @@ -309,10 +304,7 @@ export const runActionTestSuite = ({ }, }, }); - const task = initAction({ - client, - indices: ['existing_index_with_docs'], - }); + const task = checkClusterRoutingAllocationEnabled(client); const result = await task(); expect(Either.isRight(result)).toBe(true); }); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group5/active_delete.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group5/active_delete.test.ts index 37bd9f49a6cd1..d950129dac69a 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group5/active_delete.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group5/active_delete.test.ts @@ -366,7 +366,10 @@ describe('when upgrading to a new stack version', () => { const logs = await readLog(); expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE'); expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> UPDATE_SOURCE_MAPPINGS_PROPERTIES.'); - expect(logs).toMatch('UPDATE_SOURCE_MAPPINGS_PROPERTIES -> CHECK_UNKNOWN_DOCUMENTS.'); + expect(logs).toMatch( + 'UPDATE_SOURCE_MAPPINGS_PROPERTIES -> CHECK_CLUSTER_ROUTING_ALLOCATION.' + ); + expect(logs).toMatch('CHECK_CLUSTER_ROUTING_ALLOCATION -> CHECK_UNKNOWN_DOCUMENTS.'); expect(logs).toMatch('CHECK_UNKNOWN_DOCUMENTS -> SET_SOURCE_WRITE_BLOCK.'); expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS_PROPERTIES.'); expect(logs).toMatch('UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS.'); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group5/dot_kibana_split.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group5/dot_kibana_split.test.ts index c0a4c73b1664c..8f765010e37a8 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group5/dot_kibana_split.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group5/dot_kibana_split.test.ts @@ -306,7 +306,8 @@ describe('split .kibana index into multiple system indices', () => { expect(logs).toContainLogEntries( [ // .kibana_task_manager index exists and has no aliases => LEGACY_* migration path - '[.kibana_task_manager] INIT -> LEGACY_SET_WRITE_BLOCK.', + '[.kibana_task_manager] INIT -> LEGACY_CHECK_CLUSTER_ROUTING_ALLOCATION.', + '[.kibana_task_manager] LEGACY_CHECK_CLUSTER_ROUTING_ALLOCATION -> LEGACY_SET_WRITE_BLOCK.', '[.kibana_task_manager] LEGACY_REINDEX_WAIT_FOR_TASK -> LEGACY_DELETE.', '[.kibana_task_manager] LEGACY_DELETE -> SET_SOURCE_WRITE_BLOCK.', '[.kibana_task_manager] SET_SOURCE_WRITE_BLOCK -> CALCULATE_EXCLUDE_FILTERS.', @@ -360,7 +361,8 @@ describe('split .kibana index into multiple system indices', () => { expect(logs).toContainLogEntries( [ '[.kibana] INIT -> WAIT_FOR_YELLOW_SOURCE.', - '[.kibana] WAIT_FOR_YELLOW_SOURCE -> CHECK_UNKNOWN_DOCUMENTS.', + '[.kibana] WAIT_FOR_YELLOW_SOURCE -> CHECK_CLUSTER_ROUTING_ALLOCATION.', + '[.kibana] CHECK_CLUSTER_ROUTING_ALLOCATION -> CHECK_UNKNOWN_DOCUMENTS.', '[.kibana] CHECK_UNKNOWN_DOCUMENTS -> SET_SOURCE_WRITE_BLOCK.', '[.kibana] SET_SOURCE_WRITE_BLOCK -> CALCULATE_EXCLUDE_FILTERS.', '[.kibana] CALCULATE_EXCLUDE_FILTERS -> CREATE_REINDEX_TEMP.', diff --git a/src/core/server/integration_tests/saved_objects/migrations/group5/skip_reindex.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group5/skip_reindex.test.ts index 3d383a603a53f..4d244314d3acb 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group5/skip_reindex.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group5/skip_reindex.test.ts @@ -104,7 +104,10 @@ describe('when migrating to a new version', () => { const logs = await readLog(); expect(logs).toMatch('INIT -> WAIT_FOR_YELLOW_SOURCE.'); expect(logs).toMatch('WAIT_FOR_YELLOW_SOURCE -> UPDATE_SOURCE_MAPPINGS_PROPERTIES.'); - expect(logs).toMatch('UPDATE_SOURCE_MAPPINGS_PROPERTIES -> CHECK_UNKNOWN_DOCUMENTS.'); + expect(logs).toMatch( + 'UPDATE_SOURCE_MAPPINGS_PROPERTIES -> CHECK_CLUSTER_ROUTING_ALLOCATION.' + ); + expect(logs).toMatch('CHECK_CLUSTER_ROUTING_ALLOCATION -> CHECK_UNKNOWN_DOCUMENTS.'); expect(logs).toMatch('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS_PROPERTIES.'); expect(logs).toMatch('UPDATE_TARGET_MAPPINGS_META -> CHECK_VERSION_INDEX_READY_ACTIONS.'); expect(logs).toMatch('CHECK_VERSION_INDEX_READY_ACTIONS -> MARK_VERSION_INDEX_READY.'); diff --git a/src/core/tsconfig.json b/src/core/tsconfig.json index 05ae89cb1be93..017ffa69c3107 100644 --- a/src/core/tsconfig.json +++ b/src/core/tsconfig.json @@ -124,7 +124,6 @@ "@kbn/core-deprecations-common", "@kbn/core-status-server", "@kbn/core-doc-links-server", - "@kbn/analytics-client", "@kbn/core-analytics-server", "@kbn/core-lifecycle-server", "@kbn/core-doc-links-browser", diff --git a/src/dev/build/tasks/bin/scripts/kibana b/src/dev/build/tasks/bin/scripts/kibana index e0957582897f0..b127984e444a3 100755 --- a/src/dev/build/tasks/bin/scripts/kibana +++ b/src/dev/build/tasks/bin/scripts/kibana @@ -34,7 +34,7 @@ NODE="${DIR}/node/default/bin/node" {{#linux}} NODE="${DIR}/node/glibc-217/bin/node" {{#serverless}} -if [ "$KBN_DISABLE_POINTER_COMPRESSION" != 'true' ]; then +if [ "$KBN_ENABLE_POINTER_COMPRESSION" = 'true' ]; then NODE="${DIR}/node/pointer-compression/bin/node" fi {{/serverless}} diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index b7642cf7e9af7..45262fd3a57cc 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -86,7 +86,7 @@ export const LICENSE_OVERRIDES = { 'jsts@1.6.2': ['Eclipse Distribution License - v 1.0'], // cf. https://github.com/bjornharrtell/jsts '@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint '@elastic/ems-client@8.5.1': ['Elastic License 2.0'], - '@elastic/eui@94.6.0': ['SSPL-1.0 OR Elastic License 2.0'], + '@elastic/eui@95.0.0-backport.0': ['SSPL-1.0 OR Elastic License 2.0'], 'language-subtag-registry@0.3.21': ['CC-BY-4.0'], // retired ODC‑By license https://github.com/mattcg/language-subtag-registry 'buffers@0.1.1': ['MIT'], // license in importing module https://www.npmjs.com/package/binary '@bufbuild/protobuf@1.2.1': ['Apache-2.0'], // license (Apache-2.0 AND BSD-3-Clause) diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/waffle_vis_function.test.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/waffle_vis_function.test.ts index e5a14a37e0ffb..f3f4a3c9a144f 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/waffle_vis_function.test.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/waffle_vis_function.test.ts @@ -13,14 +13,12 @@ import { ValueFormats, LegendDisplay, } from '../types/expression_renderers'; -import { - ExpressionValueVisDimension, - PartitionLegendValue, -} from '@kbn/visualizations-plugin/common'; +import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common'; import { Datatable } from '@kbn/expressions-plugin/common/expression_types/specs'; import { waffleVisFunction } from './waffle_vis_function'; import { PARTITION_LABELS_VALUE, PARTITION_VIS_RENDERER_NAME } from '../constants'; import { ExecutionContext } from '@kbn/expressions-plugin/common'; +import { LegendValue } from '@elastic/charts'; describe('interpreter/functions#waffleVis', () => { const fn = functionWrapper(waffleVisFunction()); @@ -37,7 +35,7 @@ describe('interpreter/functions#waffleVis', () => { const visConfig: WaffleVisConfig = { addTooltip: true, - legendStats: [PartitionLegendValue.Value], + legendStats: [LegendValue.Value], metricsToLabels: JSON.stringify({}), legendDisplay: LegendDisplay.SHOW, legendPosition: 'right', diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/shared/arg_types.ts b/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/shared/arg_types.ts index 314eb3369b61d..a35e9b09f5662 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/shared/arg_types.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/shared/arg_types.ts @@ -6,9 +6,8 @@ * Side Public License, v 1. */ -import { Position } from '@elastic/charts'; +import { LegendValue, Position } from '@elastic/charts'; import { ArgTypes } from '@storybook/addons'; -import { PartitionLegendValue } from '@kbn/visualizations-plugin/common/constants'; import { EmptySizeRatios, LegendDisplay } from '../../../common'; import { ChartTypes } from '../../../common/types'; @@ -212,7 +211,7 @@ export const waffleArgTypes: ArgTypes = { description: 'Legend stats', type: { name: 'string', required: false }, table: { type: { summary: 'string' }, defaultValue: { summary: undefined } }, - options: [PartitionLegendValue.Value], + options: [LegendValue.Value], control: { type: 'select' }, }, }; diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/mocks.ts b/src/plugins/chart_expressions/expression_partition_vis/public/mocks.ts index 4f48c03f7f915..faea42ddc0b13 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/mocks.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/mocks.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ +import { LegendValue } from '@elastic/charts'; import { Datatable } from '@kbn/expressions-plugin/public'; -import { PartitionLegendValue } from '@kbn/visualizations-plugin/common/constants'; import { BucketColumns, PartitionVisParams, @@ -382,6 +382,6 @@ export const createMockWaffleParams = (): PartitionVisParams => { }, ], }, - legendStats: [PartitionLegendValue.Value], + legendStats: [LegendValue.Value], }; }; diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/legend_config.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/legend_config.ts index e83db93c914af..c9e524dcae818 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/legend_config.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/legend_config.ts @@ -8,7 +8,7 @@ import { HorizontalAlignment, Position, VerticalAlignment } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; -import { LegendSize } from '@kbn/visualizations-plugin/common/constants'; +import { LegendLayout, LegendSize } from '@kbn/visualizations-plugin/common/constants'; import { LEGEND_CONFIG } from '../constants'; import { LegendConfigFn } from '../types'; @@ -106,6 +106,26 @@ export const legendConfigFunction: LegendConfigFn = { defaultMessage: 'Specifies the legend stats.', }), }, + title: { + types: ['string'], + help: i18n.translate('expressionXY.legendConfig.title.help', { + defaultMessage: 'Specifies the legend title.', + }), + }, + isTitleVisible: { + types: ['boolean'], + help: i18n.translate('expressionXY.legendConfig.isTitleVisible.help', { + defaultMessage: 'Specifies if the legend title is visible.', + }), + }, + layout: { + types: ['string'], + help: i18n.translate('expressionXY.legendConfig.legendLayout.help', { + defaultMessage: 'Specifies the legend layout.', + }), + options: [LegendLayout.Table, LegendLayout.List], + strict: true, + }, }, async fn(input, args, handlers) { const { legendConfigFn } = await import('./legend_config_fn'); diff --git a/src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts index a93a8510d4506..dd4c0acbd22fc 100644 --- a/src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts +++ b/src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts @@ -16,8 +16,9 @@ import type { } from '@kbn/expressions-plugin/common'; import { LegendSize, - ExpressionValueVisDimension, XYLegendValue, + LegendLayout, + ExpressionValueVisDimension, } from '@kbn/visualizations-plugin/common'; import { EventAnnotationOutput } from '@kbn/event-annotation-plugin/common'; @@ -220,7 +221,11 @@ export interface LegendConfig { /** * metrics to display in the legend */ + legendStats?: XYLegendValue[]; + layout?: LegendLayout; + title?: string; + isTitleVisible?: boolean; } // Arguments to XY chart expression, with computed properties diff --git a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.test.tsx b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.test.tsx index da0997b7988dc..ed0eaa2ce505c 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.test.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.test.tsx @@ -30,6 +30,7 @@ import { VerticalAlignment, XYChartSeriesIdentifier, Tooltip, + LegendValue, } from '@elastic/charts'; import { Datatable } from '@kbn/expressions-plugin/common'; import { EmptyPlaceholder } from '@kbn/charts-plugin/public'; @@ -58,7 +59,7 @@ import { XYChart, XYChartRenderProps } from './xy_chart'; import { ExtendedDataLayerConfig, XYProps, AnnotationLayerConfigResult } from '../../common/types'; import { DataLayers } from './data_layers'; import { SplitChart } from './split_chart'; -import { LegendSize, XYLegendValue } from '@kbn/visualizations-plugin/common'; +import { LegendSize } from '@kbn/visualizations-plugin/common'; import type { LayerCellValueActions } from '../types'; const onClickValue = jest.fn(); @@ -744,7 +745,7 @@ describe('XYChart component', () => { {...defaultProps} args={{ ...args, - legend: { ...args.legend, legendStats: [XYLegendValue.CurrentAndLastValue] }, + legend: { ...args.legend, legendStats: [LegendValue.CurrentAndLastValue] }, }} /> ); @@ -760,14 +761,14 @@ describe('XYChart component', () => { ...args, legend: { ...args.legend, - legendStats: [XYLegendValue.CurrentAndLastValue], + legendStats: [LegendValue.CurrentAndLastValue], }, layers: [dateHistogramLayer], }} /> ); expect(component.find(Settings).at(0).prop('legendValues')).toEqual([ - XYLegendValue.CurrentAndLastValue, + LegendValue.CurrentAndLastValue, ]); }); diff --git a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx index 58a02a1bf3215..6479abbdd42d0 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx @@ -828,6 +828,8 @@ export function XYChart({ showLegend={showLegend} legendPosition={legend?.isInside ? legendInsideParams : legend.position} legendSize={LegendSizeToPixels[legend.legendSize ?? DEFAULT_LEGEND_SIZE]} + legendValues={isHistogramViz ? legend.legendStats : []} + legendTitle={getLegendTitle(legend.title, dataLayers[0], legend.isTitleVisible)} theme={[ { barSeriesStyle: { @@ -877,7 +879,6 @@ export function XYChart({ ) : undefined } - legendValues={isHistogramViz ? legend.legendStats : []} ariaLabel={args.ariaLabel} ariaUseDefaultSummary={!args.ariaLabel} orderOrdinalBinsBy={ @@ -1038,3 +1039,23 @@ export function XYChart({
); } + +const defaultLegendTitle = i18n.translate('expressionXY.xyChart.legendTitle', { + defaultMessage: 'Legend', +}); + +function getLegendTitle( + title: string | undefined, + layer?: CommonXYDataLayerConfig, + isTitleVisible?: boolean +) { + if (!isTitleVisible) { + return undefined; + } + if (typeof title === 'string' && title.length > 0) { + return title; + } + return layer?.splitAccessors?.[0] + ? getColumnByAccessor(layer.splitAccessors?.[0], layer?.table.columns)?.name + : defaultLegendTitle; +} diff --git a/src/plugins/console/public/application/containers/editor/monaco/monaco_editor.tsx b/src/plugins/console/public/application/containers/editor/monaco/monaco_editor.tsx index bc9d6cf2ee3c9..02f978f7919a5 100644 --- a/src/plugins/console/public/application/containers/editor/monaco/monaco_editor.tsx +++ b/src/plugins/console/public/application/containers/editor/monaco/monaco_editor.tsx @@ -115,6 +115,7 @@ export const MonacoEditor = ({ initialTextValue }: EditorProps) => { width: 100%; `} ref={divRef} + data-test-subj="consoleMonacoEditorContainer" > { { { - const url = removeTrailingWhitespaces(parsedRequest.url); - const method = parsedRequest.method.toUpperCase(); + const url = parsedRequest.url ? removeTrailingWhitespaces(parsedRequest.url) : ''; + const method = parsedRequest.method?.toUpperCase() ?? ''; const data = parsedRequest.data?.map((parsedData) => JSON.stringify(parsedData, null, 2)); return { url, method, data: data ?? [] }; }; diff --git a/src/plugins/console/public/application/containers/editor/monaco/utils/tokens_utils.ts b/src/plugins/console/public/application/containers/editor/monaco/utils/tokens_utils.ts index f52a0bb6a9079..decbe0d4fdd9a 100644 --- a/src/plugins/console/public/application/containers/editor/monaco/utils/tokens_utils.ts +++ b/src/plugins/console/public/application/containers/editor/monaco/utils/tokens_utils.ts @@ -25,7 +25,7 @@ export const parseLine = (line: string): ParsedLineTokens => { // try to parse into method and url (split on whitespace) const parts = line.split(whitespacesRegex); // 1st part is the method - const method = parts[0]; + const method = parts[0].toUpperCase(); // 2nd part is the url const url = parts[1]; // try to parse into url path and url params (split on question mark) diff --git a/src/plugins/console/server/config.ts b/src/plugins/console/server/config.ts index e5a54042f5003..953cc1f84ea60 100644 --- a/src/plugins/console/server/config.ts +++ b/src/plugins/console/server/config.ts @@ -31,7 +31,7 @@ const schemaLatest = schema.object( defaultValue: 'stack', }), }), - dev: schema.object({ enableMonaco: schema.boolean({ defaultValue: false }) }), + dev: schema.object({ enableMonaco: schema.boolean({ defaultValue: true }) }), }, { defaultValue: undefined } ); diff --git a/src/plugins/console/server/lib/spec_definitions/js/query/dsl.ts b/src/plugins/console/server/lib/spec_definitions/js/query/dsl.ts index 273c5eab9c889..219924b8771c4 100644 --- a/src/plugins/console/server/lib/spec_definitions/js/query/dsl.ts +++ b/src/plugins/console/server/lib/spec_definitions/js/query/dsl.ts @@ -526,6 +526,14 @@ export const query = (specService: SpecDefinitionsService) => { }, }, }, + semantic: { + __template: { + field: '', + query: '', + }, + field: '{field}', + query: '', + }, span_first: { __template: spanFirstTemplate, match: SPAN_QUERIES, diff --git a/src/plugins/controls/public/control_group/component/control_group_component.tsx b/src/plugins/controls/public/control_group/component/control_group_component.tsx index 56a50e596870c..019ef9256e91b 100644 --- a/src/plugins/controls/public/control_group/component/control_group_component.tsx +++ b/src/plugins/controls/public/control_group/component/control_group_component.tsx @@ -168,7 +168,6 @@ export const ControlGroup = () => { content={ControlGroupStrings.invalidControlWarning.getTourContent(invalidControlType)} footerAction={[ + ) : ( + ); return ( @@ -108,7 +110,7 @@ const SortableControlInner = forwardRef< enableActions={draggingIndex === -1} embeddableId={embeddableId} embeddableType={embeddableType} - customPrepend={isEditable ? dragHandle : undefined} + customPrepend={dragHandle} /> ); diff --git a/src/plugins/controls/public/control_group/control_group.scss b/src/plugins/controls/public/control_group/control_group.scss index bfc1b79bac2bf..985e8e1603d0d 100644 --- a/src/plugins/controls/public/control_group/control_group.scss +++ b/src/plugins/controls/public/control_group/control_group.scss @@ -88,20 +88,15 @@ $controlMinWidth: $euiSize * 14; max-width: 40%; } - &-isEditable { - .controlFrame__formControlLayoutLabel { - padding-left: 0; - } - } - .controlFrame__formControlLayout { width: 100%; min-width: $controlMinWidth; transition: background-color .1s, color .1s; - &Label { + .controlFrame__formControlLayoutLabel { @include euiTextTruncate; border-radius: $euiBorderRadius; + padding-left: 0; } &:not(.controlFrame__formControlLayout-clone) { diff --git a/src/plugins/dashboard/public/dashboard_actions/unlink_from_library_action.tsx b/src/plugins/dashboard/public/dashboard_actions/unlink_from_library_action.tsx index 17612fa9c6ef7..77aa1e5036c7b 100644 --- a/src/plugins/dashboard/public/dashboard_actions/unlink_from_library_action.tsx +++ b/src/plugins/dashboard/public/dashboard_actions/unlink_from_library_action.tsx @@ -77,7 +77,7 @@ export class UnlinkFromLibraryAction implements Action { return api.canUnlinkFromLibrary(); } else if (apiHasInPlaceLibraryTransforms(api)) { const canUnLink = api.canUnlinkFromLibrary ? await api.canUnlinkFromLibrary() : true; - return canUnLink && api.libraryId$.value !== undefined; + return canUnLink && Boolean(api.libraryId$.value); } throw new IncompatibleActionError(); } diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/api/duplicate_dashboard_panel.test.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/api/duplicate_dashboard_panel.test.ts deleted file mode 100644 index 256d03681447d..0000000000000 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/api/duplicate_dashboard_panel.test.ts +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { CoreStart } from '@kbn/core/public'; -import { coreMock } from '@kbn/core/public/mocks'; -import { isErrorEmbeddable, ReferenceOrValueEmbeddable } from '@kbn/embeddable-plugin/public'; -import { - ContactCardEmbeddable, - ContactCardEmbeddableFactory, - ContactCardEmbeddableInput, - ContactCardEmbeddableOutput, - CONTACT_CARD_EMBEDDABLE, -} from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables'; -import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; -import { duplicateDashboardPanel, incrementPanelTitle } from './duplicate_dashboard_panel'; -import { buildMockDashboard, getSampleDashboardPanel } from '../../../mocks'; -import { pluginServices } from '../../../services/plugin_services'; -import { DashboardContainer } from '../dashboard_container'; - -let container: DashboardContainer; -let genericEmbeddable: ContactCardEmbeddable; -let byRefOrValEmbeddable: ContactCardEmbeddable & ReferenceOrValueEmbeddable; -let coreStart: CoreStart; -beforeEach(async () => { - coreStart = coreMock.createStart(); - coreStart.savedObjects.client = { - ...coreStart.savedObjects.client, - get: jest.fn().mockImplementation(() => ({ attributes: { title: 'Holy moly' } })), - find: jest.fn().mockImplementation(() => ({ total: 15 })), - create: jest.fn().mockImplementation(() => ({ id: 'brandNewSavedObject' })), - }; - - const mockEmbeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); - - pluginServices.getServices().embeddable.getEmbeddableFactory = jest - .fn() - .mockReturnValue(mockEmbeddableFactory); - container = buildMockDashboard({ - overrides: { - panels: { - '123': getSampleDashboardPanel({ - explicitInput: { firstName: 'Kibanana', id: '123' }, - type: CONTACT_CARD_EMBEDDABLE, - }), - }, - }, - }); - - const refOrValContactCardEmbeddable = await container.addNewEmbeddable< - ContactCardEmbeddableInput, - ContactCardEmbeddableOutput, - ContactCardEmbeddable - >(CONTACT_CARD_EMBEDDABLE, { - firstName: 'RefOrValEmbeddable', - }); - - const nonRefOrValueContactCard = await container.addNewEmbeddable< - ContactCardEmbeddableInput, - ContactCardEmbeddableOutput, - ContactCardEmbeddable - >(CONTACT_CARD_EMBEDDABLE, { - firstName: 'Not a refOrValEmbeddable', - }); - - if ( - isErrorEmbeddable(refOrValContactCardEmbeddable) || - isErrorEmbeddable(nonRefOrValueContactCard) - ) { - throw new Error('Failed to create embeddables'); - } else { - genericEmbeddable = nonRefOrValueContactCard; - byRefOrValEmbeddable = embeddablePluginMock.mockRefOrValEmbeddable< - ContactCardEmbeddable, - ContactCardEmbeddableInput - >(refOrValContactCardEmbeddable, { - mockedByReferenceInput: { - savedObjectId: 'testSavedObjectId', - id: refOrValContactCardEmbeddable.id, - }, - mockedByValueInput: { firstName: 'RefOrValEmbeddable', id: refOrValContactCardEmbeddable.id }, - }); - jest.spyOn(byRefOrValEmbeddable, 'getInputAsValueType'); - } -}); - -test('Duplication adds a new embeddable', async () => { - const originalPanelCount = Object.keys(container.getInput().panels).length; - const originalPanelKeySet = new Set(Object.keys(container.getInput().panels)); - await duplicateDashboardPanel.bind(container)(byRefOrValEmbeddable.id); - - expect(Object.keys(container.getInput().panels).length).toEqual(originalPanelCount + 1); - const newPanelId = Object.keys(container.getInput().panels).find( - (key) => !originalPanelKeySet.has(key) - ); - expect(newPanelId).toBeDefined(); - const newPanel = container.getInput().panels[newPanelId!]; - expect(newPanel.type).toEqual(byRefOrValEmbeddable.type); -}); - -test('Duplicates a RefOrVal embeddable by value', async () => { - const originalPanelKeySet = new Set(Object.keys(container.getInput().panels)); - await duplicateDashboardPanel.bind(container)(byRefOrValEmbeddable.id); - const newPanelId = Object.keys(container.getInput().panels).find( - (key) => !originalPanelKeySet.has(key) - ); - - const originalFirstName = ( - container.getInput().panels[byRefOrValEmbeddable.id].explicitInput as ContactCardEmbeddableInput - ).firstName; - - const newFirstName = ( - container.getInput().panels[newPanelId!].explicitInput as ContactCardEmbeddableInput - ).firstName; - - expect(byRefOrValEmbeddable.getInputAsValueType).toHaveBeenCalled(); - - expect(originalFirstName).toEqual(newFirstName); - expect(container.getInput().panels[newPanelId!].type).toEqual(byRefOrValEmbeddable.type); -}); - -test('Duplicates a non RefOrVal embeddable by value', async () => { - const originalPanelKeySet = new Set(Object.keys(container.getInput().panels)); - await duplicateDashboardPanel.bind(container)(genericEmbeddable.id); - const newPanelId = Object.keys(container.getInput().panels).find( - (key) => !originalPanelKeySet.has(key) - ); - - const originalFirstName = ( - container.getInput().panels[genericEmbeddable.id].explicitInput as ContactCardEmbeddableInput - ).firstName; - - const newFirstName = ( - container.getInput().panels[newPanelId!].explicitInput as ContactCardEmbeddableInput - ).firstName; - - expect(originalFirstName).toEqual(newFirstName); - expect(container.getInput().panels[newPanelId!].type).toEqual(genericEmbeddable.type); -}); - -test('Gets a unique title from the dashboard', async () => { - expect(await incrementPanelTitle(container, '')).toEqual(''); - - container.getPanelTitles = jest.fn().mockImplementation(() => { - return ['testDuplicateTitle', 'testDuplicateTitle (copy)', 'testUniqueTitle']; - }); - expect(await incrementPanelTitle(container, 'testUniqueTitle')).toEqual('testUniqueTitle (copy)'); - expect(await incrementPanelTitle(container, 'testDuplicateTitle')).toEqual( - 'testDuplicateTitle (copy 1)' - ); - - container.getPanelTitles = jest.fn().mockImplementation(() => { - return ['testDuplicateTitle', 'testDuplicateTitle (copy)'].concat( - Array.from([...Array(39)], (_, index) => `testDuplicateTitle (copy ${index + 1})`) - ); - }); - expect(await incrementPanelTitle(container, 'testDuplicateTitle')).toEqual( - 'testDuplicateTitle (copy 40)' - ); - expect(await incrementPanelTitle(container, 'testDuplicateTitle (copy 100)')).toEqual( - 'testDuplicateTitle (copy 40)' - ); - - container.getPanelTitles = jest.fn().mockImplementation(() => { - return ['testDuplicateTitle (copy 100)']; - }); - expect(await incrementPanelTitle(container, 'testDuplicateTitle')).toEqual( - 'testDuplicateTitle (copy 101)' - ); - expect(await incrementPanelTitle(container, 'testDuplicateTitle (copy 100)')).toEqual( - 'testDuplicateTitle (copy 101)' - ); -}); diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/api/duplicate_dashboard_panel.test.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/api/duplicate_dashboard_panel.test.tsx new file mode 100644 index 0000000000000..39bb0f754cfbe --- /dev/null +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/api/duplicate_dashboard_panel.test.tsx @@ -0,0 +1,328 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CoreStart } from '@kbn/core/public'; +import { coreMock } from '@kbn/core/public/mocks'; +import { + isErrorEmbeddable, + ReactEmbeddableFactory, + ReferenceOrValueEmbeddable, +} from '@kbn/embeddable-plugin/public'; +import { + ContactCardEmbeddable, + ContactCardEmbeddableFactory, + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, + CONTACT_CARD_EMBEDDABLE, +} from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables'; +import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks'; +import { + DefaultEmbeddableApi, + ReactEmbeddableRenderer, + registerReactEmbeddableFactory, +} from '@kbn/embeddable-plugin/public/react_embeddable_system'; +import { BuildReactEmbeddableApiRegistration } from '@kbn/embeddable-plugin/public/react_embeddable_system/types'; +import { HasSnapshottableState, SerializedPanelState } from '@kbn/presentation-containers'; +import { HasInPlaceLibraryTransforms, HasLibraryTransforms } from '@kbn/presentation-publishing'; +import { render } from '@testing-library/react'; +import React from 'react'; +import { BehaviorSubject, lastValueFrom, Subject } from 'rxjs'; +import { buildMockDashboard, getSampleDashboardPanel } from '../../../mocks'; +import { pluginServices } from '../../../services/plugin_services'; +import { DashboardContainer } from '../dashboard_container'; +import { duplicateDashboardPanel, incrementPanelTitle } from './duplicate_dashboard_panel'; + +describe('Legacy embeddables', () => { + let container: DashboardContainer; + let genericEmbeddable: ContactCardEmbeddable; + let byRefOrValEmbeddable: ContactCardEmbeddable & ReferenceOrValueEmbeddable; + let coreStart: CoreStart; + beforeEach(async () => { + coreStart = coreMock.createStart(); + coreStart.savedObjects.client = { + ...coreStart.savedObjects.client, + get: jest.fn().mockImplementation(() => ({ attributes: { title: 'Holy moly' } })), + find: jest.fn().mockImplementation(() => ({ total: 15 })), + create: jest.fn().mockImplementation(() => ({ id: 'brandNewSavedObject' })), + }; + + const mockEmbeddableFactory = new ContactCardEmbeddableFactory((() => null) as any, {} as any); + + pluginServices.getServices().embeddable.getEmbeddableFactory = jest + .fn() + .mockReturnValue(mockEmbeddableFactory); + container = buildMockDashboard({ + overrides: { + panels: { + '123': getSampleDashboardPanel({ + explicitInput: { firstName: 'Kibanana', id: '123' }, + type: CONTACT_CARD_EMBEDDABLE, + }), + }, + }, + }); + + const refOrValContactCardEmbeddable = await container.addNewEmbeddable< + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, + ContactCardEmbeddable + >(CONTACT_CARD_EMBEDDABLE, { + firstName: 'RefOrValEmbeddable', + }); + + const nonRefOrValueContactCard = await container.addNewEmbeddable< + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, + ContactCardEmbeddable + >(CONTACT_CARD_EMBEDDABLE, { + firstName: 'Not a refOrValEmbeddable', + }); + + if ( + isErrorEmbeddable(refOrValContactCardEmbeddable) || + isErrorEmbeddable(nonRefOrValueContactCard) + ) { + throw new Error('Failed to create embeddables'); + } else { + genericEmbeddable = nonRefOrValueContactCard; + byRefOrValEmbeddable = embeddablePluginMock.mockRefOrValEmbeddable< + ContactCardEmbeddable, + ContactCardEmbeddableInput + >(refOrValContactCardEmbeddable, { + mockedByReferenceInput: { + savedObjectId: 'testSavedObjectId', + id: refOrValContactCardEmbeddable.id, + }, + mockedByValueInput: { + firstName: 'RefOrValEmbeddable', + id: refOrValContactCardEmbeddable.id, + }, + }); + jest.spyOn(byRefOrValEmbeddable, 'getInputAsValueType'); + } + }); + test('Duplication adds a new embeddable', async () => { + const originalPanelCount = Object.keys(container.getInput().panels).length; + const originalPanelKeySet = new Set(Object.keys(container.getInput().panels)); + await duplicateDashboardPanel.bind(container)(byRefOrValEmbeddable.id); + + expect(Object.keys(container.getInput().panels).length).toEqual(originalPanelCount + 1); + const newPanelId = Object.keys(container.getInput().panels).find( + (key) => !originalPanelKeySet.has(key) + ); + expect(newPanelId).toBeDefined(); + const newPanel = container.getInput().panels[newPanelId!]; + expect(newPanel.type).toEqual(byRefOrValEmbeddable.type); + }); + + test('Duplicates a RefOrVal embeddable by value', async () => { + const originalPanelKeySet = new Set(Object.keys(container.getInput().panels)); + await duplicateDashboardPanel.bind(container)(byRefOrValEmbeddable.id); + const newPanelId = Object.keys(container.getInput().panels).find( + (key) => !originalPanelKeySet.has(key) + ); + + const originalFirstName = ( + container.getInput().panels[byRefOrValEmbeddable.id] + .explicitInput as ContactCardEmbeddableInput + ).firstName; + + const newFirstName = ( + container.getInput().panels[newPanelId!].explicitInput as ContactCardEmbeddableInput + ).firstName; + + expect(byRefOrValEmbeddable.getInputAsValueType).toHaveBeenCalled(); + + expect(originalFirstName).toEqual(newFirstName); + expect(container.getInput().panels[newPanelId!].type).toEqual(byRefOrValEmbeddable.type); + }); + + test('Duplicates a non RefOrVal embeddable by value', async () => { + const originalPanelKeySet = new Set(Object.keys(container.getInput().panels)); + await duplicateDashboardPanel.bind(container)(genericEmbeddable.id); + const newPanelId = Object.keys(container.getInput().panels).find( + (key) => !originalPanelKeySet.has(key) + ); + + const originalFirstName = ( + container.getInput().panels[genericEmbeddable.id].explicitInput as ContactCardEmbeddableInput + ).firstName; + + const newFirstName = ( + container.getInput().panels[newPanelId!].explicitInput as ContactCardEmbeddableInput + ).firstName; + + expect(originalFirstName).toEqual(newFirstName); + expect(container.getInput().panels[newPanelId!].type).toEqual(genericEmbeddable.type); + }); + + test('Gets a unique title from the dashboard', async () => { + expect(await incrementPanelTitle(container, '')).toEqual(''); + + container.getPanelTitles = jest.fn().mockImplementation(() => { + return ['testDuplicateTitle', 'testDuplicateTitle (copy)', 'testUniqueTitle']; + }); + expect(await incrementPanelTitle(container, 'testUniqueTitle')).toEqual( + 'testUniqueTitle (copy)' + ); + expect(await incrementPanelTitle(container, 'testDuplicateTitle')).toEqual( + 'testDuplicateTitle (copy 1)' + ); + + container.getPanelTitles = jest.fn().mockImplementation(() => { + return ['testDuplicateTitle', 'testDuplicateTitle (copy)'].concat( + Array.from([...Array(39)], (_, index) => `testDuplicateTitle (copy ${index + 1})`) + ); + }); + expect(await incrementPanelTitle(container, 'testDuplicateTitle')).toEqual( + 'testDuplicateTitle (copy 40)' + ); + expect(await incrementPanelTitle(container, 'testDuplicateTitle (copy 100)')).toEqual( + 'testDuplicateTitle (copy 40)' + ); + + container.getPanelTitles = jest.fn().mockImplementation(() => { + return ['testDuplicateTitle (copy 100)']; + }); + expect(await incrementPanelTitle(container, 'testDuplicateTitle')).toEqual( + 'testDuplicateTitle (copy 101)' + ); + expect(await incrementPanelTitle(container, 'testDuplicateTitle (copy 100)')).toEqual( + 'testDuplicateTitle (copy 101)' + ); + }); +}); + +describe('React embeddables', () => { + const testId = '1234'; + const buildDashboardWithReactEmbeddable = async ( + testType: string, + mockApi: BuildReactEmbeddableApiRegistration<{}, {}, Api> + ) => { + const fullApi$ = new Subject>(); + const reactEmbeddableFactory: ReactEmbeddableFactory<{}, {}, Api> = { + type: testType, + deserializeState: jest.fn().mockImplementation((state) => state.rawState), + buildEmbeddable: async (state, registerApi) => { + const fullApi = registerApi( + { + ...mockApi, + }, + {} + ); + fullApi$.next(fullApi); + fullApi$.complete(); + return { + Component: () =>
TEST DUPLICATE
, + api: fullApi, + }; + }, + }; + registerReactEmbeddableFactory(testType, async () => reactEmbeddableFactory); + const dashboard = buildMockDashboard({ + overrides: { + panels: { + [testId]: getSampleDashboardPanel({ + explicitInput: { id: testId }, + type: testType, + }), + }, + }, + }); + // render a fake Dashboard to initialize react embeddables + const FakeDashboard = () => { + return ( +
+ {Object.keys(dashboard.getInput().panels).map((panelId) => { + const panel = dashboard.getInput().panels[panelId]; + return ( +
+ dashboard.children$.next({ [panelId]: api })} + getParentApi={() => ({ + getSerializedStateForChild: () => + panel.explicitInput as unknown as SerializedPanelState | undefined, + })} + /> + + ); + })} + + ); + }; + render(); + + return { dashboard, apiPromise: lastValueFrom(fullApi$) }; + }; + + it('Duplicates child without library transforms', async () => { + const mockApi = { + serializeState: jest.fn().mockImplementation(() => ({ rawState: {} })), + }; + const { dashboard, apiPromise } = await buildDashboardWithReactEmbeddable( + 'byValueOnly', + mockApi + ); + const api = await apiPromise; + + const snapshotSpy = jest.spyOn(api, 'snapshotRuntimeState'); + + await duplicateDashboardPanel.bind(dashboard)(testId); + + expect(snapshotSpy).toHaveBeenCalled(); + expect(Object.keys(dashboard.getInput().panels).length).toBe(2); + }); + + it('Duplicates child with library transforms', async () => { + const libraryTransformsMockApi: BuildReactEmbeddableApiRegistration< + {}, + {}, + DefaultEmbeddableApi & HasLibraryTransforms + > = { + serializeState: jest.fn().mockImplementation(() => ({ rawState: {} })), + saveToLibrary: jest.fn(), + getByReferenceState: jest.fn(), + getByValueState: jest.fn(), + canLinkToLibrary: jest.fn(), + canUnlinkFromLibrary: jest.fn(), + checkForDuplicateTitle: jest.fn(), + }; + const { dashboard, apiPromise } = await buildDashboardWithReactEmbeddable( + 'libraryTransforms', + libraryTransformsMockApi + ); + await apiPromise; + + await duplicateDashboardPanel.bind(dashboard)(testId); + expect(libraryTransformsMockApi.getByValueState).toHaveBeenCalled(); + }); + + it('Duplicates a child with in place library transforms', async () => { + const inPlaceLibraryTransformsMockApi: BuildReactEmbeddableApiRegistration< + {}, + {}, + DefaultEmbeddableApi & HasInPlaceLibraryTransforms + > = { + unlinkFromLibrary: jest.fn(), + saveToLibrary: jest.fn(), + checkForDuplicateTitle: jest.fn(), + libraryId$: new BehaviorSubject(''), + getByValueRuntimeSnapshot: jest.fn(), + serializeState: jest.fn().mockImplementation(() => ({ rawState: {} })), + }; + const { dashboard, apiPromise } = await buildDashboardWithReactEmbeddable( + 'inPlaceLibraryTransforms', + inPlaceLibraryTransformsMockApi + ); + await apiPromise; + + await duplicateDashboardPanel.bind(dashboard)(testId); + expect(inPlaceLibraryTransformsMockApi.getByValueRuntimeSnapshot).toHaveBeenCalled(); + }); +}); diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/api/duplicate_dashboard_panel.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/api/duplicate_dashboard_panel.ts index 225e89109639a..e0840ad0912b3 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/api/duplicate_dashboard_panel.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/api/duplicate_dashboard_panel.ts @@ -7,7 +7,14 @@ */ import { isReferenceOrValueEmbeddable, PanelNotFoundError } from '@kbn/embeddable-plugin/public'; -import { apiPublishesPanelTitle, getPanelTitle } from '@kbn/presentation-publishing'; +import { apiHasSnapshottableState } from '@kbn/presentation-containers/interfaces/serialized_state'; +import { + apiHasInPlaceLibraryTransforms, + apiHasLibraryTransforms, + apiPublishesPanelTitle, + getPanelTitle, + stateHasTitles, +} from '@kbn/presentation-publishing'; import { filter, map, max } from 'lodash'; import { v4 as uuidv4 } from 'uuid'; import { DashboardPanelState, prefixReferencesFromPanel } from '../../../../common'; @@ -52,18 +59,45 @@ const duplicateReactEmbeddableInput = async ( panelToClone: DashboardPanelState, idToDuplicate: string ) => { + const id = uuidv4(); const child = dashboard.children$.value[idToDuplicate]; const lastTitle = apiPublishesPanelTitle(child) ? getPanelTitle(child) ?? '' : ''; const newTitle = await incrementPanelTitle(dashboard, lastTitle); - const id = uuidv4(); - if (panelToClone.references) { - dashboard.savedObjectReferences.push(...prefixReferencesFromPanel(id, panelToClone.references)); + + /** + * For react embeddables that have library transforms, we need to ensure + * to clone them with serialized state and references. + * + * TODO: remove this section once all by reference capable react embeddables + * use in-place library transforms + */ + if (apiHasLibraryTransforms(child)) { + const byValueSerializedState = await child.getByValueState(); + if (panelToClone.references) { + dashboard.savedObjectReferences.push( + ...prefixReferencesFromPanel(id, panelToClone.references) + ); + } + return { + type: panelToClone.type, + explicitInput: { + ...byValueSerializedState, + title: newTitle, + id, + }, + }; } + + const runtimeSnapshot = (() => { + if (apiHasInPlaceLibraryTransforms(child)) return child.getByValueRuntimeSnapshot(); + return apiHasSnapshottableState(child) ? child.snapshotRuntimeState() : {}; + })(); + if (stateHasTitles(runtimeSnapshot)) runtimeSnapshot.title = newTitle; + + dashboard.setRuntimeStateForChild(id, runtimeSnapshot); return { type: panelToClone.type, explicitInput: { - ...panelToClone.explicitInput, - title: newTitle, id, }, }; diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx index 049bfaf00e5dd..67cad4bd7bf83 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/api/run_save_functions.tsx @@ -275,21 +275,18 @@ export async function runInteractiveSave(this: DashboardContainer, interactionMo if (lastSavedId) { const [baseTitle, baseCount] = extractTitleAndCount(newTitle); - let copyCount = baseCount + 1; - newTitle = `${baseTitle} (${copyCount})`; - - // increment count until we find a unique title - while ( - !(await checkForDuplicateDashboardTitle({ - title: newTitle, - lastSavedTitle: currentState.title, - copyOnSave: true, - isTitleDuplicateConfirmed: false, - })) - ) { - copyCount++; - newTitle = `${baseTitle} (${copyCount})`; - } + + newTitle = `${baseTitle} (${baseCount + 1})`; + + await checkForDuplicateDashboardTitle({ + title: newTitle, + lastSavedTitle: currentState.title, + copyOnSave: true, + isTitleDuplicateConfirmed: false, + onTitleDuplicate(speculativeSuggestion) { + newTitle = speculativeSuggestion; + }, + }); switch (interactionMode) { case ViewMode.EDIT: { diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts index f20e9f8c46c1b..e3a321f2355df 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts @@ -275,7 +275,7 @@ test('pulls panels from override input', async () => { // instead, the unsaved changes for React embeddables should be applied to the "restored runtime state" property of the Dashboard. expect( - (dashboard!.restoredRuntimeState!.someReactEmbeddablePanel as { title: string }).title + (dashboard!.getRuntimeStateForChild('someReactEmbeddablePanel') as { title: string }).title ).toEqual('an elegant override, from a more civilized age'); }); diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts index 8f7e8bdb21bb5..32b21f0cf1c1e 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts @@ -277,10 +277,14 @@ export const initializeDashboard = async ({ }; // -------------------------------------------------------------------------------------- - // Set latest runtime state for react embeddables. + // Set restored runtime state for react embeddables. // -------------------------------------------------------------------------------------- untilDashboardReady().then((dashboardContainer) => { - dashboardContainer.restoredRuntimeState = runtimePanelsToRestore; + for (const idWithRuntimeState of Object.keys(runtimePanelsToRestore)) { + const restoredRuntimeStateForChild = runtimePanelsToRestore[idWithRuntimeState]; + if (!restoredRuntimeStateForChild) continue; + dashboardContainer.setRuntimeStateForChild(idWithRuntimeState, restoredRuntimeStateForChild); + } }); // -------------------------------------------------------------------------------------- diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx index bb49255d41711..1bfa289a5e912 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx @@ -810,14 +810,22 @@ export class DashboardContainer public saveNotification$: Subject = new Subject(); public getSerializedStateForChild = (childId: string) => { + const rawState = this.getInput().panels[childId].explicitInput; + const { id, ...serializedState } = rawState; + if (!rawState || Object.keys(serializedState).length === 0) return; const references = getReferencesForPanelId(childId, this.savedObjectReferences); return { - rawState: this.getInput().panels[childId].explicitInput, + rawState, references, }; }; - public restoredRuntimeState: UnsavedPanelState | undefined = undefined; + private restoredRuntimeState: UnsavedPanelState | undefined = undefined; + public setRuntimeStateForChild = (childId: string, state: object) => { + const runtimeState = this.restoredRuntimeState ?? {}; + runtimeState[childId] = state; + this.restoredRuntimeState = runtimeState; + }; public getRuntimeStateForChild = (childId: string) => { return this.restoredRuntimeState?.[childId]; }; diff --git a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.test.tsx b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.test.tsx index 2fc8c2d36ee0e..b2493454d4da2 100644 --- a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.test.tsx +++ b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.test.tsx @@ -154,6 +154,7 @@ describe('useDashboardListingTable', () => { onSave: expect.any(Function), isReadonly: false, customValidators: expect.any(Object), + showActivityView: true, }, createdByEnabled: true, }; diff --git a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx index 83638b01b190e..659e5e43930ea 100644 --- a/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx +++ b/src/plugins/dashboard/public/dashboard_listing/hooks/use_dashboard_listing_table.tsx @@ -42,7 +42,9 @@ const toTableListViewSavedObject = (hit: DashboardItem): DashboardSavedObjectUse type: 'dashboard', id: hit.id, updatedAt: hit.updatedAt!, + createdAt: hit.createdAt, createdBy: hit.createdBy, + updatedBy: hit.updatedBy, references: hit.references, managed: hit.managed, attributes: { @@ -280,6 +282,7 @@ export const useDashboardListingTable = ({ isReadonly: !showWriteControls, onSave: updateItemMeta, customValidators: contentEditorValidators, + showActivityView: true, }, createItem: !showWriteControls || !showCreateDashboardButton ? undefined : createItem, deleteItems: !showWriteControls ? undefined : deleteItems, diff --git a/src/plugins/dashboard/public/services/dashboard_backup/dashboard_backup_service.ts b/src/plugins/dashboard/public/services/dashboard_backup/dashboard_backup_service.ts index 5a121ef430400..f97d88fd1c4fe 100644 --- a/src/plugins/dashboard/public/services/dashboard_backup/dashboard_backup_service.ts +++ b/src/plugins/dashboard/public/services/dashboard_backup/dashboard_backup_service.ts @@ -133,7 +133,7 @@ class DashboardBackupService implements DashboardBackupServiceType { const panelsStorage = this.sessionStorage.get(DASHBOARD_PANELS_SESSION_KEY) ?? {}; set(panelsStorage, [this.activeSpaceId, id], unsavedPanels); - this.sessionStorage.set(DASHBOARD_PANELS_SESSION_KEY, panelsStorage); + this.sessionStorage.set(DASHBOARD_PANELS_SESSION_KEY, panelsStorage, true); } catch (e) { this.notifications.toasts.addDanger({ title: backupServiceStrings.getPanelsSetError(e.message), diff --git a/src/plugins/dashboard/public/services/dashboard_content_management/lib/check_for_duplicate_dashboard_title.test.ts b/src/plugins/dashboard/public/services/dashboard_content_management/lib/check_for_duplicate_dashboard_title.test.ts new file mode 100644 index 0000000000000..edaae6a8b0d92 --- /dev/null +++ b/src/plugins/dashboard/public/services/dashboard_content_management/lib/check_for_duplicate_dashboard_title.test.ts @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ContentClient } from '@kbn/content-management-plugin/public'; +import { checkForDuplicateDashboardTitle } from './check_for_duplicate_dashboard_title'; +import { extractTitleAndCount } from '../../../dashboard_container/embeddable/api/lib/extract_title_and_count'; + +type ContentManagementStart = Parameters[1]; + +describe('checkForDuplicateDashboardTitle', () => { + const mockedContentManagementClient = { + search: jest.fn(), + } as unknown as ContentClient; + + const newTitle = 'Shiny dashboard (1)'; + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('will only search using the dashboard basename', async () => { + const [baseDashboardName] = extractTitleAndCount(newTitle); + + const pageResults = [ + { + attributes: { + title: baseDashboardName, + }, + }, + ]; + + ( + mockedContentManagementClient.search as jest.MockedFunction + ).mockImplementationOnce(() => + Promise.resolve({ + hits: pageResults, + pagination: { + total: pageResults.length, + }, + }) + ); + + await checkForDuplicateDashboardTitle( + { + title: newTitle, + lastSavedTitle: baseDashboardName, + copyOnSave: true, + isTitleDuplicateConfirmed: false, + }, + { client: mockedContentManagementClient } as ContentManagementStart + ); + + expect(mockedContentManagementClient.search).toHaveBeenCalledWith( + expect.objectContaining({ + query: expect.objectContaining({ + text: `${baseDashboardName}*`, + }), + }) + ); + }); + + it('invokes onTitleDuplicate with a speculative collision free value when the new title provided is a duplicate match', async () => { + const [baseDashboardName] = extractTitleAndCount(newTitle); + + const userTitleInput = `${baseDashboardName} (10)`; + + const pageResults = [ + { + attributes: { + title: baseDashboardName, + }, + }, + ].concat( + Array.from(new Array(5)).map((_, idx) => ({ + attributes: { + title: `${baseDashboardName} (${10 + idx})`, + }, + })) + ); + + const onTitleDuplicate = jest.fn(); + + ( + mockedContentManagementClient.search as jest.MockedFunction + ).mockImplementationOnce(() => + Promise.resolve({ + hits: pageResults, + pagination: { + total: pageResults.length, + }, + }) + ); + + await checkForDuplicateDashboardTitle( + { + title: userTitleInput, + lastSavedTitle: baseDashboardName, + copyOnSave: true, + isTitleDuplicateConfirmed: false, + onTitleDuplicate, + }, + { client: mockedContentManagementClient } as ContentManagementStart + ); + + expect(mockedContentManagementClient.search).toHaveBeenCalledWith( + expect.objectContaining({ + query: expect.objectContaining({ + text: 'Shiny dashboard*', + }), + }) + ); + + expect(onTitleDuplicate).toHaveBeenCalledWith(`${baseDashboardName} (15)`); + }); +}); diff --git a/src/plugins/dashboard/public/services/dashboard_content_management/lib/check_for_duplicate_dashboard_title.ts b/src/plugins/dashboard/public/services/dashboard_content_management/lib/check_for_duplicate_dashboard_title.ts index 0ffcf8c9788e9..a4bd93191a487 100644 --- a/src/plugins/dashboard/public/services/dashboard_content_management/lib/check_for_duplicate_dashboard_title.ts +++ b/src/plugins/dashboard/public/services/dashboard_content_management/lib/check_for_duplicate_dashboard_title.ts @@ -9,12 +9,16 @@ import { DashboardStartDependencies } from '../../../plugin'; import { DASHBOARD_CONTENT_ID } from '../../../dashboard_constants'; import { DashboardCrudTypes } from '../../../../common/content_management'; +import { extractTitleAndCount } from '../../../dashboard_container/embeddable/api/lib/extract_title_and_count'; export interface DashboardDuplicateTitleCheckProps { title: string; copyOnSave: boolean; lastSavedTitle: string; - onTitleDuplicate?: () => void; + /** + * invokes the onTitleDuplicate function if provided with a speculative title that should be collision free + */ + onTitleDuplicate?: (speculativeSuggestion: string) => void; isTitleDuplicateConfirmed: boolean; } @@ -33,6 +37,11 @@ export async function checkForDuplicateDashboardTitle( }: DashboardDuplicateTitleCheckProps, contentManagement: DashboardStartDependencies['contentManagement'] ): Promise { + // Don't check if the title is an empty string + if (!title) { + return true; + } + // Don't check for duplicates if user has already confirmed save with duplicate title if (isTitleDuplicateConfirmed) { return true; @@ -44,21 +53,37 @@ export async function checkForDuplicateDashboardTitle( return true; } + const [baseDashboardName] = extractTitleAndCount(title); + const { hits } = await contentManagement.client.search< DashboardCrudTypes['SearchIn'], DashboardCrudTypes['SearchOut'] >({ contentTypeId: DASHBOARD_CONTENT_ID, query: { - text: title ? `${title}*` : undefined, - limit: 10, + text: `${baseDashboardName}*`, + limit: 20, + }, + options: { + onlyTitle: true, }, - options: { onlyTitle: true }, }); - const duplicate = hits.find((hit) => hit.attributes.title.toLowerCase() === title.toLowerCase()); + + const duplicate = Boolean( + hits.find((hit) => hit.attributes.title.toLowerCase() === title.toLowerCase()) + ); + if (!duplicate) { return true; } - onTitleDuplicate?.(); + + const [largestDuplicationId] = hits + .map((hit) => extractTitleAndCount(hit.attributes.title)[1]) + .sort((a, b) => b - a); + + const speculativeCollisionFreeTitle = `${baseDashboardName} (${largestDuplicationId + 1})`; + + onTitleDuplicate?.(speculativeCollisionFreeTitle); + return false; } diff --git a/src/plugins/data/common/search/search_source/query_to_fields.test.ts b/src/plugins/data/common/search/search_source/query_to_fields.test.ts new file mode 100644 index 0000000000000..b7ed2f6f12333 --- /dev/null +++ b/src/plugins/data/common/search/search_source/query_to_fields.test.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EsQuerySortValue, queryToFields, SearchRequest, SortDirection } from '../..'; +import { DataViewLazy } from '@kbn/data-views-plugin/common'; + +describe('SearchSource#queryToFields', () => { + it('should include time field', async () => { + const dataView = { + timeFieldName: '@timestamp', + getSourceFiltering: jest.fn(), + getFields: jest.fn().mockResolvedValue({ + getFieldMapSorted: jest.fn(), + }), + }; + const request: SearchRequest = { query: [] }; + await queryToFields({ dataView: dataView as unknown as DataViewLazy, request }); + const { fieldName } = dataView.getFields.mock.calls[0][0]; + expect(fieldName).toEqual(['@timestamp']); + }); + + it('should include sort field', async () => { + const dataView = { + getSourceFiltering: jest.fn(), + getFields: jest.fn().mockResolvedValue({ + getFieldMapSorted: jest.fn(), + }), + }; + const sort: EsQuerySortValue = { bytes: SortDirection.asc }; + const request: SearchRequest = { query: [] }; + await queryToFields({ dataView: dataView as unknown as DataViewLazy, sort, request }); + const { fieldName } = dataView.getFields.mock.calls[0][0]; + expect(fieldName).toEqual(['bytes']); + }); + + it('should include request KQL query fields', async () => { + const dataView = { + timeFieldName: '@timestamp', + getSourceFiltering: jest.fn(), + getFields: jest.fn().mockResolvedValue({ + getFieldMapSorted: jest.fn(), + }), + }; + const request: SearchRequest = { + query: [ + { + language: 'kuery', + query: 'log.level: debug AND NOT message: unknown', + }, + ], + }; + await queryToFields({ dataView: dataView as unknown as DataViewLazy, request }); + const { fieldName } = dataView.getFields.mock.calls[0][0]; + expect(fieldName).toEqual(['@timestamp', 'log.level', 'message']); + }); + + it('should not include request Lucene query fields', async () => { + const dataView = { + timeFieldName: '@timestamp', + getSourceFiltering: jest.fn(), + getFields: jest.fn().mockResolvedValue({ + getFieldMapSorted: jest.fn(), + }), + }; + const request: SearchRequest = { + query: [ + { + language: 'lucene', + query: 'host: artifacts\\.*', + }, + ], + }; + await queryToFields({ dataView: dataView as unknown as DataViewLazy, request }); + const { fieldName } = dataView.getFields.mock.calls[0][0]; + expect(fieldName).toEqual(['@timestamp']); + }); +}); diff --git a/src/plugins/data/common/search/search_source/query_to_fields.ts b/src/plugins/data/common/search/search_source/query_to_fields.ts index 5c64a95a0a927..ebae3354dcea9 100644 --- a/src/plugins/data/common/search/search_source/query_to_fields.ts +++ b/src/plugins/data/common/search/search_source/query_to_fields.ts @@ -26,7 +26,7 @@ export async function queryToFields({ fields.push(...sortArr.flatMap((s) => Object.keys(s))); } for (const query of request.query) { - if (query.query) { + if (query.query && query.language === 'kuery') { const nodes = fromKueryExpression(query.query); const queryFields = getKqlFieldNames(nodes); fields = fields.concat(queryFields); diff --git a/src/plugins/data_views/docs/openapi/README.md b/src/plugins/data_views/docs/openapi/README.md index 1480da8537706..dd871a82de129 100644 --- a/src/plugins/data_views/docs/openapi/README.md +++ b/src/plugins/data_views/docs/openapi/README.md @@ -14,14 +14,7 @@ A guide about the openApi specification can be found at [https://swagger.io/docs ## Tools -It is possible to validate the docs before bundling them with the following -command: - -```bash -npx swagger-cli validate entrypoint.yaml -``` - -Then you can generate the `bundled` files by running the following commands: +Generate the `bundled` files by running the following commands: ```bash npx @redocly/cli bundle entrypoint.yaml --output bundled.yaml --ext yaml diff --git a/src/plugins/data_views/docs/openapi/bundled.json b/src/plugins/data_views/docs/openapi/bundled.json index 3b435603f7d34..28893b86cef3f 100644 --- a/src/plugins/data_views/docs/openapi/bundled.json +++ b/src/plugins/data_views/docs/openapi/bundled.json @@ -1,5 +1,5 @@ { - "openapi": "3.1.0", + "openapi": "3.0.3", "info": { "title": "Data views", "description": "OpenAPI schema for data view endpoints", @@ -14,7 +14,12 @@ }, "servers": [ { - "url": "/" + "url": "https://{kibana_url}", + "variables": { + "kibana_url": { + "default": "localhost:5601" + } + } } ], "security": [ @@ -34,7 +39,7 @@ "paths": { "/s/{spaceId}/api/data_views": { "get": { - "summary": "Retrieves a list of all data views.", + "summary": "Get all data views", "operationId": "getAllDataViews", "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", "tags": [ @@ -104,7 +109,7 @@ }, "/s/{spaceId}/api/data_views/data_view": { "post": { - "summary": "Creates a data view.", + "summary": "Create a data view", "operationId": "createDataView", "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", "tags": [ @@ -159,7 +164,7 @@ }, "/s/{spaceId}/api/data_views/data_view/{viewId}": { "get": { - "summary": "Retrieves a single data view by identifier.", + "summary": "Get a data view", "operationId": "getDataView", "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", "tags": [ @@ -202,7 +207,7 @@ } }, "delete": { - "summary": "Deletes a data view.", + "summary": "Delete a data view", "operationId": "deleteDataView", "description": "WARNING: When you delete a data view, it cannot be recovered. This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", "tags": [ @@ -223,21 +228,636 @@ "204": { "description": "Indicates a successful call." }, - "404": { - "description": "Object is not found.", + "404": { + "description": "Object is not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404_response" + } + } + } + } + } + }, + "post": { + "summary": "Update a data view", + "operationId": "updateDataView", + "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "tags": [ + "data views" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/view_id" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/update_data_view_request_object" + }, + "examples": { + "updateDataViewRequest": { + "$ref": "#/components/examples/update_data_view_request" + } + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/data_view_response_object" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + } + } + } + }, + "/s/{spaceId}/api/data_views/default": { + "get": { + "summary": "Get the default data view identifier", + "operationId": "getDefaultDataView", + "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "tags": [ + "data views" + ], + "parameters": [ + { + "$ref": "#/components/parameters/space_id" + } + ], + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data_view_id": { + "type": "string" + } + } + }, + "examples": { + "getDefaultDataViewResponse": { + "$ref": "#/components/examples/get_default_data_view_response" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + } + } + }, + "post": { + "summary": "Sets the default data view identifier", + "operationId": "setDefaultDatailView", + "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "tags": [ + "data views" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data_view_id" + ], + "properties": { + "data_view_id": { + "type": "string", + "nullable": true, + "description": "The data view identifier. NOTE: The API does not validate whether it is a valid identifier. Use `null` to unset the default data view.\n" + }, + "force": { + "type": "boolean", + "description": "Update an existing default data view identifier.", + "default": false + } + } + }, + "examples": { + "setDefaultDataViewRequest": { + "$ref": "#/components/examples/set_default_data_view_request" + } + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "acknowledged": { + "type": "boolean" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + } + } + } + }, + "/s/{spaceId}/api/data_views/data_view/{viewId}/fields": { + "post": { + "summary": "Update data view fields metadata", + "operationId": "updateFieldsMetadata", + "description": "Update fields presentation metadata such as count, customLabel and format. This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. You can update multiple fields in one request. Updates are merged with persisted metadata. To remove existing metadata, specify null as the value.\n", + "tags": [ + "data views" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/view_id" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "fields" + ], + "properties": { + "fields": { + "description": "The field object.", + "type": "object" + } + } + }, + "examples": { + "updateFieldsMetadataRequest": { + "$ref": "#/components/examples/update_field_metadata_request" + } + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "acknowledged": { + "type": "boolean" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + } + } + } + }, + "/s/{spaceId}/api/data_views/data_view/{viewId}/runtime_field": { + "post": { + "summary": "Create a runtime field", + "operationId": "createRuntimeField", + "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "tags": [ + "data views" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/view_id" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "runtimeField" + ], + "properties": { + "name": { + "type": "string", + "description": "The name for a runtime field.\n" + }, + "runtimeField": { + "type": "object", + "description": "The runtime field definition object.\n" + } + } + }, + "examples": { + "createRuntimeFieldRequest": { + "$ref": "#/components/examples/create_runtime_field_request" + } + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + } + }, + "put": { + "summary": "Create or update a runtime field", + "operationId": "createUpdateRuntimeField", + "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "tags": [ + "data views" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" + }, + { + "name": "viewId", + "in": "path", + "description": "The ID of the data view fields you want to update.\n", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "runtimeField" + ], + "properties": { + "name": { + "type": "string", + "description": "The name for a runtime field.\n" + }, + "runtimeField": { + "type": "object", + "description": "The runtime field definition object.\n" + } + } + }, + "examples": { + "updateRuntimeFieldRequest": { + "$ref": "#/components/examples/create_runtime_field_request" + } + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data_view": { + "type": "object" + }, + "fields": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + } + } + } + }, + "/s/{spaceId}/api/data_views/data_view/{viewId}/runtime_field/{fieldName}": { + "get": { + "summary": "Get a runtime field", + "operationId": "getRuntimeField", + "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "tags": [ + "data views" + ], + "parameters": [ + { + "$ref": "#/components/parameters/field_name" + }, + { + "$ref": "#/components/parameters/view_id" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data_view": { + "type": "object" + }, + "fields": { + "type": "array", + "items": { + "type": "object" + } + } + } + }, + "examples": { + "getRuntimeFieldResponse": { + "$ref": "#/components/examples/get_runtime_field_response" + } + } + } + } + }, + "404": { + "description": "Object is not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404_response" + } + } + } + } + } + }, + "delete": { + "summary": "Delete a runtime field from a data view", + "operationId": "deleteRuntimeField", + "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "tags": [ + "data views" + ], + "parameters": [ + { + "$ref": "#/components/parameters/field_name" + }, + { + "$ref": "#/components/parameters/view_id" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "responses": { + "200": { + "description": "Indicates a successful call." + }, + "404": { + "description": "Object is not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404_response" + } + } + } + } + } + }, + "post": { + "summary": "Update a runtime field", + "operationId": "updateRuntimeField", + "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "tags": [ + "data views" + ], + "parameters": [ + { + "$ref": "#/components/parameters/field_name" + }, + { + "$ref": "#/components/parameters/view_id" + }, + { + "$ref": "#/components/parameters/space_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "runtimeField" + ], + "properties": { + "runtimeField": { + "type": "object", + "description": "The runtime field definition object.\n\nYou can update following fields:\n\n- `type`\n- `script`\n" + } + } + }, + "examples": { + "updateRuntimeFieldRequest": { + "$ref": "#/components/examples/update_runtime_field_request" + } + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call." + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + } + } + } + }, + "/api/data_views": { + "get": { + "summary": "Get all data views in the default space", + "operationId": "getAllDataViewsDefault", + "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "tags": [ + "data views" + ], + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data_view": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "namespaces": { + "type": "array", + "items": { + "type": "string" + } + }, + "title": { + "type": "string" + }, + "typeMeta": { + "type": "object" + } + } + } + } + } + }, + "examples": { + "getAllDataViewsResponse": { + "$ref": "#/components/examples/get_data_views_response" + } + } + } + } + }, + "400": { + "description": "Bad request", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/404_response" + "$ref": "#/components/schemas/400_response" } } } } } - }, + } + }, + "/api/data_views/data_view": { "post": { - "summary": "Updates a data view.", - "operationId": "updateDataView", + "summary": "Create a data view in the default space", + "operationId": "createDataViewDefault", "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", "tags": [ "data views" @@ -245,12 +865,6 @@ "parameters": [ { "$ref": "#/components/parameters/kbn_xsrf" - }, - { - "$ref": "#/components/parameters/view_id" - }, - { - "$ref": "#/components/parameters/space_id" } ], "requestBody": { @@ -258,11 +872,11 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/update_data_view_request_object" + "$ref": "#/components/schemas/create_data_view_request_object" }, "examples": { - "updateDataViewRequest": { - "$ref": "#/components/examples/update_data_view_request" + "createDataViewRequest": { + "$ref": "#/components/examples/create_data_view_request" } } } @@ -292,17 +906,17 @@ } } }, - "/s/{spaceId}/api/data_views/default": { + "/api/data_views/data_view/{viewId}": { "get": { - "summary": "Retrieves the default data view identifier.", - "operationId": "getDefaultDataView", + "summary": "Get a data view in the default space", + "operationId": "getDataViewDefault", "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", "tags": [ "data views" ], "parameters": [ { - "$ref": "#/components/parameters/space_id" + "$ref": "#/components/parameters/view_id" } ], "responses": { @@ -311,27 +925,53 @@ "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "data_view_id": { - "type": "string" - } - } + "$ref": "#/components/schemas/data_view_response_object" }, "examples": { - "getDefaultDataViewResponse": { - "$ref": "#/components/examples/get_default_data_view_response" + "getDataViewResponse": { + "$ref": "#/components/examples/get_data_view_response" } } } } }, - "400": { - "description": "Bad request", + "404": { + "description": "Object is not found.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/400_response" + "$ref": "#/components/schemas/404_response" + } + } + } + } + } + }, + "delete": { + "summary": "Delete a data view from the default space", + "operationId": "deleteDataViewDefault", + "description": "WARNING: When you delete a data view, it cannot be recovered. This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "tags": [ + "data views" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/view_id" + } + ], + "responses": { + "204": { + "description": "Indicates a successful call." + }, + "404": { + "description": "Object is not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404_response" } } } @@ -339,8 +979,8 @@ } }, "post": { - "summary": "Sets the default data view identifier.", - "operationId": "setDefaultDatailView", + "summary": "Update a data view in the default space", + "operationId": "updateDataViewDefault", "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", "tags": [ "data views" @@ -350,7 +990,7 @@ "$ref": "#/components/parameters/kbn_xsrf" }, { - "$ref": "#/components/parameters/space_id" + "$ref": "#/components/parameters/view_id" } ], "requestBody": { @@ -358,28 +998,11 @@ "content": { "application/json": { "schema": { - "type": "object", - "required": [ - "data_view_id" - ], - "properties": { - "data_view_id": { - "type": [ - "string", - "null" - ], - "description": "The data view identifier. NOTE: The API does not validate whether it is a valid identifier. Use `null` to unset the default data view.\n" - }, - "force": { - "type": "boolean", - "description": "Update an existing default data view identifier.", - "default": false - } - } + "$ref": "#/components/schemas/update_data_view_request_object" }, "examples": { - "setDefaultDataViewRequest": { - "$ref": "#/components/examples/set_default_data_view_request" + "updateDataViewRequest": { + "$ref": "#/components/examples/update_data_view_request" } } } @@ -391,12 +1014,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "acknowledged": { - "type": "boolean" - } - } + "$ref": "#/components/schemas/data_view_response_object" } } } @@ -414,11 +1032,11 @@ } } }, - "/s/{spaceId}/api/data_views/data_view/{viewId}/fields": { + "/api/data_views/data_view/{viewId}/fields": { "post": { - "summary": "Update fields presentation metadata such as count, customLabel and format.", - "operationId": "updateFieldsMetadata", - "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. You can update multiple fields in one request. Updates are merged with persisted metadata. To remove existing metadata, specify null as the value.\n", + "summary": "Update data view fields metadata in the default space", + "operationId": "updateFieldsMetadataDefault", + "description": "Update fields presentation metadata such as count, customLabel and format. This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. You can update multiple fields in one request. Updates are merged with persisted metadata. To remove existing metadata, specify null as the value.\n", "tags": [ "data views" ], @@ -428,9 +1046,6 @@ }, { "$ref": "#/components/parameters/view_id" - }, - { - "$ref": "#/components/parameters/space_id" } ], "requestBody": { @@ -486,10 +1101,10 @@ } } }, - "/s/{spaceId}/api/data_views/data_view/{viewId}/runtime_field": { + "/api/data_views/data_view/{viewId}/runtime_field": { "post": { - "summary": "Creates a runtime field.", - "operationId": "createRuntimeField", + "summary": "Create a runtime field in the default space", + "operationId": "createRuntimeFieldDefault", "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", "tags": [ "data views" @@ -500,9 +1115,6 @@ }, { "$ref": "#/components/parameters/view_id" - }, - { - "$ref": "#/components/parameters/space_id" } ], "requestBody": { @@ -533,11 +1145,23 @@ } } } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } } }, "put": { - "summary": "Create or update an existing runtime field.", - "operationId": "createUpdateRuntimeField", + "summary": "Create or update a runtime field in the default space", + "operationId": "createUpdateRuntimeFieldDefault", "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", "tags": [ "data views" @@ -546,9 +1170,6 @@ { "$ref": "#/components/parameters/kbn_xsrf" }, - { - "$ref": "#/components/parameters/space_id" - }, { "name": "viewId", "in": "path", @@ -606,11 +1227,6 @@ } } } - }, - "examples": { - "createRuntimeFieldResponse": { - "$ref": "#/components/examples/create_runtime_field_response" - } } } } @@ -628,10 +1244,10 @@ } } }, - "/s/{spaceId}/api/data_views/data_view/{viewId}/runtime_field/{fieldName}": { + "/api/data_views/data_view/{viewId}/runtime_field/{fieldName}": { "get": { - "summary": "Retrieves a runtime field.", - "operationId": "getRuntimeField", + "summary": "Get a runtime field in the default space", + "operationId": "getRuntimeFieldDefault", "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", "tags": [ "data views" @@ -642,9 +1258,6 @@ }, { "$ref": "#/components/parameters/view_id" - }, - { - "$ref": "#/components/parameters/space_id" } ], "responses": { @@ -687,8 +1300,8 @@ } }, "delete": { - "summary": "Delete a runtime field from a data view.", - "operationId": "deleteRuntimeField", + "summary": "Delete a runtime field from a data view in the default space", + "operationId": "deleteRuntimeFieldDefault", "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", "tags": [ "data views" @@ -699,9 +1312,6 @@ }, { "$ref": "#/components/parameters/view_id" - }, - { - "$ref": "#/components/parameters/space_id" } ], "responses": { @@ -721,8 +1331,8 @@ } }, "post": { - "summary": "Update an existing runtime field.", - "operationId": "updateRuntimeField", + "summary": "Update a runtime field in the default space", + "operationId": "updateRuntimeFieldDefault", "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", "tags": [ "data views" @@ -733,9 +1343,6 @@ }, { "$ref": "#/components/parameters/view_id" - }, - { - "$ref": "#/components/parameters/space_id" } ], "requestBody": { @@ -778,6 +1385,118 @@ } } } + }, + "/api/data_views/default": { + "get": { + "summary": "Get the default data view in the default space", + "operationId": "getDefaultDataViewDefault", + "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "tags": [ + "data views" + ], + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data_view_id": { + "type": "string" + } + } + }, + "examples": { + "getDefaultDataViewResponse": { + "$ref": "#/components/examples/get_default_data_view_response" + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + } + } + }, + "post": { + "summary": "Set the default data view in the default space", + "operationId": "setDefaultDatailViewDefault", + "description": "This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n", + "tags": [ + "data views" + ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "data_view_id" + ], + "properties": { + "data_view_id": { + "type": "string", + "nullable": true, + "description": "The data view identifier. NOTE: The API does not validate whether it is a valid identifier. Use `null` to unset the default data view.\n" + }, + "force": { + "type": "boolean", + "description": "Update an existing default data view identifier.", + "default": false + } + } + }, + "examples": { + "setDefaultDataViewRequest": { + "$ref": "#/components/examples/set_default_data_view_request" + } + } + } + } + }, + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "acknowledged": { + "type": "boolean" + } + } + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/400_response" + } + } + } + } + } + } } }, "components": { @@ -790,7 +1509,7 @@ "type": "apiKey", "in": "header", "name": "Authorization", - "description": "e.g. Authorization: ApiKey base64AccessApiKey" + "description": "Serverless APIs support only key-based authentication. You must create an API key and use the encoded value in the request header. For example: 'Authorization: ApiKey base64AccessApiKey'.\n" } }, "parameters": { @@ -801,9 +1520,7 @@ "required": true, "schema": { "type": "string", - "examples": [ - "default" - ] + "example": "default" } }, "kbn_xsrf": { @@ -2090,17 +2807,6 @@ } } }, - "create_runtime_field_response": { - "summary": "The API returns created runtime field object array and updated data view object.", - "value": { - "data_view": { - "...": null - }, - "fields": [ - "..." - ] - } - }, "get_runtime_field_response": { "summary": "The get runtime field API returns a JSON object that contains information about the runtime field (`hour_of_day`) and the data view (`d3d7af60-4c81-11e8-b3d7-01146121b73d`).", "value": { diff --git a/src/plugins/data_views/docs/openapi/bundled.yaml b/src/plugins/data_views/docs/openapi/bundled.yaml index 5f8ce9d8c18f7..dee19e6b1d67b 100644 --- a/src/plugins/data_views/docs/openapi/bundled.yaml +++ b/src/plugins/data_views/docs/openapi/bundled.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.0 +openapi: 3.0.3 info: title: Data views description: OpenAPI schema for data view endpoints @@ -9,7 +9,10 @@ info: name: Elastic License 2.0 url: https://www.elastic.co/licensing/elastic-license servers: - - url: / + - url: https://{kibana_url} + variables: + kibana_url: + default: localhost:5601 security: - basicAuth: [] - apiKeyAuth: [] @@ -19,7 +22,7 @@ tags: paths: /s/{spaceId}/api/data_views: get: - summary: Retrieves a list of all data views. + summary: Get all data views operationId: getAllDataViews description: | This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -63,7 +66,7 @@ paths: $ref: '#/components/schemas/400_response' /s/{spaceId}/api/data_views/data_view: post: - summary: Creates a data view. + summary: Create a data view operationId: createDataView description: | This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -96,7 +99,7 @@ paths: $ref: '#/components/schemas/400_response' /s/{spaceId}/api/data_views/data_view/{viewId}: get: - summary: Retrieves a single data view by identifier. + summary: Get a data view operationId: getDataView description: | This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -122,7 +125,7 @@ paths: schema: $ref: '#/components/schemas/404_response' delete: - summary: Deletes a data view. + summary: Delete a data view operationId: deleteDataView description: | WARNING: When you delete a data view, it cannot be recovered. This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -142,7 +145,7 @@ paths: schema: $ref: '#/components/schemas/404_response' post: - summary: Updates a data view. + summary: Update a data view operationId: updateDataView description: | This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -176,7 +179,7 @@ paths: $ref: '#/components/schemas/400_response' /s/{spaceId}/api/data_views/default: get: - summary: Retrieves the default data view identifier. + summary: Get the default data view identifier operationId: getDefaultDataView description: | This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -204,7 +207,7 @@ paths: schema: $ref: '#/components/schemas/400_response' post: - summary: Sets the default data view identifier. + summary: Sets the default data view identifier operationId: setDefaultDatailView description: | This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -223,9 +226,8 @@ paths: - data_view_id properties: data_view_id: - type: - - string - - 'null' + type: string + nullable: true description: | The data view identifier. NOTE: The API does not validate whether it is a valid identifier. Use `null` to unset the default data view. force: @@ -253,10 +255,10 @@ paths: $ref: '#/components/schemas/400_response' /s/{spaceId}/api/data_views/data_view/{viewId}/fields: post: - summary: Update fields presentation metadata such as count, customLabel and format. + summary: Update data view fields metadata operationId: updateFieldsMetadata description: | - This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. You can update multiple fields in one request. Updates are merged with persisted metadata. To remove existing metadata, specify null as the value. + Update fields presentation metadata such as count, customLabel and format. This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. You can update multiple fields in one request. Updates are merged with persisted metadata. To remove existing metadata, specify null as the value. tags: - data views parameters: @@ -296,7 +298,7 @@ paths: $ref: '#/components/schemas/400_response' /s/{spaceId}/api/data_views/data_view/{viewId}/runtime_field: post: - summary: Creates a runtime field. + summary: Create a runtime field operationId: createRuntimeField description: | This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -327,8 +329,15 @@ paths: examples: createRuntimeFieldRequest: $ref: '#/components/examples/create_runtime_field_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object put: - summary: Create or update an existing runtime field. + summary: Create or update a runtime field operationId: createUpdateRuntimeField description: | This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -379,9 +388,6 @@ paths: type: array items: type: object - examples: - createRuntimeFieldResponse: - $ref: '#/components/examples/create_runtime_field_response' '400': description: Bad request content: @@ -390,7 +396,7 @@ paths: $ref: '#/components/schemas/400_response' /s/{spaceId}/api/data_views/data_view/{viewId}/runtime_field/{fieldName}: get: - summary: Retrieves a runtime field. + summary: Get a runtime field operationId: getRuntimeField description: | This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -424,7 +430,7 @@ paths: schema: $ref: '#/components/schemas/404_response' delete: - summary: Delete a runtime field from a data view. + summary: Delete a runtime field from a data view operationId: deleteRuntimeField description: | This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -444,7 +450,7 @@ paths: schema: $ref: '#/components/schemas/404_response' post: - summary: Update an existing runtime field. + summary: Update a runtime field operationId: updateRuntimeField description: | This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -484,6 +490,461 @@ paths: application/json: schema: $ref: '#/components/schemas/400_response' + /api/data_views: + get: + summary: Get all data views in the default space + operationId: getAllDataViewsDefault + description: | + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + data_view: + type: array + items: + type: object + properties: + id: + type: string + name: + type: string + namespaces: + type: array + items: + type: string + title: + type: string + typeMeta: + type: object + examples: + getAllDataViewsResponse: + $ref: '#/components/examples/get_data_views_response' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/400_response' + /api/data_views/data_view: + post: + summary: Create a data view in the default space + operationId: createDataViewDefault + description: | + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/create_data_view_request_object' + examples: + createDataViewRequest: + $ref: '#/components/examples/create_data_view_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '#/components/schemas/data_view_response_object' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/400_response' + /api/data_views/data_view/{viewId}: + get: + summary: Get a data view in the default space + operationId: getDataViewDefault + description: | + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/view_id' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '#/components/schemas/data_view_response_object' + examples: + getDataViewResponse: + $ref: '#/components/examples/get_data_view_response' + '404': + description: Object is not found. + content: + application/json: + schema: + $ref: '#/components/schemas/404_response' + delete: + summary: Delete a data view from the default space + operationId: deleteDataViewDefault + description: | + WARNING: When you delete a data view, it cannot be recovered. This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/view_id' + responses: + '204': + description: Indicates a successful call. + '404': + description: Object is not found. + content: + application/json: + schema: + $ref: '#/components/schemas/404_response' + post: + summary: Update a data view in the default space + operationId: updateDataViewDefault + description: | + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/view_id' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/update_data_view_request_object' + examples: + updateDataViewRequest: + $ref: '#/components/examples/update_data_view_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '#/components/schemas/data_view_response_object' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/400_response' + /api/data_views/data_view/{viewId}/fields: + post: + summary: Update data view fields metadata in the default space + operationId: updateFieldsMetadataDefault + description: | + Update fields presentation metadata such as count, customLabel and format. This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. You can update multiple fields in one request. Updates are merged with persisted metadata. To remove existing metadata, specify null as the value. + tags: + - data views + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/view_id' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - fields + properties: + fields: + description: The field object. + type: object + examples: + updateFieldsMetadataRequest: + $ref: '#/components/examples/update_field_metadata_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + acknowledged: + type: boolean + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/400_response' + /api/data_views/data_view/{viewId}/runtime_field: + post: + summary: Create a runtime field in the default space + operationId: createRuntimeFieldDefault + description: | + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/view_id' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - name + - runtimeField + properties: + name: + type: string + description: | + The name for a runtime field. + runtimeField: + type: object + description: | + The runtime field definition object. + examples: + createRuntimeFieldRequest: + $ref: '#/components/examples/create_runtime_field_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + put: + summary: Create or update a runtime field in the default space + operationId: createUpdateRuntimeFieldDefault + description: | + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + - name: viewId + in: path + description: | + The ID of the data view fields you want to update. + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - name + - runtimeField + properties: + name: + type: string + description: | + The name for a runtime field. + runtimeField: + type: object + description: | + The runtime field definition object. + examples: + updateRuntimeFieldRequest: + $ref: '#/components/examples/create_runtime_field_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + data_view: + type: object + fields: + type: array + items: + type: object + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/400_response' + /api/data_views/data_view/{viewId}/runtime_field/{fieldName}: + get: + summary: Get a runtime field in the default space + operationId: getRuntimeFieldDefault + description: | + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/field_name' + - $ref: '#/components/parameters/view_id' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + data_view: + type: object + fields: + type: array + items: + type: object + examples: + getRuntimeFieldResponse: + $ref: '#/components/examples/get_runtime_field_response' + '404': + description: Object is not found. + content: + application/json: + schema: + $ref: '#/components/schemas/404_response' + delete: + summary: Delete a runtime field from a data view in the default space + operationId: deleteRuntimeFieldDefault + description: | + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/field_name' + - $ref: '#/components/parameters/view_id' + responses: + '200': + description: Indicates a successful call. + '404': + description: Object is not found. + content: + application/json: + schema: + $ref: '#/components/schemas/404_response' + post: + summary: Update a runtime field in the default space + operationId: updateRuntimeFieldDefault + description: | + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/field_name' + - $ref: '#/components/parameters/view_id' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - runtimeField + properties: + runtimeField: + type: object + description: | + The runtime field definition object. + + You can update following fields: + + - `type` + - `script` + examples: + updateRuntimeFieldRequest: + $ref: '#/components/examples/update_runtime_field_request' + responses: + '200': + description: Indicates a successful call. + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/400_response' + /api/data_views/default: + get: + summary: Get the default data view in the default space + operationId: getDefaultDataViewDefault + description: | + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + data_view_id: + type: string + examples: + getDefaultDataViewResponse: + $ref: '#/components/examples/get_default_data_view_response' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/400_response' + post: + summary: Set the default data view in the default space + operationId: setDefaultDatailViewDefault + description: | + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '#/components/parameters/kbn_xsrf' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - data_view_id + properties: + data_view_id: + type: string + nullable: true + description: | + The data view identifier. NOTE: The API does not validate whether it is a valid identifier. Use `null` to unset the default data view. + force: + type: boolean + description: Update an existing default data view identifier. + default: false + examples: + setDefaultDataViewRequest: + $ref: '#/components/examples/set_default_data_view_request' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + acknowledged: + type: boolean + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/400_response' components: securitySchemes: basicAuth: @@ -493,7 +954,8 @@ components: type: apiKey in: header name: Authorization - description: 'e.g. Authorization: ApiKey base64AccessApiKey' + description: | + Serverless APIs support only key-based authentication. You must create an API key and use the encoded value in the request header. For example: 'Authorization: ApiKey base64AccessApiKey'. parameters: space_id: in: path @@ -502,8 +964,7 @@ components: required: true schema: type: string - examples: - - default + example: default kbn_xsrf: schema: type: string @@ -1518,13 +1979,6 @@ components: type: long script: source: emit(doc["foo"].value) - create_runtime_field_response: - summary: The API returns created runtime field object array and updated data view object. - value: - data_view: - ...: null - fields: - - ... get_runtime_field_response: summary: The get runtime field API returns a JSON object that contains information about the runtime field (`hour_of_day`) and the data view (`d3d7af60-4c81-11e8-b3d7-01146121b73d`). value: diff --git a/src/plugins/data_views/docs/openapi/components/parameters/space_id.yaml b/src/plugins/data_views/docs/openapi/components/parameters/space_id.yaml index 45787e844caec..0a9fba457e3e7 100644 --- a/src/plugins/data_views/docs/openapi/components/parameters/space_id.yaml +++ b/src/plugins/data_views/docs/openapi/components/parameters/space_id.yaml @@ -4,5 +4,4 @@ description: An identifier for the space. If `/s/` and the identifier are omitte required: true schema: type: string - examples: - - default + example: default diff --git a/src/plugins/data_views/docs/openapi/entrypoint.yaml b/src/plugins/data_views/docs/openapi/entrypoint.yaml index 86d76dbfa42d4..fdd0d3054a4c8 100644 --- a/src/plugins/data_views/docs/openapi/entrypoint.yaml +++ b/src/plugins/data_views/docs/openapi/entrypoint.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.0 +openapi: 3.0.3 info: title: Data views description: OpenAPI schema for data view endpoints @@ -12,38 +12,41 @@ tags: - name: data views description: Data view APIs enable you to manage data views, formerly known as Kibana index patterns. servers: - - url: / + - url: 'https://{kibana_url}' + variables: + kibana_url: + default: localhost:5601 paths: -# Default space - # '/api/data_views': - # $ref: 'paths/api@data_views.yaml' - # '/api/data_views/data_view': - # $ref: 'paths/api@data_views@data_view.yaml' - # '/api/data_views/data_view/{viewId}': - # $ref: 'paths/api@data_views@data_view@{viewid}.yaml' - # '/api/data_views/data_view/{viewId}/fields': - # $ref: 'paths/api@data_views@data_view@{viewid}@fields.yaml' - # '/api/data_views/data_view/{viewId}/runtime_field': - # $ref: 'paths/api@data_views@data_view@{viewid}@runtime_field.yaml' - # '/api/data_views/data_view/{viewId}/runtime_field/{fieldName}': - # $ref: 'paths/api@data_views@data_view@{viewid}@runtime_field@{fieldname}.yaml' - # '/api/data_views/default': - # $ref: 'paths/api@data_views@default.yaml' # Non-default space - '/s/{spaceId}/api/data_views': - $ref: 'paths/s@{spaceid}@api@data_views.yaml' - '/s/{spaceId}/api/data_views/data_view': - $ref: 'paths/s@{spaceid}@api@data_views@data_view.yaml' - '/s/{spaceId}/api/data_views/data_view/{viewId}': - $ref: 'paths/s@{spaceid}@api@data_views@data_view@{viewid}.yaml' - '/s/{spaceId}/api/data_views/default': - $ref: 'paths/s@{spaceid}@api@data_views@default.yaml' - '/s/{spaceId}/api/data_views/data_view/{viewId}/fields': - $ref: 'paths/s@{spaceid}@api@data_views@data_view@{viewid}@fields.yaml' - '/s/{spaceId}/api/data_views/data_view/{viewId}/runtime_field': - $ref: 'paths/s@{spaceid}@api@data_views@data_view@{viewid}@runtime_field.yaml' - '/s/{spaceId}/api/data_views/data_view/{viewId}/runtime_field/{fieldName}': - $ref: 'paths/s@{spaceid}@api@data_views@data_view@{viewid}@runtime_field@{fieldname}.yaml' + '/s/{spaceId}/api/data_views': + $ref: 'paths/s@{spaceid}@api@data_views.yaml' + '/s/{spaceId}/api/data_views/data_view': + $ref: 'paths/s@{spaceid}@api@data_views@data_view.yaml' + '/s/{spaceId}/api/data_views/data_view/{viewId}': + $ref: 'paths/s@{spaceid}@api@data_views@data_view@{viewid}.yaml' + '/s/{spaceId}/api/data_views/default': + $ref: 'paths/s@{spaceid}@api@data_views@default.yaml' + '/s/{spaceId}/api/data_views/data_view/{viewId}/fields': + $ref: 'paths/s@{spaceid}@api@data_views@data_view@{viewid}@fields.yaml' + '/s/{spaceId}/api/data_views/data_view/{viewId}/runtime_field': + $ref: 'paths/s@{spaceid}@api@data_views@data_view@{viewid}@runtime_field.yaml' + '/s/{spaceId}/api/data_views/data_view/{viewId}/runtime_field/{fieldName}': + $ref: 'paths/s@{spaceid}@api@data_views@data_view@{viewid}@runtime_field@{fieldname}.yaml' +# Default space + '/api/data_views': + $ref: 'paths/api@data_views.yaml' + '/api/data_views/data_view': + $ref: 'paths/api@data_views@data_view.yaml' + '/api/data_views/data_view/{viewId}': + $ref: 'paths/api@data_views@data_view@{viewid}.yaml' + '/api/data_views/data_view/{viewId}/fields': + $ref: 'paths/api@data_views@data_view@{viewid}@fields.yaml' + '/api/data_views/data_view/{viewId}/runtime_field': + $ref: 'paths/api@data_views@data_view@{viewid}@runtime_field.yaml' + '/api/data_views/data_view/{viewId}/runtime_field/{fieldName}': + $ref: 'paths/api@data_views@data_view@{viewid}@runtime_field@{fieldname}.yaml' + '/api/data_views/default': + $ref: 'paths/api@data_views@default.yaml' components: securitySchemes: basicAuth: @@ -53,7 +56,10 @@ components: type: apiKey in: header name: Authorization - description: 'e.g. Authorization: ApiKey base64AccessApiKey' + description: > + Serverless APIs support only key-based authentication. + You must create an API key and use the encoded value in the request header. + For example: 'Authorization: ApiKey base64AccessApiKey'. security: - basicAuth: [] - apiKeyAuth: [] diff --git a/src/plugins/data_views/docs/openapi/paths/api@data_views.yaml b/src/plugins/data_views/docs/openapi/paths/api@data_views.yaml new file mode 100644 index 0000000000000..d68e957bdef10 --- /dev/null +++ b/src/plugins/data_views/docs/openapi/paths/api@data_views.yaml @@ -0,0 +1,41 @@ +get: + summary: Get all data views in the default space + operationId: getAllDataViewsDefault + description: > + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + data_view: + type: array + items: + type: object + properties: + id: + type: string + name: + type: string + namespaces: + type: array + items: + type: string + title: + type: string + typeMeta: + type: object + examples: + getAllDataViewsResponse: + $ref: '../components/examples/get_data_views_response.yaml' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '../components/schemas/400_response.yaml' diff --git a/src/plugins/data_views/docs/openapi/paths/api@data_views@data_view.yaml b/src/plugins/data_views/docs/openapi/paths/api@data_views@data_view.yaml new file mode 100644 index 0000000000000..95946468456fc --- /dev/null +++ b/src/plugins/data_views/docs/openapi/paths/api@data_views@data_view.yaml @@ -0,0 +1,31 @@ +post: + summary: Create a data view in the default space + operationId: createDataViewDefault + description: > + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '../components/headers/kbn_xsrf.yaml' + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/create_data_view_request_object.yaml' + examples: + createDataViewRequest: + $ref: '../components/examples/create_data_view_request.yaml' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '../components/schemas/data_view_response_object.yaml' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '../components/schemas/400_response.yaml' diff --git a/src/plugins/data_views/docs/openapi/paths/api@data_views@data_view@{viewid}.yaml b/src/plugins/data_views/docs/openapi/paths/api@data_views@data_view@{viewid}.yaml new file mode 100644 index 0000000000000..700bf7e5a8b65 --- /dev/null +++ b/src/plugins/data_views/docs/openapi/paths/api@data_views@data_view@{viewid}.yaml @@ -0,0 +1,79 @@ +get: + summary: Get a data view in the default space + operationId: getDataViewDefault + description: > + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '../components/parameters/view_id.yaml' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '../components/schemas/data_view_response_object.yaml' + examples: + getDataViewResponse: + $ref: '../components/examples/get_data_view_response.yaml' + '404': + description: Object is not found. + content: + application/json: + schema: + $ref: '../components/schemas/404_response.yaml' + +delete: + summary: Delete a data view from the default space + operationId: deleteDataViewDefault + description: > + WARNING: When you delete a data view, it cannot be recovered. + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '../components/headers/kbn_xsrf.yaml' + - $ref: '../components/parameters/view_id.yaml' + responses: + '204': + description: Indicates a successful call. + '404': + description: Object is not found. + content: + application/json: + schema: + $ref: '../components/schemas/404_response.yaml' + +post: + summary: Update a data view in the default space + operationId: updateDataViewDefault + description: > + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '../components/headers/kbn_xsrf.yaml' + - $ref: '../components/parameters/view_id.yaml' + requestBody: + required: true + content: + application/json: + schema: + $ref: '../components/schemas/update_data_view_request_object.yaml' + examples: + updateDataViewRequest: + $ref: '../components/examples/update_data_view_request.yaml' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + $ref: '../components/schemas/data_view_response_object.yaml' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '../components/schemas/400_response.yaml' diff --git a/src/plugins/data_views/docs/openapi/paths/api@data_views@data_view@{viewid}@fields.yaml b/src/plugins/data_views/docs/openapi/paths/api@data_views@data_view@{viewid}@fields.yaml new file mode 100644 index 0000000000000..58dd10b88b5d3 --- /dev/null +++ b/src/plugins/data_views/docs/openapi/paths/api@data_views@data_view@{viewid}@fields.yaml @@ -0,0 +1,42 @@ +post: + summary: Update data view fields metadata in the default space + operationId: updateFieldsMetadataDefault + description: > + Update fields presentation metadata such as count, customLabel and format. + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. You can update multiple fields in one request. Updates are merged with persisted metadata. To remove existing metadata, specify null as the value. + tags: + - data views + parameters: + - $ref: '../components/headers/kbn_xsrf.yaml' + - $ref: '../components/parameters/view_id.yaml' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - fields + properties: + fields: + description: The field object. + type: object + examples: + updateFieldsMetadataRequest: + $ref: '../components/examples/update_field_metadata_request.yaml' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + acknowledged: + type: boolean + '400': + description: Bad request + content: + application/json: + schema: + $ref: '../components/schemas/400_response.yaml' diff --git a/src/plugins/data_views/docs/openapi/paths/api@data_views@data_view@{viewid}@runtime_field.yaml b/src/plugins/data_views/docs/openapi/paths/api@data_views@data_view@{viewid}@runtime_field.yaml new file mode 100644 index 0000000000000..db0fcd61c7959 --- /dev/null +++ b/src/plugins/data_views/docs/openapi/paths/api@data_views@data_view@{viewid}@runtime_field.yaml @@ -0,0 +1,99 @@ +post: + summary: Create a runtime field in the default space + operationId: createRuntimeFieldDefault + description: > + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '../components/headers/kbn_xsrf.yaml' + - $ref: '../components/parameters/view_id.yaml' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - name + - runtimeField + properties: + name: + type: string + description: | + The name for a runtime field. + runtimeField: + type: object + description: | + The runtime field definition object. + examples: + createRuntimeFieldRequest: + $ref: '../components/examples/create_runtime_field_request.yaml' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + +put: + summary: Create or update a runtime field in the default space + operationId: createUpdateRuntimeFieldDefault + description: > + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '../components/headers/kbn_xsrf.yaml' + - name: viewId + in: path + description: | + The ID of the data view fields you want to update. + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - name + - runtimeField + properties: + name: + type: string + description: | + The name for a runtime field. + runtimeField: + type: object + description: | + The runtime field definition object. + examples: + updateRuntimeFieldRequest: + $ref: '../components/examples/create_runtime_field_request.yaml' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + data_view: + type: object + fields: + type: array + items: + type: object + # examples: + # createRuntimeFieldResponse: + # $ref: '../components/examples/create_runtime_field_response.yaml' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '../components/schemas/400_response.yaml' diff --git a/src/plugins/data_views/docs/openapi/paths/api@data_views@data_view@{viewid}@runtime_field@{fieldname}.yaml b/src/plugins/data_views/docs/openapi/paths/api@data_views@data_view@{viewid}@runtime_field@{fieldname}.yaml new file mode 100644 index 0000000000000..e94dcd1b5feea --- /dev/null +++ b/src/plugins/data_views/docs/openapi/paths/api@data_views@data_view@{viewid}@runtime_field@{fieldname}.yaml @@ -0,0 +1,94 @@ +get: + summary: Get a runtime field in the default space + operationId: getRuntimeFieldDefault + description: > + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '../components/parameters/field_name.yaml' + - $ref: '../components/parameters/view_id.yaml' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + data_view: + type: object + fields: + type: array + items: + type: object + examples: + getRuntimeFieldResponse: + $ref: '../components/examples/get_runtime_field_response.yaml' + '404': + description: Object is not found. + content: + application/json: + schema: + $ref: '../components/schemas/404_response.yaml' + +delete: + summary: Delete a runtime field from a data view in the default space + operationId: deleteRuntimeFieldDefault + description: > + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '../components/parameters/field_name.yaml' + - $ref: '../components/parameters/view_id.yaml' + responses: + '200': + description: Indicates a successful call. + '404': + description: Object is not found. + content: + application/json: + schema: + $ref: '../components/schemas/404_response.yaml' + +post: + summary: Update a runtime field in the default space + operationId: updateRuntimeFieldDefault + description: > + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '../components/parameters/field_name.yaml' + - $ref: '../components/parameters/view_id.yaml' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - runtimeField + properties: + runtimeField: + type: object + description: | + The runtime field definition object. + + You can update following fields: + + - `type` + - `script` + examples: + updateRuntimeFieldRequest: + $ref: '../components/examples/update_runtime_field_request.yaml' + responses: + '200': + description: Indicates a successful call. + '400': + description: Bad request + content: + application/json: + schema: + $ref: '../components/schemas/400_response.yaml' diff --git a/src/plugins/data_views/docs/openapi/paths/api@data_views@default.yaml b/src/plugins/data_views/docs/openapi/paths/api@data_views@default.yaml new file mode 100644 index 0000000000000..89628f7e25820 --- /dev/null +++ b/src/plugins/data_views/docs/openapi/paths/api@data_views@default.yaml @@ -0,0 +1,74 @@ +get: + summary: Get the default data view in the default space + operationId: getDefaultDataViewDefault + description: > + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + data_view_id: + type: string + examples: + getDefaultDataViewResponse: + $ref: '../components/examples/get_default_data_view_response.yaml' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '../components/schemas/400_response.yaml' +post: + summary: Set the default data view in the default space + operationId: setDefaultDatailViewDefault + description: > + This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. + tags: + - data views + parameters: + - $ref: '../components/headers/kbn_xsrf.yaml' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - data_view_id + properties: + data_view_id: + type: string + nullable: true + description: > + The data view identifier. + NOTE: The API does not validate whether it is a valid identifier. + Use `null` to unset the default data view. + force: + type: boolean + description: Update an existing default data view identifier. + default: false + examples: + setDefaultDataViewRequest: + $ref: '../components/examples/set_default_data_view_request.yaml' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object + properties: + acknowledged: + type: boolean + '400': + description: Bad request + content: + application/json: + schema: + $ref: '../components/schemas/400_response.yaml' diff --git a/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views.yaml b/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views.yaml index b51fd231e4332..25430e76ab629 100644 --- a/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views.yaml +++ b/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views.yaml @@ -1,5 +1,5 @@ get: - summary: Retrieves a list of all data views. + summary: Get all data views operationId: getAllDataViews description: > This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. diff --git a/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view.yaml b/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view.yaml index 230427f30187d..d9ae9f1ff5b30 100644 --- a/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view.yaml +++ b/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view.yaml @@ -1,5 +1,5 @@ post: - summary: Creates a data view. + summary: Create a data view operationId: createDataView description: > This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. diff --git a/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view@{viewid}.yaml b/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view@{viewid}.yaml index 157899bcb578f..eedecfc8c7a70 100644 --- a/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view@{viewid}.yaml +++ b/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view@{viewid}.yaml @@ -1,5 +1,5 @@ get: - summary: Retrieves a single data view by identifier. + summary: Get a data view operationId: getDataView description: > This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -26,7 +26,7 @@ get: $ref: '../components/schemas/404_response.yaml' delete: - summary: Deletes a data view. + summary: Delete a data view operationId: deleteDataView description: > WARNING: When you delete a data view, it cannot be recovered. @@ -48,7 +48,7 @@ delete: $ref: '../components/schemas/404_response.yaml' post: - summary: Updates a data view. + summary: Update a data view operationId: updateDataView description: > This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. diff --git a/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view@{viewid}@fields.yaml b/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view@{viewid}@fields.yaml index 5e7911e6b7aec..be7cda589cdf1 100644 --- a/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view@{viewid}@fields.yaml +++ b/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view@{viewid}@fields.yaml @@ -1,7 +1,8 @@ post: - summary: Update fields presentation metadata such as count, customLabel and format. + summary: Update data view fields metadata operationId: updateFieldsMetadata description: > + Update fields presentation metadata such as count, customLabel and format. This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. You can update multiple fields in one request. Updates are merged with persisted metadata. To remove existing metadata, specify null as the value. tags: - data views diff --git a/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view@{viewid}@runtime_field.yaml b/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view@{viewid}@runtime_field.yaml index dcc2783a82afa..baaf197446838 100644 --- a/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view@{viewid}@runtime_field.yaml +++ b/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view@{viewid}@runtime_field.yaml @@ -1,5 +1,5 @@ post: - summary: Creates a runtime field. + summary: Create a runtime field operationId: createRuntimeField description: > This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -30,9 +30,16 @@ post: examples: createRuntimeFieldRequest: $ref: '../components/examples/create_runtime_field_request.yaml' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: object put: - summary: Create or update an existing runtime field. + summary: Create or update a runtime field operationId: createUpdateRuntimeField description: > This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -83,9 +90,9 @@ put: type: array items: type: object - examples: - createRuntimeFieldResponse: - $ref: '../components/examples/create_runtime_field_response.yaml' + # examples: + # createRuntimeFieldResponse: + # $ref: '../components/examples/create_runtime_field_response.yaml' '400': description: Bad request content: diff --git a/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view@{viewid}@runtime_field@{fieldname}.yaml b/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view@{viewid}@runtime_field@{fieldname}.yaml index 7508613089a47..6f9f4fc1b3fa4 100644 --- a/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view@{viewid}@runtime_field@{fieldname}.yaml +++ b/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@data_view@{viewid}@runtime_field@{fieldname}.yaml @@ -1,5 +1,5 @@ get: - summary: Retrieves a runtime field. + summary: Get a runtime field operationId: getRuntimeField description: > This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -34,7 +34,7 @@ get: $ref: '../components/schemas/404_response.yaml' delete: - summary: Delete a runtime field from a data view. + summary: Delete a runtime field from a data view operationId: deleteRuntimeField description: > This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -55,7 +55,7 @@ delete: $ref: '../components/schemas/404_response.yaml' post: - summary: Update an existing runtime field. + summary: Update a runtime field operationId: updateRuntimeField description: > This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. diff --git a/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@default.yaml b/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@default.yaml index 6664e1fc45f97..aec99b494fd63 100644 --- a/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@default.yaml +++ b/src/plugins/data_views/docs/openapi/paths/s@{spaceid}@api@data_views@default.yaml @@ -1,5 +1,5 @@ get: - summary: Retrieves the default data view identifier. + summary: Get the default data view identifier operationId: getDefaultDataView description: > This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -27,7 +27,7 @@ get: schema: $ref: '../components/schemas/400_response.yaml' post: - summary: Sets the default data view identifier. + summary: Sets the default data view identifier operationId: setDefaultDatailView description: > This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. @@ -46,7 +46,8 @@ post: - data_view_id properties: data_view_id: - type: ['string', 'null'] + type: string + nullable: true description: > The data view identifier. NOTE: The API does not validate whether it is a valid identifier. diff --git a/src/plugins/discover/common/config.ts b/src/plugins/discover/common/config.ts index f3afc0ebf08be..272f81b77d5a8 100644 --- a/src/plugins/discover/common/config.ts +++ b/src/plugins/discover/common/config.ts @@ -13,9 +13,10 @@ export const configSchema = schema.object({ experimental: schema.maybe( schema.object({ ruleFormV2Enabled: schema.maybe(schema.boolean({ defaultValue: false })), + enabledProfiles: schema.maybe(schema.arrayOf(schema.string(), { defaultValue: [] })), }) ), }); export type ConfigSchema = TypeOf; -export type ExperimentalFeatures = ConfigSchema['experimental']; +export type ExperimentalFeatures = NonNullable; diff --git a/src/plugins/discover/common/data_sources/utils.ts b/src/plugins/discover/common/data_sources/utils.ts index 4bf8b0fcf3678..f876d0b4c06f2 100644 --- a/src/plugins/discover/common/data_sources/utils.ts +++ b/src/plugins/discover/common/data_sources/utils.ts @@ -25,3 +25,11 @@ export const isDataSourceType = ( dataSource: DiscoverDataSource | undefined, type: T ): dataSource is Extract => dataSource?.type === type; + +export const isDataViewSource = ( + dataSource: DiscoverDataSource | undefined +): dataSource is DataViewDataSource => isDataSourceType(dataSource, DataSourceType.DataView); + +export const isEsqlSource = ( + dataSource: DiscoverDataSource | undefined +): dataSource is EsqlDataSource => isDataSourceType(dataSource, DataSourceType.Esql); diff --git a/src/plugins/discover/public/application/context/services/context.predecessors.test.ts b/src/plugins/discover/public/application/context/services/context.predecessors.test.ts index e60fdb3563719..6ade7624c2bf9 100644 --- a/src/plugins/discover/public/application/context/services/context.predecessors.test.ts +++ b/src/plugins/discover/public/application/context/services/context.predecessors.test.ts @@ -104,7 +104,10 @@ describe('context predecessors', function () { ({ rows }) => { expect(mockSearchSource.fetch$.calledOnce).toBe(true); expect(rows).toEqual( - buildDataTableRecordList(mockSearchSource._stubHits.slice(0, 3), dataView) + buildDataTableRecordList({ + records: mockSearchSource._stubHits.slice(0, 3), + dataView, + }) ); } ); @@ -136,7 +139,10 @@ describe('context predecessors', function () { expect(Object.keys(last(intervals) ?? {})).toEqual(['format', 'gte']); expect(intervals.length).toBeGreaterThan(1); expect(rows).toEqual( - buildDataTableRecordList(mockSearchSource._stubHits.slice(0, 3), dataView) + buildDataTableRecordList({ + records: mockSearchSource._stubHits.slice(0, 3), + dataView, + }) ); } ); @@ -172,7 +178,10 @@ describe('context predecessors', function () { expect(intervals.length).toBeGreaterThan(1); expect(rows).toEqual( - buildDataTableRecordList(mockSearchSource._stubHits.slice(-3), dataView) + buildDataTableRecordList({ + records: mockSearchSource._stubHits.slice(-3), + dataView, + }) ); } ); @@ -263,7 +272,10 @@ describe('context predecessors', function () { expect(removeFieldsSpy.calledOnce).toBe(true); expect(setFieldsSpy.calledOnce).toBe(true); expect(rows).toEqual( - buildDataTableRecordList(mockSearchSource._stubHits.slice(0, 3), dataView) + buildDataTableRecordList({ + records: mockSearchSource._stubHits.slice(0, 3), + dataView, + }) ); } ); diff --git a/src/plugins/discover/public/application/context/services/context.successors.test.ts b/src/plugins/discover/public/application/context/services/context.successors.test.ts index 54037a7071f06..39cc05bd76571 100644 --- a/src/plugins/discover/public/application/context/services/context.successors.test.ts +++ b/src/plugins/discover/public/application/context/services/context.successors.test.ts @@ -104,7 +104,10 @@ describe('context successors', function () { ({ rows }) => { expect(mockSearchSource.fetch$.calledOnce).toBe(true); expect(rows).toEqual( - buildDataTableRecordList(mockSearchSource._stubHits.slice(-3), dataView) + buildDataTableRecordList({ + records: mockSearchSource._stubHits.slice(-3), + dataView, + }) ); } ); @@ -136,7 +139,10 @@ describe('context successors', function () { expect(Object.keys(last(intervals) ?? {})).toEqual(['format', 'lte']); expect(intervals.length).toBeGreaterThan(1); expect(rows).toEqual( - buildDataTableRecordList(mockSearchSource._stubHits.slice(-3), dataView) + buildDataTableRecordList({ + records: mockSearchSource._stubHits.slice(-3), + dataView, + }) ); } ); @@ -166,7 +172,10 @@ describe('context successors', function () { expect(moment(last(intervals)?.gte).valueOf()).toBeGreaterThan(MS_PER_DAY * 2200); expect(intervals.length).toBeGreaterThan(1); expect(rows).toEqual( - buildDataTableRecordList(mockSearchSource._stubHits.slice(0, 4), dataView) + buildDataTableRecordList({ + records: mockSearchSource._stubHits.slice(0, 4), + dataView, + }) ); } ); @@ -252,7 +261,10 @@ describe('context successors', function () { ({ rows, interceptedWarnings }) => { expect(mockSearchSource.fetch$.calledOnce).toBe(true); expect(rows).toEqual( - buildDataTableRecordList(mockSearchSource._stubHits.slice(-3), dataView) + buildDataTableRecordList({ + records: mockSearchSource._stubHits.slice(-3), + dataView, + }) ); const setFieldsSpy = mockSearchSource.setField.withArgs('fields'); const removeFieldsSpy = mockSearchSource.removeField.withArgs('fieldsFromSource'); @@ -324,7 +336,10 @@ describe('context successors', function () { ({ rows, interceptedWarnings }) => { expect(mockSearchSource.fetch$.calledOnce).toBe(true); expect(rows).toEqual( - buildDataTableRecordList(mockSearchSource._stubHits.slice(-3), dataView) + buildDataTableRecordList({ + records: mockSearchSource._stubHits.slice(-3), + dataView, + }) ); expect(dataPluginMock.search.showWarnings).toHaveBeenCalledTimes(1); expect(interceptedWarnings?.length).toBe(1); diff --git a/src/plugins/discover/public/application/main/data_fetching/fetch_documents.ts b/src/plugins/discover/public/application/main/data_fetching/fetch_documents.ts index 4ffdd211c0e5e..bcacf1aef8a87 100644 --- a/src/plugins/discover/public/application/main/data_fetching/fetch_documents.ts +++ b/src/plugins/discover/public/application/main/data_fetching/fetch_documents.ts @@ -10,7 +10,6 @@ import { filter, map } from 'rxjs'; import { lastValueFrom } from 'rxjs'; import { isRunningResponse, ISearchSource } from '@kbn/data-plugin/public'; import { buildDataTableRecordList } from '@kbn/discover-utils'; -import type { EsHitRecord } from '@kbn/discover-utils/types'; import type { SearchResponseWarning } from '@kbn/search-response-warnings'; import { DataViewType } from '@kbn/data-views-plugin/public'; import type { RecordsFetchResponse } from '../../types'; @@ -67,7 +66,9 @@ export const fetchDocuments = ( .pipe( filter((res) => !isRunningResponse(res)), map((res) => { - return buildDataTableRecordList(res.rawResponse.hits.hits as EsHitRecord[], dataView, { + return buildDataTableRecordList({ + records: res.rawResponse.hits.hits, + dataView, processRecord: (record) => services.profilesManager.resolveDocumentProfile({ record }), }); }) diff --git a/src/plugins/discover/public/application/main/hooks/use_esql_mode.test.tsx b/src/plugins/discover/public/application/main/hooks/use_esql_mode.test.tsx index 12109ea01a422..5f6d35afe8434 100644 --- a/src/plugins/discover/public/application/main/hooks/use_esql_mode.test.tsx +++ b/src/plugins/discover/public/application/main/hooks/use_esql_mode.test.tsx @@ -150,10 +150,11 @@ describe('useEsqlMode', () => { }); }); - test('changing an ES|QL query with same result columns should not change state when loading and finished', async () => { + test('changing an ES|QL query with same result columns but a different index pattern should change state when loading and finished', async () => { const { replaceUrlState, stateContainer } = renderHookWithContext(false); const documents$ = stateContainer.dataState.data$.documents$; stateContainer.dataState.data$.documents$.next(msgComplete); + replaceUrlState.mockReset(); documents$.next({ fetchStatus: FetchStatus.PARTIAL, @@ -166,7 +167,54 @@ describe('useEsqlMode', () => { ], query: { esql: 'from the-data-view-2' }, }); + await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1)); + + await waitFor(() => { + expect(replaceUrlState).toHaveBeenCalledWith({ + columns: [], + }); + }); + }); + + test('changing a ES|QL query with no transformational commands should not change state when loading and finished if index pattern is the same', async () => { + const { replaceUrlState, stateContainer } = renderHookWithContext(false); + const documents$ = stateContainer.dataState.data$.documents$; + stateContainer.dataState.data$.documents$.next(msgComplete); + await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(0)); + replaceUrlState.mockReset(); + + documents$.next({ + fetchStatus: FetchStatus.PARTIAL, + result: [ + { + id: '1', + raw: { field1: 1 }, + flattened: { field1: 1 }, + } as unknown as DataTableRecord, + ], + // non transformational command + query: { esql: 'from the-data-view-title | where field1 > 0' }, + }); await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(0)); + replaceUrlState.mockReset(); + + documents$.next({ + fetchStatus: FetchStatus.PARTIAL, + result: [ + { + id: '1', + raw: { field1: 1 }, + flattened: { field1: 1 }, + } as unknown as DataTableRecord, + ], + // non transformational command + query: { esql: 'from the-data-view-title2 | where field1 > 0' }, + }); + await waitFor(() => { + expect(replaceUrlState).toHaveBeenCalledWith({ + columns: [], + }); + }); }); test('only changing an ES|QL query with same result columns should not change columns', async () => { @@ -268,7 +316,13 @@ describe('useEsqlMode', () => { query: { esql: 'from the-data-view-title | keep field 1 | WHERE field1=1' }, }); - expect(replaceUrlState).toHaveBeenCalledTimes(0); + await waitFor(() => expect(replaceUrlState).toHaveBeenCalledTimes(1)); + await waitFor(() => { + expect(replaceUrlState).toHaveBeenCalledWith({ + columns: ['field1', 'field2'], + }); + }); + replaceUrlState.mockReset(); documents$.next({ fetchStatus: FetchStatus.PARTIAL, diff --git a/src/plugins/discover/public/application/main/hooks/use_esql_mode.ts b/src/plugins/discover/public/application/main/hooks/use_esql_mode.ts index 2f3c99763fb04..841badc11537c 100644 --- a/src/plugins/discover/public/application/main/hooks/use_esql_mode.ts +++ b/src/plugins/discover/public/application/main/hooks/use_esql_mode.ts @@ -7,8 +7,8 @@ */ import { isEqual } from 'lodash'; -import { isOfAggregateQueryType, getAggregateQueryMode } from '@kbn/es-query'; -import { hasTransformationalCommand } from '@kbn/esql-utils'; +import { isOfAggregateQueryType } from '@kbn/es-query'; +import { hasTransformationalCommand, getIndexPatternFromESQLQuery } from '@kbn/esql-utils'; import { useCallback, useEffect, useRef } from 'react'; import type { DataViewsContract } from '@kbn/data-views-plugin/public'; import { switchMap } from 'rxjs'; @@ -31,9 +31,9 @@ export function useEsqlMode({ }) { const prev = useRef<{ query: string; - columns: string[]; + recentlyUpdatedToColumns: string[]; }>({ - columns: [], + recentlyUpdatedToColumns: [], query: '', }); const initialFetch = useRef(true); @@ -43,9 +43,10 @@ export function useEsqlMode({ if (prev.current.query) { // cleanup when it's not an ES|QL query prev.current = { - columns: [], + recentlyUpdatedToColumns: [], query: '', }; + initialFetch.current = true; } }, []); @@ -57,55 +58,60 @@ export function useEsqlMode({ if (!query || next.fetchStatus === FetchStatus.ERROR) { return; } + const sendComplete = () => { stateContainer.dataState.data$.documents$.next({ ...next, fetchStatus: FetchStatus.COMPLETE, }); }; + const { viewMode } = stateContainer.appState.getState(); - let nextColumns: string[] = []; const isEsqlQuery = isOfAggregateQueryType(query); - const hasResults = Boolean(next.result?.length); - let queryHasTransformationalCommands = false; - if ('esql' in query) { - if (hasTransformationalCommand(query.esql)) { - queryHasTransformationalCommands = true; - } - } if (isEsqlQuery) { - const language = getAggregateQueryMode(query); + const hasResults = Boolean(next.result?.length); + if (next.fetchStatus !== FetchStatus.PARTIAL) { return; } + let nextColumns: string[] = prev.current.recentlyUpdatedToColumns; + if (hasResults) { - // check if state needs to contain column transformation due to a different columns in the resultset const firstRow = next.result![0]; - const firstRowColumns = Object.keys(firstRow.raw).slice(0, MAX_NUM_OF_COLUMNS); - if (!queryHasTransformationalCommands) { - nextColumns = []; - initialFetch.current = false; + const firstRowColumns = Object.keys(firstRow.raw); + + if (hasTransformationalCommand(query.esql)) { + nextColumns = firstRowColumns.slice(0, MAX_NUM_OF_COLUMNS); } else { - nextColumns = firstRowColumns; - if (initialFetch.current && !prev.current.columns.length) { - prev.current.columns = firstRowColumns; - } + nextColumns = []; } } - const addColumnsToState = !isEqual(nextColumns, prev.current.columns); - const queryChanged = query[language] !== prev.current.query; + + if (initialFetch.current) { + initialFetch.current = false; + prev.current.query = query.esql; + prev.current.recentlyUpdatedToColumns = nextColumns; + } + + const indexPatternChanged = + getIndexPatternFromESQLQuery(query.esql) !== + getIndexPatternFromESQLQuery(prev.current.query); + + const addColumnsToState = + indexPatternChanged || !isEqual(nextColumns, prev.current.recentlyUpdatedToColumns); + const changeViewMode = viewMode !== getValidViewMode({ viewMode, isEsqlMode: true }); - if (!queryChanged || (!addColumnsToState && !changeViewMode)) { + + if (!indexPatternChanged && !addColumnsToState && !changeViewMode) { sendComplete(); return; } - if (queryChanged) { - prev.current.query = query[language]; - prev.current.columns = nextColumns; - } + prev.current.query = query.esql; + prev.current.recentlyUpdatedToColumns = nextColumns; + // just change URL state if necessary if (addColumnsToState || changeViewMode) { const nextState = { diff --git a/src/plugins/discover/public/application/main/state_management/discover_internal_state_container.ts b/src/plugins/discover/public/application/main/state_management/discover_internal_state_container.ts index 4b26822bf04a5..4ebbe94832d0c 100644 --- a/src/plugins/discover/public/application/main/state_management/discover_internal_state_container.ts +++ b/src/plugins/discover/public/application/main/state_management/discover_internal_state_container.ts @@ -73,6 +73,8 @@ export function getInternalStateContainer() { setDataView: (prevState: InternalState) => (nextDataView: DataView) => ({ ...prevState, dataView: nextDataView, + expandedDoc: + nextDataView?.id !== prevState.dataView?.id ? undefined : prevState.expandedDoc, }), setIsDataViewLoading: (prevState: InternalState) => (loading: boolean) => ({ ...prevState, @@ -130,6 +132,7 @@ export function getInternalStateContainer() { resetOnSavedSearchChange: (prevState: InternalState) => () => ({ ...prevState, overriddenVisContextAfterInvalidation: undefined, + expandedDoc: undefined, }), }, {}, diff --git a/src/plugins/discover/public/application/main/state_management/utils/get_esql_data_view.ts b/src/plugins/discover/public/application/main/state_management/utils/get_esql_data_view.ts index c4dd4d7e163b8..7f30ced5be035 100644 --- a/src/plugins/discover/public/application/main/state_management/utils/get_esql_data_view.ts +++ b/src/plugins/discover/public/application/main/state_management/utils/get_esql_data_view.ts @@ -21,14 +21,7 @@ export async function getEsqlDataView( currentDataView?.isPersisted() || indexPatternFromQuery !== currentDataView?.getIndexPattern() ) { - const dataViewObj = await getESQLAdHocDataview(indexPatternFromQuery, services.dataViews); - - // If the indexPatternFromQuery is empty string means that the user used either the ROW or SHOW META / SHOW INFO commands - // we don't want to add the @timestamp field in this case https://github.com/elastic/kibana/issues/163417 - if (indexPatternFromQuery && dataViewObj.fields.getByName('@timestamp')?.type === 'date') { - dataViewObj.timeFieldName = '@timestamp'; - } - return dataViewObj; + return await getESQLAdHocDataview(indexPatternFromQuery, services.dataViews); } return currentDataView; } diff --git a/src/plugins/discover/public/build_services.ts b/src/plugins/discover/public/build_services.ts index 519d6a36fb528..e1ccef0105d48 100644 --- a/src/plugins/discover/public/build_services.ts +++ b/src/plugins/discover/public/build_services.ts @@ -56,7 +56,7 @@ import { memoize, noop } from 'lodash'; import type { NoDataPagePluginStart } from '@kbn/no-data-page-plugin/public'; import type { AiopsPluginStart } from '@kbn/aiops-plugin/public'; import type { DataVisualizerPluginStart } from '@kbn/data-visualizer-plugin/public'; -import type { DiscoverStartPlugins } from './plugin'; +import type { DiscoverStartPlugins } from './types'; import type { DiscoverContextAppLocator } from './application/context/services/locator'; import type { DiscoverSingleDocLocator } from './application/doc/locator'; import type { DiscoverAppLocator } from '../common'; diff --git a/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.test.tsx b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.test.tsx index cb02e3b736663..2129610aad3a1 100644 --- a/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.test.tsx +++ b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.test.tsx @@ -19,7 +19,7 @@ import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefi import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { DataTableRecord, EsHitRecord } from '@kbn/discover-utils/types'; -import { buildDataTableRecord } from '@kbn/discover-utils'; +import { buildDataTableRecord, buildDataTableRecordList } from '@kbn/discover-utils'; import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; import { setUnifiedDocViewerServices } from '@kbn/unified-doc-viewer-plugin/public/plugin'; @@ -64,19 +64,8 @@ const waitNextUpdate = async (component: ReactWrapper) => { }; describe('Discover flyout', function () { - const mountComponent = async ({ - dataView, - hits, - hitIndex, - query, - }: { - dataView?: DataView; - hits?: DataTableRecord[]; - hitIndex?: number; - query?: Query | AggregateQuery; - }) => { - const onClose = jest.fn(); - const services = { + const getServices = () => { + return { ...discoverServiceMock, filterManager: createFilterManagerMock(), addBasePath: (path: string) => `/base${path}`, @@ -92,22 +81,35 @@ describe('Discover flyout', function () { addSuccess: jest.fn(), }, } as unknown as DiscoverServices; + }; + + const mountComponent = async ({ + dataView, + records, + expandedHit, + query, + services = getServices(), + }: { + dataView?: DataView; + records?: DataTableRecord[]; + expandedHit?: EsHitRecord; + query?: Query | AggregateQuery; + services?: DiscoverServices; + }) => { + const onClose = jest.fn(); setUnifiedDocViewerServices(mockUnifiedDocViewerServices); - const hit = buildDataTableRecord( - hitIndex ? esHitsMock[hitIndex] : (esHitsMock[0] as EsHitRecord), - dataViewMock - ); + const currentRecords = + records || + esHitsMock.map((entry: EsHitRecord) => buildDataTableRecord(entry, dataView || dataViewMock)); const props = { columns: ['date'], dataView: dataView || dataViewMock, - hit, - hits: - hits || - esHitsMock.map((entry: EsHitRecord) => - buildDataTableRecord(entry, dataView || dataViewMock) - ), + hit: expandedHit + ? buildDataTableRecord(expandedHit, dataView || dataViewMock) + : currentRecords[0], + hits: currentRecords, query, onAddColumn: jest.fn(), onClose, @@ -131,6 +133,7 @@ describe('Discover flyout', function () { beforeEach(() => { mockFlyoutCustomization.actions.defaultActions = undefined; mockFlyoutCustomization.Content = undefined; + mockFlyoutCustomization.title = undefined; jest.clearAllMocks(); (useDiscoverCustomization as jest.Mock).mockImplementation(() => mockFlyoutCustomization); @@ -158,19 +161,19 @@ describe('Discover flyout', function () { it('displays document navigation when there is more than 1 doc available', async () => { const { component } = await mountComponent({ dataView: dataViewWithTimefieldMock }); - const docNav = findTestSubject(component, 'dscDocNavigation'); + const docNav = findTestSubject(component, 'docViewerFlyoutNavigation'); expect(docNav.length).toBeTruthy(); }); it('displays no document navigation when there are 0 docs available', async () => { - const { component } = await mountComponent({ hits: [] }); - const docNav = findTestSubject(component, 'dscDocNavigation'); + const { component } = await mountComponent({ records: [], expandedHit: esHitsMock[0] }); + const docNav = findTestSubject(component, 'docViewerFlyoutNavigation'); expect(docNav.length).toBeFalsy(); }); it('displays no document navigation when the expanded doc is not part of the given docs', async () => { // scenario: you've expanded a doc, and in the next request differed docs where fetched - const hits = [ + const records = [ { _index: 'new', _id: '1', @@ -186,8 +189,8 @@ describe('Discover flyout', function () { _source: { date: '2020-20-01T12:12:12.124', name: 'test2', extension: 'jpg' }, }, ].map((hit) => buildDataTableRecord(hit, dataViewMock)); - const { component } = await mountComponent({ hits }); - const docNav = findTestSubject(component, 'dscDocNavigation'); + const { component } = await mountComponent({ records, expandedHit: esHitsMock[0] }); + const docNav = findTestSubject(component, 'docViewerFlyoutNavigation'); expect(docNav.length).toBeFalsy(); }); @@ -208,14 +211,18 @@ describe('Discover flyout', function () { it('doesnt allow you to navigate to the next doc, if expanded doc is the last', async () => { // scenario: you've expanded a doc, and in the next request differed docs where fetched - const { component, props } = await mountComponent({ hitIndex: esHitsMock.length - 1 }); + const { component, props } = await mountComponent({ + expandedHit: esHitsMock[esHitsMock.length - 1], + }); findTestSubject(component, 'pagination-button-next').simulate('click'); expect(props.setExpandedDoc).toHaveBeenCalledTimes(0); }); it('allows you to navigate to the previous doc, if expanded doc is the last', async () => { // scenario: you've expanded a doc, and in the next request differed docs where fetched - const { component, props } = await mountComponent({ hitIndex: esHitsMock.length - 1 }); + const { component, props } = await mountComponent({ + expandedHit: esHitsMock[esHitsMock.length - 1], + }); findTestSubject(component, 'pagination-button-previous').simulate('click'); expect(props.setExpandedDoc).toHaveBeenCalledTimes(1); expect(props.setExpandedDoc.mock.calls[0][0].raw._id).toBe('4'); @@ -223,19 +230,19 @@ describe('Discover flyout', function () { it('allows navigating with arrow keys through documents', async () => { const { component, props } = await mountComponent({}); - findTestSubject(component, 'docTableDetailsFlyout').simulate('keydown', { key: 'ArrowRight' }); + findTestSubject(component, 'docViewerFlyout').simulate('keydown', { key: 'ArrowRight' }); expect(props.setExpandedDoc).toHaveBeenCalledWith(expect.objectContaining({ id: 'i::2::' })); component.setProps({ ...props, hit: props.hits[1] }); - findTestSubject(component, 'docTableDetailsFlyout').simulate('keydown', { key: 'ArrowLeft' }); + findTestSubject(component, 'docViewerFlyout').simulate('keydown', { key: 'ArrowLeft' }); expect(props.setExpandedDoc).toHaveBeenCalledWith(expect.objectContaining({ id: 'i::1::' })); }); it('should not navigate with keypresses when already at the border of documents', async () => { const { component, props } = await mountComponent({}); - findTestSubject(component, 'docTableDetailsFlyout').simulate('keydown', { key: 'ArrowLeft' }); + findTestSubject(component, 'docViewerFlyout').simulate('keydown', { key: 'ArrowLeft' }); expect(props.setExpandedDoc).not.toHaveBeenCalled(); component.setProps({ ...props, hit: props.hits[props.hits.length - 1] }); - findTestSubject(component, 'docTableDetailsFlyout').simulate('keydown', { key: 'ArrowRight' }); + findTestSubject(component, 'docViewerFlyout').simulate('keydown', { key: 'ArrowRight' }); expect(props.setExpandedDoc).not.toHaveBeenCalled(); }); @@ -260,7 +267,7 @@ describe('Discover flyout', function () { }); const singleDocumentView = findTestSubject(component, 'docTableRowAction'); expect(singleDocumentView.length).toBeFalsy(); - const flyoutTitle = findTestSubject(component, 'docTableRowDetailsTitle'); + const flyoutTitle = findTestSubject(component, 'docViewerRowDetailsTitle'); expect(flyoutTitle.text()).toBe('Result'); }); @@ -272,7 +279,7 @@ describe('Discover flyout', function () { const { component } = await mountComponent({}); - const titleNode = findTestSubject(component, 'docTableRowDetailsTitle'); + const titleNode = findTestSubject(component, 'docViewerRowDetailsTitle'); expect(titleNode.text()).toBe(customTitle); }); @@ -470,5 +477,37 @@ describe('Discover flyout', function () { expect(props.onFilter).toHaveBeenCalled(); }); }); + + describe('context awareness', () => { + it('should render flyout per the defined document profile', async () => { + const services = getServices(); + const hits = [ + { + _index: 'new', + _id: '1', + _score: 1, + _type: '_doc', + _source: { date: '2020-20-01T12:12:12.123', message: 'test1', bytes: 20 }, + }, + { + _index: 'new', + _id: '2', + _score: 1, + _type: '_doc', + _source: { date: '2020-20-01T12:12:12.124', name: 'test2', extension: 'jpg' }, + }, + ]; + const records = buildDataTableRecordList({ + records: hits as EsHitRecord[], + dataView: dataViewMock, + processRecord: (record) => services.profilesManager.resolveDocumentProfile({ record }), + }); + const { component } = await mountComponent({ records, services }); + const title = findTestSubject(component, 'docViewerRowDetailsTitle'); + expect(title.text()).toBe('Document #new::1::'); + const content = findTestSubject(component, 'kbnDocViewer'); + expect(content.text()).toBe('Mock tab'); + }); + }); }); }); diff --git a/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx index d6273d7669391..778d05435e709 100644 --- a/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx +++ b/src/plugins/discover/public/components/discover_grid_flyout/discover_grid_flyout.tsx @@ -6,37 +6,21 @@ * Side Public License, v 1. */ -import React, { useMemo, useCallback } from 'react'; -import { get } from 'lodash'; -import { i18n } from '@kbn/i18n'; -import { css } from '@emotion/react'; +import React, { useMemo } from 'react'; import type { DataView } from '@kbn/data-views-plugin/public'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiFlyoutResizable, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiFlyoutHeader, - EuiTitle, - EuiSpacer, - EuiPortal, - EuiPagination, - keys, - EuiButtonEmpty, - useEuiTheme, - useIsWithinMinBreakpoint, -} from '@elastic/eui'; import { Filter, Query, AggregateQuery, isOfAggregateQueryType } from '@kbn/es-query'; import type { DataTableRecord } from '@kbn/discover-utils/types'; import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; import type { DataTableColumnsMeta } from '@kbn/unified-data-table'; -import { UnifiedDocViewer } from '@kbn/unified-doc-viewer-plugin/public'; -import useLocalStorage from 'react-use/lib/useLocalStorage'; +import type { DocViewsRegistry } from '@kbn/unified-doc-viewer'; +import { UnifiedDocViewerFlyout } from '@kbn/unified-doc-viewer-plugin/public'; import { useDiscoverServices } from '../../hooks/use_discover_services'; import { useFlyoutActions } from './use_flyout_actions'; import { useDiscoverCustomization } from '../../customizations'; import { DiscoverGridFlyoutActions } from './discover_grid_flyout_actions'; +import { useProfileAccessor } from '../../context_awareness'; + +export const FLYOUT_WIDTH_KEY = 'discover:flyoutWidth'; export interface DiscoverGridFlyoutProps { savedSearchId?: string; @@ -54,13 +38,6 @@ export interface DiscoverGridFlyoutProps { setExpandedDoc: (doc?: DataTableRecord) => void; } -function getIndexByDocId(hits: DataTableRecord[], id: string) { - return hits.findIndex((h) => { - return h.id === id; - }); -} - -export const FLYOUT_WIDTH_KEY = 'discover:flyoutWidth'; /** * Flyout displaying an expanded Elasticsearch document */ @@ -81,212 +58,60 @@ export function DiscoverGridFlyout({ }: DiscoverGridFlyoutProps) { const services = useDiscoverServices(); const flyoutCustomization = useDiscoverCustomization('flyout'); - const { euiTheme } = useEuiTheme(); - const isXlScreen = useIsWithinMinBreakpoint('xl'); - const DEFAULT_WIDTH = euiTheme.base * 34; - const defaultWidth = flyoutCustomization?.size ?? DEFAULT_WIDTH; // Give enough room to search bar to not wrap - const [flyoutWidth, setFlyoutWidth] = useLocalStorage(FLYOUT_WIDTH_KEY, defaultWidth); - const minWidth = euiTheme.base * 24; - const maxWidth = euiTheme.breakpoint.xl; - const isEsqlQuery = isOfAggregateQueryType(query); + const isESQLQuery = isOfAggregateQueryType(query); // Get actual hit with updated highlighted searches const actualHit = useMemo(() => hits?.find(({ id }) => id === hit?.id) || hit, [hit, hits]); - const pageCount = useMemo(() => (hits ? hits.length : 0), [hits]); - const activePage = useMemo(() => { - const id = hit.id; - if (!hits || pageCount <= 1) { - return -1; - } - - return getIndexByDocId(hits, id); - }, [hits, hit, pageCount]); - - const setPage = useCallback( - (index: number) => { - if (hits && hits[index]) { - setExpandedDoc(hits[index]); - } - }, - [hits, setExpandedDoc] - ); - - const onKeyDown = useCallback( - (ev: React.KeyboardEvent) => { - const nodeName = get(ev, 'target.nodeName', null); - if (typeof nodeName === 'string' && nodeName.toLowerCase() === 'input') { - return; - } - if (ev.key === keys.ARROW_LEFT || ev.key === keys.ARROW_RIGHT) { - ev.preventDefault(); - ev.stopPropagation(); - setPage(activePage + (ev.key === keys.ARROW_RIGHT ? 1 : -1)); - } - }, - [activePage, setPage] - ); const { flyoutActions } = useFlyoutActions({ actions: flyoutCustomization?.actions, dataView, - rowIndex: hit.raw._index, - rowId: hit.raw._id, + rowIndex: actualHit.raw._index, + rowId: actualHit.raw._id, columns, filters, savedSearchId, }); - const addColumn = useCallback( - (columnName: string) => { - onAddColumn(columnName); - services.toastNotifications.addSuccess( - i18n.translate('discover.grid.flyout.toastColumnAdded', { - defaultMessage: `Column ''{columnName}'' was added`, - values: { columnName }, - }) - ); - }, - [onAddColumn, services.toastNotifications] - ); - - const removeColumn = useCallback( - (columnName: string) => { - onRemoveColumn(columnName); - services.toastNotifications.addSuccess( - i18n.translate('discover.grid.flyout.toastColumnRemoved', { - defaultMessage: `Column ''{columnName}'' was removed`, - values: { columnName }, - }) - ); - }, - [onRemoveColumn, services.toastNotifications] - ); - - const renderDefaultContent = useCallback( - () => ( - - ), - [ - actualHit, - addColumn, - columns, - columnsMeta, - dataView, - hits, - isEsqlQuery, - onFilter, - removeColumn, - flyoutCustomization?.docViewsRegistry, - ] - ); - - const contentActions = useMemo( - () => ({ - filter: onFilter, - onAddColumn: addColumn, - onRemoveColumn: removeColumn, - }), - [onFilter, addColumn, removeColumn] - ); - - const bodyContent = flyoutCustomization?.Content ? ( - - ) : ( - renderDefaultContent() - ); - - const defaultFlyoutTitle = isEsqlQuery - ? i18n.translate('discover.grid.tableRow.docViewerEsqlDetailHeading', { - defaultMessage: 'Result', - }) - : i18n.translate('discover.grid.tableRow.docViewerDetailHeading', { - defaultMessage: 'Document', - }); - const flyoutTitle = flyoutCustomization?.title ?? defaultFlyoutTitle; + const getDocViewerAccessor = useProfileAccessor('getDocViewer', { + record: actualHit, + }); + const docViewer = useMemo(() => { + const getDocViewer = getDocViewerAccessor(() => ({ + title: flyoutCustomization?.title, + docViewsRegistry: (registry: DocViewsRegistry) => + typeof flyoutCustomization?.docViewsRegistry === 'function' + ? flyoutCustomization.docViewsRegistry(registry) + : registry, + })); + + return getDocViewer({ record: actualHit }); + }, [flyoutCustomization, getDocViewerAccessor, actualHit]); return ( - - - - - - -

{flyoutTitle}

-
-
- {activePage !== -1 && ( - - - - )} -
- {isEsqlQuery || !flyoutActions.length ? null : ( - <> - - - - )} -
- {bodyContent} - - - {i18n.translate('discover.grid.flyout.close', { - defaultMessage: 'Close', - })} - - -
-
+ 0 ? ( + + ) : null + } + flyoutWidthLocalStorageKey={FLYOUT_WIDTH_KEY} + FlyoutCustomBody={flyoutCustomization?.Content} + services={services} + docViewsRegistry={docViewer.docViewsRegistry} + isEsqlQuery={isESQLQuery} + hit={hit} + hits={hits} + dataView={dataView} + columns={columns} + columnsMeta={columnsMeta} + onAddColumn={onAddColumn} + onRemoveColumn={onRemoveColumn} + onClose={onClose} + onFilter={onFilter} + setExpandedDoc={setExpandedDoc} + /> ); } diff --git a/src/plugins/discover/public/components/doc_table/components/table_row.test.tsx b/src/plugins/discover/public/components/doc_table/components/table_row.test.tsx index ad8b1c5a2f547..523d254b85311 100644 --- a/src/plugins/discover/public/components/doc_table/components/table_row.test.tsx +++ b/src/plugins/discover/public/components/doc_table/components/table_row.test.tsx @@ -113,16 +113,16 @@ describe('Doc table row component', () => { describe('details row', () => { it('should be empty by default', () => { const component = mountComponent(defaultProps); - expect(findTestSubject(component, 'docTableRowDetailsTitle').exists()).toBeFalsy(); + expect(findTestSubject(component, 'docViewerRowDetailsTitle').exists()).toBeFalsy(); }); it('should expand the detail row when the toggle arrow is clicked', () => { const component = mountComponent(defaultProps); const toggleButton = findTestSubject(component, 'docTableExpandToggleColumn'); - expect(findTestSubject(component, 'docTableRowDetailsTitle').exists()).toBeFalsy(); + expect(findTestSubject(component, 'docViewerRowDetailsTitle').exists()).toBeFalsy(); toggleButton.simulate('click'); - expect(findTestSubject(component, 'docTableRowDetailsTitle').exists()).toBeTruthy(); + expect(findTestSubject(component, 'docViewerRowDetailsTitle').exists()).toBeTruthy(); }); it('should hide the single/surrounding views for ES|QL mode', () => { @@ -133,7 +133,7 @@ describe('Doc table row component', () => { const component = mountComponent(props); const toggleButton = findTestSubject(component, 'docTableExpandToggleColumn'); toggleButton.simulate('click'); - expect(findTestSubject(component, 'docTableRowDetailsTitle').text()).toBe('Expanded result'); + expect(findTestSubject(component, 'docViewerRowDetailsTitle').text()).toBe('Expanded result'); expect(findTestSubject(component, 'docTableRowAction').length).toBeFalsy(); }); }); diff --git a/src/plugins/discover/public/components/doc_table/components/table_row_details.tsx b/src/plugins/discover/public/components/doc_table/components/table_row_details.tsx index 313b9e12090aa..03cee5ad78462 100644 --- a/src/plugins/discover/public/components/doc_table/components/table_row_details.tsx +++ b/src/plugins/discover/public/components/doc_table/components/table_row_details.tsx @@ -58,7 +58,7 @@ export const TableRowDetails = ({ - +

{isEsqlMode && ( { +export const createContextAwarenessMocks = ({ + shouldRegisterProviders = true, +}: { shouldRegisterProviders?: boolean } = {}) => { const rootProfileProviderMock: RootProfileProvider = { profileId: 'root-profile', profile: { @@ -61,6 +64,24 @@ export const createContextAwarenessMocks = () => { ...prev(), rootProfile: () => 'document-profile', })), + getDocViewer: (prev) => (params) => { + const recordId = params.record.id; + const prevValue = prev(params); + return { + title: `${prevValue.title ?? 'Document'} #${recordId}`, + docViewsRegistry: (registry) => { + registry.add({ + id: 'doc_view_mock', + title: 'Mock tab', + order: 10, + component: () => { + return null; + }, + }); + return prevValue.docViewsRegistry(registry); + }, + }; + }, } as DocumentProfileProvider['profile'], resolve: jest.fn(() => ({ isMatch: true, @@ -73,15 +94,15 @@ export const createContextAwarenessMocks = () => { const records = getDataTableRecords(dataViewWithTimefieldMock); const contextRecordMock = records[0]; const contextRecordMock2 = records[1]; - const rootProfileServiceMock = new RootProfileService(); - rootProfileServiceMock.registerProvider(rootProfileProviderMock); - const dataSourceProfileServiceMock = new DataSourceProfileService(); - dataSourceProfileServiceMock.registerProvider(dataSourceProfileProviderMock); - const documentProfileServiceMock = new DocumentProfileService(); - documentProfileServiceMock.registerProvider(documentProfileProviderMock); + + if (shouldRegisterProviders) { + rootProfileServiceMock.registerProvider(rootProfileProviderMock); + dataSourceProfileServiceMock.registerProvider(dataSourceProfileProviderMock); + documentProfileServiceMock.registerProvider(documentProfileProviderMock); + } const profilesManagerMock = new ProfilesManager( rootProfileServiceMock, @@ -89,12 +110,18 @@ export const createContextAwarenessMocks = () => { documentProfileServiceMock ); + const profileProviderServices = createProfileProviderServices(); + return { rootProfileProviderMock, dataSourceProfileProviderMock, documentProfileProviderMock, + rootProfileServiceMock, + dataSourceProfileServiceMock, + documentProfileServiceMock, contextRecordMock, contextRecordMock2, profilesManagerMock, + profileProviderServices, }; }; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/example_data_source_profile/index.ts b/src/plugins/discover/public/context_awareness/profile_providers/example_data_source_profile/index.ts new file mode 100644 index 0000000000000..032da55d4f6d0 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/example_data_source_profile/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { exampleDataSourceProfileProvider } from './profile'; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/example_data_source_profile/profile.tsx b/src/plugins/discover/public/context_awareness/profile_providers/example_data_source_profile/profile.tsx new file mode 100644 index 0000000000000..080c2ffed222b --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/example_data_source_profile/profile.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EuiBadge } from '@elastic/eui'; +import type { DataTableRecord } from '@kbn/discover-utils'; +import { isOfAggregateQueryType } from '@kbn/es-query'; +import { getIndexPatternFromESQLQuery } from '@kbn/esql-utils'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { capitalize } from 'lodash'; +import React from 'react'; +import { DataSourceType, isDataSourceType } from '../../../../common/data_sources'; +import { DataSourceCategory, DataSourceProfileProvider } from '../../profiles'; + +export const exampleDataSourceProfileProvider: DataSourceProfileProvider = { + profileId: 'example-data-source-profile', + profile: { + getCellRenderers: (prev) => () => ({ + ...prev(), + 'log.level': (props) => { + const level = getFieldValue(props.row, 'log.level'); + + if (!level) { + return ( + + (None) + + ); + } + + const levelMap: Record = { + info: 'primary', + debug: 'default', + error: 'danger', + }; + + return ( + + {capitalize(level)} + + ); + }, + }), + }, + resolve: (params) => { + let indexPattern: string | undefined; + + if (isDataSourceType(params.dataSource, DataSourceType.Esql)) { + if (!isOfAggregateQueryType(params.query)) { + return { isMatch: false }; + } + + indexPattern = getIndexPatternFromESQLQuery(params.query.esql); + } else if (isDataSourceType(params.dataSource, DataSourceType.DataView) && params.dataView) { + indexPattern = params.dataView.getIndexPattern(); + } + + if (indexPattern !== 'my-example-logs') { + return { isMatch: false }; + } + + return { + isMatch: true, + context: { category: DataSourceCategory.Logs }, + }; + }, +}; + +const getFieldValue = (record: DataTableRecord, field: string) => { + const value = record.flattened[field]; + return Array.isArray(value) ? value[0] : value; +}; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/example_document_profile/index.ts b/src/plugins/discover/public/context_awareness/profile_providers/example_document_profile/index.ts new file mode 100644 index 0000000000000..dab5364d8af3e --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/example_document_profile/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { exampleDocumentProfileProvider } from './profile'; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/example_document_profile/profile.ts b/src/plugins/discover/public/context_awareness/profile_providers/example_document_profile/profile.ts new file mode 100644 index 0000000000000..303efa103e327 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/example_document_profile/profile.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { DataTableRecord } from '@kbn/discover-utils'; +import { DocumentProfileProvider, DocumentType } from '../../profiles'; + +export const exampleDocumentProfileProvider: DocumentProfileProvider = { + profileId: 'example-document-profile', + profile: {}, + resolve: (params) => { + if (getFieldValue(params.record, 'data_stream.type') !== 'logs') { + return { isMatch: false }; + } + + return { + isMatch: true, + context: { + type: DocumentType.Log, + }, + }; + }, +}; + +const getFieldValue = (record: DataTableRecord, field: string) => { + const value = record.flattened[field]; + return Array.isArray(value) ? value[0] : value; +}; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/example_root_pofile/index.ts b/src/plugins/discover/public/context_awareness/profile_providers/example_root_pofile/index.ts new file mode 100644 index 0000000000000..53f4cdc955db4 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/example_root_pofile/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { exampleRootProfileProvider } from './profile'; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/example_root_pofile/profile.tsx b/src/plugins/discover/public/context_awareness/profile_providers/example_root_pofile/profile.tsx new file mode 100644 index 0000000000000..389059c518217 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/example_root_pofile/profile.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EuiBadge } from '@elastic/eui'; +import type { DataTableRecord } from '@kbn/discover-utils'; +import React from 'react'; +import { RootProfileProvider, SolutionType } from '../../profiles'; + +export const exampleRootProfileProvider: RootProfileProvider = { + profileId: 'example-root-profile', + profile: { + getCellRenderers: (prev) => () => ({ + ...prev(), + '@timestamp': (props) => { + const timestamp = getFieldValue(props.row, '@timestamp'); + + return ( + + {timestamp} + + ); + }, + }), + }, + resolve: (params) => { + if (params.solutionNavId != null) { + return { isMatch: false }; + } + + return { isMatch: true, context: { solutionType: SolutionType.Default } }; + }, +}; + +const getFieldValue = (record: DataTableRecord, field: string) => { + const value = record.flattened[field]; + return Array.isArray(value) ? value[0] : value; +}; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/index.ts b/src/plugins/discover/public/context_awareness/profile_providers/index.ts new file mode 100644 index 0000000000000..1a5979f5a37f8 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { registerProfileProviders } from './register_profile_providers'; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/log_document_profile/index.ts b/src/plugins/discover/public/context_awareness/profile_providers/log_document_profile/index.ts new file mode 100644 index 0000000000000..c31d26b4a8989 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/log_document_profile/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { createLogDocumentProfileProvider } from './profile'; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/log_document_profile/profile.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/log_document_profile/profile.test.ts new file mode 100644 index 0000000000000..6514eb699e553 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/log_document_profile/profile.test.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { buildDataTableRecord } from '@kbn/discover-utils'; +import { DocumentType } from '../../profiles'; +import { createContextAwarenessMocks } from '../../__mocks__'; +import { createLogDocumentProfileProvider } from './profile'; + +const mockServices = createContextAwarenessMocks().profileProviderServices; + +describe('logDocumentProfileProvider', () => { + const logDocumentProfileProvider = createLogDocumentProfileProvider(mockServices); + const RESOLUTION_MATCH = { + isMatch: true, + context: { + type: DocumentType.Log, + }, + }; + const RESOLUTION_MISMATCH = { + isMatch: false, + }; + + it('matches records with the correct data stream type', () => { + expect( + logDocumentProfileProvider.resolve({ + record: buildMockRecord('logs-2000-01-01', { + 'data_stream.type': ['logs'], + }), + }) + ).toEqual(RESOLUTION_MATCH); + }); + + it('matches records with fields prefixed with "log."', () => { + expect( + logDocumentProfileProvider.resolve({ + record: buildMockRecord('logs-2000-01-01', { + 'log.level': ['INFO'], + }), + }) + ).toEqual(RESOLUTION_MATCH); + }); + + it('matches records with indices matching the allowed pattern', () => { + expect( + logDocumentProfileProvider.resolve({ + record: buildMockRecord('logs-2000-01-01'), + }) + ).toEqual(RESOLUTION_MATCH); + expect( + logDocumentProfileProvider.resolve({ + record: buildMockRecord('remote_cluster:filebeat'), + }) + ).toEqual(RESOLUTION_MATCH); + }); + + it('does not match records with neither characteristic', () => { + expect( + logDocumentProfileProvider.resolve({ + record: buildMockRecord('another-index'), + }) + ).toEqual(RESOLUTION_MISMATCH); + }); +}); + +const buildMockRecord = (index: string, fields: Record = {}) => + buildDataTableRecord({ + _id: '', + _index: index, + fields: { + _index: index, + ...fields, + }, + }); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/log_document_profile/profile.ts b/src/plugins/discover/public/context_awareness/profile_providers/log_document_profile/profile.ts new file mode 100644 index 0000000000000..2d03c9e35398a --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/log_document_profile/profile.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DataTableRecord } from '@kbn/discover-utils'; +import { DocumentProfileProvider, DocumentType } from '../../profiles'; +import { ProfileProviderServices } from '../profile_provider_services'; + +export const createLogDocumentProfileProvider = ( + services: ProfileProviderServices +): DocumentProfileProvider => ({ + profileId: 'log-document-profile', + profile: {}, + resolve: ({ record }) => { + const isLogRecord = getIsLogRecord(record, services.logsContextService.isLogsIndexPattern); + + if (!isLogRecord) { + return { isMatch: false }; + } + + return { + isMatch: true, + context: { + type: DocumentType.Log, + }, + }; + }, +}); + +const getIsLogRecord = ( + record: DataTableRecord, + isLogsIndexPattern: ProfileProviderServices['logsContextService']['isLogsIndexPattern'] +) => { + return ( + getDataStreamType(record).includes('logs') || + hasFieldsWithPrefix('log.')(record) || + getIndices(record).some(isLogsIndexPattern) + ); +}; + +const getFieldValues = + (field: string) => + (record: DataTableRecord): unknown[] => { + const value = record.flattened[field]; + return Array.isArray(value) ? value : [value]; + }; + +const getDataStreamType = getFieldValues('data_stream.type'); +const getIndices = getFieldValues('_index'); + +const hasFieldsWithPrefix = (prefix: string) => (record: DataTableRecord) => { + return Object.keys(record.flattened).some((field) => field.startsWith(prefix)); +}; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/index.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/index.ts new file mode 100644 index 0000000000000..f7d780da6ef02 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { createLogsDataSourceProfileProvider } from './profile'; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/profile.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/profile.test.ts new file mode 100644 index 0000000000000..e6b99802235cf --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/profile.test.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createStubIndexPattern } from '@kbn/data-views-plugin/common/data_view.stub'; +import { createDataViewDataSource, createEsqlDataSource } from '../../../../common/data_sources'; +import { DataSourceCategory } from '../../profiles'; +import { createContextAwarenessMocks } from '../../__mocks__'; +import { createLogsDataSourceProfileProvider } from './profile'; + +const mockServices = createContextAwarenessMocks().profileProviderServices; + +describe('logsDataSourceProfileProvider', () => { + const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(mockServices); + const VALID_INDEX_PATTERN = 'logs-nginx.access-*'; + const MIXED_INDEX_PATTERN = 'logs-nginx.access-*,metrics-*'; + const INVALID_INDEX_PATTERN = 'my_source-access-*'; + + const RESOLUTION_MATCH = { + isMatch: true, + context: { category: DataSourceCategory.Logs }, + }; + const RESOLUTION_MISMATCH = { + isMatch: false, + }; + + it('should match ES|QL sources with an allowed index pattern in its query', () => { + expect( + logsDataSourceProfileProvider.resolve({ + dataSource: createEsqlDataSource(), + query: { esql: `from ${VALID_INDEX_PATTERN}` }, + }) + ).toEqual(RESOLUTION_MATCH); + }); + + it('should NOT match ES|QL sources with a mixed or not allowed index pattern in its query', () => { + expect( + logsDataSourceProfileProvider.resolve({ + dataSource: createEsqlDataSource(), + query: { esql: `from ${INVALID_INDEX_PATTERN}` }, + }) + ).toEqual(RESOLUTION_MISMATCH); + expect( + logsDataSourceProfileProvider.resolve({ + dataSource: createEsqlDataSource(), + query: { esql: `from ${MIXED_INDEX_PATTERN}` }, + }) + ).toEqual(RESOLUTION_MISMATCH); + }); + + it('should match data view sources with an allowed index pattern', () => { + expect( + logsDataSourceProfileProvider.resolve({ + dataSource: createDataViewDataSource({ dataViewId: VALID_INDEX_PATTERN }), + dataView: createStubIndexPattern({ spec: { title: VALID_INDEX_PATTERN } }), + }) + ).toEqual(RESOLUTION_MATCH); + }); + + it('should NOT match data view sources with a mixed or not allowed index pattern', () => { + expect( + logsDataSourceProfileProvider.resolve({ + dataSource: createDataViewDataSource({ dataViewId: INVALID_INDEX_PATTERN }), + dataView: createStubIndexPattern({ spec: { title: INVALID_INDEX_PATTERN } }), + }) + ).toEqual(RESOLUTION_MISMATCH); + expect( + logsDataSourceProfileProvider.resolve({ + dataSource: createDataViewDataSource({ dataViewId: MIXED_INDEX_PATTERN }), + dataView: createStubIndexPattern({ spec: { title: MIXED_INDEX_PATTERN } }), + }) + ).toEqual(RESOLUTION_MISMATCH); + }); +}); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/profile.ts b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/profile.ts new file mode 100644 index 0000000000000..e4a12aa9e4baf --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/logs_data_source_profile/profile.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { isOfAggregateQueryType } from '@kbn/es-query'; +import { getIndexPatternFromESQLQuery } from '@kbn/esql-utils'; +import { isDataViewSource, isEsqlSource } from '../../../../common/data_sources'; +import { + DataSourceCategory, + DataSourceProfileProvider, + DataSourceProfileProviderParams, +} from '../../profiles'; +import { ProfileProviderServices } from '../profile_provider_services'; + +export const createLogsDataSourceProfileProvider = ( + services: ProfileProviderServices +): DataSourceProfileProvider => ({ + profileId: 'logs-data-source-profile', + profile: {}, + resolve: (params) => { + const indexPattern = extractIndexPatternFrom(params); + + if (!services.logsContextService.isLogsIndexPattern(indexPattern)) { + return { isMatch: false }; + } + + return { + isMatch: true, + context: { category: DataSourceCategory.Logs }, + }; + }, +}); + +const extractIndexPatternFrom = ({ + dataSource, + dataView, + query, +}: DataSourceProfileProviderParams) => { + if (isEsqlSource(dataSource) && isOfAggregateQueryType(query)) { + return getIndexPatternFromESQLQuery(query.esql); + } else if (isDataViewSource(dataSource) && dataView) { + return dataView.getIndexPattern(); + } + + return null; +}; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/profile_provider_services.ts b/src/plugins/discover/public/context_awareness/profile_providers/profile_provider_services.ts new file mode 100644 index 0000000000000..eced97659ecfa --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/profile_provider_services.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createLogsContextService, LogsContextService } from '@kbn/discover-utils'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ProfileProviderDeps { + // We will probably soon add uiSettings as a dependency + // to consume user configured indices +} + +export interface ProfileProviderServices { + logsContextService: LogsContextService; +} + +export const createProfileProviderServices = ( + _deps: ProfileProviderDeps = {} +): ProfileProviderServices => { + return { + logsContextService: createLogsContextService(), + }; +}; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.test.ts new file mode 100644 index 0000000000000..c58726ecc1c11 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.test.ts @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createEsqlDataSource } from '../../../common/data_sources'; +import { createContextAwarenessMocks } from '../__mocks__'; +import { exampleDataSourceProfileProvider } from './example_data_source_profile'; +import { exampleDocumentProfileProvider } from './example_document_profile'; +import { exampleRootProfileProvider } from './example_root_pofile'; +import { + registerEnabledProfileProviders, + registerProfileProviders, +} from './register_profile_providers'; + +describe('registerEnabledProfileProviders', () => { + it('should register enabled profile providers', async () => { + const { rootProfileServiceMock, rootProfileProviderMock } = createContextAwarenessMocks({ + shouldRegisterProviders: false, + }); + registerEnabledProfileProviders({ + profileService: rootProfileServiceMock, + availableProviders: [rootProfileProviderMock], + enabledProfileIds: ['root-profile'], + }); + const context = await rootProfileServiceMock.resolve({ solutionNavId: null }); + expect(rootProfileServiceMock.getProfile(context)).toBe(rootProfileProviderMock.profile); + }); + + it('should not register disabled profile providers', async () => { + const { rootProfileServiceMock, rootProfileProviderMock } = createContextAwarenessMocks({ + shouldRegisterProviders: false, + }); + registerEnabledProfileProviders({ + profileService: rootProfileServiceMock, + availableProviders: [rootProfileProviderMock], + enabledProfileIds: [], + }); + const context = await rootProfileServiceMock.resolve({ solutionNavId: null }); + expect(rootProfileServiceMock.getProfile(context)).not.toBe(rootProfileProviderMock.profile); + }); +}); + +describe('registerProfileProviders', () => { + it('should register enabled experimental profile providers', async () => { + const { rootProfileServiceMock, dataSourceProfileServiceMock, documentProfileServiceMock } = + createContextAwarenessMocks({ + shouldRegisterProviders: false, + }); + registerProfileProviders({ + rootProfileService: rootProfileServiceMock, + dataSourceProfileService: dataSourceProfileServiceMock, + documentProfileService: documentProfileServiceMock, + experimentalProfileIds: [ + exampleRootProfileProvider.profileId, + exampleDataSourceProfileProvider.profileId, + exampleDocumentProfileProvider.profileId, + ], + }); + const rootContext = await rootProfileServiceMock.resolve({ solutionNavId: null }); + const dataSourceContext = await dataSourceProfileServiceMock.resolve({ + dataSource: createEsqlDataSource(), + query: { esql: 'from my-example-logs' }, + }); + const documentContext = documentProfileServiceMock.resolve({ + record: { + id: 'test', + flattened: { 'data_stream.type': 'logs' }, + raw: {}, + }, + }); + expect(rootProfileServiceMock.getProfile(rootContext)).toBe(exampleRootProfileProvider.profile); + expect(dataSourceProfileServiceMock.getProfile(dataSourceContext)).toBe( + exampleDataSourceProfileProvider.profile + ); + expect(documentProfileServiceMock.getProfile(documentContext)).toBe( + exampleDocumentProfileProvider.profile + ); + }); + + it('should not register disabled experimental profile providers', async () => { + const { rootProfileServiceMock, dataSourceProfileServiceMock, documentProfileServiceMock } = + createContextAwarenessMocks({ + shouldRegisterProviders: false, + }); + registerProfileProviders({ + rootProfileService: rootProfileServiceMock, + dataSourceProfileService: dataSourceProfileServiceMock, + documentProfileService: documentProfileServiceMock, + experimentalProfileIds: [], + }); + const rootContext = await rootProfileServiceMock.resolve({ solutionNavId: null }); + const dataSourceContext = await dataSourceProfileServiceMock.resolve({ + dataSource: createEsqlDataSource(), + query: { esql: 'from my-example-logs' }, + }); + const documentContext = documentProfileServiceMock.resolve({ + record: { + id: 'test', + flattened: { 'data_stream.type': 'logs' }, + raw: {}, + }, + }); + expect(rootProfileServiceMock.getProfile(rootContext)).not.toBe( + exampleRootProfileProvider.profile + ); + expect(dataSourceProfileServiceMock.getProfile(dataSourceContext)).not.toBe( + exampleDataSourceProfileProvider.profile + ); + expect(documentProfileServiceMock.getProfile(documentContext)).not.toBe( + exampleDocumentProfileProvider.profile + ); + }); +}); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.ts b/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.ts new file mode 100644 index 0000000000000..1f4f2fbb7d931 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { uniq } from 'lodash'; +import type { + DataSourceProfileService, + DocumentProfileService, + RootProfileService, +} from '../profiles'; +import type { BaseProfileProvider, BaseProfileService } from '../profile_service'; +import { exampleDataSourceProfileProvider } from './example_data_source_profile'; +import { exampleDocumentProfileProvider } from './example_document_profile'; +import { exampleRootProfileProvider } from './example_root_pofile'; +import { createLogsDataSourceProfileProvider } from './logs_data_source_profile'; +import { createLogDocumentProfileProvider } from './log_document_profile'; +import { createProfileProviderServices } from './profile_provider_services'; + +export const registerProfileProviders = ({ + rootProfileService, + dataSourceProfileService, + documentProfileService, + experimentalProfileIds, +}: { + rootProfileService: RootProfileService; + dataSourceProfileService: DataSourceProfileService; + documentProfileService: DocumentProfileService; + experimentalProfileIds: string[]; +}) => { + const providerServices = createProfileProviderServices(); + const logsDataSourceProfileProvider = createLogsDataSourceProfileProvider(providerServices); + const logsDocumentProfileProvider = createLogDocumentProfileProvider(providerServices); + const rootProfileProviders = [exampleRootProfileProvider]; + const dataSourceProfileProviders = [ + exampleDataSourceProfileProvider, + logsDataSourceProfileProvider, + ]; + const documentProfileProviders = [exampleDocumentProfileProvider, logsDocumentProfileProvider]; + const enabledProfileIds = uniq([ + logsDataSourceProfileProvider.profileId, + logsDocumentProfileProvider.profileId, + ...experimentalProfileIds, + ]); + + registerEnabledProfileProviders({ + profileService: rootProfileService, + availableProviders: rootProfileProviders, + enabledProfileIds, + }); + + registerEnabledProfileProviders({ + profileService: dataSourceProfileService, + availableProviders: dataSourceProfileProviders, + enabledProfileIds, + }); + + registerEnabledProfileProviders({ + profileService: documentProfileService, + availableProviders: documentProfileProviders, + enabledProfileIds, + }); +}; + +export const registerEnabledProfileProviders = < + TProvider extends BaseProfileProvider<{}>, + TService extends BaseProfileService +>({ + profileService, + availableProviders, + enabledProfileIds, +}: { + profileService: TService; + availableProviders: TProvider[]; + enabledProfileIds: string[]; +}) => { + for (const profile of availableProviders) { + if (enabledProfileIds.includes(profile.profileId)) { + profileService.registerProvider(profile); + } + } +}; diff --git a/src/plugins/discover/public/context_awareness/profile_service.ts b/src/plugins/discover/public/context_awareness/profile_service.ts index 2b43595761d19..efc143e222414 100644 --- a/src/plugins/discover/public/context_awareness/profile_service.ts +++ b/src/plugins/discover/public/context_awareness/profile_service.ts @@ -15,38 +15,33 @@ export type ResolveProfileResult = | { isMatch: true; context: TContext } | { isMatch: false }; -export type ProfileProviderMode = 'sync' | 'async'; +export type ContextWithProfileId = TContext & { profileId: string }; -export interface ProfileProvider< - TProfile extends PartialProfile, - TParams, - TContext, - TMode extends ProfileProviderMode -> { +export interface BaseProfileProvider { profileId: string; profile: ComposableProfile; +} + +export interface ProfileProvider + extends BaseProfileProvider { + resolve: (params: TParams) => ResolveProfileResult; +} + +export interface AsyncProfileProvider + extends BaseProfileProvider { resolve: ( params: TParams - ) => TMode extends 'sync' - ? ResolveProfileResult - : ResolveProfileResult | Promise>; + ) => ResolveProfileResult | Promise>; } -export type ContextWithProfileId = TContext & { profileId: string }; - const EMPTY_PROFILE = {}; -abstract class BaseProfileService< - TProfile extends PartialProfile, - TParams, - TContext, - TMode extends ProfileProviderMode -> { - protected readonly providers: Array> = []; +export abstract class BaseProfileService, TContext> { + protected readonly providers: TProvider[] = []; protected constructor(public readonly defaultContext: ContextWithProfileId) {} - public registerProvider(provider: ProfileProvider) { + public registerProvider(provider: TProvider) { this.providers.push(provider); } @@ -54,19 +49,13 @@ abstract class BaseProfileService< const provider = this.providers.find((current) => current.profileId === context.profileId); return provider?.profile ?? EMPTY_PROFILE; } - - public abstract resolve( - params: TParams - ): TMode extends 'sync' - ? ContextWithProfileId - : Promise>; } export class ProfileService< TProfile extends PartialProfile, TParams, TContext -> extends BaseProfileService { +> extends BaseProfileService, TContext> { public resolve(params: TParams) { for (const provider of this.providers) { const result = provider.resolve(params); @@ -87,7 +76,7 @@ export class AsyncProfileService< TProfile extends PartialProfile, TParams, TContext -> extends BaseProfileService { +> extends BaseProfileService, TContext> { public async resolve(params: TParams) { for (const provider of this.providers) { const result = await provider.resolve(params); diff --git a/src/plugins/discover/public/context_awareness/profiles/data_source_profile.ts b/src/plugins/discover/public/context_awareness/profiles/data_source_profile.ts index f616fef913259..1dbb3c20d8b51 100644 --- a/src/plugins/discover/public/context_awareness/profiles/data_source_profile.ts +++ b/src/plugins/discover/public/context_awareness/profiles/data_source_profile.ts @@ -9,7 +9,7 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import type { AggregateQuery, Query } from '@kbn/es-query'; import type { DiscoverDataSource } from '../../../common/data_sources'; -import { AsyncProfileService } from '../profile_service'; +import { AsyncProfileProvider, AsyncProfileService } from '../profile_service'; import { Profile } from '../types'; export enum DataSourceCategory { @@ -17,6 +17,8 @@ export enum DataSourceCategory { Default = 'default', } +export type DataSourceProfile = Profile; + export interface DataSourceProfileProviderParams { dataSource?: DiscoverDataSource; dataView?: DataView; @@ -27,7 +29,11 @@ export interface DataSourceContext { category: DataSourceCategory; } -export type DataSourceProfile = Profile; +export type DataSourceProfileProvider = AsyncProfileProvider< + DataSourceProfile, + DataSourceProfileProviderParams, + DataSourceContext +>; export class DataSourceProfileService extends AsyncProfileService< DataSourceProfile, @@ -41,5 +47,3 @@ export class DataSourceProfileService extends AsyncProfileService< }); } } - -export type DataSourceProfileProvider = Parameters[0]; diff --git a/src/plugins/discover/public/context_awareness/profiles/document_profile.ts b/src/plugins/discover/public/context_awareness/profiles/document_profile.ts index 70b134da452e4..c6fd19b8f2014 100644 --- a/src/plugins/discover/public/context_awareness/profiles/document_profile.ts +++ b/src/plugins/discover/public/context_awareness/profiles/document_profile.ts @@ -8,13 +8,15 @@ import type { DataTableRecord } from '@kbn/discover-utils'; import type { Profile } from '../types'; -import { ProfileService } from '../profile_service'; +import { ProfileProvider, ProfileService } from '../profile_service'; export enum DocumentType { Log = 'log', Default = 'default', } +export type DocumentProfile = Omit; + export interface DocumentProfileProviderParams { record: DataTableRecord; } @@ -23,7 +25,11 @@ export interface DocumentContext { type: DocumentType; } -export type DocumentProfile = Omit; +export type DocumentProfileProvider = ProfileProvider< + DocumentProfile, + DocumentProfileProviderParams, + DocumentContext +>; export class DocumentProfileService extends ProfileService< DocumentProfile, @@ -37,5 +43,3 @@ export class DocumentProfileService extends ProfileService< }); } } - -export type DocumentProfileProvider = Parameters[0]; diff --git a/src/plugins/discover/public/context_awareness/profiles/example_profiles.tsx b/src/plugins/discover/public/context_awareness/profiles/example_profiles.tsx deleted file mode 100644 index 3835337b25304..0000000000000 --- a/src/plugins/discover/public/context_awareness/profiles/example_profiles.tsx +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { EuiBadge } from '@elastic/eui'; -import { - DataTableRecord, - getMessageFieldWithFallbacks, - LogDocumentOverview, -} from '@kbn/discover-utils'; -import { isOfAggregateQueryType } from '@kbn/es-query'; -import { getIndexPatternFromESQLQuery } from '@kbn/esql-utils'; -import { euiThemeVars } from '@kbn/ui-theme'; -import { capitalize } from 'lodash'; -import React from 'react'; -import { DataSourceType, isDataSourceType } from '../../../common/data_sources'; -import { DataSourceCategory, DataSourceProfileProvider } from './data_source_profile'; -import { DocumentProfileProvider, DocumentType } from './document_profile'; -import { RootProfileProvider, SolutionType } from './root_profile'; - -export const o11yRootProfileProvider: RootProfileProvider = { - profileId: 'o11y-root-profile', - profile: {}, - resolve: (params) => { - if (params.solutionNavId === 'oblt') { - return { - isMatch: true, - context: { - solutionType: SolutionType.Observability, - }, - }; - } - - return { isMatch: false }; - }, -}; - -export const logsDataSourceProfileProvider: DataSourceProfileProvider = { - profileId: 'logs-data-source-profile', - profile: { - getCellRenderers: (prev) => () => ({ - ...prev(), - '@timestamp': (props) => { - const timestamp = getFieldValue(props.row, '@timestamp'); - return ( - - {timestamp} - - ); - }, - 'log.level': (props) => { - const level = getFieldValue(props.row, 'log.level'); - if (!level) { - return (None); - } - const levelMap: Record = { - info: 'primary', - debug: 'default', - error: 'danger', - }; - return ( - - {capitalize(level)} - - ); - }, - message: (props) => { - const { value } = getMessageFieldWithFallbacks( - props.row.flattened as unknown as LogDocumentOverview - ); - return value || (None); - }, - }), - }, - resolve: (params) => { - let indices: string[] = []; - - if (isDataSourceType(params.dataSource, DataSourceType.Esql)) { - if (!isOfAggregateQueryType(params.query)) { - return { isMatch: false }; - } - - indices = getIndexPatternFromESQLQuery(params.query.esql).split(','); - } else if (isDataSourceType(params.dataSource, DataSourceType.DataView) && params.dataView) { - indices = params.dataView.getIndexPattern().split(','); - } - - if (indices.every((index) => index.startsWith('logs-'))) { - return { - isMatch: true, - context: { category: DataSourceCategory.Logs }, - }; - } - - return { isMatch: false }; - }, -}; - -export const logDocumentProfileProvider: DocumentProfileProvider = { - profileId: 'log-document-profile', - profile: {}, - resolve: (params) => { - if (getFieldValue(params.record, 'data_stream.type') === 'logs') { - return { - isMatch: true, - context: { - type: DocumentType.Log, - }, - }; - } - - return { isMatch: false }; - }, -}; - -const getFieldValue = (record: DataTableRecord, field: string) => { - const value = record.flattened[field]; - return Array.isArray(value) ? value[0] : value; -}; diff --git a/src/plugins/discover/public/context_awareness/profiles/root_profile.ts b/src/plugins/discover/public/context_awareness/profiles/root_profile.ts index 42497fe680c5c..77bf9d9d63b0b 100644 --- a/src/plugins/discover/public/context_awareness/profiles/root_profile.ts +++ b/src/plugins/discover/public/context_awareness/profiles/root_profile.ts @@ -7,7 +7,7 @@ */ import type { Profile } from '../types'; -import { AsyncProfileService } from '../profile_service'; +import { AsyncProfileProvider, AsyncProfileService } from '../profile_service'; export enum SolutionType { Observability = 'oblt', @@ -16,6 +16,8 @@ export enum SolutionType { Default = 'default', } +export type RootProfile = Profile; + export interface RootProfileProviderParams { solutionNavId?: string | null; } @@ -24,7 +26,11 @@ export interface RootContext { solutionType: SolutionType; } -export type RootProfile = Profile; +export type RootProfileProvider = AsyncProfileProvider< + RootProfile, + RootProfileProviderParams, + RootContext +>; export class RootProfileService extends AsyncProfileService< RootProfile, @@ -38,5 +44,3 @@ export class RootProfileService extends AsyncProfileService< }); } } - -export type RootProfileProvider = Parameters[0]; diff --git a/src/plugins/discover/public/context_awareness/types.ts b/src/plugins/discover/public/context_awareness/types.ts index b612b2ce29907..9dbec9fab8dae 100644 --- a/src/plugins/discover/public/context_awareness/types.ts +++ b/src/plugins/discover/public/context_awareness/types.ts @@ -7,7 +7,19 @@ */ import type { CustomCellRenderer } from '@kbn/unified-data-table'; +import type { DocViewsRegistry } from '@kbn/unified-doc-viewer'; +import type { DataTableRecord } from '@kbn/discover-utils'; + +export interface DocViewerExtension { + title: string | undefined; + docViewsRegistry: (prevRegistry: DocViewsRegistry) => DocViewsRegistry; +} + +export interface DocViewerExtensionParams { + record: DataTableRecord; +} export interface Profile { getCellRenderers: () => CustomCellRenderer; + getDocViewer: (params: DocViewerExtensionParams) => DocViewerExtension; } diff --git a/src/plugins/discover/public/index.ts b/src/plugins/discover/public/index.ts index a2ce384dd0e60..b54356c8fd50b 100644 --- a/src/plugins/discover/public/index.ts +++ b/src/plugins/discover/public/index.ts @@ -9,7 +9,7 @@ import type { PluginInitializerContext } from '@kbn/core/public'; import { DiscoverPlugin } from './plugin'; -export type { DiscoverSetup, DiscoverStart } from './plugin'; +export type { DiscoverSetup, DiscoverStart } from './types'; export function plugin(initializerContext: PluginInitializerContext) { return new DiscoverPlugin(initializerContext); } @@ -24,7 +24,6 @@ export type { DiscoverCustomization, DiscoverCustomizationService, FlyoutCustomization, - FlyoutContentProps, SearchBarCustomization, UnifiedHistogramCustomization, TopNavCustomization, diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index 7228070fe2d2c..78da5820f1ddb 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { ComponentType } from 'react'; +import React from 'react'; import { BehaviorSubject, map, Observable } from 'rxjs'; import { AppMountParameters, @@ -17,42 +17,11 @@ import { PluginInitializerContext, ScopedHistory, } from '@kbn/core/public'; -import { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; -import { ExpressionsSetup, ExpressionsStart } from '@kbn/expressions-plugin/public'; -import { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public'; -import { ChartsPluginStart } from '@kbn/charts-plugin/public'; -import type { GlobalSearchPluginSetup } from '@kbn/global-search-plugin/public'; -import { NavigationPublicPluginStart as NavigationStart } from '@kbn/navigation-plugin/public'; -import { SharePluginStart, SharePluginSetup } from '@kbn/share-plugin/public'; -import { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding-plugin/public'; -import { HomePublicPluginSetup } from '@kbn/home-plugin/public'; -import { Start as InspectorPublicPluginStart } from '@kbn/inspector-plugin/public'; -import { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public'; import { ENABLE_ESQL } from '@kbn/esql-utils'; -import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; -import { IndexPatternFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public'; -import { DataViewsServicePublic } from '@kbn/data-views-plugin/public'; -import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; -import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; -import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; -import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; -import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; -import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public'; -import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; -import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public'; -import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; -import type { UnifiedDocViewerStart } from '@kbn/unified-doc-viewer-plugin/public'; import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public'; -import type { LensPublicStart } from '@kbn/lens-plugin/public'; import { TRUNCATE_MAX_HEIGHT } from '@kbn/discover-utils'; -import type { NoDataPagePluginStart } from '@kbn/no-data-page-plugin/public'; -import type { - ObservabilityAIAssistantPublicSetup, - ObservabilityAIAssistantPublicStart, -} from '@kbn/observability-ai-assistant-plugin/public'; -import type { AiopsPluginStart } from '@kbn/aiops-plugin/public'; -import type { DataVisualizerPluginStart } from '@kbn/data-visualizer-plugin/public'; +import { once } from 'lodash'; import { PLUGIN_ID } from '../common'; import { registerFeature } from './register_feature'; import { buildServices, UrlTracker } from './build_services'; @@ -88,132 +57,7 @@ import { ProfilesManager, RootProfileService, } from './context_awareness'; - -/** - * @public - */ -export interface DiscoverSetup { - /** - * `share` plugin URL locator for Discover app. Use it to generate links into - * Discover application, for example, navigate: - * - * ```ts - * await plugins.discover.locator.navigate({ - * savedSearchId: '571aaf70-4c88-11e8-b3d7-01146121b73d', - * indexPatternId: 'c367b774-a4c2-11ea-bb37-0242ac130002', - * timeRange: { - * to: 'now', - * from: 'now-15m', - * mode: 'relative', - * }, - * }); - * ``` - * - * Generate a location: - * - * ```ts - * const location = await plugins.discover.locator.getLocation({ - * savedSearchId: '571aaf70-4c88-11e8-b3d7-01146121b73d', - * indexPatternId: 'c367b774-a4c2-11ea-bb37-0242ac130002', - * timeRange: { - * to: 'now', - * from: 'now-15m', - * mode: 'relative', - * }, - * }); - * ``` - */ - readonly locator: undefined | DiscoverAppLocator; - readonly showInlineTopNav: () => void; - readonly configureInlineTopNav: ( - projectNavId: string, - options: DiscoverCustomizationContext['inlineTopNav'] - ) => void; -} - -export interface DiscoverStart { - /** - * `share` plugin URL locator for Discover app. Use it to generate links into - * Discover application, for example, navigate: - * - * ```ts - * await plugins.discover.locator.navigate({ - * savedSearchId: '571aaf70-4c88-11e8-b3d7-01146121b73d', - * indexPatternId: 'c367b774-a4c2-11ea-bb37-0242ac130002', - * timeRange: { - * to: 'now', - * from: 'now-15m', - * mode: 'relative', - * }, - * }); - * ``` - * - * Generate a location: - * - * ```ts - * const location = await plugins.discover.locator.getLocation({ - * savedSearchId: '571aaf70-4c88-11e8-b3d7-01146121b73d', - * indexPatternId: 'c367b774-a4c2-11ea-bb37-0242ac130002', - * timeRange: { - * to: 'now', - * from: 'now-15m', - * mode: 'relative', - * }, - * }); - * ``` - */ - readonly locator: undefined | DiscoverAppLocator; - readonly DiscoverContainer: ComponentType; -} - -/** - * @internal - */ -export interface DiscoverSetupPlugins { - dataViews: DataViewsServicePublic; - share?: SharePluginSetup; - uiActions: UiActionsSetup; - embeddable: EmbeddableSetup; - urlForwarding: UrlForwardingSetup; - home?: HomePublicPluginSetup; - data: DataPublicPluginSetup; - expressions: ExpressionsSetup; - globalSearch?: GlobalSearchPluginSetup; - observabilityAIAssistant?: ObservabilityAIAssistantPublicSetup; -} - -/** - * @internal - */ -export interface DiscoverStartPlugins { - aiops?: AiopsPluginStart; - dataViews: DataViewsServicePublic; - dataViewEditor: DataViewEditorStart; - dataVisualizer?: DataVisualizerPluginStart; - uiActions: UiActionsStart; - embeddable: EmbeddableStart; - navigation: NavigationStart; - charts: ChartsPluginStart; - data: DataPublicPluginStart; - fieldFormats: FieldFormatsStart; - share?: SharePluginStart; - urlForwarding: UrlForwardingStart; - inspector: InspectorPublicPluginStart; - usageCollection?: UsageCollectionSetup; - dataViewFieldEditor: IndexPatternFieldEditorStart; - spaces?: SpacesPluginStart; - triggersActionsUi: TriggersAndActionsUIPublicPluginStart; - expressions: ExpressionsStart; - savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart; - savedObjectsManagement: SavedObjectsManagementPluginStart; - savedSearch: SavedSearchPublicPluginStart; - unifiedSearch: UnifiedSearchPublicPluginStart; - unifiedDocViewer: UnifiedDocViewerStart; - lens: LensPublicStart; - contentManagement: ContentManagementPublicStart; - noDataPage?: NoDataPagePluginStart; - observabilityAIAssistant?: ObservabilityAIAssistantPublicStart; -} +import { DiscoverSetup, DiscoverSetupPlugins, DiscoverStart, DiscoverStartPlugins } from './types'; /** * Contains Discover, one of the oldest parts of Kibana @@ -222,16 +66,11 @@ export interface DiscoverStartPlugins { export class DiscoverPlugin implements Plugin { - private readonly rootProfileService = new RootProfileService(); - private readonly dataSourceProfileService = new DataSourceProfileService(); - private readonly documentProfileService = new DocumentProfileService(); private readonly appStateUpdater = new BehaviorSubject(() => ({})); private readonly historyService = new HistoryService(); private readonly inlineTopNav: Map = new Map([[null, defaultCustomizationContext.inlineTopNav]]); - private readonly experimentalFeatures: ExperimentalFeatures = { - ruleFormV2Enabled: false, - }; + private readonly experimentalFeatures: ExperimentalFeatures; private scopedHistory?: ScopedHistory; private urlTracker?: UrlTracker; @@ -241,8 +80,12 @@ export class DiscoverPlugin private singleDocLocator?: DiscoverSingleDocLocator; constructor(private readonly initializerContext: PluginInitializerContext) { - this.experimentalFeatures = - initializerContext.config.get().experimental ?? this.experimentalFeatures; + const experimental = this.initializerContext.config.get().experimental; + + this.experimentalFeatures = { + ruleFormV2Enabled: experimental?.ruleFormV2Enabled ?? false, + enabledProfiles: experimental?.enabledProfiles ?? [], + }; } setup( @@ -338,7 +181,7 @@ export class DiscoverPlugin history: this.historyService.getHistory(), scopedHistory: this.scopedHistory, urlTracker: this.urlTracker!, - profilesManager: this.createProfilesManager(), + profilesManager: await this.createProfilesManager(), setHeaderActionMenu: params.setHeaderActionMenu, }); @@ -421,8 +264,6 @@ export class DiscoverPlugin } start(core: CoreStart, plugins: DiscoverStartPlugins): DiscoverStart { - this.registerProfiles(); - const viewSavedSearchAction = new ViewSavedSearchAction(core.application, this.locator!); plugins.uiActions.addTriggerAction('CONTEXT_MENU_TRIGGER', viewSavedSearchAction); @@ -458,18 +299,31 @@ export class DiscoverPlugin } } - private registerProfiles() { - // TODO: Conditionally register example profiles for functional testing in a follow up PR - // this.rootProfileService.registerProvider(o11yRootProfileProvider); - // this.dataSourceProfileService.registerProvider(logsDataSourceProfileProvider); - // this.documentProfileService.registerProvider(logDocumentProfileProvider); - } + private createProfileServices = once(async () => { + const { registerProfileProviders } = await import('./context_awareness/profile_providers'); + const rootProfileService = new RootProfileService(); + const dataSourceProfileService = new DataSourceProfileService(); + const documentProfileService = new DocumentProfileService(); + const experimentalProfileIds = this.experimentalFeatures.enabledProfiles ?? []; + + registerProfileProviders({ + rootProfileService, + dataSourceProfileService, + documentProfileService, + experimentalProfileIds, + }); + + return { rootProfileService, dataSourceProfileService, documentProfileService }; + }); + + private async createProfilesManager() { + const { rootProfileService, dataSourceProfileService, documentProfileService } = + await this.createProfileServices(); - private createProfilesManager() { return new ProfilesManager( - this.rootProfileService, - this.dataSourceProfileService, - this.documentProfileService + rootProfileService, + dataSourceProfileService, + documentProfileService ); } @@ -484,7 +338,7 @@ export class DiscoverPlugin private getDiscoverServices = ( core: CoreStart, plugins: DiscoverStartPlugins, - profilesManager = this.createProfilesManager() + profilesManager: ProfilesManager ) => { return buildServices({ core, @@ -510,7 +364,8 @@ export class DiscoverPlugin const getDiscoverServicesInternal = async () => { const [coreStart, deps] = await core.getStartServices(); - return this.getDiscoverServices(coreStart, deps); + const profilesManager = await this.createProfilesManager(); + return this.getDiscoverServices(coreStart, deps, profilesManager); }; const factory = new SearchEmbeddableFactory(getStartServices, getDiscoverServicesInternal); diff --git a/src/plugins/discover/public/types.ts b/src/plugins/discover/public/types.ts new file mode 100644 index 0000000000000..54291a36a989b --- /dev/null +++ b/src/plugins/discover/public/types.ts @@ -0,0 +1,170 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ComponentType } from 'react'; +import { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; +import { ExpressionsSetup, ExpressionsStart } from '@kbn/expressions-plugin/public'; +import { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public'; +import { ChartsPluginStart } from '@kbn/charts-plugin/public'; +import type { GlobalSearchPluginSetup } from '@kbn/global-search-plugin/public'; +import { NavigationPublicPluginStart as NavigationStart } from '@kbn/navigation-plugin/public'; +import { SharePluginStart, SharePluginSetup } from '@kbn/share-plugin/public'; +import { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding-plugin/public'; +import { HomePublicPluginSetup } from '@kbn/home-plugin/public'; +import { Start as InspectorPublicPluginStart } from '@kbn/inspector-plugin/public'; +import { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; +import { IndexPatternFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public'; +import { DataViewsServicePublic } from '@kbn/data-views-plugin/public'; +import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; +import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; +import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; +import { ContentManagementPublicStart } from '@kbn/content-management-plugin/public'; +import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; +import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public'; +import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; +import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public'; +import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { UnifiedDocViewerStart } from '@kbn/unified-doc-viewer-plugin/public'; +import type { LensPublicStart } from '@kbn/lens-plugin/public'; +import type { NoDataPagePluginStart } from '@kbn/no-data-page-plugin/public'; +import type { + ObservabilityAIAssistantPublicSetup, + ObservabilityAIAssistantPublicStart, +} from '@kbn/observability-ai-assistant-plugin/public'; +import type { AiopsPluginStart } from '@kbn/aiops-plugin/public'; +import type { DataVisualizerPluginStart } from '@kbn/data-visualizer-plugin/public'; +import { DiscoverAppLocator } from '../common'; +import { DiscoverCustomizationContext } from './customizations'; +import { type DiscoverContainerProps } from './components/discover_container'; + +/** + * @public + */ +export interface DiscoverSetup { + /** + * `share` plugin URL locator for Discover app. Use it to generate links into + * Discover application, for example, navigate: + * + * ```ts + * await plugins.discover.locator.navigate({ + * savedSearchId: '571aaf70-4c88-11e8-b3d7-01146121b73d', + * indexPatternId: 'c367b774-a4c2-11ea-bb37-0242ac130002', + * timeRange: { + * to: 'now', + * from: 'now-15m', + * mode: 'relative', + * }, + * }); + * ``` + * + * Generate a location: + * + * ```ts + * const location = await plugins.discover.locator.getLocation({ + * savedSearchId: '571aaf70-4c88-11e8-b3d7-01146121b73d', + * indexPatternId: 'c367b774-a4c2-11ea-bb37-0242ac130002', + * timeRange: { + * to: 'now', + * from: 'now-15m', + * mode: 'relative', + * }, + * }); + * ``` + */ + readonly locator: undefined | DiscoverAppLocator; + readonly showInlineTopNav: () => void; + readonly configureInlineTopNav: ( + projectNavId: string, + options: DiscoverCustomizationContext['inlineTopNav'] + ) => void; +} + +export interface DiscoverStart { + /** + * `share` plugin URL locator for Discover app. Use it to generate links into + * Discover application, for example, navigate: + * + * ```ts + * await plugins.discover.locator.navigate({ + * savedSearchId: '571aaf70-4c88-11e8-b3d7-01146121b73d', + * indexPatternId: 'c367b774-a4c2-11ea-bb37-0242ac130002', + * timeRange: { + * to: 'now', + * from: 'now-15m', + * mode: 'relative', + * }, + * }); + * ``` + * + * Generate a location: + * + * ```ts + * const location = await plugins.discover.locator.getLocation({ + * savedSearchId: '571aaf70-4c88-11e8-b3d7-01146121b73d', + * indexPatternId: 'c367b774-a4c2-11ea-bb37-0242ac130002', + * timeRange: { + * to: 'now', + * from: 'now-15m', + * mode: 'relative', + * }, + * }); + * ``` + */ + readonly locator: undefined | DiscoverAppLocator; + readonly DiscoverContainer: ComponentType; +} + +/** + * @internal + */ +export interface DiscoverSetupPlugins { + data: DataPublicPluginSetup; + dataViews: DataViewsServicePublic; + embeddable: EmbeddableSetup; + expressions: ExpressionsSetup; + globalSearch?: GlobalSearchPluginSetup; + home?: HomePublicPluginSetup; + observabilityAIAssistant?: ObservabilityAIAssistantPublicSetup; + share?: SharePluginSetup; + uiActions: UiActionsSetup; + urlForwarding: UrlForwardingSetup; +} + +/** + * @internal + */ +export interface DiscoverStartPlugins { + aiops?: AiopsPluginStart; + charts: ChartsPluginStart; + contentManagement: ContentManagementPublicStart; + data: DataPublicPluginStart; + dataViewEditor: DataViewEditorStart; + dataViewFieldEditor: IndexPatternFieldEditorStart; + dataViews: DataViewsServicePublic; + dataVisualizer?: DataVisualizerPluginStart; + embeddable: EmbeddableStart; + expressions: ExpressionsStart; + fieldFormats: FieldFormatsStart; + inspector: InspectorPublicPluginStart; + lens: LensPublicStart; + navigation: NavigationStart; + noDataPage?: NoDataPagePluginStart; + observabilityAIAssistant?: ObservabilityAIAssistantPublicStart; + savedObjectsManagement: SavedObjectsManagementPluginStart; + savedObjectsTaggingOss?: SavedObjectTaggingOssPluginStart; + savedSearch: SavedSearchPublicPluginStart; + share?: SharePluginStart; + spaces?: SpacesPluginStart; + triggersActionsUi: TriggersAndActionsUIPublicPluginStart; + uiActions: UiActionsStart; + unifiedDocViewer: UnifiedDocViewerStart; + unifiedSearch: UnifiedSearchPublicPluginStart; + urlForwarding: UrlForwardingStart; + usageCollection?: UsageCollectionSetup; +} diff --git a/src/plugins/discover/public/utils/initialize_kbn_url_tracking.test.ts b/src/plugins/discover/public/utils/initialize_kbn_url_tracking.test.ts index e4a9a3edf6f64..999c75238739a 100644 --- a/src/plugins/discover/public/utils/initialize_kbn_url_tracking.test.ts +++ b/src/plugins/discover/public/utils/initialize_kbn_url_tracking.test.ts @@ -8,7 +8,7 @@ import { AppUpdater } from '@kbn/core/public'; import { BehaviorSubject, Observable } from 'rxjs'; import { coreMock, scopedHistoryMock } from '@kbn/core/public/mocks'; -import { DiscoverSetupPlugins } from '../plugin'; +import { DiscoverSetupPlugins } from '../types'; import { initializeKbnUrlTracking } from './initialize_kbn_url_tracking'; describe('initializeKbnUrlTracking', () => { diff --git a/src/plugins/discover/public/utils/initialize_kbn_url_tracking.ts b/src/plugins/discover/public/utils/initialize_kbn_url_tracking.ts index 7ae8ac6d132ef..8a4ed741acecf 100644 --- a/src/plugins/discover/public/utils/initialize_kbn_url_tracking.ts +++ b/src/plugins/discover/public/utils/initialize_kbn_url_tracking.ts @@ -12,7 +12,7 @@ import { createKbnUrlTracker } from '@kbn/kibana-utils-plugin/public'; import { replaceUrlHashQuery } from '@kbn/kibana-utils-plugin/common'; import { isFilterPinned } from '@kbn/es-query'; import { SEARCH_SESSION_ID_QUERY_PARAM } from '../constants'; -import type { DiscoverSetupPlugins } from '../plugin'; +import type { DiscoverSetupPlugins } from '../types'; /** * It creates the kbn url tracker for Discover to listens to history changes and optionally to global state diff --git a/src/plugins/discover/server/ui_settings.ts b/src/plugins/discover/server/ui_settings.ts index fab0d56c0d828..5e66e1f2252d1 100644 --- a/src/plugins/discover/server/ui_settings.ts +++ b/src/plugins/discover/server/ui_settings.ts @@ -207,6 +207,15 @@ export const getUiSettings: ( type: METRIC_TYPE.CLICK, name: 'discover:useLegacyDataGrid', }, + deprecation: { + message: i18n.translate( + 'discover.advancedSettings.discover.disableDocumentExplorerDeprecation', + { + defaultMessage: 'This setting is deprecated and will be removed in Kibana 9.0.', + } + ), + docLinksKey: 'discoverSettings', + }, }, [MODIFY_COLUMNS_ON_SWITCH]: { name: i18n.translate('discover.advancedSettings.discover.modifyColumnsOnSwitchTitle', { @@ -236,6 +245,15 @@ export const getUiSettings: ( value: false, category: ['discover'], schema: schema.boolean(), + deprecation: { + message: i18n.translate( + 'discover.advancedSettings.discover.readFieldsFromSourceDeprecation', + { + defaultMessage: 'This setting is deprecated and will be removed in Kibana 9.0.', + } + ), + docLinksKey: 'discoverSettings', + }, }, [SHOW_FIELD_STATISTICS]: { name: i18n.translate('discover.advancedSettings.discover.showFieldStatistics', { diff --git a/src/plugins/embeddable/public/lib/embeddables/compatibility/link_legacy_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/compatibility/link_legacy_embeddable.ts index 69ee8bb675282..6dc2b2f9400f8 100644 --- a/src/plugins/embeddable/public/lib/embeddables/compatibility/link_legacy_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/compatibility/link_legacy_embeddable.ts @@ -27,8 +27,8 @@ export const canLinkLegacyEmbeddable = async (embeddable: CommonLegacyEmbeddable return false; } - const { maps, visualize } = core.application.capabilities; - const canSave = embeddable.type === 'map' ? maps.save : visualize.save; + const { visualize } = core.application.capabilities; + const canSave = visualize.save; const { isOfAggregateQueryType } = await import('@kbn/es-query'); const query = isFilterableEmbeddable(embeddable) && embeddable.getQuery(); diff --git a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_registry.ts b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_registry.ts index b1d67cc8a1d90..eea89f191db82 100644 --- a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_registry.ts +++ b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_registry.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { DefaultEmbeddableApi, ReactEmbeddableFactory } from './types'; -const registry: { [key: string]: () => Promise> } = {}; +const registry: { [key: string]: () => Promise> } = {}; /** * Registers a new React embeddable factory. This should be called at plugin start time. @@ -20,11 +20,14 @@ const registry: { [key: string]: () => Promise> */ export const registerReactEmbeddableFactory = < SerializedState extends object = object, - Api extends DefaultEmbeddableApi = DefaultEmbeddableApi, - RuntimeState extends object = SerializedState + RuntimeState extends object = SerializedState, + Api extends DefaultEmbeddableApi = DefaultEmbeddableApi< + SerializedState, + RuntimeState + > >( type: string, - getFactory: () => Promise> + getFactory: () => Promise> ) => { if (registry[type] !== undefined) throw new Error( @@ -40,11 +43,14 @@ export const reactEmbeddableRegistryHasKey = (key: string) => registry[key] !== export const getReactEmbeddableFactory = async < SerializedState extends object = object, - Api extends DefaultEmbeddableApi = DefaultEmbeddableApi, - RuntimeState extends object = SerializedState + RuntimeState extends object = SerializedState, + Api extends DefaultEmbeddableApi = DefaultEmbeddableApi< + SerializedState, + RuntimeState + > >( key: string -): Promise> => { +): Promise> => { if (registry[key] === undefined) throw new Error( i18n.translate('embeddableApi.reactEmbeddable.factoryNotFoundError', { diff --git a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.test.tsx b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.test.tsx index 3b6c8cddaa085..5c753777eae9b 100644 --- a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.test.tsx +++ b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.test.tsx @@ -212,6 +212,43 @@ describe('react embeddable renderer', () => { ) ); }); + + it('catches error when thrown in deserialize', async () => { + const buildEmbeddable = jest.fn(); + const errorInInitializeFactory: ReactEmbeddableFactory<{ name: string; bork: string }> = { + ...testEmbeddableFactory, + type: 'errorInDeserialize', + buildEmbeddable, + deserializeState: (state) => { + throw new Error('error in deserialize'); + }, + }; + registerReactEmbeddableFactory('errorInDeserialize', () => + Promise.resolve(errorInInitializeFactory) + ); + setupPresentationPanelServices(); + + const onApiAvailable = jest.fn(); + const embeddable = render( + ({ + getSerializedStateForChild: () => ({ + rawState: {}, + }), + })} + /> + ); + + await waitFor(() => expect(embeddable.getByTestId('errorMessageMarkdown')).toBeInTheDocument()); + expect(onApiAvailable).not.toBeCalled(); + expect(buildEmbeddable).not.toBeCalled(); + expect(embeddable.getByTestId('errorMessageMarkdown')).toHaveTextContent( + 'error in deserialize' + ); + }); }); describe('reactEmbeddable phase events', () => { diff --git a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx index a10420a33f67f..91f4e02527bbb 100644 --- a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx +++ b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_renderer.tsx @@ -6,7 +6,12 @@ * Side Public License, v 1. */ -import { HasSerializedChildState, SerializedPanelState } from '@kbn/presentation-containers'; +import { + apiIsPresentationContainer, + HasSerializedChildState, + HasSnapshottableState, + SerializedPanelState, +} from '@kbn/presentation-containers'; import { PresentationPanel, PresentationPanelProps } from '@kbn/presentation-panel-plugin/public'; import { apiPublishesDataLoading, @@ -20,9 +25,9 @@ import { v4 as generateId } from 'uuid'; import { getReactEmbeddableFactory } from './react_embeddable_registry'; import { initializeReactEmbeddableState } from './react_embeddable_state'; import { + BuildReactEmbeddableApiRegistration, DefaultEmbeddableApi, SetReactEmbeddableApiRegistration, - BuildReactEmbeddableApiRegistration, } from './types'; const ON_STATE_CHANGE_DEBOUNCE = 100; @@ -34,8 +39,11 @@ const ON_STATE_CHANGE_DEBOUNCE = 100; */ export const ReactEmbeddableRenderer = < SerializedState extends object = object, - Api extends DefaultEmbeddableApi = DefaultEmbeddableApi, RuntimeState extends object = SerializedState, + Api extends DefaultEmbeddableApi = DefaultEmbeddableApi< + SerializedState, + RuntimeState + >, ParentApi extends HasSerializedChildState = HasSerializedChildState >({ type, @@ -91,17 +99,11 @@ export const ReactEmbeddableRenderer = < */ return (async () => { const parentApi = getParentApi(); - const factory = await getReactEmbeddableFactory(type); + const factory = await getReactEmbeddableFactory(type); const subscriptions = new Subscription(); - const { initialState, startStateDiffing } = await initializeReactEmbeddableState< - SerializedState, - Api, - RuntimeState - >(uuid, factory, parentApi); - const setApi = ( - apiRegistration: SetReactEmbeddableApiRegistration + apiRegistration: SetReactEmbeddableApiRegistration ) => { const fullApi = { ...apiRegistration, @@ -114,75 +116,108 @@ export const ReactEmbeddableRenderer = < return fullApi; }; - const buildApi = ( - apiRegistration: BuildReactEmbeddableApiRegistration, - comparators: StateComparators - ) => { - if (onAnyStateChange) { - /** - * To avoid unnecessary re-renders, only subscribe to the comparator publishing subjects if - * an `onAnyStateChange` callback is provided - */ - const comparatorDefinitions: Array< - ComparatorDefinition - > = Object.values(comparators); - subscriptions.add( - combineLatest(comparatorDefinitions.map((comparator) => comparator[0])) - .pipe( - skip(1), - debounceTime(ON_STATE_CHANGE_DEBOUNCE), - switchMap(() => { - const isAsync = - apiRegistration.serializeState.prototype?.name === 'AsyncFunction'; - return isAsync - ? (apiRegistration.serializeState() as Promise< - SerializedPanelState - >) - : Promise.resolve(apiRegistration.serializeState()); + const buildEmbeddable = async () => { + const { initialState, startStateDiffing } = await initializeReactEmbeddableState< + SerializedState, + RuntimeState, + Api + >(uuid, factory, parentApi); + + const buildApi = ( + apiRegistration: BuildReactEmbeddableApiRegistration< + SerializedState, + RuntimeState, + Api + >, + comparators: StateComparators + ) => { + if (onAnyStateChange) { + /** + * To avoid unnecessary re-renders, only subscribe to the comparator publishing subjects if + * an `onAnyStateChange` callback is provided + */ + const comparatorDefinitions: Array< + ComparatorDefinition + > = Object.values(comparators); + subscriptions.add( + combineLatest(comparatorDefinitions.map((comparator) => comparator[0])) + .pipe( + skip(1), + debounceTime(ON_STATE_CHANGE_DEBOUNCE), + switchMap(() => { + const isAsync = + apiRegistration.serializeState.prototype?.name === 'AsyncFunction'; + return isAsync + ? (apiRegistration.serializeState() as Promise< + SerializedPanelState + >) + : Promise.resolve(apiRegistration.serializeState()); + }) + ) + .subscribe((serializedState) => { + onAnyStateChange(serializedState); }) - ) - .subscribe((serializedState) => { - onAnyStateChange(serializedState); - }) + ); + } + + const { unsavedChanges, resetUnsavedChanges, cleanup, snapshotRuntimeState } = + startStateDiffing(comparators); + + const fullApi = setApi({ + ...apiRegistration, + unsavedChanges, + resetUnsavedChanges, + snapshotRuntimeState, + } as unknown as SetReactEmbeddableApiRegistration); + + cleanupFunction.current = () => cleanup(); + return fullApi as Api & HasSnapshottableState; + }; + + const { api, Component } = await factory.buildEmbeddable( + initialState, + buildApi, + uuid, + parentApi, + setApi + ); + + if (apiPublishesDataLoading(api)) { + subscriptions.add( + api.dataLoading.subscribe((loading) => reportPhaseChange(Boolean(loading))) ); + } else { + reportPhaseChange(false); } - - const { unsavedChanges, resetUnsavedChanges, cleanup, snapshotRuntimeState } = - startStateDiffing(comparators); - - const fullApi = setApi({ - ...apiRegistration, - unsavedChanges, - resetUnsavedChanges, - snapshotRuntimeState, - } as unknown as SetReactEmbeddableApiRegistration); - - cleanupFunction.current = () => cleanup(); - return fullApi; + return { api, Component }; }; - const { api, Component } = await factory.buildEmbeddable( - initialState, - buildApi, - uuid, - parentApi, - setApi - ); - - if (apiPublishesDataLoading(api)) { - subscriptions.add( - api.dataLoading.subscribe((loading) => reportPhaseChange(Boolean(loading))) - ); - } else { - reportPhaseChange(false); + try { + const { api, Component } = await buildEmbeddable(); + return React.forwardRef((_, ref) => { + // expose the api into the imperative handle + useImperativeHandle(ref, () => api, []); + + return ; + }); + } catch (e) { + /** + * critical error encountered when trying to build the api / embeddable; + * since no API is available, create a dummy API that allows the panel to be deleted + * */ + const errorApi = { + uuid, + blockingError: new BehaviorSubject(e), + } as unknown as Api; + if (apiIsPresentationContainer(parentApi)) { + errorApi.parentApi = parentApi; + } + return React.forwardRef((_, ref) => { + // expose the dummy error api into the imperative handle + useImperativeHandle(ref, () => errorApi, []); + return null; + }); } - - return React.forwardRef((_, ref) => { - // expose the api into the imperative handle - useImperativeHandle(ref, () => api, []); - - return ; - }); })(); }, /** diff --git a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_state.ts b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_state.ts index 605b8d20a7cd1..f29d418bd3963 100644 --- a/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_state.ts +++ b/src/plugins/embeddable/public/react_embeddable_system/react_embeddable_state.ts @@ -29,16 +29,20 @@ import { DefaultEmbeddableApi, ReactEmbeddableFactory } from './types'; export const initializeReactEmbeddableState = async < SerializedState extends object = object, - Api extends DefaultEmbeddableApi = DefaultEmbeddableApi, - RuntimeState extends object = SerializedState + RuntimeState extends object = SerializedState, + Api extends DefaultEmbeddableApi = DefaultEmbeddableApi< + SerializedState, + RuntimeState + > >( uuid: string, - factory: ReactEmbeddableFactory, + factory: ReactEmbeddableFactory, parentApi: HasSerializedChildState ) => { - const lastSavedRuntimeState = await factory.deserializeState( - parentApi.getSerializedStateForChild(uuid) - ); + const serializedState = parentApi.getSerializedStateForChild(uuid); + const lastSavedRuntimeState = serializedState + ? await factory.deserializeState(serializedState) + : ({} as RuntimeState); // If the parent provides runtime state for the child (usually as a state backup or cache), // we merge it with the last saved runtime state. diff --git a/src/plugins/embeddable/public/react_embeddable_system/types.ts b/src/plugins/embeddable/public/react_embeddable_system/types.ts index 8a05698934e9c..e9a5a697f07e5 100644 --- a/src/plugins/embeddable/public/react_embeddable_system/types.ts +++ b/src/plugins/embeddable/public/react_embeddable_system/types.ts @@ -41,7 +41,11 @@ export interface DefaultEmbeddableApi< */ export type SetReactEmbeddableApiRegistration< SerializedState extends object = object, - Api extends DefaultEmbeddableApi = DefaultEmbeddableApi + RuntimeState extends object = SerializedState, + Api extends DefaultEmbeddableApi = DefaultEmbeddableApi< + SerializedState, + RuntimeState + > > = Omit; /** @@ -50,9 +54,13 @@ export type SetReactEmbeddableApiRegistration< */ export type BuildReactEmbeddableApiRegistration< SerializedState extends object = object, - Api extends DefaultEmbeddableApi = DefaultEmbeddableApi + RuntimeState extends object = SerializedState, + Api extends DefaultEmbeddableApi = DefaultEmbeddableApi< + SerializedState, + RuntimeState + > > = Omit< - SetReactEmbeddableApiRegistration, + SetReactEmbeddableApiRegistration, 'unsavedChanges' | 'resetUnsavedChanges' | 'snapshotRuntimeState' >; @@ -65,8 +73,11 @@ export type BuildReactEmbeddableApiRegistration< **/ export interface ReactEmbeddableFactory< SerializedState extends object = object, - Api extends DefaultEmbeddableApi = DefaultEmbeddableApi, - RuntimeState extends object = SerializedState + RuntimeState extends object = SerializedState, + Api extends DefaultEmbeddableApi = DefaultEmbeddableApi< + SerializedState, + RuntimeState + > > { /** * A unique key for the type of this embeddable. The React Embeddable Renderer will use this type @@ -101,12 +112,12 @@ export interface ReactEmbeddableFactory< * changes logic that the dashboard expects using the provided comparators */ buildApi: ( - apiRegistration: BuildReactEmbeddableApiRegistration, + apiRegistration: BuildReactEmbeddableApiRegistration, comparators: StateComparators - ) => Api, + ) => Api & HasSnapshottableState, uuid: string, parentApi: unknown | undefined, /** `setApi` should be used when the unsaved changes logic in `buildApi` is unnecessary */ - setApi: (api: SetReactEmbeddableApiRegistration) => Api + setApi: (api: SetReactEmbeddableApiRegistration) => Api ) => Promise<{ Component: React.FC<{}>; api: Api }>; } diff --git a/src/plugins/esql_datagrid/public/data_grid.tsx b/src/plugins/esql_datagrid/public/data_grid.tsx index c9e507295b9f3..f45f1ce7f9ead 100644 --- a/src/plugins/esql_datagrid/public/data_grid.tsx +++ b/src/plugins/esql_datagrid/public/data_grid.tsx @@ -62,7 +62,7 @@ const DataGrid: React.FC = (props) => { ) => ( { { const closeFlyoutSpy = jest.fn(); renderComponent(closeFlyoutSpy); await waitFor(() => { - userEvent.click(screen.getByTestId('esqlRowDetailsFlyoutCloseBtn')); + userEvent.click(screen.getByTestId('docViewerFlyoutCloseButton')); expect(closeFlyoutSpy).toHaveBeenCalled(); }); }); @@ -106,14 +106,14 @@ describe('RowViewer', () => { }, } as unknown as DataTableRecord); await waitFor(() => { - expect(screen.getByTestId('esqlTableRowNavigation')).toBeInTheDocument(); + expect(screen.getByTestId('docViewerFlyoutNavigation')).toBeInTheDocument(); }); }); it('doesnt display row navigation when there is only 1 row available', async () => { renderComponent(); await waitFor(() => { - expect(screen.queryByTestId('esqlTableRowNavigation')).not.toBeInTheDocument(); + expect(screen.queryByTestId('docViewerFlyoutNavigation')).not.toBeInTheDocument(); }); }); }); diff --git a/src/plugins/esql_datagrid/public/row_viewer.tsx b/src/plugins/esql_datagrid/public/row_viewer.tsx index 35c2b807fbbb3..d02c48b7c85e4 100644 --- a/src/plugins/esql_datagrid/public/row_viewer.tsx +++ b/src/plugins/esql_datagrid/public/row_viewer.tsx @@ -6,34 +6,15 @@ * Side Public License, v 1. */ -import React, { useMemo, useCallback } from 'react'; -import { get } from 'lodash'; -import { i18n } from '@kbn/i18n'; -import { css } from '@emotion/react'; +import React, { useMemo } from 'react'; import type { DataView } from '@kbn/data-views-plugin/public'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiFlyoutResizable, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiFlyoutHeader, - EuiTitle, - EuiPortal, - EuiPagination, - keys, - EuiButtonEmpty, - useEuiTheme, - useIsWithinMinBreakpoint, -} from '@elastic/eui'; import type { DataTableRecord } from '@kbn/discover-utils/types'; import type { DataTableColumnsMeta } from '@kbn/unified-data-table'; -import { UnifiedDocViewer } from '@kbn/unified-doc-viewer-plugin/public'; -import useLocalStorage from 'react-use/lib/useLocalStorage'; +import { UnifiedDocViewerFlyout } from '@kbn/unified-doc-viewer-plugin/public'; import { NotificationsStart } from '@kbn/core-notifications-browser'; export interface RowViewerProps { - toastNotifications?: NotificationsStart; + notifications?: NotificationsStart; columns: string[]; columnsMeta?: DataTableColumnsMeta; hit: DataTableRecord; @@ -46,12 +27,6 @@ export interface RowViewerProps { setExpandedDoc: (doc?: DataTableRecord) => void; } -function getIndexByDocId(hits: DataTableRecord[], id: string) { - return hits.findIndex((h) => { - return h.id === id; - }); -} - export const FLYOUT_WIDTH_KEY = 'esqlTable:flyoutWidth'; /** * Flyout displaying an expanded ES|QL row @@ -62,172 +37,32 @@ export function RowViewer({ dataView, columns, columnsMeta, - toastNotifications, + notifications, flyoutType = 'push', onClose, onRemoveColumn, onAddColumn, setExpandedDoc, }: RowViewerProps) { - const { euiTheme } = useEuiTheme(); - - const isXlScreen = useIsWithinMinBreakpoint('xl'); - const DEFAULT_WIDTH = euiTheme.base * 34; - const defaultWidth = DEFAULT_WIDTH; - const [flyoutWidth, setFlyoutWidth] = useLocalStorage(FLYOUT_WIDTH_KEY, defaultWidth); - const minWidth = euiTheme.base * 24; - const maxWidth = euiTheme.breakpoint.xl; - - const actualHit = useMemo(() => hits?.find(({ id }) => id === hit?.id) || hit, [hit, hits]); - const pageCount = useMemo(() => (hits ? hits.length : 0), [hits]); - const activePage = useMemo(() => { - const id = hit.id; - if (!hits || pageCount <= 1) { - return -1; - } - - return getIndexByDocId(hits, id); - }, [hits, hit, pageCount]); - - const setPage = useCallback( - (index: number) => { - if (hits && hits[index]) { - setExpandedDoc(hits[index]); - } - }, - [hits, setExpandedDoc] - ); - - const onKeyDown = useCallback( - (ev: React.KeyboardEvent) => { - const nodeName = get(ev, 'target.nodeName', null); - if (typeof nodeName === 'string' && nodeName.toLowerCase() === 'input') { - return; - } - if (ev.key === keys.ARROW_LEFT || ev.key === keys.ARROW_RIGHT) { - ev.preventDefault(); - ev.stopPropagation(); - setPage(activePage + (ev.key === keys.ARROW_RIGHT ? 1 : -1)); - } - }, - [activePage, setPage] - ); - - const addColumn = useCallback( - (columnName: string) => { - onAddColumn(columnName); - toastNotifications?.toasts?.addSuccess?.( - i18n.translate('esqlDataGrid.grid.flyout.toastColumnAdded', { - defaultMessage: `Column '{columnName}' was added`, - values: { columnName }, - }) - ); - }, - [onAddColumn, toastNotifications] - ); - - const removeColumn = useCallback( - (columnName: string) => { - onRemoveColumn(columnName); - toastNotifications?.toasts?.addSuccess?.( - i18n.translate('esqlDataGrid.grid.flyout.toastColumnRemoved', { - defaultMessage: `Column '{columnName}' was removed`, - values: { columnName }, - }) - ); - }, - [onRemoveColumn, toastNotifications] - ); - - const renderDefaultContent = useCallback( - () => ( - - ), - [actualHit, addColumn, columns, columnsMeta, dataView, hits, removeColumn] - ); - - const bodyContent = renderDefaultContent(); + const services = useMemo(() => ({ toastNotifications: notifications?.toasts }), [notifications]); return ( - - - - - - -

- {i18n.translate('esqlDataGrid.grid.tableRow.docViewerEsqlDetailHeading', { - defaultMessage: 'Result', - })} -

-
-
- {activePage !== -1 && ( - - - - )} -
-
- {bodyContent} - - - {i18n.translate('esqlDataGrid.grid.flyout.close', { - defaultMessage: 'Close', - })} - - -
-
+ ); } diff --git a/src/plugins/esql_datagrid/tsconfig.json b/src/plugins/esql_datagrid/tsconfig.json index 5db30eb35fd20..1d8685bf55982 100644 --- a/src/plugins/esql_datagrid/tsconfig.json +++ b/src/plugins/esql_datagrid/tsconfig.json @@ -22,10 +22,9 @@ "@kbn/core", "@kbn/ui-actions-plugin", "@kbn/field-formats-plugin", - "@kbn/i18n", "@kbn/unified-doc-viewer-plugin", "@kbn/core-notifications-browser", - "@kbn/shared-ux-utility" + "@kbn/shared-ux-utility", ], "exclude": [ "target/**/*", diff --git a/src/plugins/image_embeddable/public/image_embeddable/get_image_embeddable_factory.tsx b/src/plugins/image_embeddable/public/image_embeddable/get_image_embeddable_factory.tsx index d5b15122f70f1..2bbff60a50efe 100644 --- a/src/plugins/image_embeddable/public/image_embeddable/get_image_embeddable_factory.tsx +++ b/src/plugins/image_embeddable/public/image_embeddable/get_image_embeddable_factory.tsx @@ -30,6 +30,7 @@ export const getImageEmbeddableFactory = ({ embeddableEnhanced?: EmbeddableEnhancedPluginStart; }) => { const imageEmbeddableFactory: ReactEmbeddableFactory< + ImageEmbeddableSerializedState, ImageEmbeddableSerializedState, ImageEmbeddableApi > = { diff --git a/src/plugins/kibana_usage_collection/public/ebt_counters/register_ebt_counters.test.ts b/src/plugins/kibana_usage_collection/public/ebt_counters/register_ebt_counters.test.ts index a546f54f52261..9e2be1c709f0a 100644 --- a/src/plugins/kibana_usage_collection/public/ebt_counters/register_ebt_counters.test.ts +++ b/src/plugins/kibana_usage_collection/public/ebt_counters/register_ebt_counters.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { TelemetryCounter } from '@kbn/analytics-client'; +import type { TelemetryCounter } from '@kbn/core/public'; import { coreMock } from '@kbn/core/public/mocks'; import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/public/mocks'; import { registerEbtCounters } from './register_ebt_counters'; diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts index 0e12abb64bcc7..4ffe9d483a1a4 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts @@ -135,6 +135,7 @@ export const applicationUsageSchema = { canvas: commonSchema, enterpriseSearch: commonSchema, enterpriseSearchContent: commonSchema, + enterpriseSearchInferenceEndpoints: commonSchema, enterpriseSearchAnalytics: commonSchema, enterpriseSearchApplications: commonSchema, enterpriseSearchAISearch: commonSchema, diff --git a/src/plugins/kibana_usage_collection/server/ebt_counters/register_ebt_counters.test.ts b/src/plugins/kibana_usage_collection/server/ebt_counters/register_ebt_counters.test.ts index c58a81dda10e6..a2ac2366c0b3e 100644 --- a/src/plugins/kibana_usage_collection/server/ebt_counters/register_ebt_counters.test.ts +++ b/src/plugins/kibana_usage_collection/server/ebt_counters/register_ebt_counters.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { TelemetryCounter } from '@kbn/analytics-client'; +import type { TelemetryCounter } from '@kbn/core/server'; import { coreMock } from '@kbn/core/server/mocks'; import { createUsageCollectionSetupMock } from '@kbn/usage-collection-plugin/server/mocks'; import { registerEbtCounters } from './register_ebt_counters'; diff --git a/src/plugins/kibana_usage_collection/tsconfig.json b/src/plugins/kibana_usage_collection/tsconfig.json index d56cb860cd463..2fb915d541052 100644 --- a/src/plugins/kibana_usage_collection/tsconfig.json +++ b/src/plugins/kibana_usage_collection/tsconfig.json @@ -13,7 +13,6 @@ "kbn_references": [ "@kbn/core", "@kbn/usage-collection-plugin", - "@kbn/analytics-client", "@kbn/i18n", "@kbn/logging", "@kbn/core-test-helpers-kbn-server", diff --git a/src/plugins/kibana_utils/public/storage/storage.ts b/src/plugins/kibana_utils/public/storage/storage.ts index 7f759c005daad..497fdef49abc0 100644 --- a/src/plugins/kibana_utils/public/storage/storage.ts +++ b/src/plugins/kibana_utils/public/storage/storage.ts @@ -32,9 +32,13 @@ export class Storage implements IStorageWrapper { } }; - public set = (key: string, value: any) => { + public set = (key: string, value: any, includeUndefined: boolean = false) => { + const replacer = includeUndefined + ? (_: string, currentValue: any) => + typeof currentValue === 'undefined' ? null : currentValue + : undefined; try { - return this.store.setItem(key, JSON.stringify(value)); + return this.store.setItem(key, JSON.stringify(value, replacer)); } catch (e) { return false; } diff --git a/src/plugins/navigation/kibana.jsonc b/src/plugins/navigation/kibana.jsonc index 06518a8c3f2c4..96980c3c983f5 100644 --- a/src/plugins/navigation/kibana.jsonc +++ b/src/plugins/navigation/kibana.jsonc @@ -6,7 +6,7 @@ "id": "navigation", "server": true, "browser": true, - "optionalPlugins": ["cloud", "cloudExperiments"], + "optionalPlugins": ["cloud", "cloudExperiments", "spaces"], "requiredPlugins": ["unifiedSearch"], "requiredBundles": [] } diff --git a/src/plugins/navigation/public/plugin.test.ts b/src/plugins/navigation/public/plugin.test.ts index 62a6a6cbf9bcd..6ebdfbe5003a2 100644 --- a/src/plugins/navigation/public/plugin.test.ts +++ b/src/plugins/navigation/public/plugin.test.ts @@ -6,11 +6,13 @@ * Side Public License, v 1. */ -import { firstValueFrom } from 'rxjs'; +import { firstValueFrom, of } from 'rxjs'; import { coreMock } from '@kbn/core/public/mocks'; import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; import { cloudExperimentsMock } from '@kbn/cloud-experiments-plugin/common/mocks'; +import { spacesPluginMock } from '@kbn/spaces-plugin/public/mocks'; +import type { Space } from '@kbn/spaces-plugin/public'; import type { BuildFlavor } from '@kbn/config'; import type { UserSettingsData } from '@kbn/user-profile-components'; import { SOLUTION_NAV_FEATURE_FLAG_NAME } from '../common'; @@ -44,6 +46,7 @@ const setup = ( const unifiedSearch = unifiedSearchPluginMock.createStartContract(); const cloud = cloudMock.createStart(); const cloudExperiments = cloudExperimentsMock.createStartMock(); + const spaces = spacesPluginMock.createStartContract(); cloudExperiments.getVariation.mockImplementation((key) => { if (key === SOLUTION_NAV_FEATURE_FLAG_NAME) { return Promise.resolve(config.featureOn); @@ -65,6 +68,7 @@ const setup = ( unifiedSearch, cloud, cloudExperiments, + spaces, config, setChromeStyle, }; @@ -93,18 +97,50 @@ describe('Navigation Plugin', () => { describe('feature flag enabled', () => { const featureOn = true; - // TODO: this test will have to be updated when we will read the space state - it.skip('should add the default solution navs **and** set the active nav', async () => { - const { plugin, coreStart, unifiedSearch, cloud, cloudExperiments } = setup({ featureOn }); + it('should change the active solution navigation', async () => { + const { plugin, coreStart, unifiedSearch, cloud, cloudExperiments, spaces } = setup({ + featureOn, + }); + + spaces.getActiveSpace$ = jest + .fn() + .mockReturnValue(of({ solution: 'es' } as Pick)); - plugin.start(coreStart, { unifiedSearch, cloud, cloudExperiments }); + plugin.start(coreStart, { unifiedSearch, cloud, cloudExperiments, spaces }); await new Promise((resolve) => setTimeout(resolve)); - expect(coreStart.chrome.project.updateSolutionNavigations).toHaveBeenCalled(); + expect(coreStart.chrome.project.changeActiveSolutionNavigation).toHaveBeenCalledWith('es'); + }); - expect(coreStart.chrome.project.changeActiveSolutionNavigation).toHaveBeenCalledWith( - 'security' - ); + describe('addSolutionNavigation()', () => { + it('should update the solution navigation definitions', async () => { + const { plugin, coreStart, unifiedSearch, cloud, cloudExperiments } = setup({ + featureOn, + }); + + const { addSolutionNavigation } = plugin.start(coreStart, { + unifiedSearch, + cloud, + cloudExperiments, + }); + await new Promise((resolve) => setTimeout(resolve)); + + const definition = { + id: 'es', + title: 'Elasticsearch', + navigationTree$: of({ body: [] }), + }; + addSolutionNavigation(definition); + + await new Promise((resolve) => setTimeout(resolve)); + + expect(coreStart.chrome.project.updateSolutionNavigations).toHaveBeenCalledWith({ + es: { + ...definition, + sideNavComponent: expect.any(Function), + }, + }); + }); }); describe('set Chrome style', () => { @@ -118,6 +154,37 @@ describe('Navigation Plugin', () => { expect(coreStart.chrome.setChromeStyle).toHaveBeenCalledWith('classic'); }); + it('should set the Chrome style to "classic" when spaces plugin is not available', async () => { + const { plugin, coreStart, unifiedSearch, cloud, cloudExperiments } = setup( + { featureOn: true } // feature not enabled but no spaces plugin + ); + + plugin.start(coreStart, { unifiedSearch, cloud, cloudExperiments }); + await new Promise((resolve) => setTimeout(resolve)); + expect(coreStart.chrome.setChromeStyle).toHaveBeenCalledWith('classic'); + }); + + it('should set the Chrome style to "classic" when active space solution is "classic"', async () => { + const { plugin, coreStart, unifiedSearch, cloud, cloudExperiments, spaces } = setup({ + featureOn: true, + }); + + // Spaces plugin is available but activeSpace is undefined + spaces.getActiveSpace$ = jest.fn().mockReturnValue(of(undefined)); + plugin.start(coreStart, { unifiedSearch, cloud, cloudExperiments, spaces }); + await new Promise((resolve) => setTimeout(resolve)); + expect(coreStart.chrome.setChromeStyle).toHaveBeenCalledWith('classic'); + + // Spaces plugin is available and activeSpace has solution "classic" + coreStart.chrome.setChromeStyle.mockReset(); + spaces.getActiveSpace$ = jest + .fn() + .mockReturnValue(of({ solution: 'classic' } as Pick)); + plugin.start(coreStart, { unifiedSearch, cloud, cloudExperiments, spaces }); + await new Promise((resolve) => setTimeout(resolve)); + expect(coreStart.chrome.setChromeStyle).toHaveBeenCalledWith('classic'); + }); + it('should NOT set the Chrome style when the feature is enabled BUT on serverless', async () => { const { plugin, coreStart, unifiedSearch, cloud, cloudExperiments } = setup( { featureOn: true }, // feature enabled @@ -129,15 +196,25 @@ describe('Navigation Plugin', () => { expect(coreStart.chrome.setChromeStyle).not.toHaveBeenCalled(); }); - // TODO: this test will have to be updated when we will read the space state - it.skip('should set the Chrome style to "project" when the feature is enabled', async () => { - const { plugin, coreStart, unifiedSearch, cloud, cloudExperiments } = setup({ + it('should set the Chrome style to "project" when space solution is a known solution', async () => { + const { plugin, coreStart, unifiedSearch, cloud, cloudExperiments, spaces } = setup({ featureOn: true, }); - plugin.start(coreStart, { unifiedSearch, cloud, cloudExperiments }); + for (const solution of ['es', 'oblt', 'security']) { + spaces.getActiveSpace$ = jest + .fn() + .mockReturnValue(of({ solution } as Pick)); + plugin.start(coreStart, { unifiedSearch, cloud, cloudExperiments, spaces }); + await new Promise((resolve) => setTimeout(resolve)); + expect(coreStart.chrome.setChromeStyle).toHaveBeenCalledWith('project'); + coreStart.chrome.setChromeStyle.mockReset(); + } + + spaces.getActiveSpace$ = jest.fn().mockReturnValue(of({ solution: 'unknown' })); + plugin.start(coreStart, { unifiedSearch, cloud, cloudExperiments, spaces }); await new Promise((resolve) => setTimeout(resolve)); - expect(coreStart.chrome.setChromeStyle).toHaveBeenCalledWith('project'); + expect(coreStart.chrome.setChromeStyle).toHaveBeenCalledWith('classic'); }); }); diff --git a/src/plugins/navigation/public/plugin.tsx b/src/plugins/navigation/public/plugin.tsx index f953e0c6272cd..8aa9eea2351d6 100644 --- a/src/plugins/navigation/public/plugin.tsx +++ b/src/plugins/navigation/public/plugin.tsx @@ -6,9 +6,10 @@ * Side Public License, v 1. */ import React from 'react'; -import { firstValueFrom, from, of, ReplaySubject, shareReplay, take } from 'rxjs'; +import { firstValueFrom, from, of, ReplaySubject, shareReplay, take, combineLatest } from 'rxjs'; import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import type { Space } from '@kbn/spaces-plugin/public'; import type { SolutionNavigationDefinition } from '@kbn/core-chrome-browser'; import { InternalChromeStart } from '@kbn/core-chrome-browser-internal'; import type { PanelContentProvider } from '@kbn/shared-ux-chrome-navigation'; @@ -57,9 +58,10 @@ export class NavigationPublicPlugin this.coreStart = core; this.depsStart = depsStart; - const { unifiedSearch, cloud, cloudExperiments } = depsStart; + const { unifiedSearch, cloud, cloudExperiments, spaces } = depsStart; const extensions = this.topNavMenuExtensionsRegistry.getAll(); const chrome = core.chrome as InternalChromeStart; + const activeSpace$ = spaces?.getActiveSpace$() ?? of(undefined); /* * @@ -91,13 +93,19 @@ export class NavigationPublicPlugin } // Initialize the solution navigation if it is enabled - this.isSolutionNavExperiementEnabled$.pipe(take(1)).subscribe((isEnabled) => { - this.initiateChromeStyleAndSideNav(chrome, { isFeatureEnabled: isEnabled, isServerless }); + combineLatest([this.isSolutionNavExperiementEnabled$, activeSpace$]) + .pipe(take(1)) + .subscribe(([isEnabled, activeSpace]) => { + this.initiateChromeStyleAndSideNav(chrome, { + isFeatureEnabled: isEnabled, + isServerless, + activeSpace, + }); - if (!isEnabled) return; + if (!isEnabled) return; - chrome.project.setCloudUrls(cloud!); - }); + chrome.project.setCloudUrls(cloud!); + }); return { ui: { @@ -154,25 +162,30 @@ export class NavigationPublicPlugin private initiateChromeStyleAndSideNav( chrome: InternalChromeStart, - { isFeatureEnabled, isServerless }: { isFeatureEnabled: boolean; isServerless: boolean } + { + isFeatureEnabled, + isServerless, + activeSpace, + }: { isFeatureEnabled: boolean; isServerless: boolean; activeSpace?: Space } ) { - // Here we will read the space state and decide if we are in classic or project style - const mockSpaceState: { solutionView?: 'classic' | 'es' | 'oblt' | 'security' } = { - solutionView: 'security', // Change this value to test different solution views - }; - + const solutionView = activeSpace?.solution; const isProjectNav = isFeatureEnabled && - Boolean(mockSpaceState.solutionView) && - mockSpaceState.solutionView !== 'classic'; + Boolean(solutionView) && + isKnownSolutionView(solutionView) && + solutionView !== 'classic'; // On serverless the chrome style is already set by the serverless plugin if (!isServerless) { chrome.setChromeStyle(isProjectNav ? 'project' : 'classic'); } - if (isProjectNav && mockSpaceState.solutionView) { - chrome.project.changeActiveSolutionNavigation(mockSpaceState.solutionView); + if (isProjectNav) { + chrome.project.changeActiveSolutionNavigation(solutionView!); } } } + +function isKnownSolutionView(solution?: string) { + return solution && ['oblt', 'es', 'security'].includes(solution); +} diff --git a/src/plugins/navigation/public/types.ts b/src/plugins/navigation/public/types.ts index a3c0720274a85..4ce2efdfef84f 100644 --- a/src/plugins/navigation/public/types.ts +++ b/src/plugins/navigation/public/types.ts @@ -11,6 +11,7 @@ import type { Observable } from 'rxjs'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { SolutionNavigationDefinition } from '@kbn/core-chrome-browser'; import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; +import type { SpacesPluginSetup, SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { CloudExperimentsPluginStart } from '@kbn/cloud-experiments-plugin/common'; import { PanelContentProvider } from '@kbn/shared-ux-chrome-navigation'; @@ -46,12 +47,14 @@ export interface NavigationPublicStart { export interface NavigationPublicSetupDependencies { cloud?: CloudSetup; + spaces?: SpacesPluginSetup; } export interface NavigationPublicStartDependencies { unifiedSearch: UnifiedSearchPublicPluginStart; cloud?: CloudStart; cloudExperiments?: CloudExperimentsPluginStart; + spaces?: SpacesPluginStart; } export type SolutionType = 'es' | 'oblt' | 'security' | 'analytics'; diff --git a/src/plugins/navigation/tsconfig.json b/src/plugins/navigation/tsconfig.json index 32b43a02e9fa3..69c26355ea7eb 100644 --- a/src/plugins/navigation/tsconfig.json +++ b/src/plugins/navigation/tsconfig.json @@ -24,6 +24,7 @@ "@kbn/config", "@kbn/user-profile-components", "@kbn/cloud-experiments-plugin", + "@kbn/spaces-plugin", ], "exclude": [ "target/**/*", diff --git a/src/plugins/presentation_panel/public/panel_component/panel_header/use_presentation_panel_header_actions.tsx b/src/plugins/presentation_panel/public/panel_component/panel_header/use_presentation_panel_header_actions.tsx index d98820fcaea21..740aa18606837 100644 --- a/src/plugins/presentation_panel/public/panel_component/panel_header/use_presentation_panel_header_actions.tsx +++ b/src/plugins/presentation_panel/public/panel_component/panel_header/use_presentation_panel_header_actions.tsx @@ -115,7 +115,11 @@ export const usePresentationPanelHeaderActions = < const badgeElements = useMemo(() => { if (!showBadges) return []; return badges?.map((badge) => { - return ( + const tooltipText = badge.getDisplayNameTooltip?.({ + embeddable: api, + trigger: panelBadgeTrigger, + }); + const badgeElement = ( badge.execute({ embeddable: api, trigger: panelBadgeTrigger })} onClickAriaLabel={badge.getDisplayName({ embeddable: api, trigger: panelBadgeTrigger })} data-test-subj={`embeddablePanelBadge-${badge.id}`} + {...(tooltipText ? { 'aria-label': tooltipText } : {})} > {badge.MenuItem ? React.createElement(badge.MenuItem, { @@ -134,6 +139,12 @@ export const usePresentationPanelHeaderActions = < : badge.getDisplayName({ embeddable: api, trigger: panelBadgeTrigger })} ); + + return tooltipText ? ( + {badgeElement} + ) : ( + badgeElement + ); }); }, [api, badges, showBadges]); diff --git a/src/plugins/presentation_panel/public/panel_component/presentation_panel_error.test.tsx b/src/plugins/presentation_panel/public/panel_component/presentation_panel_error.test.tsx new file mode 100644 index 0000000000000..82cdb97773c87 --- /dev/null +++ b/src/plugins/presentation_panel/public/panel_component/presentation_panel_error.test.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; +import { PresentationPanelError } from './presentation_panel_error'; + +describe('PresentationPanelError', () => { + test('should display error', async () => { + render(); + await waitFor(() => screen.getByTestId('errorMessageMarkdown')); + }); + + test('should display error with empty message', async () => { + render(); + await waitFor(() => screen.getByTestId('errorMessageMarkdown')); + }); +}); diff --git a/src/plugins/presentation_panel/public/panel_component/presentation_panel_error.tsx b/src/plugins/presentation_panel/public/panel_component/presentation_panel_error.tsx index 5b7de4ec05cc2..5eb20b7461f09 100644 --- a/src/plugins/presentation_panel/public/panel_component/presentation_panel_error.tsx +++ b/src/plugins/presentation_panel/public/panel_component/presentation_panel_error.tsx @@ -14,6 +14,7 @@ import { useStateFromPublishingSubject } from '@kbn/presentation-publishing'; import { renderSearchError } from '@kbn/search-errors'; import { Markdown } from '@kbn/shared-ux-markdown'; import { Subscription } from 'rxjs'; +import { i18n } from '@kbn/i18n'; import { editPanelAction } from '../panel_actions/panel_actions'; import { getErrorCallToAction } from './presentation_panel_strings'; import { DefaultPresentationPanelApi } from './types'; @@ -82,7 +83,11 @@ export const PresentationPanelError = ({ searchErrorDisplay?.body ?? ( - {error.message} + {error.message?.length + ? error.message + : i18n.translate('presentationPanel.emptyErrorMessage', { + defaultMessage: 'Error', + })} ) diff --git a/src/plugins/saved_objects_tagging_oss/common/types.ts b/src/plugins/saved_objects_tagging_oss/common/types.ts index 58384465027c6..f72aabfb7fd02 100644 --- a/src/plugins/saved_objects_tagging_oss/common/types.ts +++ b/src/plugins/saved_objects_tagging_oss/common/types.ts @@ -30,6 +30,7 @@ export interface CreateTagOptions { id?: string; overwrite?: boolean; refresh?: boolean | 'wait_for'; + managed?: boolean; } export interface ITagsClient { diff --git a/src/plugins/share/README.mdx b/src/plugins/share/README.mdx index 5c32853b79895..16fa4ac2d7c63 100644 --- a/src/plugins/share/README.mdx +++ b/src/plugins/share/README.mdx @@ -13,10 +13,14 @@ The `share` plugin contains various utilities for displaying sharing context men generating deep links to other apps using *locators*, and creating short URLs. -## Sharing context menu +## Sharing context menu and modal -You can register an item into sharing context menu (which is displayed in -Dashboard, Discover, and Visualize apps). +You can register an item into sharing context modal or menu (which is displayed in +Dashboard, Discover, and Visualize apps). In early 2024, the Shared UX team created a tabbed share modal redesign. +Canvas still is using the older share context menu, but Dashboard, Discover, and Visualize apps are since using +the new modal implementation. This change was made for a less cluttered UI and streamlined user experience. +Copy links default to short links based on user feedback. Reports/Exports have been consolidated into one tab called +Exports, versus the separated panels in the share context menu. ## Locators @@ -204,15 +208,3 @@ const url = await shortUrls.create({ To resolve the short URL, navigate to `/r/s/` in the browser. -### Redesign of Share Context menu -April 2024 the share context menu changed from using EUI panels to a tabbed modal. One of the goals -was to streamline the user experience and remove areas of confusion. For instance, the saved object -and snapshot radio options in the Link portion was confusing to users. The following was implemented -in the redesign: - -When user clicks the ‘copy link’ button -For dashboard: copy the “snapshot” URL to user clipboard -For lens: copy the “saved object” URL to user clipboard. -If lens is not saved to library you cannot copy (show unsaved changed error as in figma) -For discover: discover is saved: copy the “snapshot” URL to user clipboard -Default to short URL where possible diff --git a/src/plugins/share/public/components/tabs/export/export_content.tsx b/src/plugins/share/public/components/tabs/export/export_content.tsx index b1106b7df7774..61c58986477bd 100644 --- a/src/plugins/share/public/components/tabs/export/export_content.tsx +++ b/src/plugins/share/public/components/tabs/export/export_content.tsx @@ -133,6 +133,7 @@ const ExportContentUi = ({ iconType="copyClipboard" onClick={copy} data-test-subj="shareReportingCopyURL" + data-share-url={absoluteUrl} > (''); const [urlParams] = useState(undefined); const [isTextCopied, setTextCopied] = useState(false); - const [, setShortUrlCache] = useState(undefined); + const [shortUrlCache, setShortUrlCache] = useState(undefined); const getUrlWithUpdatedParams = useCallback( (tempUrl: string): string => { @@ -72,7 +72,6 @@ export const LinkContent = ({ // persist updated url to state setUrl(urlWithUpdatedParams); - return urlWithUpdatedParams; }, [urlParams] @@ -93,6 +92,7 @@ export const LinkContent = ({ const snapshotUrl = getSnapshotUrl(); const shortUrl = await urlService.shortUrls.get(null).createFromLongUrl(snapshotUrl); setShortUrlCache(shortUrl.url); + return shortUrl.url; } }, [shareableUrlLocatorParams, urlService.shortUrls, getSnapshotUrl, setShortUrlCache]); @@ -111,8 +111,14 @@ export const LinkContent = ({ copyToClipboard(urlToCopy); setUrl(urlToCopy); setTextCopied(true); + return urlToCopy; }, [url, delegatedShareUrlHandler, allowShortUrl, createShortUrl, getSnapshotUrl]); + const handleTestUrl = useMemo(() => { + if (objectType !== 'search' || !allowShortUrl) return getSnapshotUrl(); + else if (objectType === 'search' && allowShortUrl) return shortUrlCache; + return copyUrlHelper(); + }, [objectType, getSnapshotUrl, allowShortUrl, shortUrlCache, copyUrlHelper]); return ( <> @@ -154,7 +160,7 @@ export const LinkContent = ({ (objectType === 'lens' && isDirty ? null : setTextCopied(false))} onClick={copyUrlHelper} color={objectType === 'lens' && isDirty ? 'warning' : 'primary'} diff --git a/src/plugins/telemetry/public/plugin.test.ts b/src/plugins/telemetry/public/plugin.test.ts index 842e11f12cc68..f943f2cae86d1 100644 --- a/src/plugins/telemetry/public/plugin.test.ts +++ b/src/plugins/telemetry/public/plugin.test.ts @@ -7,7 +7,7 @@ */ import { of } from 'rxjs'; -import { ElasticV3BrowserShipper } from '@kbn/analytics-shippers-elastic-v3-browser'; +import { ElasticV3BrowserShipper } from '@kbn/ebt/shippers/elastic_v3/browser'; import { coreMock } from '@kbn/core/public/mocks'; import { homePluginMock } from '@kbn/home-plugin/public/mocks'; import { screenshotModePluginMock } from '@kbn/screenshot-mode-plugin/public/mocks'; diff --git a/src/plugins/telemetry/public/plugin.ts b/src/plugins/telemetry/public/plugin.ts index 26a4f50af96ca..9b5a2c3af596a 100644 --- a/src/plugins/telemetry/public/plugin.ts +++ b/src/plugins/telemetry/public/plugin.ts @@ -22,7 +22,7 @@ import type { ScreenshotModePluginStart, } from '@kbn/screenshot-mode-plugin/public'; import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; -import { ElasticV3BrowserShipper } from '@kbn/analytics-shippers-elastic-v3-browser'; +import { ElasticV3BrowserShipper } from '@kbn/ebt/shippers/elastic_v3/browser'; import { isSyntheticsMonitor } from '@kbn/analytics-collection-utils'; import { BehaviorSubject, map, switchMap, tap } from 'rxjs'; diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 417a59ad71bf7..1e2c942ba2a5c 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -2098,6 +2098,137 @@ } } }, + "enterpriseSearchInferenceEndpoints": { + "properties": { + "appId": { + "type": "keyword", + "_meta": { + "description": "The application being tracked" + } + }, + "viewId": { + "type": "keyword", + "_meta": { + "description": "Always `main`" + } + }, + "clicks_total": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application since we started counting them" + } + }, + "clicks_7_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 7 days" + } + }, + "clicks_30_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 30 days" + } + }, + "clicks_90_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application over the last 90 days" + } + }, + "minutes_on_screen_total": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen since we started counting them." + } + }, + "minutes_on_screen_7_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 7 days" + } + }, + "minutes_on_screen_30_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 30 days" + } + }, + "minutes_on_screen_90_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen over the last 90 days" + } + }, + "views": { + "type": "array", + "items": { + "properties": { + "appId": { + "type": "keyword", + "_meta": { + "description": "The application being tracked" + } + }, + "viewId": { + "type": "keyword", + "_meta": { + "description": "The application view being tracked" + } + }, + "clicks_total": { + "type": "long", + "_meta": { + "description": "General number of clicks in the application sub view since we started counting them" + } + }, + "clicks_7_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 7 days" + } + }, + "clicks_30_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 30 days" + } + }, + "clicks_90_days": { + "type": "long", + "_meta": { + "description": "General number of clicks in the active application sub view over the last 90 days" + } + }, + "minutes_on_screen_total": { + "type": "float", + "_meta": { + "description": "Minutes the application sub view is active and on-screen since we started counting them." + } + }, + "minutes_on_screen_7_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" + } + }, + "minutes_on_screen_30_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" + } + }, + "minutes_on_screen_90_days": { + "type": "float", + "_meta": { + "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" + } + } + } + } + } + } + }, "enterpriseSearchAnalytics": { "properties": { "appId": { diff --git a/src/plugins/telemetry/server/plugin.test.ts b/src/plugins/telemetry/server/plugin.test.ts index 7d997bbc7c528..6410deb2daa59 100644 --- a/src/plugins/telemetry/server/plugin.test.ts +++ b/src/plugins/telemetry/server/plugin.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { ElasticV3ServerShipper } from '@kbn/analytics-shippers-elastic-v3-server'; +import { ElasticV3ServerShipper } from '@kbn/ebt/shippers/elastic_v3/server'; import { coreMock } from '@kbn/core/server/mocks'; import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/server/mocks'; import { telemetryCollectionManagerPluginMock } from '@kbn/telemetry-collection-manager-plugin/server/mocks'; diff --git a/src/plugins/telemetry/server/plugin.ts b/src/plugins/telemetry/server/plugin.ts index 394bfa18cb21e..be8f298218e0f 100644 --- a/src/plugins/telemetry/server/plugin.ts +++ b/src/plugins/telemetry/server/plugin.ts @@ -22,7 +22,7 @@ import { map, } from 'rxjs'; -import { ElasticV3ServerShipper } from '@kbn/analytics-shippers-elastic-v3-server'; +import { ElasticV3ServerShipper } from '@kbn/ebt/shippers/elastic_v3/server'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import type { diff --git a/src/plugins/telemetry/tsconfig.json b/src/plugins/telemetry/tsconfig.json index 89d15d4f5a0a7..c6198c377832e 100644 --- a/src/plugins/telemetry/tsconfig.json +++ b/src/plugins/telemetry/tsconfig.json @@ -20,12 +20,10 @@ "@kbn/telemetry-collection-manager-plugin", "@kbn/usage-collection-plugin", "@kbn/security-plugin", - "@kbn/analytics-shippers-elastic-v3-browser", "@kbn/test-jest-helpers", "@kbn/shared-ux-utility", "@kbn/i18n", "@kbn/i18n-react", - "@kbn/analytics-shippers-elastic-v3-server", "@kbn/config-schema", "@kbn/utils", "@kbn/core-saved-objects-server", @@ -37,6 +35,7 @@ "@kbn/analytics-collection-utils", "@kbn/react-kibana-mount", "@kbn/core-node-server", + "@kbn/ebt", ], "exclude": [ "target/**/*", diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_flyout/doc_viewer_flyout.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer_flyout/doc_viewer_flyout.tsx new file mode 100644 index 0000000000000..640dbdeb4abfa --- /dev/null +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_flyout/doc_viewer_flyout.tsx @@ -0,0 +1,311 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useMemo, useCallback, type ComponentType } from 'react'; +import { get } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { css } from '@emotion/react'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiFlyoutResizable, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiTitle, + EuiSpacer, + EuiPortal, + EuiPagination, + keys, + EuiButtonEmpty, + useEuiTheme, + useIsWithinMinBreakpoint, + EuiFlyoutProps, +} from '@elastic/eui'; +import type { DataTableRecord } from '@kbn/discover-utils/types'; +import type { DataTableColumnsMeta } from '@kbn/unified-data-table'; +import useLocalStorage from 'react-use/lib/useLocalStorage'; +import type { ToastsStart } from '@kbn/core-notifications-browser'; +import type { DocViewFilterFn, DocViewRenderProps } from '@kbn/unified-doc-viewer/types'; +import { UnifiedDocViewer } from '../lazy_doc_viewer'; + +export interface UnifiedDocViewerFlyoutProps { + 'data-test-subj'?: string; + flyoutTitle?: string; + flyoutDefaultWidth?: EuiFlyoutProps['size']; + flyoutActions?: React.ReactNode; + flyoutType?: 'push' | 'overlay'; + flyoutWidthLocalStorageKey?: string; + FlyoutCustomBody?: ComponentType<{ + actions: Pick; + doc: DataTableRecord; + renderDefaultContent: () => React.ReactNode; + }>; + services: { + toastNotifications?: ToastsStart; + }; + docViewsRegistry?: DocViewRenderProps['docViewsRegistry']; + isEsqlQuery: boolean; + columns: string[]; + columnsMeta?: DataTableColumnsMeta; + hit: DataTableRecord; + hits?: DataTableRecord[]; + dataView: DataView; + onAddColumn: (column: string) => void; + onClose: () => void; + onFilter?: DocViewFilterFn; + onRemoveColumn: (column: string) => void; + setExpandedDoc: (doc?: DataTableRecord) => void; +} + +function getIndexByDocId(hits: DataTableRecord[], id: string) { + return hits.findIndex((h) => { + return h.id === id; + }); +} + +export const FLYOUT_WIDTH_KEY = 'unifiedDocViewer:flyoutWidth'; +/** + * Flyout displaying an expanded row details + */ +export function UnifiedDocViewerFlyout({ + 'data-test-subj': dataTestSubj, + flyoutTitle, + flyoutActions, + flyoutDefaultWidth, + flyoutType, + flyoutWidthLocalStorageKey, + FlyoutCustomBody, + services, + docViewsRegistry, + isEsqlQuery, + hit, + hits, + dataView, + columns, + columnsMeta, + onFilter, + onClose, + onRemoveColumn, + onAddColumn, + setExpandedDoc, +}: UnifiedDocViewerFlyoutProps) { + const { euiTheme } = useEuiTheme(); + const isXlScreen = useIsWithinMinBreakpoint('xl'); + const DEFAULT_WIDTH = euiTheme.base * 34; + const defaultWidth = flyoutDefaultWidth ?? DEFAULT_WIDTH; // Give enough room to search bar to not wrap + const [flyoutWidth, setFlyoutWidth] = useLocalStorage( + flyoutWidthLocalStorageKey ?? FLYOUT_WIDTH_KEY, + defaultWidth + ); + const minWidth = euiTheme.base * 24; + const maxWidth = euiTheme.breakpoint.xl; + // Get actual hit with updated highlighted searches + const actualHit = useMemo(() => hits?.find(({ id }) => id === hit?.id) || hit, [hit, hits]); + const pageCount = useMemo(() => (hits ? hits.length : 0), [hits]); + const activePage = useMemo(() => { + const id = hit.id; + if (!hits || pageCount <= 1) { + return -1; + } + + return getIndexByDocId(hits, id); + }, [hits, hit, pageCount]); + + const setPage = useCallback( + (index: number) => { + if (hits && hits[index]) { + setExpandedDoc(hits[index]); + } + }, + [hits, setExpandedDoc] + ); + + const onKeyDown = useCallback( + (ev: React.KeyboardEvent) => { + const nodeClasses = get(ev, 'target.className', ''); + if (typeof nodeClasses === 'string' && nodeClasses.includes('euiDataGrid')) { + // ignore events triggered from the data grid + return; + } + + const nodeName = get(ev, 'target.nodeName', null); + if (typeof nodeName === 'string' && nodeName.toLowerCase() === 'input') { + // ignore events triggered from the search input + return; + } + if (ev.key === keys.ARROW_LEFT || ev.key === keys.ARROW_RIGHT) { + ev.preventDefault(); + ev.stopPropagation(); + setPage(activePage + (ev.key === keys.ARROW_RIGHT ? 1 : -1)); + } + }, + [activePage, setPage] + ); + + const addColumn = useCallback( + (columnName: string) => { + onAddColumn(columnName); + services.toastNotifications?.addSuccess( + i18n.translate('unifiedDocViewer.flyout.toastColumnAdded', { + defaultMessage: `Column ''{columnName}'' was added`, + values: { columnName }, + }) + ); + }, + [onAddColumn, services.toastNotifications] + ); + + const removeColumn = useCallback( + (columnName: string) => { + onRemoveColumn(columnName); + services.toastNotifications?.addSuccess( + i18n.translate('unifiedDocViewer.flyout.toastColumnRemoved', { + defaultMessage: `Column ''{columnName}'' was removed`, + values: { columnName }, + }) + ); + }, + [onRemoveColumn, services.toastNotifications] + ); + + const renderDefaultContent = useCallback( + () => ( + + ), + [ + actualHit, + addColumn, + columns, + columnsMeta, + dataView, + hits, + isEsqlQuery, + onFilter, + removeColumn, + docViewsRegistry, + ] + ); + + const contentActions = useMemo( + () => ({ + filter: onFilter, + onAddColumn: addColumn, + onRemoveColumn: removeColumn, + }), + [onFilter, addColumn, removeColumn] + ); + + const bodyContent = FlyoutCustomBody ? ( + + ) : ( + renderDefaultContent() + ); + + const defaultFlyoutTitle = isEsqlQuery + ? i18n.translate('unifiedDocViewer.flyout.docViewerEsqlDetailHeading', { + defaultMessage: 'Result', + }) + : i18n.translate('unifiedDocViewer.flyout.docViewerDetailHeading', { + defaultMessage: 'Document', + }); + const currentFlyoutTitle = flyoutTitle ?? defaultFlyoutTitle; + + return ( + + + + + + +

{currentFlyoutTitle}

+
+
+ {activePage !== -1 && ( + + + + )} +
+ {isEsqlQuery || !flyoutActions ? null : ( + <> + + {flyoutActions} + + )} +
+ {bodyContent} + + + {i18n.translate('unifiedDocViewer.flyout.closeButtonLabel', { + defaultMessage: 'Close', + })} + + +
+
+ ); +} diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_flyout/index.ts b/src/plugins/unified_doc_viewer/public/components/doc_viewer_flyout/index.ts new file mode 100644 index 0000000000000..81cb2f5cbb928 --- /dev/null +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_flyout/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { UnifiedDocViewerFlyout } from './doc_viewer_flyout'; + +// Required for usage in React.lazy +// eslint-disable-next-line import/no-default-export +export default UnifiedDocViewerFlyout; diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/get_height.test.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/get_height.test.tsx index e28df4d89d5bc..6c65b9ddf0bdb 100644 --- a/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/get_height.test.tsx +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/get_height.test.tsx @@ -7,8 +7,7 @@ */ import { monaco } from '@kbn/monaco'; -import { getHeight } from './get_height'; -import { MARGIN_BOTTOM } from './source'; +import { getHeight, DEFAULT_MARGIN_BOTTOM } from './get_height'; describe('getHeight', () => { Object.defineProperty(window, 'innerHeight', { writable: true, configurable: true, value: 500 }); @@ -32,28 +31,31 @@ describe('getHeight', () => { test('when using document explorer, returning the available height in the flyout', () => { const monacoMock = getMonacoMock(500, 0); - const height = getHeight(monacoMock, true); - expect(height).toBe(500 - MARGIN_BOTTOM); + const height = getHeight(monacoMock, true, DEFAULT_MARGIN_BOTTOM); + expect(height).toBe(484); + + const heightCustom = getHeight(monacoMock, true, 80); + expect(heightCustom).toBe(420); }); test('when using document explorer, returning the available height in the flyout has a minimun guarenteed height', () => { const monacoMock = getMonacoMock(500); - const height = getHeight(monacoMock, true); + const height = getHeight(monacoMock, true, DEFAULT_MARGIN_BOTTOM); expect(height).toBe(400); }); test('when using classic table, its displayed inline without scrolling', () => { const monacoMock = getMonacoMock(100); - const height = getHeight(monacoMock, false); + const height = getHeight(monacoMock, false, DEFAULT_MARGIN_BOTTOM); expect(height).toBe(1020); }); test('when using classic table, limited height > 500 lines to allow scrolling', () => { const monacoMock = getMonacoMock(1000); - const height = getHeight(monacoMock, false); + const height = getHeight(monacoMock, false, DEFAULT_MARGIN_BOTTOM); expect(height).toBe(5020); }); }); diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/get_height.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/get_height.tsx index dbab289018a63..5abade066cb95 100644 --- a/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/get_height.tsx +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/get_height.tsx @@ -6,9 +6,29 @@ * Side Public License, v 1. */ import { monaco } from '@kbn/monaco'; -import { MARGIN_BOTTOM, MAX_LINES_CLASSIC_TABLE, MIN_HEIGHT } from './source'; +import { MAX_LINES_CLASSIC_TABLE, MIN_HEIGHT } from './source'; -export function getHeight(editor: monaco.editor.IStandaloneCodeEditor, useDocExplorer: boolean) { +// Displayed margin of the tab content to the window bottom +export const DEFAULT_MARGIN_BOTTOM = 16; + +export function getTabContentAvailableHeight( + elementRef: HTMLElement | undefined, + decreaseAvailableHeightBy: number +): number { + if (!elementRef) { + return 0; + } + + // assign a good height filling the available space of the document flyout + const position = elementRef.getBoundingClientRect(); + return window.innerHeight - position.top - decreaseAvailableHeightBy; +} + +export function getHeight( + editor: monaco.editor.IStandaloneCodeEditor, + useDocExplorer: boolean, + decreaseAvailableHeightBy: number +) { const editorElement = editor?.getDomNode(); if (!editorElement) { return 0; @@ -16,9 +36,7 @@ export function getHeight(editor: monaco.editor.IStandaloneCodeEditor, useDocExp let result; if (useDocExplorer) { - // assign a good height filling the available space of the document flyout - const position = editorElement.getBoundingClientRect(); - result = window.innerHeight - position.top - MARGIN_BOTTOM; + result = getTabContentAvailableHeight(editorElement, decreaseAvailableHeightBy); } else { // takes care of the classic table, display a maximum of 500 lines // why not display it all? Due to performance issues when the browser needs to render it all diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.tsx index 430da5e15f1ad..bd88ec151189a 100644 --- a/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.tsx +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_source/source.tsx @@ -18,7 +18,7 @@ import { ElasticRequestState } from '@kbn/unified-doc-viewer'; import { isLegacyTableEnabled, SEARCH_FIELDS_FROM_SOURCE } from '@kbn/discover-utils'; import { getUnifiedDocViewerServices } from '../../plugin'; import { useEsDocSearch } from '../../hooks'; -import { getHeight } from './get_height'; +import { getHeight, DEFAULT_MARGIN_BOTTOM } from './get_height'; import { JSONCodeEditorCommonMemoized } from '../json_code_editor'; interface SourceViewerProps { @@ -28,6 +28,7 @@ interface SourceViewerProps { textBasedHits?: DataTableRecord[]; hasLineNumbers: boolean; width?: number; + decreaseAvailableHeightBy?: number; requestState?: ElasticRequestState; onRefresh: () => void; } @@ -35,8 +36,6 @@ interface SourceViewerProps { // Ihe number of lines displayed without scrolling used for classic table, which renders the component // inline limitation was necessary to enable virtualized scrolling, which improves performance export const MAX_LINES_CLASSIC_TABLE = 500; -// Displayed margin of the code editor to the window bottom when rendered in the document explorer flyout -export const MARGIN_BOTTOM = 80; // DocViewer flyout has a footer // Minimum height for the source content to guarantee minimum space when the flyout is scrollable. export const MIN_HEIGHT = 400; @@ -47,6 +46,7 @@ export const DocViewerSource = ({ width, hasLineNumbers, textBasedHits, + decreaseAvailableHeightBy, onRefresh, }: SourceViewerProps) => { const [editor, setEditor] = useState(); @@ -85,7 +85,11 @@ export const DocViewerSource = ({ return; } - const height = getHeight(editor, useDocExplorer); + const height = getHeight( + editor, + useDocExplorer, + decreaseAvailableHeightBy ?? DEFAULT_MARGIN_BOTTOM + ); if (height === 0) { return; } @@ -95,7 +99,7 @@ export const DocViewerSource = ({ } else { setEditorHeight(height); } - }, [editor, jsonValue, useDocExplorer, setEditorHeight]); + }, [editor, jsonValue, useDocExplorer, setEditorHeight, decreaseAvailableHeightBy]); const loadingState = (
diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/__snapshots__/table_cell_actions.test.tsx.snap b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/__snapshots__/table_cell_actions.test.tsx.snap new file mode 100644 index 0000000000000..bbc8ee91569ad --- /dev/null +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/__snapshots__/table_cell_actions.test.tsx.snap @@ -0,0 +1,351 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TableActions getFieldCellActions should render correctly for undefined functions 1`] = ` +Array [ + , + , +] +`; + +exports[`TableActions getFieldCellActions should render correctly for undefined functions 2`] = ` +Array [ + , +] +`; + +exports[`TableActions getFieldCellActions should render the panels correctly for defined onFilter function 1`] = ` +Array [ + , + , + , +] +`; + +exports[`TableActions getFieldValueCellActions should render correctly for undefined functions 1`] = `Array []`; + +exports[`TableActions getFieldValueCellActions should render the panels correctly for defined onFilter function 1`] = ` +Array [ + , + , +] +`; diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/legacy/table.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/legacy/table.tsx index deb5e5a52fb7a..0bdab73fe6c7f 100644 --- a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/legacy/table.tsx +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/legacy/table.tsx @@ -37,19 +37,19 @@ export const DocViewerLegacyTable = ({ const tableColumns = useMemo(() => { return !hideActionsColumn ? [ACTIONS_COLUMN, ...MAIN_COLUMNS] : MAIN_COLUMNS; }, [hideActionsColumn]); - const onToggleColumn = useCallback( - (field: string) => { - if (!onRemoveColumn || !onAddColumn || !columns) { - return; - } + + const onToggleColumn = useMemo(() => { + if (!onRemoveColumn || !onAddColumn || !columns) { + return undefined; + } + return (field: string) => { if (columns.includes(field)) { onRemoveColumn(field); } else { onAddColumn(field); } - }, - [onRemoveColumn, onAddColumn, columns] - ); + }; + }, [onRemoveColumn, onAddColumn, columns]); const onSetRowProps = useCallback(({ field: { field } }: FieldRecordLegacy) => { return { diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/legacy/table_cell_actions.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/legacy/table_cell_actions.tsx index 0b187c6d5b884..40d2265232294 100644 --- a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/legacy/table_cell_actions.tsx +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/legacy/table_cell_actions.tsx @@ -20,7 +20,7 @@ interface TableActionsProps { flattenedField: unknown; fieldMapping?: DataViewField; onFilter: DocViewFilterFn; - onToggleColumn: (field: string) => void; + onToggleColumn: ((field: string) => void) | undefined; ignoredValue: boolean; } @@ -47,11 +47,13 @@ export const TableActions = ({ onClick={() => onFilter(fieldMapping, flattenedField, '-')} /> )} - onToggleColumn(field)} - /> + {onToggleColumn && ( + onToggleColumn(field)} + /> + )} {onFilter && ( ; - field: { - pinned: boolean; - onTogglePinned: (field: string) => void; - } & FieldRecordLegacy['field']; - value: FieldRecordLegacy['value']; -} +import { + type TableRow, + getFieldCellActions, + getFieldValueCellActions, + getFilterExistsDisabledWarning, + getFilterInOutPairDisabledWarning, +} from './table_cell_actions'; +import { + DEFAULT_MARGIN_BOTTOM, + getTabContentAvailableHeight, +} from '../doc_viewer_source/get_height'; + +export type FieldRecord = TableRow; interface ItemsEntry { pinnedItems: FieldRecord[]; restItems: FieldRecord[]; } -const MOBILE_OPTIONS = { header: false }; -const PAGE_SIZE_OPTIONS = [25, 50, 100]; +const MIN_NAME_COLUMN_WIDTH = 150; +const MAX_NAME_COLUMN_WIDTH = 350; +const PAGE_SIZE_OPTIONS = [25, 50, 100, 250, 500]; const DEFAULT_PAGE_SIZE = 25; const PINNED_FIELDS_KEY = 'discover:pinnedFields'; const PAGE_SIZE = 'discover:pageSize'; const SEARCH_TEXT = 'discover:searchText'; +const GRID_COLUMN_FIELD_NAME = 'name'; +const GRID_COLUMN_FIELD_VALUE = 'value'; + +const GRID_PROPS: Pick = { + columnVisibility: { + visibleColumns: ['name', 'value'], + setVisibleColumns: () => null, + }, + rowHeightsOptions: { defaultHeight: 'auto' }, + gridStyle: { + border: 'horizontal', + stripes: true, + rowHover: 'highlight', + header: 'underline', + cellPadding: 'm', + fontSize: 's', + }, +}; + const getPinnedFields = (dataViewId: string, storage: Storage): string[] => { const pinnedFieldsEntry = storage.get(PINNED_FIELDS_KEY); if ( @@ -114,18 +135,12 @@ export const DocViewerTable = ({ columnsMeta, hit, dataView, - hideActionsColumn, filter, + decreaseAvailableHeightBy, onAddColumn, onRemoveColumn, }: DocViewRenderProps) => { - const { euiTheme } = useEuiTheme(); - const [ref, setRef] = useState(null); - const dimensions = useResizeObserver(ref); - const showActionsInsideTableCell = dimensions?.width - ? dimensions.width > euiTheme.breakpoint.m - : false; - + const [containerRef, setContainerRef] = useState(null); const { fieldFormats, storage, uiSettings } = getUnifiedDocViewerServices(); const showMultiFields = uiSettings.get(SHOW_MULTIFIELDS); const currentDataViewId = dataView.id!; @@ -147,19 +162,18 @@ export const DocViewerTable = ({ const mapping = useCallback((name: string) => dataView.fields.getByName(name), [dataView.fields]); - const onToggleColumn = useCallback( - (field: string) => { - if (!onRemoveColumn || !onAddColumn || !columns) { - return; - } + const onToggleColumn = useMemo(() => { + if (!onRemoveColumn || !onAddColumn || !columns) { + return undefined; + } + return (field: string) => { if (columns.includes(field)) { onRemoveColumn(field); } else { onAddColumn(field); } - }, - [onRemoveColumn, onAddColumn, columns] - ); + }; + }, [onRemoveColumn, onAddColumn, columns]); const onTogglePinned = useCallback( (field: string) => { @@ -173,6 +187,15 @@ export const DocViewerTable = ({ [currentDataViewId, pinnedFields, storage] ); + const onSearch = useCallback( + (event: React.ChangeEvent) => { + const newSearchText = event.currentTarget.value; + updateSearchText(newSearchText, storage); + setSearchText(newSearchText); + }, + [storage] + ); + const fieldToItem = useCallback( (field: string, isPinned: boolean) => { const fieldMapping = mapping(field); @@ -193,7 +216,6 @@ export const DocViewerTable = ({ action: { onToggleColumn, onFilter: filter, - isActive: !!columns?.includes(field), flattenedField: flattened[field], }, field: { @@ -223,7 +245,6 @@ export const DocViewerTable = ({ hit, onToggleColumn, filter, - columns, columnsMeta, flattened, onTogglePinned, @@ -231,15 +252,6 @@ export const DocViewerTable = ({ ] ); - const handleOnChange = useCallback( - (event: React.ChangeEvent) => { - const newSearchText = event.currentTarget.value; - updateSearchText(newSearchText, storage); - setSearchText(newSearchText); - }, - [storage] - ); - const { pinnedItems, restItems } = Object.keys(flattened) .sort((fieldA, fieldB) => { const mappingA = mapping(fieldA); @@ -278,11 +290,12 @@ export const DocViewerTable = ({ } ); - const { curPageIndex, pageSize, totalPages, startIndex, changePageIndex, changePageSize } = - usePager({ - initialPageSize: getPageSize(storage), - totalItems: restItems.length, - }); + const rows = useMemo(() => [...pinnedItems, ...restItems], [pinnedItems, restItems]); + + const { curPageIndex, pageSize, totalPages, changePageIndex, changePageSize } = usePager({ + initialPageSize: getPageSize(storage), + totalItems: rows.length, + }); const showPagination = totalPages !== 0; const onChangePageSize = useCallback( @@ -293,126 +306,160 @@ export const DocViewerTable = ({ [changePageSize, storage] ); - const headers = [ - !hideActionsColumn && ( - - - - { + return showPagination + ? { + onChangeItemsPerPage: onChangePageSize, + onChangePage: changePageIndex, + pageIndex: curPageIndex, + pageSize, + pageSizeOptions: PAGE_SIZE_OPTIONS, + } + : undefined; + }, [showPagination, curPageIndex, pageSize, onChangePageSize, changePageIndex]); + + const fieldCellActions = useMemo( + () => getFieldCellActions({ rows, filter, onToggleColumn }), + [rows, filter, onToggleColumn] + ); + const fieldValueCellActions = useMemo( + () => getFieldValueCellActions({ rows, filter }), + [rows, filter] + ); + + useWindowSize(); // trigger re-render on window resize to recalculate the grid container height + const { width: containerWidth } = useResizeObserver(containerRef); + + const gridColumns: EuiDataGridProps['columns'] = useMemo( + () => [ + { + id: GRID_COLUMN_FIELD_NAME, + displayAsText: i18n.translate('unifiedDocViewer.fieldChooser.discoverField.name', { + defaultMessage: 'Field', + }), + initialWidth: Math.min( + Math.max(Math.round(containerWidth * 0.3), MIN_NAME_COLUMN_WIDTH), + MAX_NAME_COLUMN_WIDTH + ), + actions: false, + visibleCellActions: 3, + cellActions: fieldCellActions, + }, + { + id: GRID_COLUMN_FIELD_VALUE, + displayAsText: i18n.translate('unifiedDocViewer.fieldChooser.discoverField.value', { + defaultMessage: 'Value', + }), + actions: false, + visibleCellActions: 2, + cellActions: fieldValueCellActions, + }, + ], + [fieldCellActions, fieldValueCellActions, containerWidth] + ); + + const renderCellValue: EuiDataGridProps['renderCellValue'] = useCallback( + ({ rowIndex, columnId, isDetails }) => { + const row = rows[rowIndex]; + + if (!row) { + return null; + } + + const { + action: { flattenedField }, + field: { field, fieldMapping, fieldType, scripted, pinned }, + value: { formattedValue, ignored }, + } = row; + + if (columnId === 'name') { + return ( +
+ - - - - ), - - - - - - - , - - - - + +
+ ) : null} +
+ ); + } + + if (columnId === 'value') { + return ( + - - - , - ]; - - const renderRows = useCallback( - (items: FieldRecord[]) => { - return items.map( - ({ - action: { flattenedField, onFilter }, - field: { field, fieldMapping, fieldType, scripted, pinned }, - value: { formattedValue, ignored }, - }: FieldRecord) => { - return ( - - {!hideActionsColumn && ( - - - - )} - - - - - - - - ); - } + ); + } + + return null; + }, + [rows, searchText] + ); + + const renderCellPopover = useCallback( + (props: EuiDataGridCellPopoverElementProps) => { + const { columnId, children, cellActions, rowIndex } = props; + const row = rows[rowIndex]; + + let warningMessage: string | undefined; + if (columnId === GRID_COLUMN_FIELD_VALUE) { + warningMessage = getFilterInOutPairDisabledWarning(row); + } else if (columnId === GRID_COLUMN_FIELD_NAME) { + warningMessage = getFilterExistsDisabledWarning(row); + } + + return ( + <> + {children} + {cellActions} + {Boolean(warningMessage) && ( +
+ + +
+ )} + ); }, - [hideActionsColumn, showActionsInsideTableCell, onToggleColumn, onTogglePinned, searchText] + [rows] ); - const rowElements = [ - ...renderRows(pinnedItems), - ...renderRows(restItems.slice(startIndex, pageSize + startIndex)), - ]; + const containerHeight = containerRef + ? getTabContentAvailableHeight(containerRef, decreaseAvailableHeightBy ?? DEFAULT_MARGIN_BOTTOM) + : 0; return ( - + @@ -421,14 +468,14 @@ export const DocViewerTable = ({ - {rowElements.length === 0 ? ( + {rows.length === 0 ? (

) : ( - - - {headers} - {rowElements} - - - )} - - - - - - {showPagination && ( - - - + <> + + + + + + + )} ); diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table_cell_actions.test.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table_cell_actions.test.tsx index 076ca2d67be10..86b6cb6acf833 100644 --- a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table_cell_actions.test.tsx +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table_cell_actions.test.tsx @@ -6,56 +6,82 @@ * Side Public License, v 1. */ import React from 'react'; -import { render, screen } from '@testing-library/react'; -import { TableActions } from './table_cell_actions'; +import { getFieldCellActions, getFieldValueCellActions, TableRow } from './table_cell_actions'; import { DataViewField } from '@kbn/data-views-plugin/common'; describe('TableActions', () => { - it('should render the panels correctly for undefined onFilter function', () => { - render( - - ); - expect(screen.queryByTestId('addFilterForValueButton-message')).not.toBeInTheDocument(); - expect(screen.queryByTestId('addFilterOutValueButton-message')).not.toBeInTheDocument(); - expect(screen.queryByTestId('addExistsFilterButton-message')).not.toBeInTheDocument(); - expect(screen.getByTestId('toggleColumnButton-message')).not.toBeDisabled(); - expect(screen.getByTestId('togglePinFilterButton-message')).not.toBeDisabled(); + const rows: TableRow[] = [ + { + action: { + onFilter: jest.fn(), + flattenedField: 'flattenedField', + onToggleColumn: jest.fn(), + }, + field: { + pinned: true, + onTogglePinned: jest.fn(), + field: 'message', + fieldMapping: new DataViewField({ + type: 'keyword', + name: 'message', + searchable: true, + aggregatable: true, + }), + fieldType: 'keyword', + displayName: 'message', + scripted: false, + }, + value: { + ignored: undefined, + formattedValue: 'test', + }, + }, + ]; + + const Component = () =>

Component
; + const EuiCellParams = { + Component, + rowIndex: 0, + colIndex: 0, + columnId: 'test', + isExpanded: false, + }; + + describe('getFieldCellActions', () => { + it('should render correctly for undefined functions', () => { + expect( + getFieldCellActions({ rows, filter: undefined, onToggleColumn: jest.fn() }).map((item) => + item(EuiCellParams) + ) + ).toMatchSnapshot(); + + expect( + getFieldCellActions({ rows, filter: undefined, onToggleColumn: undefined }).map((item) => + item(EuiCellParams) + ) + ).toMatchSnapshot(); + }); + + it('should render the panels correctly for defined onFilter function', () => { + expect( + getFieldCellActions({ rows, filter: jest.fn(), onToggleColumn: jest.fn() }).map((item) => + item(EuiCellParams) + ) + ).toMatchSnapshot(); + }); }); - it('should render the panels correctly for defined onFilter function', () => { - render( - - ); - expect(screen.getByTestId('addFilterForValueButton-message')).not.toBeDisabled(); - expect(screen.getByTestId('addFilterOutValueButton-message')).not.toBeDisabled(); - expect(screen.getByTestId('addExistsFilterButton-message')).not.toBeDisabled(); - expect(screen.getByTestId('toggleColumnButton-message')).not.toBeDisabled(); - expect(screen.getByTestId('togglePinFilterButton-message')).not.toBeDisabled(); + describe('getFieldValueCellActions', () => { + it('should render correctly for undefined functions', () => { + expect( + getFieldValueCellActions({ rows, filter: undefined }).map((item) => item(EuiCellParams)) + ).toMatchSnapshot(); + }); + + it('should render the panels correctly for defined onFilter function', () => { + expect( + getFieldValueCellActions({ rows, filter: jest.fn() }).map((item) => item(EuiCellParams)) + ).toMatchSnapshot(); + }); }); }); diff --git a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table_cell_actions.tsx b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table_cell_actions.tsx index cfbfd1b1cde03..7814405e09201 100644 --- a/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table_cell_actions.tsx +++ b/src/plugins/unified_doc_viewer/public/components/doc_viewer_table/table_cell_actions.tsx @@ -6,125 +6,210 @@ * Side Public License, v 1. */ -import React, { useCallback, useState } from 'react'; -import { - EuiButtonIcon, - EuiContextMenu, - EuiPopover, - EuiFlexGroup, - EuiFlexItem, - EuiToolTip, -} from '@elastic/eui'; +import React from 'react'; +import { EuiDataGridColumnCellActionProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import type { DataViewField } from '@kbn/data-views-plugin/public'; -import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; +import { DocViewFilterFn, FieldRecordLegacy } from '@kbn/unified-doc-viewer/types'; + +export interface TableRow { + action: Omit; + field: { + pinned: boolean; + onTogglePinned: (field: string) => void; + } & FieldRecordLegacy['field']; + value: FieldRecordLegacy['value']; +} interface TableActionsProps { - mode?: 'inline' | 'as_popover'; - field: string; - pinned: boolean; - flattenedField: unknown; - fieldMapping?: DataViewField; - onFilter?: DocViewFilterFn; - onToggleColumn: (field: string) => void; - ignoredValue: boolean; - onTogglePinned: (field: string) => void; + Component: EuiDataGridColumnCellActionProps['Component']; + row: TableRow | undefined; // as we pass `rows[rowIndex]` it's safer to assume that `row` prop can be undefined } -interface PanelItem { - name: string; - 'aria-label': string; - toolTipContent?: string; - disabled?: boolean; - 'data-test-subj': string; - icon: string; - onClick: () => void; +export function isFilterInOutPairDisabled(row: TableRow | undefined): boolean { + if (!row) { + return false; + } + const { + action: { onFilter }, + field: { fieldMapping }, + value: { ignored }, + } = row; + + return Boolean(onFilter && (!fieldMapping || !fieldMapping.filterable || ignored)); } -export const TableActions = ({ - mode = 'as_popover', - pinned, - field, - fieldMapping, - flattenedField, - onToggleColumn, - onFilter, - ignoredValue, - onTogglePinned, -}: TableActionsProps) => { - const [isOpen, setIsOpen] = useState(false); - const openActionsLabel = i18n.translate('unifiedDocViewer.docView.table.actions.open', { - defaultMessage: 'Open actions', - }); - const actionsLabel = i18n.translate('unifiedDocViewer.docView.table.actions.label', { - defaultMessage: 'Actions', - }); +export function getFilterInOutPairDisabledWarning(row: TableRow | undefined): string | undefined { + if (!row || !isFilterInOutPairDisabled(row)) { + return undefined; + } + const { + field: { fieldMapping }, + value: { ignored }, + } = row; + + if (ignored) { + return i18n.translate( + 'unifiedDocViewer.docViews.table.ignoredValuesCanNotBeSearchedWarningMessage', + { + defaultMessage: 'Ignored values cannot be searched', + } + ); + } + + return !fieldMapping + ? i18n.translate( + 'unifiedDocViewer.docViews.table.unindexedFieldsCanNotBeSearchedWarningMessage', + { + defaultMessage: 'Unindexed fields cannot be searched', + } + ) + : undefined; +} + +export const FilterIn: React.FC = ({ Component, row }) => { + if (!row) { + return null; + } + + const { + action: { onFilter, flattenedField }, + field: { field, fieldMapping }, + } = row; // Filters pair - const filtersPairDisabled = !fieldMapping || !fieldMapping.filterable || ignoredValue; const filterAddLabel = i18n.translate( 'unifiedDocViewer.docViews.table.filterForValueButtonTooltip', { defaultMessage: 'Filter for value', } ); - const filterAddAriaLabel = i18n.translate( - 'unifiedDocViewer.docViews.table.filterForValueButtonAriaLabel', - { defaultMessage: 'Filter for value' } + + if (!onFilter) { + return null; + } + + return ( + onFilter(fieldMapping, flattenedField, '+')} + > + {filterAddLabel} + ); +}; + +export const FilterOut: React.FC = ({ Component, row }) => { + if (!row) { + return null; + } + + const { + action: { onFilter, flattenedField }, + field: { field, fieldMapping }, + } = row; + + // Filters pair const filterOutLabel = i18n.translate( 'unifiedDocViewer.docViews.table.filterOutValueButtonTooltip', { defaultMessage: 'Filter out value', } ); - const filterOutAriaLabel = i18n.translate( - 'unifiedDocViewer.docViews.table.filterOutValueButtonAriaLabel', - { defaultMessage: 'Filter out value' } + + if (!onFilter) { + return null; + } + + return ( + onFilter(fieldMapping, flattenedField, '-')} + > + {filterOutLabel} + ); - const filtersPairToolTip = - (filtersPairDisabled && - i18n.translate('unifiedDocViewer.docViews.table.unindexedFieldsCanNotBeSearchedTooltip', { - defaultMessage: 'Unindexed fields or ignored values cannot be searched', - })) || - undefined; +}; + +export function isFilterExistsDisabled(row: TableRow | undefined): boolean { + if (!row) { + return false; + } + const { + action: { onFilter }, + field: { fieldMapping }, + } = row; + + return Boolean(onFilter && (!fieldMapping || !fieldMapping.filterable || fieldMapping.scripted)); +} + +export function getFilterExistsDisabledWarning(row: TableRow | undefined): string | undefined { + if (!row || !isFilterExistsDisabled(row)) { + return undefined; + } + const { + field: { fieldMapping }, + } = row; + + return fieldMapping?.scripted + ? i18n.translate( + 'unifiedDocViewer.docViews.table.unableToFilterForPresenceOfScriptedFieldsWarningMessage', + { + defaultMessage: 'Unable to filter for presence of scripted fields', + } + ) + : undefined; +} + +export const FilterExist: React.FC = ({ Component, row }) => { + if (!row) { + return null; + } + + const { + action: { onFilter }, + field: { field }, + } = row; // Filter exists const filterExistsLabel = i18n.translate( 'unifiedDocViewer.docViews.table.filterForFieldPresentButtonTooltip', { defaultMessage: 'Filter for field present' } ); - const filterExistsAriaLabel = i18n.translate( - 'unifiedDocViewer.docViews.table.filterForFieldPresentButtonAriaLabel', - { defaultMessage: 'Filter for field present' } - ); - const filtersExistsDisabled = !fieldMapping || !fieldMapping.filterable; - const filtersExistsToolTip = - (filtersExistsDisabled && - (fieldMapping && fieldMapping.scripted - ? i18n.translate( - 'unifiedDocViewer.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip', - { - defaultMessage: 'Unable to filter for presence of scripted fields', - } - ) - : i18n.translate( - 'unifiedDocViewer.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip', - { - defaultMessage: 'Unable to filter for presence of meta fields', - } - ))) || - undefined; - - // Toggle columns - const toggleColumnsLabel = i18n.translate( - 'unifiedDocViewer.docViews.table.toggleColumnInTableButtonTooltip', - { defaultMessage: 'Toggle column in table' } - ); - const toggleColumnsAriaLabel = i18n.translate( - 'unifiedDocViewer.docViews.table.toggleColumnInTableButtonAriaLabel', - { defaultMessage: 'Toggle column in table' } + + if (!onFilter) { + return null; + } + + return ( + onFilter('_exists_', field, '+')} + > + {filterExistsLabel} + ); +}; + +export const PinToggle: React.FC = ({ Component, row }) => { + if (!row) { + return null; + } + + const { + field: { field, pinned, onTogglePinned }, + } = row; // Pinned const pinnedLabel = pinned @@ -134,128 +219,101 @@ export const TableActions = ({ : i18n.translate('unifiedDocViewer.docViews.table.pinFieldLabel', { defaultMessage: 'Pin field', }); - const pinnedAriaLabel = pinned - ? i18n.translate('unifiedDocViewer.docViews.table.unpinFieldAriaLabel', { - defaultMessage: 'Unpin field', - }) - : i18n.translate('unifiedDocViewer.docViews.table.pinFieldAriaLabel', { - defaultMessage: 'Pin field', - }); const pinnedIconType = pinned ? 'pinFilled' : 'pin'; - const toggleOpenPopover = useCallback(() => setIsOpen((current) => !current), []); - const closePopover = useCallback(() => setIsOpen(false), []); - const togglePinned = useCallback(() => onTogglePinned(field), [field, onTogglePinned]); - const onClickAction = useCallback( - (callback: () => void) => () => { - callback(); - closePopover(); - }, - [closePopover] + return ( + onTogglePinned(field)} + > + {pinnedLabel} + ); +}; - let panelItems: PanelItem[] = [ - { - name: toggleColumnsLabel, - 'aria-label': toggleColumnsAriaLabel, - 'data-test-subj': `toggleColumnButton-${field}`, - icon: 'listAdd', - onClick: onClickAction(onToggleColumn.bind({}, field)), - }, - { - name: pinnedLabel, - 'aria-label': pinnedAriaLabel, - icon: pinnedIconType, - 'data-test-subj': `togglePinFilterButton-${field}`, - onClick: onClickAction(togglePinned), - }, - ]; - - if (onFilter) { - panelItems = [ - { - name: filterAddLabel, - 'aria-label': filterAddAriaLabel, - toolTipContent: filtersPairToolTip, - icon: 'plusInCircle', - disabled: filtersPairDisabled, - 'data-test-subj': `addFilterForValueButton-${field}`, - onClick: onClickAction(onFilter.bind({}, fieldMapping, flattenedField, '+')), - }, - { - name: filterOutLabel, - 'aria-label': filterOutAriaLabel, - toolTipContent: filtersPairToolTip, - icon: 'minusInCircle', - disabled: filtersPairDisabled, - 'data-test-subj': `addFilterOutValueButton-${field}`, - onClick: onClickAction(onFilter.bind({}, fieldMapping, flattenedField, '-')), - }, - { - name: filterExistsLabel, - 'aria-label': filterExistsAriaLabel, - toolTipContent: filtersExistsToolTip, - icon: 'filter', - disabled: filtersExistsDisabled, - 'data-test-subj': `addExistsFilterButton-${field}`, - onClick: onClickAction(onFilter.bind({}, '_exists_', field, '+')), - }, - ...panelItems, - ]; +export const ToggleColumn: React.FC = ({ Component, row }) => { + if (!row) { + return null; } - const panels = [ - { - id: 0, - title: actionsLabel, - items: panelItems, - }, - ]; + const { + action: { onToggleColumn }, + field: { field }, + } = row; - if (mode === 'inline') { - return ( - - {panels[0].items.map((item) => ( - - - - - - ))} - - ); + if (!onToggleColumn) { + return null; } + // Toggle column + const toggleColumnLabel = i18n.translate( + 'unifiedDocViewer.docViews.table.toggleColumnTableButtonTooltip', + { + defaultMessage: 'Toggle column in table', + } + ); + return ( - - } - isOpen={isOpen} - closePopover={closePopover} - display="block" - panelPaddingSize="none" + onToggleColumn(field)} > - - + {toggleColumnLabel} +
); }; + +export function getFieldCellActions({ + rows, + filter, + onToggleColumn, +}: { + rows: TableRow[]; + filter?: DocViewFilterFn; + onToggleColumn: ((field: string) => void) | undefined; +}) { + return [ + ...(filter + ? [ + ({ Component, rowIndex }: EuiDataGridColumnCellActionProps) => { + return ; + }, + ] + : []), + ...(onToggleColumn + ? [ + ({ Component, rowIndex }: EuiDataGridColumnCellActionProps) => { + return ; + }, + ] + : []), + ({ Component, rowIndex }: EuiDataGridColumnCellActionProps) => { + return ; + }, + ]; +} + +export function getFieldValueCellActions({ + rows, + filter, +}: { + rows: TableRow[]; + filter?: DocViewFilterFn; +}) { + return filter + ? [ + ({ Component, rowIndex }: EuiDataGridColumnCellActionProps) => { + return ; + }, + ({ Component, rowIndex }: EuiDataGridColumnCellActionProps) => { + return ; + }, + ] + : []; +} diff --git a/src/plugins/unified_doc_viewer/public/components/index.ts b/src/plugins/unified_doc_viewer/public/components/index.ts index b5f3a8948d689..c7a2cef523f76 100644 --- a/src/plugins/unified_doc_viewer/public/components/index.ts +++ b/src/plugins/unified_doc_viewer/public/components/index.ts @@ -7,6 +7,7 @@ */ export * from './doc_viewer'; +export * from './doc_viewer_flyout'; export * from './doc_viewer_source'; export * from './doc_viewer_table'; export * from './json_code_editor'; diff --git a/src/plugins/unified_doc_viewer/public/components/lazy_doc_viewer.tsx b/src/plugins/unified_doc_viewer/public/components/lazy_doc_viewer.tsx new file mode 100644 index 0000000000000..06bbb3e71bde3 --- /dev/null +++ b/src/plugins/unified_doc_viewer/public/components/lazy_doc_viewer.tsx @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { withSuspense } from '@kbn/shared-ux-utility'; +import type { DocViewRenderProps } from '@kbn/unified-doc-viewer/src/services/types'; +import { EuiDelayRender, EuiSkeletonText } from '@elastic/eui'; + +const LazyUnifiedDocViewer = React.lazy(() => import('./doc_viewer')); +export const UnifiedDocViewer = withSuspense( + LazyUnifiedDocViewer, + + + +); diff --git a/src/plugins/unified_doc_viewer/public/components/lazy_doc_viewer_flyout.tsx b/src/plugins/unified_doc_viewer/public/components/lazy_doc_viewer_flyout.tsx new file mode 100644 index 0000000000000..e81dfaec2f319 --- /dev/null +++ b/src/plugins/unified_doc_viewer/public/components/lazy_doc_viewer_flyout.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { withSuspense } from '@kbn/shared-ux-utility'; +import type { UnifiedDocViewerFlyoutProps } from './doc_viewer_flyout/doc_viewer_flyout'; + +const LazyUnifiedDocViewerFlyout = React.lazy(() => import('./doc_viewer_flyout')); +export const UnifiedDocViewerFlyout = withSuspense( + LazyUnifiedDocViewerFlyout, + <> +); diff --git a/src/plugins/unified_doc_viewer/public/index.tsx b/src/plugins/unified_doc_viewer/public/index.tsx index ffe5c3f16d78c..b594d8f06c42f 100644 --- a/src/plugins/unified_doc_viewer/public/index.tsx +++ b/src/plugins/unified_doc_viewer/public/index.tsx @@ -9,7 +9,6 @@ import React from 'react'; import { withSuspense } from '@kbn/shared-ux-utility'; import { EuiDelayRender, EuiSkeletonText } from '@elastic/eui'; -import { DocViewRenderProps } from '@kbn/unified-doc-viewer/src/services/types'; import type { JsonCodeEditorProps } from './components'; import { UnifiedDocViewerPublicPlugin } from './plugin'; @@ -26,14 +25,8 @@ export const JsonCodeEditor = withSuspense( ); -const LazyUnifiedDocViewer = React.lazy(() => import('./components/doc_viewer')); -export const UnifiedDocViewer = withSuspense( - LazyUnifiedDocViewer, - - - -); - export { useEsDocSearch } from './hooks'; +export { UnifiedDocViewer } from './components/lazy_doc_viewer'; +export { UnifiedDocViewerFlyout } from './components/lazy_doc_viewer_flyout'; export const plugin = () => new UnifiedDocViewerPublicPlugin(); diff --git a/src/plugins/unified_doc_viewer/public/plugin.tsx b/src/plugins/unified_doc_viewer/public/plugin.tsx index 524a02eec9ee9..13027a2541084 100644 --- a/src/plugins/unified_doc_viewer/public/plugin.tsx +++ b/src/plugins/unified_doc_viewer/public/plugin.tsx @@ -99,7 +99,7 @@ export class UnifiedDocViewerPublicPlugin defaultMessage: 'JSON', }), order: 20, - component: ({ hit, dataView, textBasedHits }) => { + component: ({ hit, dataView, textBasedHits, decreaseAvailableHeightBy }) => { return ( {}} /> ); diff --git a/src/plugins/unified_doc_viewer/tsconfig.json b/src/plugins/unified_doc_viewer/tsconfig.json index 43cfe7945f188..fbe2ac83c5f1a 100644 --- a/src/plugins/unified_doc_viewer/tsconfig.json +++ b/src/plugins/unified_doc_viewer/tsconfig.json @@ -30,7 +30,9 @@ "@kbn/react-field", "@kbn/ui-theme", "@kbn/discover-shared-plugin", - "@kbn/fields-metadata-plugin" + "@kbn/fields-metadata-plugin", + "@kbn/unified-data-table", + "@kbn/core-notifications-browser" ], "exclude": [ "target/**/*", diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx index 892144d07fb06..941040dfd30f8 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -566,7 +566,7 @@ export const QueryBarTopRow = React.memo( : strings.getRefreshQueryLabel(); const buttonLabelRun = textBasedRunShortcut; - const iconDirty = Boolean(isQueryLangSelected) ? 'play' : 'kqlFunction'; + const iconDirty = Boolean(isQueryLangSelected) ? 'playFilled' : 'kqlFunction'; const tooltipDirty = Boolean(isQueryLangSelected) ? buttonLabelRun : buttonLabelUpdate; const isDirtyButtonLabel = Boolean(isQueryLangSelected) @@ -588,7 +588,7 @@ export const QueryBarTopRow = React.memo( onClick={onClickSubmitButton} size={shouldShowDatePickerAsBadge() ? 's' : 'm'} color={props.isDirty ? 'success' : 'primary'} - fill={props.isDirty} + fill={false} needsUpdate={props.isDirty} data-test-subj="querySubmitButton" toolTipProps={{ diff --git a/src/plugins/usage_collection/server/collector/types.ts b/src/plugins/usage_collection/server/collector/types.ts index 50a04560e384b..9c1802348a113 100644 --- a/src/plugins/usage_collection/server/collector/types.ts +++ b/src/plugins/usage_collection/server/collector/types.ts @@ -8,7 +8,7 @@ import type { ElasticsearchClient, SavedObjectsClientContract, Logger } from '@kbn/core/server'; -import type { PossibleSchemaTypes, SchemaMetaOptional } from '@kbn/analytics-client'; +import type { PossibleSchemaTypes, SchemaMetaOptional } from '@kbn/ebt/client'; export type { AllowedSchemaTypes, @@ -16,7 +16,7 @@ export type { AllowedSchemaBooleanTypes, AllowedSchemaNumberTypes, PossibleSchemaTypes, -} from '@kbn/analytics-client'; +} from '@kbn/ebt/client'; /** * Helper to find out whether to keep recursively looking or if we are on an end value diff --git a/src/plugins/usage_collection/tsconfig.json b/src/plugins/usage_collection/tsconfig.json index d7cf3f1e4c19f..e7c24d604be96 100644 --- a/src/plugins/usage_collection/tsconfig.json +++ b/src/plugins/usage_collection/tsconfig.json @@ -17,12 +17,12 @@ "@kbn/config-schema", "@kbn/screenshot-mode-plugin", "@kbn/std", - "@kbn/analytics-client", "@kbn/utility-types", "@kbn/i18n", "@kbn/core-http-server-mocks", "@kbn/analytics-collection-utils", "@kbn/logging", + "@kbn/ebt", ], "exclude": [ "target/**/*", diff --git a/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.test.ts b/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.test.ts index d1da337a87862..5d7074ce44022 100644 --- a/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.test.ts +++ b/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { PartitionLegendValue } from '@kbn/visualizations-plugin/common/constants'; +import { LegendValue } from '@elastic/charts'; import { getConfiguration } from '.'; import { samplePieVis } from '../../sample_vis.test.mocks'; @@ -39,7 +39,7 @@ describe('getConfiguration', () => { percentDecimals: 2, primaryGroups: ['bucket-1'], secondaryGroups: [], - legendStats: [PartitionLegendValue.Value], + legendStats: [LegendValue.Value], truncateLegend: true, }, ], diff --git a/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.ts index acad50d7f3d30..dc542e9e42ff4 100644 --- a/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.ts +++ b/src/plugins/vis_types/pie/public/convert_to_lens/configurations/index.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ +import { LegendValue } from '@elastic/charts'; import { LegendDisplay, PartitionVisParams } from '@kbn/expression-partition-vis-plugin/common'; -import { PartitionLegendValue } from '@kbn/visualizations-plugin/common/constants'; import { CategoryDisplayTypes, NumberDisplayTypes, @@ -28,7 +28,7 @@ const getLayers = ( const showValuesInLegend = vis.params.labels.values ?? (vis.params.legendStats - ? vis.params.legendStats?.[0] === PartitionLegendValue.Value + ? vis.params.legendStats?.[0] === LegendValue.Value : vis.type.visConfig.defaults.showValuesInLegend); return [ @@ -50,7 +50,7 @@ const getLayers = ( vis.params.legendDisplay ?? vis.type.visConfig.defaults.legendDisplay, legendPosition: vis.params.legendPosition ?? vis.type.visConfig.defaults.legendPosition, - legendStats: showValuesInLegend ? [PartitionLegendValue.Value] : undefined, + legendStats: showValuesInLegend ? [LegendValue.Value] : undefined, nestedLegend: vis.params.nestedLegend ?? vis.type.visConfig.defaults.nestedLegend, percentDecimals: vis.params.labels.percentDecimals ?? vis.type.visConfig.defaults.labels.percentDecimals, diff --git a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts index bc1bd87140654..88b60ba49a55f 100644 --- a/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts +++ b/src/plugins/vis_types/xy/public/convert_to_lens/configurations/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Position, ScaleType as ECScaleType } from '@elastic/charts'; +import { LegendValue, Position, ScaleType as ECScaleType } from '@elastic/charts'; import { SeriesTypes, Column, @@ -15,7 +15,6 @@ import { XYReferenceLineLayerConfig, } from '@kbn/visualizations-plugin/common/convert_to_lens'; import { Vis } from '@kbn/visualizations-plugin/public'; -import { XYLegendValue } from '@kbn/visualizations-plugin/common/constants'; import { Layer } from '..'; import { ChartType } from '../../../common'; import { @@ -237,7 +236,7 @@ export const getConfiguration = ( maxLines: vis.params.maxLegendLines ?? vis.type.visConfig.defaults.maxLegendLines, showSingleSeries: true, legendStats: Boolean(vis.params.labels.show ?? vis.type.visConfig.defaults.labels?.show) - ? [XYLegendValue.CurrentAndLastValue] + ? [LegendValue.CurrentAndLastValue] : undefined, }, fittingFunction: fittingFunction diff --git a/src/plugins/vis_types/xy/public/to_ast.ts b/src/plugins/vis_types/xy/public/to_ast.ts index c855aae1865dd..0a6694f1d5a8e 100644 --- a/src/plugins/vis_types/xy/public/to_ast.ts +++ b/src/plugins/vis_types/xy/public/to_ast.ts @@ -7,7 +7,7 @@ */ import moment from 'moment'; -import { Position, ScaleType as ECScaleType } from '@elastic/charts'; +import { LegendValue, Position, ScaleType as ECScaleType } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; import { VisToExpressionAst, @@ -20,7 +20,6 @@ import { buildExpression, buildExpressionFunction } from '@kbn/expressions-plugi import { BUCKET_TYPES } from '@kbn/data-plugin/public'; import type { TimeRangeBounds } from '@kbn/data-plugin/common'; import type { PaletteOutput } from '@kbn/charts-plugin/common/expressions/palette/types'; -import { XYLegendValue } from '@kbn/visualizations-plugin/common/constants'; import { Dimensions, Dimension, @@ -48,7 +47,7 @@ const prepareLengend = (params: VisParams, legendSize?: LegendSize) => { shouldTruncate: params.truncateLegend, showSingleSeries: true, legendSize, - legendStats: params.labels.show ? [XYLegendValue.CurrentAndLastValue] : undefined, + legendStats: params.labels.show ? [LegendValue.CurrentAndLastValue] : undefined, }); return buildExpression([legend]); diff --git a/src/plugins/visualizations/common/constants.ts b/src/plugins/visualizations/common/constants.ts index f8f6409b71122..4ae3e174fc5c6 100644 --- a/src/plugins/visualizations/common/constants.ts +++ b/src/plugins/visualizations/common/constants.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { LegendValue } from '@elastic/charts'; import { METRIC_TYPES, BUCKET_TYPES } from '@kbn/data-plugin/common'; export const SAVED_OBJECTS_LIMIT_SETTING = 'savedObjects:listingLimit'; @@ -37,6 +38,11 @@ export enum LegendSize { EXTRA_LARGE = 'xlarge', } +export enum LegendLayout { + Table = 'table', + List = 'list', +} + export const LegendSizeToPixels = { [LegendSize.AUTO]: undefined, [LegendSize.SMALL]: 80, @@ -52,10 +58,25 @@ export const SUPPORTED_AGGREGATIONS = [ ...Object.values(BUCKET_TYPES), ] as const; -export enum XYLegendValue { - CurrentAndLastValue = 'currentAndLastValue', -} +export type XYLegendValue = Extract< + LegendValue, + | 'currentAndLastValue' + | 'lastValue' + | 'lastNonNullValue' + | 'average' + | 'median' + | 'max' + | 'min' + | 'firstValue' + | 'firstNonNullValue' + | 'total' + | 'count' + | 'distinctCount' + | 'variance' + | 'stdDeviation' + | 'range' + | 'difference' + | 'differencePercent' +>; -export enum PartitionLegendValue { - Value = 'value', -} +export type PartitionLegendValue = Extract; diff --git a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts index 675355dc69315..d167935720cf4 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts @@ -10,7 +10,7 @@ import { HorizontalAlignment, LayoutDirection, Position, VerticalAlignment } fro import { $Values } from '@kbn/utility-types'; import type { CustomPaletteParams, PaletteOutput } from '@kbn/coloring'; import { KibanaQueryOutput } from '@kbn/data-plugin/common'; -import { LegendSize, XYLegendValue, PartitionLegendValue } from '../../constants'; +import { LegendSize, type XYLegendValue, type PartitionLegendValue } from '../../constants'; import { CategoryDisplayTypes, PartitionChartTypes, diff --git a/src/plugins/visualizations/common/index.ts b/src/plugins/visualizations/common/index.ts index f5047212603c1..82dfd8d76b29a 100644 --- a/src/plugins/visualizations/common/index.ts +++ b/src/plugins/visualizations/common/index.ts @@ -20,6 +20,7 @@ export { LegendSize, LegendSizeToPixels, DEFAULT_LEGEND_SIZE, - XYLegendValue, - PartitionLegendValue, + LegendLayout, + type XYLegendValue, + type PartitionLegendValue, } from './constants'; diff --git a/test/accessibility/apps/discover.ts b/test/accessibility/apps/discover.ts index 454b48b97ebca..abdfe096efab2 100644 --- a/test/accessibility/apps/discover.ts +++ b/test/accessibility/apps/discover.ts @@ -134,15 +134,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('a11y test for actions on a field', async () => { await PageObjects.discover.clickDocViewerTab('doc_view_table'); - if (await testSubjects.exists('openFieldActionsButton-Cancelled')) { - await testSubjects.click('openFieldActionsButton-Cancelled'); // Open the actions - } else { - await testSubjects.existOrFail('fieldActionsGroup-Cancelled'); - } + await dataGrid.expandFieldNameCellInFlyout('Cancelled'); await a11y.testAppSnapshot(); - if (await testSubjects.exists('openFieldActionsButton-Cancelled')) { - await testSubjects.click('openFieldActionsButton-Cancelled'); // Close the actions - } + await browser.pressKeys(browser.keys.ESCAPE); }); it('a11y test for data-grid table with columns', async () => { diff --git a/test/analytics/plugins/analytics_ftr_helpers/common/fetch_events.ts b/test/analytics/plugins/analytics_ftr_helpers/common/fetch_events.ts index 5a5eeba914b7b..9a2e235a0aa6a 100644 --- a/test/analytics/plugins/analytics_ftr_helpers/common/fetch_events.ts +++ b/test/analytics/plugins/analytics_ftr_helpers/common/fetch_events.ts @@ -18,7 +18,7 @@ import { toArray, } from 'rxjs'; import { get } from 'lodash'; -import type { Event } from '@kbn/analytics-client'; +import type { Event } from '@kbn/ebt/client'; import type { GetEventsOptions } from './types'; export async function fetchEvents( diff --git a/test/analytics/plugins/analytics_ftr_helpers/common/types.ts b/test/analytics/plugins/analytics_ftr_helpers/common/types.ts index 97e1faff823a4..25c3a8b738fe2 100644 --- a/test/analytics/plugins/analytics_ftr_helpers/common/types.ts +++ b/test/analytics/plugins/analytics_ftr_helpers/common/types.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { Event, EventType } from '@kbn/analytics-client'; +import type { Event, EventType } from '@kbn/ebt/client'; export type FiltersOptions = { [key in 'eq' | 'gte' | 'gt' | 'lte' | 'lt']?: unknown; diff --git a/test/analytics/plugins/analytics_ftr_helpers/public/custom_shipper.ts b/test/analytics/plugins/analytics_ftr_helpers/public/custom_shipper.ts index df876da5e9cce..c122c6310e04b 100644 --- a/test/analytics/plugins/analytics_ftr_helpers/public/custom_shipper.ts +++ b/test/analytics/plugins/analytics_ftr_helpers/public/custom_shipper.ts @@ -7,8 +7,7 @@ */ import { Subject } from 'rxjs'; -import type { AnalyticsClientInitContext } from '@kbn/analytics-client'; -import type { Event, IShipper } from '@kbn/core/public'; +import type { AnalyticsClientInitContext, Event, IShipper } from '@kbn/core/public'; export class CustomShipper implements IShipper { public static shipperName = 'FTR-helpers-shipper'; diff --git a/test/analytics/plugins/analytics_ftr_helpers/server/custom_shipper.ts b/test/analytics/plugins/analytics_ftr_helpers/server/custom_shipper.ts index c1ed593673f81..f15d03efe3d7d 100644 --- a/test/analytics/plugins/analytics_ftr_helpers/server/custom_shipper.ts +++ b/test/analytics/plugins/analytics_ftr_helpers/server/custom_shipper.ts @@ -7,8 +7,7 @@ */ import { Subject } from 'rxjs'; -import type { AnalyticsClientInitContext } from '@kbn/analytics-client'; -import type { IShipper, Event } from '@kbn/core/server'; +import type { AnalyticsClientInitContext, IShipper, Event } from '@kbn/core/server'; export class CustomShipper implements IShipper { public static shipperName = 'FTR-helpers-shipper'; diff --git a/test/analytics/plugins/analytics_ftr_helpers/tsconfig.json b/test/analytics/plugins/analytics_ftr_helpers/tsconfig.json index c6f087def9b01..352e95c717114 100644 --- a/test/analytics/plugins/analytics_ftr_helpers/tsconfig.json +++ b/test/analytics/plugins/analytics_ftr_helpers/tsconfig.json @@ -15,8 +15,8 @@ ], "kbn_references": [ "@kbn/core", - "@kbn/analytics-client", "@kbn/std", "@kbn/config-schema", + "@kbn/ebt", ] } diff --git a/test/analytics/tests/instrumented_events/from_the_server/core_overall_status_changed.ts b/test/analytics/tests/instrumented_events/from_the_server/core_overall_status_changed.ts index aa11e35249509..7ca8b42085025 100644 --- a/test/analytics/tests/instrumented_events/from_the_server/core_overall_status_changed.ts +++ b/test/analytics/tests/instrumented_events/from_the_server/core_overall_status_changed.ts @@ -7,7 +7,7 @@ */ import expect from '@kbn/expect'; -import { Event } from '@kbn/analytics-client'; +import { Event } from '@kbn/core/server'; import { FtrProviderContext } from '../../../services'; export default function ({ getService }: FtrProviderContext) { diff --git a/test/api_integration/apis/esql/errors.ts b/test/api_integration/apis/esql/errors.ts index ec81e25e7d0f7..e74f86efcb44c 100644 --- a/test/api_integration/apis/esql/errors.ts +++ b/test/api_integration/apis/esql/errors.ts @@ -152,6 +152,7 @@ export default function ({ getService }: FtrProviderContext) { ); for (const policy of policies) { log.info(`deleting policy "${policy}"...`); + // TODO: Maybe `policy` -> `policy.name`? await es.enrich.deletePolicy({ name: policy }, { ignore: [404] }); } } diff --git a/test/api_integration/apis/saved_objects_management/relationships.ts b/test/api_integration/apis/saved_objects_management/relationships.ts index d609938a0d50f..dda99c138ebb8 100644 --- a/test/api_integration/apis/saved_objects_management/relationships.ts +++ b/test/api_integration/apis/saved_objects_management/relationships.ts @@ -342,7 +342,7 @@ export default function ({ getService }: FtrProviderContext) { }); describe('index patterns', () => { - it('should validate visualization response schema', async () => { + it('should validate index-pattern response schema', async () => { const resp = await supertest .get(relationshipsUrl('index-pattern', '8963ca30-3224-11e8-a572-ffca06da1357')) .expect(200); diff --git a/test/functional/apps/console/_autocomplete.ts b/test/functional/apps/console/ace/_autocomplete.ts similarity index 99% rename from test/functional/apps/console/_autocomplete.ts rename to test/functional/apps/console/ace/_autocomplete.ts index 2f9d8db9cf9d2..fe329e8d2c320 100644 --- a/test/functional/apps/console/_autocomplete.ts +++ b/test/functional/apps/console/ace/_autocomplete.ts @@ -9,7 +9,7 @@ import _ from 'lodash'; import expect from '@kbn/expect'; import { asyncForEach } from '@kbn/std'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); diff --git a/test/functional/apps/console/_comments.ts b/test/functional/apps/console/ace/_comments.ts similarity index 98% rename from test/functional/apps/console/_comments.ts rename to test/functional/apps/console/ace/_comments.ts index de8bcda60786b..8ef708d68b44f 100644 --- a/test/functional/apps/console/_comments.ts +++ b/test/functional/apps/console/ace/_comments.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { asyncForEach } from '@kbn/std'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); diff --git a/test/functional/apps/console/_console.ts b/test/functional/apps/console/ace/_console.ts similarity index 90% rename from test/functional/apps/console/_console.ts rename to test/functional/apps/console/ace/_console.ts index cc3b8dbc7073d..19de1d941fdae 100644 --- a/test/functional/apps/console/_console.ts +++ b/test/functional/apps/console/ace/_console.ts @@ -8,33 +8,8 @@ import expect from '@kbn/expect'; import { asyncForEach } from '@kbn/std'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -const DEFAULT_REQUEST = ` -# Welcome to the Dev Tools Console! -# -# You can use Console to explore the Elasticsearch API. See the \n Elasticsearch API reference to learn more: -# https://www.elastic.co/guide/en/elasticsearch/reference/current\n /rest-apis.html -# -# Here are a few examples to get you started. - - -# Create an index -PUT /my-index - - -# Add a document to my-index -POST /my-index/_doc -{ - "id": "park_rocky-mountain", - "title": "Rocky Mountain", - "description": "Bisected north to south by the Continental \n Divide, this portion of the Rockies has ecosystems varying \n from over 150 riparian lakes to montane and subalpine forests \n to treeless alpine tundra." -} - - -# Perform a search in my-index -GET /my-index/_search?q="rocky mountain" -`.trim(); +import { DEFAULT_INPUT_VALUE } from '@kbn/console-plugin/common/constants'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); @@ -58,7 +33,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.try(async () => { const actualRequest = await PageObjects.console.getRequest(); log.debug(actualRequest); - expect(actualRequest.trim()).to.eql(DEFAULT_REQUEST); + expect(actualRequest.replace(/\s/g, '')).to.eql(DEFAULT_INPUT_VALUE.replace(/\s/g, '')); }); }); diff --git a/test/functional/apps/console/_console_ccs.ts b/test/functional/apps/console/ace/_console_ccs.ts similarity index 96% rename from test/functional/apps/console/_console_ccs.ts rename to test/functional/apps/console/ace/_console_ccs.ts index 8778c2e6e70bb..57ab547af4e49 100644 --- a/test/functional/apps/console/_console_ccs.ts +++ b/test/functional/apps/console/ace/_console_ccs.ts @@ -7,7 +7,7 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); diff --git a/test/functional/apps/console/_context_menu.ts b/test/functional/apps/console/ace/_context_menu.ts similarity index 98% rename from test/functional/apps/console/_context_menu.ts rename to test/functional/apps/console/ace/_context_menu.ts index 4051804d7407f..677aa1c43fe39 100644 --- a/test/functional/apps/console/_context_menu.ts +++ b/test/functional/apps/console/ace/_context_menu.ts @@ -7,7 +7,7 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); diff --git a/test/functional/apps/console/_misc_console_behavior.ts b/test/functional/apps/console/ace/_misc_console_behavior.ts similarity index 98% rename from test/functional/apps/console/_misc_console_behavior.ts rename to test/functional/apps/console/ace/_misc_console_behavior.ts index 2920813f2cca0..303004ad42446 100644 --- a/test/functional/apps/console/_misc_console_behavior.ts +++ b/test/functional/apps/console/ace/_misc_console_behavior.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); diff --git a/test/functional/apps/console/_settings.ts b/test/functional/apps/console/ace/_settings.ts similarity index 95% rename from test/functional/apps/console/_settings.ts rename to test/functional/apps/console/ace/_settings.ts index a2802db67e859..f42a078ff48e7 100644 --- a/test/functional/apps/console/_settings.ts +++ b/test/functional/apps/console/ace/_settings.ts @@ -7,7 +7,7 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); diff --git a/test/functional/apps/console/_text_input.ts b/test/functional/apps/console/ace/_text_input.ts similarity index 98% rename from test/functional/apps/console/_text_input.ts rename to test/functional/apps/console/ace/_text_input.ts index 8826b24931c07..b60a3b8df8f79 100644 --- a/test/functional/apps/console/_text_input.ts +++ b/test/functional/apps/console/ace/_text_input.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); diff --git a/test/functional/apps/console/_variables.ts b/test/functional/apps/console/ace/_variables.ts similarity index 97% rename from test/functional/apps/console/_variables.ts rename to test/functional/apps/console/ace/_variables.ts index 8e09460b4dc6c..91474de06d678 100644 --- a/test/functional/apps/console/_variables.ts +++ b/test/functional/apps/console/ace/_variables.ts @@ -7,7 +7,7 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default ({ getService, getPageObjects }: FtrProviderContext) => { const retry = getService('retry'); diff --git a/test/functional/apps/console/_vector_tile.ts b/test/functional/apps/console/ace/_vector_tile.ts similarity index 96% rename from test/functional/apps/console/_vector_tile.ts rename to test/functional/apps/console/ace/_vector_tile.ts index 77b186227ed28..376bf5ccb7eef 100644 --- a/test/functional/apps/console/_vector_tile.ts +++ b/test/functional/apps/console/ace/_vector_tile.ts @@ -7,7 +7,7 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'console', 'header', 'home']); diff --git a/test/functional/apps/console/_xjson.ts b/test/functional/apps/console/ace/_xjson.ts similarity index 99% rename from test/functional/apps/console/_xjson.ts rename to test/functional/apps/console/ace/_xjson.ts index 6a4f352a3ad01..ae9bfb519ba67 100644 --- a/test/functional/apps/console/_xjson.ts +++ b/test/functional/apps/console/ace/_xjson.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { rgbToHex } from '@elastic/eui'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default ({ getService, getPageObjects }: FtrProviderContext) => { const retry = getService('retry'); diff --git a/test/functional/apps/console/ace/config.ts b/test/functional/apps/console/ace/config.ts new file mode 100644 index 0000000000000..013c5b26d0cdb --- /dev/null +++ b/test/functional/apps/console/ace/config.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; +import { configureHTTP2 } from '../../../../common/configure_http2'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js')); + + return configureHTTP2({ + ...functionalConfig.getAll(), + testFiles: [require.resolve('.')], + kbnTestServer: { + ...functionalConfig.get('kbnTestServer'), + serverArgs: [ + ...functionalConfig.get('kbnTestServer.serverArgs'), + // disabling the monaco editor to run tests for ace + `--console.dev.enableMonaco=false`, + ], + }, + }); +} diff --git a/test/functional/apps/console/index.js b/test/functional/apps/console/ace/index.ts similarity index 89% rename from test/functional/apps/console/index.js rename to test/functional/apps/console/ace/index.ts index 36fb3f5421540..7274118daaafe 100644 --- a/test/functional/apps/console/index.js +++ b/test/functional/apps/console/ace/index.ts @@ -6,7 +6,9 @@ * Side Public License, v 1. */ -export default function ({ getService, loadTestFile }) { +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, loadTestFile }: FtrProviderContext) { const browser = getService('browser'); const config = getService('config'); diff --git a/test/functional/apps/console/monaco/_autocomplete.ts b/test/functional/apps/console/monaco/_autocomplete.ts new file mode 100644 index 0000000000000..e6ed83ad26338 --- /dev/null +++ b/test/functional/apps/console/monaco/_autocomplete.ts @@ -0,0 +1,374 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import _ from 'lodash'; +import expect from '@kbn/expect'; +import { asyncForEach } from '@kbn/std'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const log = getService('log'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'console', 'header']); + const find = getService('find'); + + describe('console autocomplete feature', function describeIndexTests() { + this.tags('includeFirefox'); + before(async () => { + log.debug('navigateTo console'); + await PageObjects.common.navigateToApp('console'); + // Ensure that the text area can be interacted with + await PageObjects.console.closeHelpIfExists(); + await PageObjects.console.monaco.clearEditorText(); + log.debug('setAutocompleteTrace true'); + await PageObjects.console.setAutocompleteTrace(true); + }); + + after(async () => { + log.debug('setAutocompleteTrace false'); + await PageObjects.console.setAutocompleteTrace(false); + }); + + it('should provide basic auto-complete functionality', async () => { + await PageObjects.console.monaco.enterText(`GET _search\n`); + await PageObjects.console.monaco.pressEnter(); + await PageObjects.console.monaco.enterText(`{\n\t"query": {`); + await PageObjects.console.monaco.pressEnter(); + await PageObjects.console.sleepForDebouncePeriod(); + await PageObjects.console.monaco.promptAutocomplete(); + expect(PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(true); + }); + + describe('Autocomplete behavior', () => { + beforeEach(async () => { + await PageObjects.console.monaco.clearEditorText(); + }); + + it('HTTP methods', async () => { + const suggestions = { + G: ['GET'], + P: ['PATCH', 'POST', 'PUT'], + D: ['DELETE'], + H: ['HEAD'], + }; + for (const [char, methods] of Object.entries(suggestions)) { + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type "%s"', char); + await PageObjects.console.monaco.enterText(char); + + await retry.waitFor('autocomplete to be visible', () => + PageObjects.console.monaco.isAutocompleteVisible() + ); + expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(true); + + for (const [i, method] of methods.entries()) { + expect(await PageObjects.console.monaco.getAutocompleteSuggestion(i)).to.be.eql(method); + } + + await PageObjects.console.monaco.pressEscape(); + await PageObjects.console.monaco.clearEditorText(); + } + }); + + it('ES API endpoints', async () => { + const suggestions = { + 'GET _': ['_alias', '_all'], + 'PUT _': ['_all'], + 'POST _': ['_aliases', '_all'], + 'DELETE _': ['_all'], + 'HEAD _': ['_alias', '_all'], + }; + for (const [text, endpoints] of Object.entries(suggestions)) { + for (const char of text) { + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type "%s"', char); + await PageObjects.console.monaco.enterText(char); + } + + await retry.waitFor('autocomplete to be visible', () => + PageObjects.console.monaco.isAutocompleteVisible() + ); + expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(true); + + for (const [i, endpoint] of endpoints.entries()) { + expect(await PageObjects.console.monaco.getAutocompleteSuggestion(i)).to.be.eql( + endpoint + ); + } + + await PageObjects.console.monaco.pressEscape(); + await PageObjects.console.monaco.pressEnter(); + } + }); + + it('JSON autocompletion with placeholder fields', async () => { + await PageObjects.console.monaco.enterText('GET _search\n'); + await PageObjects.console.monaco.enterText('{'); + await PageObjects.console.monaco.pressEnter(); + + for (const char of '"ag') { + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type "%s"', char); + await PageObjects.console.monaco.enterText(char); + } + await retry.waitFor('autocomplete to be visible', () => + PageObjects.console.monaco.isAutocompleteVisible() + ); + expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(true); + await PageObjects.console.monaco.pressEnter(); + await PageObjects.console.sleepForDebouncePeriod(); + + expect((await PageObjects.console.monaco.getEditorText()).replace(/\s/g, '')).to.be.eql( + ` +GET _search +{ + "aggs": { + "NAME": { + "AGG_TYPE": {} + } + } +} +`.replace(/\s/g, '') + ); + }); + + it('Dynamic autocomplete', async () => { + await PageObjects.console.monaco.enterText('POST test/_doc\n{}'); + await PageObjects.console.clickPlay(); + + await PageObjects.header.waitUntilLoadingHasFinished(); + expect(await PageObjects.console.getResponseStatus()).to.be('201'); + + await PageObjects.console.monaco.pressEnter(); + for (const char of 'POST t') { + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type "%s"', char); + await PageObjects.console.monaco.enterText(char); + } + await retry.waitFor('autocomplete to be visible', () => + PageObjects.console.monaco.isAutocompleteVisible() + ); + expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(true); + + expect(await PageObjects.console.monaco.getAutocompleteSuggestion(0)).to.be.eql('test'); + }); + }); + + describe('anti-regression watchdogs', () => { + beforeEach(async () => { + await PageObjects.console.monaco.clearEditorText(); + }); + + // flaky + it.skip('should suppress auto-complete on arrow keys', async () => { + await PageObjects.console.monaco.enterText(`\nGET _search\nGET _search`); + await PageObjects.console.monaco.pressEnter(); + const keyPresses = [ + 'pressUp', + 'pressUp', + 'pressDown', + 'pressDown', + 'pressRight', + 'pressRight', + 'pressLeft', + 'pressLeft', + ] as const; + for (const keyPress of keyPresses) { + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key', keyPress); + await PageObjects.console.monaco[keyPress](); + expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(false); + } + }); + + it('should activate auto-complete for methods case-insensitively', async () => { + const methods = _.sampleSize( + _.compact( + ` + GET GEt GeT Get gET gEt geT get + PUT PUt PuT Put pUT pUt puT put + POST POSt POsT POst PoST PoSt PosT Post pOST pOSt pOsT pOst poST poSt posT post + DELETE DELETe DELEtE DELEte DELeTE DELeTe DELetE DELete DElETE DElETe DElEtE DElEte DEleTE DEleTe DEletE DElete + DeLETE DeLETe DeLEtE DeLEte DeLeTE DeLeTe DeLetE DeLete DelETE DelETe DelEtE DelEte DeleTE DeleTe DeletE Delete + dELETE dELETe dELEtE dELEte dELeTE dELeTe dELetE dELete dElETE dElETe dElEtE dElEte dEleTE dEleTe dEletE dElete + deLETE deLETe deLEtE deLEte deLeTE deLeTe deLetE deLete delETE delETe delEtE delEte deleTE deleTe deletE delete + HEAD HEAd HEaD HEad HeAD HeAd HeaD Head hEAD hEAd hEaD hEad heAD heAd heaD head + `.split(/\s+/m) + ), + 20 // 20 of 112 (approx. one-fifth) should be enough for testing + ); + + for (const method of methods) { + await PageObjects.console.monaco.clearEditorText(); + + for (const char of method.slice(0, -1)) { + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type "%s"', char); + await PageObjects.console.monaco.enterText(char); // e.g. 'P' -> 'Po' -> 'Pos' + await retry.waitFor('autocomplete to be visible', () => + PageObjects.console.monaco.isAutocompleteVisible() + ); + expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(true); + } + + for (const char of [method.at(-1), ' ', '_']) { + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type "%s"', char); + await PageObjects.console.monaco.enterText(char!); // e.g. 'Post ' -> 'Post _' + } + + await retry.waitFor('autocomplete to be visible', () => + PageObjects.console.monaco.isAutocompleteVisible() + ); + expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(true); + } + }); + + it('should activate auto-complete for a single character immediately following a slash in URL', async () => { + await PageObjects.console.monaco.enterText('GET .kibana'); + + for (const char of ['/', '_']) { + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type "%s"', char); + await PageObjects.console.monaco.enterText(char); // i.e. 'GET .kibana/' -> 'GET .kibana/_' + } + + await retry.waitFor('autocomplete to be visible', () => + PageObjects.console.monaco.isAutocompleteVisible() + ); + expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(true); + }); + + it('should activate auto-complete for multiple indices after comma in URL', async () => { + await PageObjects.console.monaco.enterText('GET _cat/indices/.kibana'); + + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type ","'); + await PageObjects.console.monaco.enterText(','); // i.e. 'GET /_cat/indices/.kibana,' + + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type Ctrl+SPACE'); + await PageObjects.console.monaco.pressCtrlSpace(); + + await retry.waitFor('autocomplete to be visible', () => + PageObjects.console.monaco.isAutocompleteVisible() + ); + expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(true); + }); + + // not fixed for monaco yet https://github.com/elastic/kibana/issues/184442 + it.skip('should not activate auto-complete after comma following endpoint in URL', async () => { + await PageObjects.console.monaco.enterText('GET _search'); + + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type ","'); + await PageObjects.console.monaco.enterText(','); // i.e. 'GET _search,' + + await PageObjects.console.sleepForDebouncePeriod(); + log.debug('Key type Ctrl+SPACE'); + await PageObjects.console.monaco.pressCtrlSpace(); + + expect(await PageObjects.console.monaco.isAutocompleteVisible()).to.be.eql(false); + }); + }); + + // not implemented for monaco yet https://github.com/elastic/kibana/issues/184856 + describe.skip('with a missing comma in query', () => { + const LINE_NUMBER = 4; + beforeEach(async () => { + await PageObjects.console.clearTextArea(); + await PageObjects.console.enterRequest(); + await PageObjects.console.pressEnter(); + }); + + it('should add a comma after previous non empty line', async () => { + await PageObjects.console.enterText(`{\n\t"query": {\n\t\t"match": {}`); + await PageObjects.console.pressEnter(); + await PageObjects.console.pressEnter(); + await PageObjects.console.pressEnter(); + await PageObjects.console.sleepForDebouncePeriod(); + await PageObjects.console.promptAutocomplete(); + await PageObjects.console.pressEnter(); + await retry.try(async () => { + let conApp = await find.byCssSelector('.conApp'); + const firstInnerHtml = await conApp.getAttribute('innerHTML'); + await PageObjects.common.sleep(500); + conApp = await find.byCssSelector('.conApp'); + const secondInnerHtml = await conApp.getAttribute('innerHTML'); + return firstInnerHtml === secondInnerHtml; + }); + const textAreaString = await PageObjects.console.getAllVisibleText(); + log.debug('Text Area String Value==================\n'); + log.debug(textAreaString); + expect(textAreaString).to.contain(','); + const text = await PageObjects.console.getVisibleTextAt(LINE_NUMBER); + const lastChar = text.charAt(text.length - 1); + expect(lastChar).to.be.eql(','); + }); + + it('should add a comma after the triple quoted strings', async () => { + await PageObjects.console.enterText(`{\n\t"query": {\n\t\t"term": """some data"""`); + await PageObjects.console.pressEnter(); + await PageObjects.console.sleepForDebouncePeriod(); + await PageObjects.console.promptAutocomplete(); + await PageObjects.console.pressEnter(); + + await retry.waitForWithTimeout('text area to contain comma', 25000, async () => { + const textAreaString = await PageObjects.console.getAllVisibleText(); + return textAreaString.includes(','); + }); + + const text = await PageObjects.console.getVisibleTextAt(LINE_NUMBER); + const lastChar = text.charAt(text.length - 1); + expect(lastChar).to.be.eql(','); + }); + }); + + describe('with conditional templates', async () => { + const CONDITIONAL_TEMPLATES = [ + { + type: 'fs', + template: `"location": "path"`, + }, + { + type: 'url', + template: `"url": ""`, + }, + { type: 's3', template: `"bucket": ""` }, + { + type: 'azure', + template: `"path": ""`, + }, + ]; + + beforeEach(async () => { + await PageObjects.console.monaco.clearEditorText(); + await PageObjects.console.monaco.enterText('POST _snapshot/test_repo\n'); + }); + + await asyncForEach(CONDITIONAL_TEMPLATES, async ({ type, template }) => { + it('should insert different templates depending on the value of type', async () => { + await PageObjects.console.monaco.enterText(`{\n\t"type": "${type}",\n`); + await PageObjects.console.sleepForDebouncePeriod(); + // Prompt autocomplete for 'settings' + await PageObjects.console.monaco.promptAutocomplete('s'); + + await retry.waitFor('autocomplete to be visible', () => + PageObjects.console.monaco.isAutocompleteVisible() + ); + await PageObjects.console.monaco.pressEnter(); + await retry.try(async () => { + const request = await PageObjects.console.monaco.getEditorText(); + log.debug(request); + expect(request).to.contain(`${template}`); + }); + }); + }); + }); + }); +} diff --git a/test/functional/apps/console/monaco/_comments.ts b/test/functional/apps/console/monaco/_comments.ts new file mode 100644 index 0000000000000..ba8d8b6f04f07 --- /dev/null +++ b/test/functional/apps/console/monaco/_comments.ts @@ -0,0 +1,149 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { asyncForEach } from '@kbn/std'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const log = getService('log'); + const PageObjects = getPageObjects(['common', 'console', 'header']); + + // flaky + describe.skip('console app', function testComments() { + this.tags('includeFirefox'); + before(async () => { + log.debug('navigateTo console'); + await PageObjects.common.navigateToApp('console'); + await PageObjects.console.closeHelpIfExists(); + }); + + describe('with comments', async () => { + const enterRequest = async (url: string, body: string) => { + await PageObjects.console.monaco.clearEditorText(); + await PageObjects.console.monaco.enterText(`${url}\n${body}`); + }; + + async function runTests( + tests: Array<{ description: string; url?: string; body: string }>, + fn: () => Promise + ) { + await asyncForEach(tests, async ({ description, url, body }) => { + it(description, async () => { + await enterRequest(url ?? '\nGET search', body); + await fn(); + }); + }); + } + + describe('with single line comments', async () => { + await runTests( + [ + { + url: '\n// GET _search', + body: '', + description: 'should allow in request url, using //', + }, + { + body: '{\n\t\t"query": {\n\t\t\t// "match_all": {}\n}\n}', + description: 'should allow in request body, using //', + }, + { + url: '\n # GET _search', + body: '', + description: 'should allow in request url, using #', + }, + { + body: '{\n\t\t"query": {\n\t\t\t# "match_all": {}\n}\n}', + description: 'should allow in request body, using #', + }, + { + description: 'should accept as field names, using //', + body: '{\n "//": {} }', + }, + { + description: 'should accept as field values, using //', + body: '{\n "f": "//" }', + }, + { + description: 'should accept as field names, using #', + body: '{\n "#": {} }', + }, + { + description: 'should accept as field values, using #', + body: '{\n "f": "#" }', + }, + ], + async () => { + expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(false); + } + ); + }); + + describe('with multiline comments', async () => { + await runTests( + [ + { + url: '\n /* \nGET _search \n*/', + body: '', + description: 'should allow in request url, using /* */', + }, + { + body: '{\n\t\t"query": {\n\t\t\t/* "match_all": {} */ \n}\n}', + description: 'should allow in request body, using /* */', + }, + { + description: 'should accept as field names, using /*', + body: '{\n "/*": {} \n\t\t /* "f": 1 */ \n}', + }, + { + description: 'should accept as field values, using */', + body: '{\n /* "f": 1 */ \n"f": "*/" \n}', + }, + ], + async () => { + expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(false); + } + ); + }); + + describe('with invalid syntax in request body', async () => { + await runTests( + [ + { + description: 'should highlight invalid syntax', + body: '{\n "query": \'\'', // E.g. using single quotes + }, + ], + + async () => { + expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(true); + } + ); + }); + + describe('with invalid request', async () => { + await runTests( + [ + { + description: 'with invalid character should display error marker', + body: '{\n $ "query": {}', + }, + { + description: 'with missing field name', + body: '{\n "query": {},\n {}', + }, + ], + async () => { + expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(true); + } + ); + }); + }); + }); +} diff --git a/test/functional/apps/console/monaco/_console.ts b/test/functional/apps/console/monaco/_console.ts new file mode 100644 index 0000000000000..36bb893d5b90f --- /dev/null +++ b/test/functional/apps/console/monaco/_console.ts @@ -0,0 +1,150 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { asyncForEach } from '@kbn/std'; +import { DEFAULT_INPUT_VALUE } from '@kbn/console-plugin/common/constants'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const retry = getService('retry'); + const log = getService('log'); + const browser = getService('browser'); + const PageObjects = getPageObjects(['common', 'console', 'header']); + const security = getService('security'); + + describe('console app', function describeIndexTests() { + this.tags('includeFirefox'); + before(async () => { + log.debug('navigateTo console'); + await PageObjects.common.navigateToApp('console'); + }); + beforeEach(async () => { + await PageObjects.console.closeHelpIfExists(); + }); + + it('should show the default request', async () => { + await retry.try(async () => { + const actualRequest = await PageObjects.console.monaco.getEditorText(); + log.debug(actualRequest); + expect(actualRequest.replace(/\s/g, '')).to.eql(DEFAULT_INPUT_VALUE.replace(/\s/g, '')); + }); + }); + + // issue with the url params with whitespaces https://github.com/elastic/kibana/issues/184927 + it.skip('default request response should include `"timed_out" : false`', async () => { + const expectedResponseContains = `"timed_out": false`; + await PageObjects.console.monaco.selectAllRequests(); + await PageObjects.console.clickPlay(); + await retry.try(async () => { + const actualResponse = await PageObjects.console.monaco.getOutputText(); + log.debug(actualResponse); + expect(actualResponse).to.contain(expectedResponseContains); + }); + }); + + // the resizer doesn't work the same as in ace https://github.com/elastic/kibana/issues/184352 + it.skip('should resize the editor', async () => { + const editor = await PageObjects.console.monaco.getEditor(); + await browser.setWindowSize(1300, 1100); + const initialSize = await editor.getSize(); + await browser.setWindowSize(1000, 1100); + const afterSize = await editor.getSize(); + expect(initialSize.width).to.be.greaterThan(afterSize.width); + }); + + it('should return statusCode 400 to unsupported HTTP verbs', async () => { + const expectedResponseContains = '"statusCode": 400'; + await PageObjects.console.monaco.clearEditorText(); + await PageObjects.console.monaco.enterText('OPTIONS /'); + await PageObjects.console.clickPlay(); + await retry.try(async () => { + const actualResponse = await PageObjects.console.monaco.getOutputText(); + log.debug(actualResponse); + expect(actualResponse).to.contain(expectedResponseContains); + + expect(await PageObjects.console.hasSuccessBadge()).to.be(false); + }); + }); + + describe('with kbn: prefix in request', () => { + before(async () => { + await PageObjects.console.monaco.clearEditorText(); + }); + it('it should send successful request to Kibana API', async () => { + const expectedResponseContains = 'default space'; + await PageObjects.console.monaco.enterText('GET kbn:/api/spaces/space'); + await PageObjects.console.clickPlay(); + await retry.try(async () => { + const actualResponse = await PageObjects.console.monaco.getOutputText(); + log.debug(actualResponse); + expect(actualResponse).to.contain(expectedResponseContains); + }); + }); + }); + + describe('with query params', () => { + it('should issue a successful request', async () => { + await PageObjects.console.monaco.clearEditorText(); + await PageObjects.console.monaco.enterText( + 'GET _cat/aliases?format=json&v=true&pretty=true' + ); + await PageObjects.console.clickPlay(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + // set the width of the browser, so that the response status is visible + await browser.setWindowSize(1300, 1100); + await retry.try(async () => { + const status = await PageObjects.console.getResponseStatus(); + expect(status).to.eql(200); + }); + }); + }); + + describe('multiple requests output', function () { + const sendMultipleRequests = async (requests: string[]) => { + await asyncForEach(requests, async (request) => { + await PageObjects.console.monaco.enterText(request); + }); + await PageObjects.console.monaco.selectAllRequests(); + await PageObjects.console.clickPlay(); + }; + + before(async () => { + await security.testUser.setRoles(['kibana_admin', 'test_index']); + }); + + after(async () => { + await security.testUser.restoreDefaults(); + }); + + beforeEach(async () => { + await PageObjects.console.closeHelpIfExists(); + await PageObjects.console.monaco.clearEditorText(); + }); + + it('should contain comments starting with # symbol', async () => { + await sendMultipleRequests(['\n PUT test-index', '\n DELETE test-index']); + await retry.try(async () => { + const response = await PageObjects.console.monaco.getOutputText(); + log.debug(response); + expect(response).to.contain('# PUT test-index 200'); + expect(response).to.contain('# DELETE test-index 200'); + }); + }); + + // not implemented for monaco yet https://github.com/elastic/kibana/issues/184010 + it.skip('should display status badges', async () => { + await sendMultipleRequests(['\n GET _search/test', '\n GET _search']); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect(await PageObjects.console.hasWarningBadge()).to.be(true); + expect(await PageObjects.console.hasSuccessBadge()).to.be(true); + }); + }); + }); +} diff --git a/test/functional/apps/console/monaco/_console_ccs.ts b/test/functional/apps/console/monaco/_console_ccs.ts new file mode 100644 index 0000000000000..410518a6bfc81 --- /dev/null +++ b/test/functional/apps/console/monaco/_console_ccs.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const retry = getService('retry'); + const log = getService('log'); + const browser = getService('browser'); + const PageObjects = getPageObjects(['common', 'console']); + const remoteEsArchiver = getService('remoteEsArchiver' as 'esArchiver'); + + describe('Console App CCS', function describeIndexTests() { + this.tags('includeFirefox'); + before(async () => { + await remoteEsArchiver.loadIfNeeded( + 'test/functional/fixtures/es_archiver/logstash_functional' + ); + // resize the editor to allow the whole of the response to be displayed + await browser.setWindowSize(1200, 1800); + log.debug('navigateTo console'); + await PageObjects.common.navigateToApp('console'); + await retry.try(async () => { + await PageObjects.console.collapseHelp(); + }); + }); + + after(async () => { + await remoteEsArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); + }); + + describe('Perform CCS Search in Console', () => { + before(async () => { + await PageObjects.console.monaco.clearEditorText(); + }); + it('it should be able to access remote data', async () => { + await PageObjects.console.monaco.enterText( + '\nGET ftr-remote:logstash-*/_search\n {\n "query": {\n "bool": {\n "must": [\n {"match": {"extension" : "jpg"} \n}\n]\n}\n}\n}' + ); + await PageObjects.console.clickPlay(); + await retry.try(async () => { + const actualResponse = await PageObjects.console.monaco.getOutputText(); + expect(actualResponse).to.contain('"_index": "ftr-remote:logstash-2015.09.20"'); + }); + }); + }); + }); +} diff --git a/test/functional/apps/console/monaco/_context_menu.ts b/test/functional/apps/console/monaco/_context_menu.ts new file mode 100644 index 0000000000000..f74e9d2814242 --- /dev/null +++ b/test/functional/apps/console/monaco/_context_menu.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const log = getService('log'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'console']); + const browser = getService('browser'); + const toasts = getService('toasts'); + + describe('console context menu', function testContextMenu() { + before(async () => { + await PageObjects.common.navigateToApp('console'); + // Ensure that the text area can be interacted with + await PageObjects.console.closeHelpIfExists(); + await PageObjects.console.monaco.clearEditorText(); + await PageObjects.console.monaco.enterText('GET _search'); + }); + + it('should open context menu', async () => { + expect(await PageObjects.console.isContextMenuOpen()).to.be(false); + await PageObjects.console.clickContextMenu(); + expect(PageObjects.console.isContextMenuOpen()).to.be.eql(true); + }); + + it('should have options to copy as curl, open documentation, and auto indent', async () => { + await PageObjects.console.clickContextMenu(); + expect(PageObjects.console.isContextMenuOpen()).to.be.eql(true); + expect(PageObjects.console.isCopyAsCurlButtonVisible()).to.be.eql(true); + expect(PageObjects.console.isOpenDocumentationButtonVisible()).to.be.eql(true); + expect(PageObjects.console.isAutoIndentButtonVisible()).to.be.eql(true); + }); + + it('should copy as curl and show toast when copy as curl button is clicked', async () => { + await PageObjects.console.clickContextMenu(); + await PageObjects.console.clickCopyAsCurlButton(); + + const resultToast = await toasts.getElementByIndex(1); + const toastText = await resultToast.getVisibleText(); + + if (toastText.includes('Write permission denied')) { + log.debug('Write permission denied, skipping test'); + return; + } + + expect(toastText).to.be('Request copied as cURL'); + + const canReadClipboard = await browser.checkBrowserPermission('clipboard-read'); + if (canReadClipboard) { + const clipboardText = await browser.getClipboardValue(); + expect(clipboardText).to.contain('curl -XGET'); + } + }); + + it('should open documentation when open documentation button is clicked', async () => { + await PageObjects.console.clickContextMenu(); + await PageObjects.console.clickOpenDocumentationButton(); + + await retry.tryForTime(10000, async () => { + await browser.switchTab(1); + }); + + // Retry until the documentation is loaded + await retry.try(async () => { + const url = await browser.getCurrentUrl(); + expect(url).to.contain('search-search.html'); + }); + + // Close the documentation tab + await browser.closeCurrentWindow(); + await browser.switchTab(0); + }); + + // not implemented yet for monaco https://github.com/elastic/kibana/issues/185891 + it.skip('should toggle auto indent when auto indent button is clicked', async () => { + await PageObjects.console.clearTextArea(); + await PageObjects.console.enterRequest('GET _search\n{"query": {"match_all": {}}}'); + await PageObjects.console.clickContextMenu(); + await PageObjects.console.clickAutoIndentButton(); + // Retry until the request is auto indented + await retry.try(async () => { + const request = await PageObjects.console.getRequest(); + expect(request).to.be.eql('GET _search\n{\n "query": {\n "match_all": {}\n }\n}'); + }); + + await PageObjects.console.clickContextMenu(); + // Click the auto-indent button again to condense request + await PageObjects.console.clickAutoIndentButton(); + // Retry until the request is condensed + await retry.try(async () => { + const request = await PageObjects.console.getRequest(); + expect(request).to.be.eql('GET _search\n{"query":{"match_all":{}}}'); + }); + }); + }); +} diff --git a/test/functional/apps/console/monaco/_misc_console_behavior.ts b/test/functional/apps/console/monaco/_misc_console_behavior.ts new file mode 100644 index 0000000000000..bc179ccc83208 --- /dev/null +++ b/test/functional/apps/console/monaco/_misc_console_behavior.ts @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const retry = getService('retry'); + const browser = getService('browser'); + const PageObjects = getPageObjects(['common', 'console', 'header']); + + describe('misc console behavior', function testMiscConsoleBehavior() { + this.tags('includeFirefox'); + before(async () => { + await browser.setWindowSize(1200, 800); + await PageObjects.common.navigateToApp('console'); + // Ensure that the text area can be interacted with + await PageObjects.console.closeHelpIfExists(); + }); + + beforeEach(async () => { + await PageObjects.console.monaco.clearEditorText(); + }); + + describe('keyboard shortcuts', () => { + let tabCount = 1; + + after(async () => { + if (tabCount > 1) { + await browser.closeCurrentWindow(); + await browser.switchTab(0); + } + }); + + it('should execute the request when Ctrl+Enter is pressed', async () => { + await PageObjects.console.monaco.enterText('GET _search'); + await PageObjects.console.monaco.pressCtrlEnter(); + await retry.try(async () => { + const response = await PageObjects.console.monaco.getOutputText(); + expect(response).to.contain('"timed_out": false'); + }); + }); + + it('should auto indent current request when Ctrl+I is pressed', async () => { + await PageObjects.console.monaco.enterText('GET _search\n{"query": {"match_all": {}}}'); + await PageObjects.console.monaco.selectCurrentRequest(); + await PageObjects.console.monaco.pressCtrlI(); + await retry.waitFor('request to be auto indented', async () => { + const request = await PageObjects.console.monaco.getEditorText(); + return request === 'GET _search\n{\n "query": {\n "match_all": {}\n }\n}'; + }); + }); + + it('should jump to the previous request when Ctrl+Up is pressed', async () => { + await PageObjects.console.monaco.enterText('\nGET _search/foo'); + await PageObjects.console.monaco.enterText('\nGET _search/bar'); + await PageObjects.console.monaco.pressCtrlUp(); + await retry.waitFor('request to be selected', async () => { + const request = await PageObjects.console.monaco.getEditorTextAtLine(1); + return request === 'GET _search/foo'; + }); + }); + + it('should jump to the next request when Ctrl+Down is pressed', async () => { + await PageObjects.console.monaco.enterText('\nGET _search/foo'); + await PageObjects.console.monaco.enterText('\nGET _search/bar'); + await PageObjects.console.monaco.pressCtrlUp(); + await PageObjects.console.monaco.pressCtrlDown(); + await retry.waitFor('request to be selected', async () => { + const request = await PageObjects.console.monaco.getEditorTextAtLine(2); + return request === 'GET _search/bar'; + }); + }); + + // flaky + it.skip('should go to line number when Ctrl+L is pressed', async () => { + await PageObjects.console.monaco.enterText( + '\nGET _search/foo\n{\n "query": {\n "match_all": {} \n} \n}' + ); + await PageObjects.console.monaco.pressCtrlL(); + // Sleep to allow the line number input to be focused + await PageObjects.common.sleep(1000); + const alert = await browser.getAlert(); + await alert?.sendKeys('4'); + await alert?.accept(); + await PageObjects.common.sleep(1000); + expect(await PageObjects.console.monaco.getCurrentLineNumber()).to.be(4); + }); + + // flaky + it.skip('should open documentation when Ctrl+/ is pressed', async () => { + await PageObjects.console.monaco.enterText('GET _search'); + await PageObjects.console.monaco.pressEscape(); + await PageObjects.console.monaco.pressCtrlSlash(); + await retry.tryForTime(10000, async () => { + await browser.switchTab(1); + tabCount++; + }); + + // Retry until the documentation is loaded + await retry.try(async () => { + const url = await browser.getCurrentUrl(); + expect(url).to.contain('search-search.html'); + }); + }); + }); + + describe('customizable font size', () => { + // flaky + it.skip('should allow the font size to be customized', async () => { + await PageObjects.console.setFontSizeSetting(20); + await retry.try(async () => { + // the settings are not applied synchronously, so we retry for a time + expect(await PageObjects.console.monaco.getFontSize()).to.be('20px'); + }); + + await PageObjects.console.setFontSizeSetting(24); + await retry.try(async () => { + // the settings are not applied synchronously, so we retry for a time + expect(await PageObjects.console.monaco.getFontSize()).to.be('24px'); + }); + }); + }); + }); +} diff --git a/test/functional/apps/console/monaco/_settings.ts b/test/functional/apps/console/monaco/_settings.ts new file mode 100644 index 0000000000000..f998a281b560d --- /dev/null +++ b/test/functional/apps/console/monaco/_settings.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const log = getService('log'); + const PageObjects = getPageObjects(['common', 'console']); + + describe('console settings', function testSettings() { + this.tags('includeFirefox'); + before(async () => { + log.debug('navigateTo console'); + await PageObjects.common.navigateToApp('console'); + // Ensure that the text area can be interacted with + await PageObjects.console.closeHelpIfExists(); + await PageObjects.console.monaco.clearEditorText(); + }); + + it('displays the a11y overlay', async () => { + await PageObjects.console.monaco.pressEscape(); + const isOverlayVisible = await PageObjects.console.monaco.isA11yOverlayVisible(); + expect(isOverlayVisible).to.be(true); + }); + + it('disables the a11y overlay via settings', async () => { + await PageObjects.console.openSettings(); + await PageObjects.console.toggleA11yOverlaySetting(); + + await PageObjects.console.monaco.pressEscape(); + const isOverlayVisible = await PageObjects.console.monaco.isA11yOverlayVisible(); + expect(isOverlayVisible).to.be(false); + }); + }); +} diff --git a/test/functional/apps/console/monaco/_text_input.ts b/test/functional/apps/console/monaco/_text_input.ts new file mode 100644 index 0000000000000..b238a557bbb38 --- /dev/null +++ b/test/functional/apps/console/monaco/_text_input.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const retry = getService('retry'); + const toasts = getService('toasts'); + const PageObjects = getPageObjects(['common', 'console', 'header']); + + describe('text input', function testTextInput() { + before(async () => { + await PageObjects.common.navigateToApp('console'); + await PageObjects.console.closeHelpIfExists(); + }); + + beforeEach(async () => { + await PageObjects.console.monaco.clearEditorText(); + }); + + describe('with a data URI in the load_from query', () => { + it('loads the data from the URI', async () => { + await PageObjects.common.navigateToApp('console', { + hash: '#/console?load_from=data:text/plain,BYUwNmD2Q', + }); + + await retry.try(async () => { + const actualRequest = await PageObjects.console.monaco.getEditorText(); + expect(actualRequest.trim()).to.eql('hello'); + }); + }); + + describe('with invalid data', () => { + it('shows a toast error', async () => { + await PageObjects.common.navigateToApp('console', { + hash: '#/console?load_from=data:text/plain,BYUwNmD2', + }); + + await retry.try(async () => { + expect(await toasts.getCount()).to.equal(1); + }); + }); + }); + }); + + // not yet implemented for monaco https://github.com/elastic/kibana/issues/186001 + describe.skip('copy/pasting cURL commands into the console', () => { + it('should convert cURL commands into the console request format', async () => { + await PageObjects.console.monaco.enterText( + `\n curl -XGET "http://localhost:9200/_search?pretty" -d'\n{"query": {"match_all": {}}}'` + ); + await PageObjects.console.monaco.copyRequestsToClipboard(); + await PageObjects.console.monaco.clearEditorText(); + await PageObjects.console.monaco.pasteClipboardValue(); + await retry.try(async () => { + const actualRequest = await PageObjects.console.monaco.getEditorText(); + expect(actualRequest.trim()).to.eql('GET /_search?pretty\n {"query": {"match_all": {}}}'); + }); + }); + }); + + describe('console history', () => { + const sendRequest = async (request: string) => { + await PageObjects.console.monaco.enterText(request); + await PageObjects.console.clickPlay(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }; + + it('should show the history', async () => { + await sendRequest('GET /_search?pretty'); + await PageObjects.console.clickHistory(); + await retry.try(async () => { + const history = await PageObjects.console.getHistoryEntries(); + expect(history).to.eql(['/_search?pretty (a few seconds ago)']); + }); + + // Clear the history + await PageObjects.console.clickClearHistory(); + await PageObjects.console.closeHistory(); + }); + + it('should load a request from history', async () => { + await sendRequest('GET _search\n{"query": {"match_all": {}}}'); + await PageObjects.console.monaco.clearEditorText(); + await PageObjects.console.clickHistory(); + await PageObjects.console.loadRequestFromHistory(0); + await retry.try(async () => { + const actualRequest = await PageObjects.console.monaco.getEditorText(); + expect(actualRequest.trim()).to.eql( + 'GET _search\n{\n "query": {\n "match_all": {}\n }\n}' + ); + }); + }); + }); + }); +} diff --git a/test/functional/apps/console/monaco/_variables.ts b/test/functional/apps/console/monaco/_variables.ts new file mode 100644 index 0000000000000..5d8719e9ea4d3 --- /dev/null +++ b/test/functional/apps/console/monaco/_variables.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ getService, getPageObjects }: FtrProviderContext) => { + const retry = getService('retry'); + const log = getService('log'); + const PageObjects = getPageObjects(['common', 'console', 'header']); + + describe('Console variables', function testConsoleVariables() { + // FLAKY on firefox: https://github.com/elastic/kibana/issues/157776 + // this.tags('includeFirefox'); + before(async () => { + log.debug('navigateTo console'); + await PageObjects.common.navigateToApp('console'); + await PageObjects.console.closeHelpIfExists(); + await PageObjects.console.monaco.clearEditorText(); + }); + + it('should allow creating a new variable', async () => { + await PageObjects.console.addNewVariable({ name: 'index1', value: 'test' }); + const variables = await PageObjects.console.getVariables(); + log.debug(variables); + expect(variables).to.contain('index1'); + }); + + it('should allow removing a variable', async () => { + await PageObjects.console.addNewVariable({ name: 'index2', value: 'test' }); + await PageObjects.console.removeVariables(); + const variables = await PageObjects.console.getVariables(); + expect(variables).to.eql([]); + }); + + describe('with variables in url', () => { + it('should send a successful request', async () => { + await PageObjects.console.addNewVariable({ name: 'index3', value: '_search' }); + await PageObjects.console.monaco.enterText('\n GET ${index3}'); + await PageObjects.console.clickPlay(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await retry.try(async () => { + const status = await PageObjects.console.getResponseStatus(); + expect(status).to.eql(200); + }); + }); + }); + + describe('with variables in request body', () => { + // bug in monaco https://github.com/elastic/kibana/issues/185999 + it.skip('should send a successful request', async () => { + await PageObjects.console.addNewVariable({ name: 'query1', value: '{"match_all": {}}' }); + await PageObjects.console.monaco.enterText('\n GET _search\n'); + await PageObjects.console.monaco.enterText(`{\n\t"query": "\${query1}"`); + await PageObjects.console.clickPlay(); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await retry.try(async () => { + const status = await PageObjects.console.getResponseStatus(); + expect(status).to.eql(200); + }); + }); + }); + }); +}; diff --git a/test/functional/apps/console/monaco/_vector_tile.ts b/test/functional/apps/console/monaco/_vector_tile.ts new file mode 100644 index 0000000000000..4c00c5345c92f --- /dev/null +++ b/test/functional/apps/console/monaco/_vector_tile.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'console', 'header', 'home']); + const retry = getService('retry'); + const security = getService('security'); + + describe('console vector tiles response validation', function describeIndexTests() { + before(async () => { + await security.testUser.setRoles(['kibana_admin', 'kibana_sample_admin']); + await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { + useActualUrl: true, + }); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.home.addSampleDataSet('logs'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.common.navigateToApp('console'); + await PageObjects.console.closeHelpIfExists(); + await PageObjects.console.monaco.clearEditorText(); + }); + + it('should validate response', async () => { + await PageObjects.console.monaco.enterText( + `GET kibana_sample_data_logs/_mvt/geo.coordinates/0/0/0` + ); + await PageObjects.console.clickPlay(); + await retry.try(async () => { + const actualResponse = await PageObjects.console.monaco.getOutputText(); + expect(actualResponse).to.contain('"meta": ['); + }); + }); + + after(async () => { + await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { + useActualUrl: true, + }); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.home.removeSampleDataSet('logs'); + await security.testUser.restoreDefaults(); + }); + }); +} diff --git a/test/functional/apps/console/monaco/_xjson.ts b/test/functional/apps/console/monaco/_xjson.ts new file mode 100644 index 0000000000000..8f0a8d368ed06 --- /dev/null +++ b/test/functional/apps/console/monaco/_xjson.ts @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ getService, getPageObjects }: FtrProviderContext) => { + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'console', 'header']); + + describe('XJSON', function testXjson() { + this.tags('includeFirefox'); + before(async () => { + await PageObjects.common.navigateToApp('console'); + await PageObjects.console.closeHelpIfExists(); + }); + + beforeEach(async () => { + await PageObjects.console.monaco.clearEditorText(); + }); + + const executeRequest = async (request = '\n GET _search') => { + await PageObjects.console.monaco.enterText(request); + await PageObjects.console.clickPlay(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }; + + describe('inline http request', () => { + it('should not have validation errors', async () => { + await PageObjects.console.monaco.enterText('\n GET foo/bar'); + expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(false); + }); + + it('should have validation error for invalid method', async () => { + await PageObjects.console.monaco.enterText('\n FOO foo/bar'); + // Retry because the error marker is not always immediately visible. + await retry.try(async () => { + expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(true); + }); + }); + + it('should have validation error for invalid path', async () => { + await PageObjects.console.monaco.enterText('\n GET'); + // Retry because the error marker is not always immediately visible. + await retry.try(async () => { + expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(true); + }); + }); + + it('should have validation error for invalid body', async () => { + await PageObjects.console.monaco.enterText('\n POST foo/bar\n {"foo": "bar"'); + // Retry because the error marker is not always immediately visible. + await retry.try(async () => { + expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(true); + }); + }); + + it('should not trigger error for multiple bodies for _msearch requests', async () => { + await PageObjects.console.monaco.enterText( + '\nGET foo/_msearch \n{}\n{"query": {"match_all": {}}}\n{"index": "bar"}\n{"query": {"match_all": {}}}' + ); + // Retry until typing is finished. + await retry.try(async () => { + expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(false); + }); + }); + + it('should not trigger validation errors for multiple JSON blocks', async () => { + await PageObjects.console.monaco.enterText('\nPOST test/doc/1 \n{\n "foo": "bar"\n}'); + await PageObjects.console.monaco.enterText('\nPOST test/doc/2 \n{\n "foo": "baz"\n}'); + await PageObjects.console.monaco.enterText('\nPOST test/doc/3 \n{\n "foo": "qux"\n}'); + // Retry until typing is finished. + await retry.try(async () => { + expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(false); + }); + }); + + it('should allow escaping quotation mark by wrapping it in triple quotes', async () => { + await PageObjects.console.monaco.enterText( + '\nPOST test/_doc/1 \n{\n "foo": """look "escaped" quotes"""\n}' + ); + // Retry until typing is finished and validation errors are gone. + await retry.try(async () => { + expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(false); + }); + }); + + it('should allow inline comments in request url row', async () => { + await executeRequest('\n GET _search // inline comment'); + expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(false); + expect(await PageObjects.console.getResponseStatus()).to.eql(200); + }); + + it('should allow inline comments in request body', async () => { + await executeRequest( + '\n GET _search \n{\n "query": {\n "match_all": {} // inline comment\n}\n}' + ); + expect(await PageObjects.console.monaco.hasInvalidSyntax()).to.be(false); + expect(await PageObjects.console.getResponseStatus()).to.eql(200); + }); + + it('should print warning for deprecated request', async () => { + await executeRequest('\nGET .kibana'); + expect(await PageObjects.console.monaco.responseHasDeprecationWarning()).to.be(true); + }); + + it('should not print warning for non-deprecated request', async () => { + await executeRequest('\n GET _search'); + expect(await PageObjects.console.monaco.responseHasDeprecationWarning()).to.be(false); + }); + }); + }); +}; diff --git a/test/functional/apps/console/config.ts b/test/functional/apps/console/monaco/config.ts similarity index 87% rename from test/functional/apps/console/config.ts rename to test/functional/apps/console/monaco/config.ts index f295f1b826492..13fd8b21c9162 100644 --- a/test/functional/apps/console/config.ts +++ b/test/functional/apps/console/monaco/config.ts @@ -7,10 +7,10 @@ */ import { FtrConfigProviderContext } from '@kbn/test'; -import { configureHTTP2 } from '../../../common/configure_http2'; +import { configureHTTP2 } from '../../../../common/configure_http2'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const functionalConfig = await readConfigFile(require.resolve('../../config.base.js')); + const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js')); return configureHTTP2({ ...functionalConfig.getAll(), diff --git a/test/functional/apps/console/monaco/index.ts b/test/functional/apps/console/monaco/index.ts new file mode 100644 index 0000000000000..7274118daaafe --- /dev/null +++ b/test/functional/apps/console/monaco/index.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const browser = getService('browser'); + const config = getService('config'); + + describe('console app', function () { + before(async function () { + await browser.setWindowSize(1300, 1100); + }); + if (config.get('esTestCluster.ccs')) { + loadTestFile(require.resolve('./_console_ccs')); + } else { + loadTestFile(require.resolve('./_console')); + loadTestFile(require.resolve('./_autocomplete')); + loadTestFile(require.resolve('./_vector_tile')); + loadTestFile(require.resolve('./_comments')); + loadTestFile(require.resolve('./_variables')); + loadTestFile(require.resolve('./_xjson')); + loadTestFile(require.resolve('./_misc_console_behavior')); + loadTestFile(require.resolve('./_context_menu')); + loadTestFile(require.resolve('./_text_input')); + loadTestFile(require.resolve('./_settings')); + } + }); +} diff --git a/test/functional/apps/context/_filters.ts b/test/functional/apps/context/_filters.ts index 9aff3f6c805fc..71cb9eefe2dd2 100644 --- a/test/functional/apps/context/_filters.ts +++ b/test/functional/apps/context/_filters.ts @@ -22,7 +22,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); const kibanaServer = getService('kibanaServer'); - const PageObjects = getPageObjects(['common', 'context']); + const PageObjects = getPageObjects(['common', 'context', 'discover']); const testSubjects = getService('testSubjects'); describe('context filters', function contextSize() { @@ -41,6 +41,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('inclusive filter should be addable via expanded data grid rows', async function () { await retry.waitFor(`filter ${TEST_ANCHOR_FILTER_FIELD} in filterbar`, async () => { await dataGrid.clickRowToggle({ isAnchorRow: true, renderMoreRows: true }); + await PageObjects.discover.findFieldByNameInDocViewer(TEST_ANCHOR_FILTER_FIELD); await dataGrid.clickFieldActionInFlyout( TEST_ANCHOR_FILTER_FIELD, 'addFilterForValueButton' diff --git a/test/functional/apps/dashboard/group1/embeddable_data_grid.ts b/test/functional/apps/dashboard/group1/embeddable_data_grid.ts index 010644b1d2029..0b2199a976d0b 100644 --- a/test/functional/apps/dashboard/group1/embeddable_data_grid.ts +++ b/test/functional/apps/dashboard/group1/embeddable_data_grid.ts @@ -49,7 +49,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.try(async function () { await dataGrid.clickRowToggle({ isAnchorRow: false, rowIndex: 0 }); const detailsEl = await dataGrid.getDetailsRows(); - const defaultMessageEl = await detailsEl[0].findByTestSubject('docTableRowDetailsTitle'); + const defaultMessageEl = await detailsEl[0].findByTestSubject('docViewerRowDetailsTitle'); expect(defaultMessageEl).to.be.ok(); await dataGrid.closeFlyout(); }); diff --git a/test/functional/apps/dashboard/group1/url_field_formatter.ts b/test/functional/apps/dashboard/group1/url_field_formatter.ts index 12863bcc17fc4..b408e0aed14d6 100644 --- a/test/functional/apps/dashboard/group1/url_field_formatter.ts +++ b/test/functional/apps/dashboard/group1/url_field_formatter.ts @@ -19,6 +19,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ]); const kibanaServer = getService('kibanaServer'); const testSubjects = getService('testSubjects'); + const find = getService('find'); const browser = getService('browser'); const fieldName = 'clientip'; const deployment = getService('deployment'); @@ -82,13 +83,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.waitForWithTimeout(`${fieldName} is visible`, 30000, async () => { return await testSubjects.isDisplayed(`tableDocViewRow-${fieldName}-value`); }); - const fieldLink = await testSubjects.find(`tableDocViewRow-${fieldName}-value`); + const fieldLink = await find.byCssSelector( + `[data-test-subj="tableDocViewRow-${fieldName}-value"] a` + ); const fieldValue = await fieldLink.getVisibleText(); await fieldLink.click(); await retry.try(async () => { await checkUrl(fieldValue); }); }); + afterEach(async function () { const windowHandlers = await browser.getAllWindowHandles(); if (windowHandlers.length > 1) { diff --git a/test/functional/apps/dashboard/group3/dashboard_state.ts b/test/functional/apps/dashboard/group3/dashboard_state.ts index df80d35ce2a64..2c1ebee573599 100644 --- a/test/functional/apps/dashboard/group3/dashboard_state.ts +++ b/test/functional/apps/dashboard/group3/dashboard_state.ts @@ -185,8 +185,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.waitForRenderComplete(); }; - // FLAKY: https://github.com/elastic/kibana/issues/139762 - describe.skip('Directly modifying url updates dashboard state', () => { + describe('Directly modifying url updates dashboard state', () => { before(async () => { await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); diff --git a/test/functional/apps/dashboard/group4/dashboard_clone.ts b/test/functional/apps/dashboard/group4/dashboard_clone.ts index 1e15f4d44e740..25ad42ac10fe7 100644 --- a/test/functional/apps/dashboard/group4/dashboard_clone.ts +++ b/test/functional/apps/dashboard/group4/dashboard_clone.ts @@ -49,5 +49,20 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.gotoDashboardLandingPage(); await listingTable.searchAndExpectItemsCount('dashboard', `${dashboardName} (2)`, 1); }); + + it('Clone should always increment from the last duplicated dashboard with a unique title', async function () { + await PageObjects.dashboard.loadSavedDashboard(clonedDashboardName); + // force dashboard duplicate id to increment out of logical progression bounds + await PageObjects.dashboard.duplicateDashboard(`${dashboardName} (20)`); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await listingTable.searchAndExpectItemsCount('dashboard', `${dashboardName} (20)`, 1); + // load dashboard with duplication id 1 + await PageObjects.dashboard.loadSavedDashboard(clonedDashboardName); + // run normal clone + await PageObjects.dashboard.duplicateDashboard(); + await PageObjects.dashboard.gotoDashboardLandingPage(); + // clone gets duplication id, that picks off from last duplicated dashboard + await listingTable.searchAndExpectItemsCount('dashboard', `${dashboardName} (21)`, 1); + }); }); } diff --git a/test/functional/apps/dashboard/group5/share.ts b/test/functional/apps/dashboard/group5/share.ts index dfcb2e56aef36..43af46a238589 100644 --- a/test/functional/apps/dashboard/group5/share.ts +++ b/test/functional/apps/dashboard/group5/share.ts @@ -49,13 +49,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.share.clickShareTopNavButton(); return await PageObjects.share.isShareMenuOpen(); }); - // if (mode === 'savedObject') { - // await PageObjects.share.exportAsSavedObject(); - // } - return PageObjects.share.getSharedUrl(); + return await PageObjects.share.getSharedUrl(); }; - describe.skip('share dashboard', () => { + describe('share dashboard', () => { const testFilterState = async (mode: TestingModes) => { it('should not have "filters" state in either app or global state when no filters', async () => { expect(await getSharedUrl(mode)).to.not.contain('filters'); @@ -120,7 +117,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.common.unsetTime(); }); - describe.skip('snapshot share', async () => { + describe('snapshot share', async () => { describe('test local state', async () => { it('should not have "panels" state when not in unsaved changes state', async () => { await testSubjects.missingOrFail('dashboardUnsavedChangesBadge'); @@ -147,7 +144,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe.skip('test filter state', async () => { + describe('test filter state', async () => { await testFilterState('snapshot'); }); @@ -158,7 +155,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe.skip('saved object share', async () => { + describe('saved object share', async () => { describe('test filter state', async () => { await testFilterState('savedObject'); }); diff --git a/test/functional/apps/dashboard_elements/controls/common/config.ts b/test/functional/apps/dashboard_elements/controls/common/config.ts index 60497ce2dae41..7e7e9ada1644d 100644 --- a/test/functional/apps/dashboard_elements/controls/common/config.ts +++ b/test/functional/apps/dashboard_elements/controls/common/config.ts @@ -17,5 +17,13 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { junit: { reportName: 'Dashboard Elements - Controls tests', }, + kbnTestServer: { + ...functionalConfig.get('kbnTestServer'), + serverArgs: [ + ...functionalConfig.get('kbnTestServer.serverArgs'), + // disabling the monaco editor to run tests for ace + `--console.dev.enableMonaco=false`, + ], + }, }; } diff --git a/test/functional/apps/dashboard_elements/controls/options_list/config.ts b/test/functional/apps/dashboard_elements/controls/options_list/config.ts index c55987afa5570..150da945a1203 100644 --- a/test/functional/apps/dashboard_elements/controls/options_list/config.ts +++ b/test/functional/apps/dashboard_elements/controls/options_list/config.ts @@ -17,5 +17,13 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { junit: { reportName: 'Dashboard Elements - Controls Options List tests', }, + kbnTestServer: { + ...functionalConfig.get('kbnTestServer'), + serverArgs: [ + ...functionalConfig.get('kbnTestServer.serverArgs'), + // disabling the monaco editor to run tests for ace + `--console.dev.enableMonaco=false`, + ], + }, }; } diff --git a/test/functional/apps/discover/classic/_doc_table.ts b/test/functional/apps/discover/classic/_doc_table.ts index 7ab640cf42ee1..7539cce991af8 100644 --- a/test/functional/apps/discover/classic/_doc_table.ts +++ b/test/functional/apps/discover/classic/_doc_table.ts @@ -161,7 +161,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await docTable.clickRowToggle({ isAnchorRow: false, rowIndex: rowToInspect - 1 }); const detailsEl = await docTable.getDetailsRows(); const defaultMessageEl = await detailsEl[0].findByTestSubject( - 'docTableRowDetailsTitle' + 'docViewerRowDetailsTitle' ); expect(defaultMessageEl).to.be.ok(); }); @@ -187,14 +187,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await docTable.clickRowToggle({ isAnchorRow: false, rowIndex: rowToInspect - 1 }); const detailsEl = await docTable.getDetailsRows(); const defaultMessageEl = await detailsEl[0].findByTestSubject( - 'docTableRowDetailsTitle' + 'docViewerRowDetailsTitle' ); expect(defaultMessageEl).to.be.ok(); await queryBar.submitQuery(); const nrOfFetchesResubmit = await PageObjects.discover.getNrOfFetches(); expect(nrOfFetchesResubmit).to.be.above(nrOfFetches); const defaultMessageElResubmit = await detailsEl[0].findByTestSubject( - 'docTableRowDetailsTitle' + 'docViewerRowDetailsTitle' ); expect(defaultMessageElResubmit).to.be.ok(); diff --git a/test/functional/apps/discover/classic/_esql_grid.ts b/test/functional/apps/discover/classic/_esql_grid.ts index 7dfd331acec12..d2bacbcd4947a 100644 --- a/test/functional/apps/discover/classic/_esql_grid.ts +++ b/test/functional/apps/discover/classic/_esql_grid.ts @@ -65,7 +65,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dataGrid.clickRowToggle({ rowIndex: 0 }); - await testSubjects.existOrFail('docTableDetailsFlyout'); + await testSubjects.existOrFail('docViewerFlyout'); await PageObjects.discover.saveSearch(savedSearchESQL); @@ -81,7 +81,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dataGrid.clickRowToggle({ rowIndex: 0 }); - await testSubjects.existOrFail('docTableDetailsFlyout'); + await testSubjects.existOrFail('docViewerFlyout'); await dashboardPanelActions.removePanelByTitle(savedSearchESQL); diff --git a/test/functional/apps/discover/context_awareness/_data_source_profile.ts b/test/functional/apps/discover/context_awareness/_data_source_profile.ts new file mode 100644 index 0000000000000..d203f33c887e8 --- /dev/null +++ b/test/functional/apps/discover/context_awareness/_data_source_profile.ts @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import kbnRison from '@kbn/rison'; +import expect from '@kbn/expect'; +import type { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'timePicker', 'discover', 'unifiedFieldList']); + const testSubjects = getService('testSubjects'); + const dataViews = getService('dataViews'); + + describe('data source profile', () => { + describe('ES|QL mode', () => { + describe('cell renderers', () => { + it('should render custom @timestamp but not custom log.level', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-* | sort @timestamp desc' }, + }); + await PageObjects.common.navigateToApp('discover', { + hash: `/?_a=${state}`, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp'); + await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level'); + const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp'); + expect(timestamps).to.have.length(6); + expect(await timestamps[0].getVisibleText()).to.be('2024-06-10T16:30:00.000Z'); + expect(await timestamps[5].getVisibleText()).to.be('2024-06-10T14:00:00.000Z'); + const logLevels = await testSubjects.findAll('exampleDataSourceProfileLogLevel', 2500); + expect(logLevels).to.have.length(0); + }); + + it('should render custom @timestamp and custom log.level', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-logs | sort @timestamp desc' }, + }); + await PageObjects.common.navigateToApp('discover', { + hash: `/?_a=${state}`, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp'); + await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level'); + const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp'); + expect(timestamps).to.have.length(3); + expect(await timestamps[0].getVisibleText()).to.be('2024-06-10T16:00:00.000Z'); + expect(await timestamps[2].getVisibleText()).to.be('2024-06-10T14:00:00.000Z'); + const logLevels = await testSubjects.findAll('exampleDataSourceProfileLogLevel'); + expect(logLevels).to.have.length(3); + expect(await logLevels[0].getVisibleText()).to.be('Debug'); + expect(await logLevels[2].getVisibleText()).to.be('Info'); + }); + }); + }); + + describe('data view mode', () => { + describe('cell renderers', () => { + it('should render custom @timestamp but not custom log.level', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-*'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp'); + await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level'); + const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp'); + expect(timestamps).to.have.length(6); + expect(await timestamps[0].getVisibleText()).to.be('2024-06-10T16:30:00.000Z'); + expect(await timestamps[5].getVisibleText()).to.be('2024-06-10T14:00:00.000Z'); + const logLevels = await testSubjects.findAll('exampleDataSourceProfileLogLevel', 2500); + expect(logLevels).to.have.length(0); + }); + + it('should render custom @timestamp and custom log.level', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-logs'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp'); + await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level'); + const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp'); + expect(timestamps).to.have.length(3); + expect(await timestamps[0].getVisibleText()).to.be('2024-06-10T16:00:00.000Z'); + expect(await timestamps[2].getVisibleText()).to.be('2024-06-10T14:00:00.000Z'); + const logLevels = await testSubjects.findAll('exampleDataSourceProfileLogLevel'); + expect(logLevels).to.have.length(3); + expect(await logLevels[0].getVisibleText()).to.be('Debug'); + expect(await logLevels[2].getVisibleText()).to.be('Info'); + }); + }); + }); + }); +} diff --git a/test/functional/apps/discover/context_awareness/_root_profile.ts b/test/functional/apps/discover/context_awareness/_root_profile.ts new file mode 100644 index 0000000000000..c0bb4885699f8 --- /dev/null +++ b/test/functional/apps/discover/context_awareness/_root_profile.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import kbnRison from '@kbn/rison'; +import expect from '@kbn/expect'; +import type { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'timePicker', 'discover']); + const testSubjects = getService('testSubjects'); + const dataViews = getService('dataViews'); + + describe('root profile', () => { + describe('ES|QL mode', () => { + describe('cell renderers', () => { + it('should render custom @timestamp', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-* | sort @timestamp desc' }, + }); + await PageObjects.common.navigateToApp('discover', { + hash: `/?_a=${state}`, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp'); + expect(timestamps).to.have.length(6); + expect(await timestamps[0].getVisibleText()).to.be('2024-06-10T16:30:00.000Z'); + expect(await timestamps[5].getVisibleText()).to.be('2024-06-10T14:00:00.000Z'); + }); + }); + }); + + describe('data view mode', () => { + describe('cell renderers', () => { + it('should render custom @timestamp', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-*'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp'); + expect(timestamps).to.have.length(6); + expect(await timestamps[0].getVisibleText()).to.be('2024-06-10T16:30:00.000Z'); + expect(await timestamps[5].getVisibleText()).to.be('2024-06-10T14:00:00.000Z'); + }); + }); + }); + }); +} diff --git a/test/functional/apps/discover/context_awareness/config.ts b/test/functional/apps/discover/context_awareness/config.ts new file mode 100644 index 0000000000000..c8e43a6bb2c9d --- /dev/null +++ b/test/functional/apps/discover/context_awareness/config.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js')); + const baseConfig = functionalConfig.getAll(); + + return { + ...baseConfig, + testFiles: [require.resolve('.')], + kbnTestServer: { + ...baseConfig.kbnTestServer, + serverArgs: [ + ...baseConfig.kbnTestServer.serverArgs, + '--discover.experimental.enabledProfiles=["example-root-profile","example-data-source-profile","example-document-profile"]', + ], + }, + }; +} diff --git a/test/functional/apps/discover/context_awareness/index.ts b/test/functional/apps/discover/context_awareness/index.ts new file mode 100644 index 0000000000000..f280024ec7c80 --- /dev/null +++ b/test/functional/apps/discover/context_awareness/index.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService, getPageObjects, loadTestFile }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['timePicker']); + const from = '2024-06-10T14:00:00.000Z'; + const to = '2024-06-10T16:30:00.000Z'; + + describe('discover/context_awareness', () => { + before(async () => { + await esArchiver.load('test/functional/fixtures/es_archiver/discover/context_awareness'); + await kibanaServer.importExport.load( + 'test/functional/fixtures/kbn_archiver/discover/context_awareness' + ); + await kibanaServer.uiSettings.update({ + 'timepicker:timeDefaults': `{ "from": "${from}", "to": "${to}"}`, + }); + }); + + after(async () => { + await esArchiver.unload('test/functional/fixtures/es_archiver/discover/context_awareness'); + await kibanaServer.importExport.unload( + 'test/functional/fixtures/kbn_archiver/discover/context_awareness' + ); + await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings(); + }); + + loadTestFile(require.resolve('./_root_profile')); + loadTestFile(require.resolve('./_data_source_profile')); + }); +} diff --git a/test/functional/apps/discover/esql/_esql_columns.ts b/test/functional/apps/discover/esql/_esql_columns.ts new file mode 100644 index 0000000000000..d48ec83f6ba91 --- /dev/null +++ b/test/functional/apps/discover/esql/_esql_columns.ts @@ -0,0 +1,246 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../ftr_provider_context'; + +const SAVED_SEARCH_NON_TRANSFORMATIONAL_INITIAL_COLUMNS = 'nonTransformationalInitialColumns'; +const SAVED_SEARCH_NON_TRANSFORMATIONAL_CUSTOM_COLUMNS = 'nonTransformationalCustomColumns'; +const SAVED_SEARCH_TRANSFORMATIONAL_INITIAL_COLUMNS = 'transformationalInitialColumns'; +const SAVED_SEARCH_TRANSFORMATIONAL_CUSTOM_COLUMNS = 'transformationalCustomColumns'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const security = getService('security'); + const dataGrid = getService('dataGrid'); + const browser = getService('browser'); + const monacoEditor = getService('monacoEditor'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects([ + 'common', + 'discover', + 'dashboard', + 'header', + 'timePicker', + 'unifiedFieldList', + ]); + + const defaultSettings = { + defaultIndex: 'logstash-*', + }; + + // FLAKY: https://github.com/elastic/kibana/issues/186416 + describe.skip('discover esql columns', async function () { + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']); + await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); + await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); + await kibanaServer.uiSettings.replace(defaultSettings); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.discover.selectTextBaseLang(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + }); + + beforeEach(async () => { + await PageObjects.discover.clickNewSearchButton(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + }); + + it('should render initial columns for non-transformational commands correctly', async () => { + const columns = ['@timestamp', 'Document']; + expect(await dataGrid.getHeaderFields()).to.eql(columns); + + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(columns); + + await PageObjects.discover.saveSearch(SAVED_SEARCH_NON_TRANSFORMATIONAL_INITIAL_COLUMNS); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(columns); + }); + + it('should render custom columns for non-transformational commands correctly', async () => { + const columns = ['bytes', 'extension']; + await PageObjects.unifiedFieldList.clickFieldListItemAdd('bytes'); + await PageObjects.unifiedFieldList.clickFieldListItemAdd('extension'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(columns); + + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(columns); + + await PageObjects.discover.saveSearch(SAVED_SEARCH_NON_TRANSFORMATIONAL_CUSTOM_COLUMNS); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(columns); + }); + + it('should reset columns only if index pattern changes in non-transformational query', async () => { + const columns = ['@timestamp', 'Document']; + expect(await dataGrid.getHeaderFields()).to.eql(columns); + + await monacoEditor.setCodeEditorValue('from logstash-* | limit 1'); + await testSubjects.click('querySubmitButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(columns); + + await monacoEditor.setCodeEditorValue('from logs* | limit 1'); + await testSubjects.click('querySubmitButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(columns); + + await PageObjects.unifiedFieldList.clickFieldListItemAdd('bytes'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(['bytes']); + + // different index pattern => reset columns + await monacoEditor.setCodeEditorValue('from logstash-* | limit 1'); + await testSubjects.click('querySubmitButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(columns); + + await PageObjects.unifiedFieldList.clickFieldListItemAdd('extension'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(['extension']); + + // same index pattern => don't reset columns + await monacoEditor.setCodeEditorValue( + `${await monacoEditor.getCodeEditorValue()} | where bytes > 0` + ); + await testSubjects.click('querySubmitButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(['extension']); + }); + + it('should render initial columns for a transformational command correctly', async () => { + const columns = ['ip', '@timestamp']; + await monacoEditor.setCodeEditorValue( + `${await monacoEditor.getCodeEditorValue()} | keep ip, @timestamp` + ); + await testSubjects.click('querySubmitButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(columns); + + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(columns); + + await PageObjects.discover.saveSearch(SAVED_SEARCH_TRANSFORMATIONAL_INITIAL_COLUMNS); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(columns); + }); + + it('should render custom columns for a transformational command correctly', async () => { + const columns = ['ip', 'bytes']; + await monacoEditor.setCodeEditorValue( + `${await monacoEditor.getCodeEditorValue()} | keep ip, @timestamp, bytes` + ); + await testSubjects.click('querySubmitButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(['ip', '@timestamp', 'bytes']); + + await PageObjects.unifiedFieldList.clickFieldListItemRemove('@timestamp'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(columns); + + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(columns); + + await PageObjects.discover.saveSearch(SAVED_SEARCH_TRANSFORMATIONAL_CUSTOM_COLUMNS); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(columns); + }); + + it('should reset columns if available fields or index pattern are different in transformational query', async () => { + await monacoEditor.setCodeEditorValue('from logstash-* | keep ip, @timestamp'); + await testSubjects.click('querySubmitButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(['ip', '@timestamp']); + + // reset columns if available fields are different + await monacoEditor.setCodeEditorValue('from logstash-* | keep ip, @timestamp, bytes'); + await testSubjects.click('querySubmitButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(['ip', '@timestamp', 'bytes']); + + // don't reset columns if available fields and index pattern are the same + await monacoEditor.setCodeEditorValue( + 'from logstash-* | keep ip, @timestamp, bytes | limit 1' + ); + await testSubjects.click('querySubmitButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(['ip', '@timestamp', 'bytes']); + await PageObjects.unifiedFieldList.clickFieldListItemRemove('@timestamp'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(['ip', 'bytes']); + + // reset columns if index pattern is different + await monacoEditor.setCodeEditorValue('from logs* | keep ip, @timestamp, bytes | limit 1'); + await testSubjects.click('querySubmitButton'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(['ip', '@timestamp', 'bytes']); + }); + + it('should restore columns correctly when switching between saved searches', async () => { + await PageObjects.discover.loadSavedSearch(SAVED_SEARCH_NON_TRANSFORMATIONAL_INITIAL_COLUMNS); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(['@timestamp', 'Document']); + + await PageObjects.discover.loadSavedSearch(SAVED_SEARCH_NON_TRANSFORMATIONAL_CUSTOM_COLUMNS); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(['bytes', 'extension']); + + await PageObjects.discover.loadSavedSearch(SAVED_SEARCH_TRANSFORMATIONAL_INITIAL_COLUMNS); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(['ip', '@timestamp']); + + await PageObjects.discover.loadSavedSearch(SAVED_SEARCH_TRANSFORMATIONAL_CUSTOM_COLUMNS); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(['ip', 'bytes']); + + await PageObjects.discover.clickNewSearchButton(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + expect(await dataGrid.getHeaderFields()).to.eql(['@timestamp', 'Document']); + }); + }); +} diff --git a/test/functional/apps/discover/group4/_esql_view.ts b/test/functional/apps/discover/esql/_esql_view.ts similarity index 100% rename from test/functional/apps/discover/group4/_esql_view.ts rename to test/functional/apps/discover/esql/_esql_view.ts diff --git a/packages/kbn-alerts-ui-shared/src/common/types.ts b/test/functional/apps/discover/esql/config.ts similarity index 52% rename from packages/kbn-alerts-ui-shared/src/common/types.ts rename to test/functional/apps/discover/esql/config.ts index d7808487772c4..a70a190ca63f8 100644 --- a/packages/kbn-alerts-ui-shared/src/common/types.ts +++ b/test/functional/apps/discover/esql/config.ts @@ -6,8 +6,13 @@ * Side Public License, v 1. */ -import { RuleType } from '@kbn/triggers-actions-ui-types'; +import { FtrConfigProviderContext } from '@kbn/test'; -export type RuleTypeWithDescription = RuleType & { description?: string }; +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js')); -export type RuleTypeIndexWithDescriptions = Map; + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('.')], + }; +} diff --git a/test/functional/apps/discover/esql/index.ts b/test/functional/apps/discover/esql/index.ts new file mode 100644 index 0000000000000..5ba21ac0a8aa2 --- /dev/null +++ b/test/functional/apps/discover/esql/index.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const browser = getService('browser'); + + describe('discover/esql', function () { + before(async function () { + await browser.setWindowSize(1600, 1200); + }); + + after(async function unloadMakelogs() { + await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); + }); + + loadTestFile(require.resolve('./_esql_columns')); + loadTestFile(require.resolve('./_esql_view')); + }); +} diff --git a/test/functional/apps/discover/group2_data_grid1/_data_grid_doc_table.ts b/test/functional/apps/discover/group2_data_grid1/_data_grid_doc_table.ts index 81fd740ba21fa..ac77fe3f70714 100644 --- a/test/functional/apps/discover/group2_data_grid1/_data_grid_doc_table.ts +++ b/test/functional/apps/discover/group2_data_grid1/_data_grid_doc_table.ts @@ -163,7 +163,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.try(async function () { await dataGrid.clickRowToggle({ isAnchorRow: false, rowIndex: rowToInspect - 1 }); const detailsEl = await dataGrid.getDetailsRows(); - const defaultMessageEl = await detailsEl[0].findByTestSubject('docTableRowDetailsTitle'); + const defaultMessageEl = await detailsEl[0].findByTestSubject('docViewerRowDetailsTitle'); expect(defaultMessageEl).to.be.ok(); await dataGrid.closeFlyout(); }); @@ -185,9 +185,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should allow paginating docs in the flyout by clicking in the doc table', async function () { await retry.try(async function () { await dataGrid.clickRowToggle({ rowIndex: rowToInspect - 1 }); - await testSubjects.exists(`dscDocNavigationPage0`); + await testSubjects.exists(`docViewerFlyoutNavigationPage0`); await dataGrid.clickRowToggle({ rowIndex: rowToInspect }); - await testSubjects.exists(`dscDocNavigationPage1`); + await testSubjects.exists(`docViewerFlyoutNavigationPage1`); await dataGrid.closeFlyout(); }); }); diff --git a/test/functional/apps/discover/group2_data_grid2/_data_grid_field_tokens.ts b/test/functional/apps/discover/group2_data_grid2/_data_grid_field_tokens.ts index d22e8b39bd4a5..fad92414cfb1c 100644 --- a/test/functional/apps/discover/group2_data_grid2/_data_grid_field_tokens.ts +++ b/test/functional/apps/discover/group2_data_grid2/_data_grid_field_tokens.ts @@ -43,7 +43,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { let fieldTokens: string[] | undefined = []; await retry.try(async () => { await dataGrid.clickRowToggle({ rowIndex: 0 }); - fieldTokens = await findFirstFieldIcons('docTableDetailsFlyout'); + fieldTokens = await findFirstFieldIcons('docViewerFlyout'); }); return fieldTokens; } diff --git a/test/functional/apps/discover/group3/_doc_viewer.ts b/test/functional/apps/discover/group3/_doc_viewer.ts index 8fa3a94b969e3..140129c6a251f 100644 --- a/test/functional/apps/discover/group3/_doc_viewer.ts +++ b/test/functional/apps/discover/group3/_doc_viewer.ts @@ -41,13 +41,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); describe('search', function () { - const itemsPerPage = 25; - beforeEach(async () => { await dataGrid.clickRowToggle(); await PageObjects.discover.isShowingDocViewer(); await retry.waitFor('rendered items', async () => { - return (await find.allByCssSelector('.kbnDocViewer__fieldName')).length === itemsPerPage; + return (await find.allByCssSelector('.kbnDocViewer__fieldName')).length > 0; }); }); @@ -95,7 +93,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // expect no changes in the list await retry.waitFor('all items', async () => { - return (await find.allByCssSelector('.kbnDocViewer__fieldName')).length === itemsPerPage; + return (await find.allByCssSelector('.kbnDocViewer__fieldName')).length > 0; }); }); }); diff --git a/test/functional/apps/discover/group4/index.ts b/test/functional/apps/discover/group4/index.ts index 211b4501ed329..bb685ce8cbe6b 100644 --- a/test/functional/apps/discover/group4/index.ts +++ b/test/functional/apps/discover/group4/index.ts @@ -22,7 +22,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_discover_fields_api')); loadTestFile(require.resolve('./_adhoc_data_views')); - loadTestFile(require.resolve('./_esql_view')); loadTestFile(require.resolve('./_date_nested')); loadTestFile(require.resolve('./_chart_hidden')); loadTestFile(require.resolve('./_context_encoded_url_params')); diff --git a/test/functional/apps/discover/group5/_shared_links.ts b/test/functional/apps/discover/group5/_shared_links.ts index 168702fd86487..3aeda46316c59 100644 --- a/test/functional/apps/discover/group5/_shared_links.ts +++ b/test/functional/apps/discover/group5/_shared_links.ts @@ -61,11 +61,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); describe('permalink', function () { - it('should allow for copying the snapshot URL as a short URL', async function () { - const re = new RegExp(baseUrl + '/app/r/s/.+$'); + it('should allow for copying the snapshot URL', async function () { + const re = new RegExp(baseUrl + '/app/r.+$'); await retry.try(async () => { const actualUrl = await PageObjects.share.getSharedUrl(); - expect(actualUrl).to.match(re); + expect(actualUrl).match(re); }); }); @@ -104,12 +104,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await teardown(); }); - it('should allow for copying the snapshot URL as a short URL and should open it', async function () { - const re = new RegExp(baseUrl + '/app/r/s/.+$'); + it('should allow for copying the snapshot URL and should open it', async function () { + const re = new RegExp(baseUrl + '/app/r.*$'); let actualUrl: string = ''; await retry.try(async () => { actualUrl = await PageObjects.share.getSharedUrl(); - expect(actualUrl).to.match(re); + expect(actualUrl).match(re); }); const actualTime = await PageObjects.timePicker.getTimeConfig(); diff --git a/test/functional/apps/discover/group7/_runtime_fields_editor.ts b/test/functional/apps/discover/group7/_runtime_fields_editor.ts index de43dc8e74e92..3028096adc60c 100644 --- a/test/functional/apps/discover/group7/_runtime_fields_editor.ts +++ b/test/functional/apps/discover/group7/_runtime_fields_editor.ts @@ -105,7 +105,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // check it in the doc viewer too await dataGrid.clickRowToggle({ rowIndex: 0 }); - await testSubjects.click('fieldDescriptionPopoverButton-agent'); + await dataGrid.expandFieldNameCellInFlyout('agent'); await retry.waitFor('doc viewer popover text', async () => { return (await testSubjects.getVisibleText('fieldDescription-agent')) === customDescription2; }); diff --git a/test/functional/config.ccs.ts b/test/functional/config.ccs.ts index cea1de2be8316..506f57c72d183 100644 --- a/test/functional/config.ccs.ts +++ b/test/functional/config.ccs.ts @@ -20,7 +20,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { testFiles: [ require.resolve('./apps/dashboard/group3'), require.resolve('./apps/discover/ccs_compatibility'), - require.resolve('./apps/console/_console_ccs'), + require.resolve('./apps/console/monaco/_console_ccs'), require.resolve('./apps/management/ccs_compatibility'), require.resolve('./apps/getting_started'), ], diff --git a/test/functional/firefox/console.config.ts b/test/functional/firefox/console.config.ts index 420eee1f438cb..96141382047d0 100644 --- a/test/functional/firefox/console.config.ts +++ b/test/functional/firefox/console.config.ts @@ -16,7 +16,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { return { ...baseConfig.getAll(), - testFiles: [require.resolve('../apps/console')], + testFiles: [require.resolve('../apps/console/monaco')], junit: { reportName: 'Firefox UI Functional Tests - Console', diff --git a/test/functional/firefox/discover.config.ts b/test/functional/firefox/discover.config.ts index 5c9f9c0939754..61ffcb9fd21bf 100644 --- a/test/functional/firefox/discover.config.ts +++ b/test/functional/firefox/discover.config.ts @@ -18,6 +18,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { testFiles: [ require.resolve('../apps/discover/classic'), + require.resolve('../apps/discover/esql'), require.resolve('../apps/discover/group1'), require.resolve('../apps/discover/group2_data_grid1'), require.resolve('../apps/discover/group2_data_grid2'), diff --git a/test/functional/fixtures/es_archiver/discover/context_awareness/data.json.gz b/test/functional/fixtures/es_archiver/discover/context_awareness/data.json.gz new file mode 100644 index 0000000000000..cd1f670958d7d Binary files /dev/null and b/test/functional/fixtures/es_archiver/discover/context_awareness/data.json.gz differ diff --git a/test/functional/fixtures/es_archiver/discover/context_awareness/mappings.json b/test/functional/fixtures/es_archiver/discover/context_awareness/mappings.json new file mode 100644 index 0000000000000..ef7a7edf29e00 --- /dev/null +++ b/test/functional/fixtures/es_archiver/discover/context_awareness/mappings.json @@ -0,0 +1,76 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "my-example-logs", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "data_stream": { + "properties": { + "type": { + "type": "constant_keyword", + "value": "logs" + } + } + }, + "log": { + "properties": { + "level": { + "type": "keyword" + } + } + }, + "message": { + "type": "match_only_text" + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "my-example-metrics", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "data_stream": { + "properties": { + "type": { + "type": "constant_keyword", + "value": "metrics" + } + } + }, + "event": { + "properties": { + "duration": { + "type": "long" + } + } + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/test/functional/fixtures/kbn_archiver/discover/context_awareness.json b/test/functional/fixtures/kbn_archiver/discover/context_awareness.json new file mode 100644 index 0000000000000..5232a109e5799 --- /dev/null +++ b/test/functional/fixtures/kbn_archiver/discover/context_awareness.json @@ -0,0 +1,49 @@ +{ + "attributes": { + "allowHidden": false, + "fieldAttrs": "{}", + "fieldFormatMap": "{}", + "fields": "[]", + "name": "my-example-*", + "runtimeFieldMap": "{}", + "sourceFilters": "[]", + "timeFieldName": "@timestamp", + "title": "my-example-*" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2024-06-12T22:23:09.061Z", + "created_by": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0", + "id": "47fbfd7b-a51d-442b-86ff-b1529cf5b935", + "managed": false, + "references": [], + "type": "index-pattern", + "typeMigrationVersion": "8.0.0", + "updated_at": "2024-06-12T22:23:09.061Z", + "updated_by": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0", + "version": "WzgsMV0=" +} + +{ + "attributes": { + "allowHidden": false, + "fieldAttrs": "{}", + "fieldFormatMap": "{}", + "fields": "[]", + "name": "my-example-logs", + "runtimeFieldMap": "{}", + "sourceFilters": "[]", + "timeFieldName": "@timestamp", + "title": "my-example-logs" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2024-06-12T22:23:21.331Z", + "created_by": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0", + "id": "795df528-add1-491a-8e25-72a862c4bf8c", + "managed": false, + "references": [], + "type": "index-pattern", + "typeMigrationVersion": "8.0.0", + "updated_at": "2024-06-12T22:23:21.331Z", + "updated_by": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0", + "version": "WzEwLDFd" +} \ No newline at end of file diff --git a/test/functional/page_objects/console_page.ts b/test/functional/page_objects/console_page.ts index 3fdc1e8ab0f74..84267de85a595 100644 --- a/test/functional/page_objects/console_page.ts +++ b/test/functional/page_objects/console_page.ts @@ -18,6 +18,166 @@ export class ConsolePageObject extends FtrService { private readonly common = this.ctx.getPageObject('common'); private readonly browser = this.ctx.getService('browser'); + public monaco = { + getTextArea: async () => { + const codeEditor = await this.testSubjects.find('consoleMonacoEditor'); + return await codeEditor.findByTagName('textarea'); + }, + getEditorText: async () => { + const codeEditor = await this.testSubjects.find('consoleMonacoEditor'); + const editorViewDiv = await codeEditor.findByClassName('view-lines'); + return await editorViewDiv.getVisibleText(); + }, + getEditorTextAtLine: async (line: number) => { + const codeEditor = await this.testSubjects.find('consoleMonacoEditor'); + const editorViewDiv = await codeEditor.findAllByClassName('view-line'); + return await editorViewDiv[line].getVisibleText(); + }, + getCurrentLineNumber: async () => { + const textArea = await this.monaco.getTextArea(); + const styleAttribute = (await textArea.getAttribute('style')) ?? ''; + const height = parseFloat(styleAttribute.replace(/.*height: ([+-]?\d+(\.\d+)?).*/, '$1')); + const top = parseFloat(styleAttribute.replace(/.*top: ([+-]?\d+(\.\d+)?).*/, '$1')); + // calculate the line number by dividing the top position by the line height + // and adding 1 because line numbers start at 1 + return Math.ceil(top / height) + 1; + }, + clearEditorText: async () => { + const textArea = await this.monaco.getTextArea(); + await textArea.clickMouseButton(); + await textArea.clearValueWithKeyboard(); + }, + getOutputText: async () => { + const outputPanel = await this.testSubjects.find('consoleMonacoOutput'); + const outputViewDiv = await outputPanel.findByClassName('monaco-scrollable-element'); + return await outputViewDiv.getVisibleText(); + }, + pressEnter: async () => { + const textArea = await this.monaco.getTextArea(); + await textArea.pressKeys(Key.ENTER); + }, + enterText: async (text: string) => { + const textArea = await this.monaco.getTextArea(); + await textArea.type(text); + }, + promptAutocomplete: async (letter = 'b') => { + const textArea = await this.monaco.getTextArea(); + await textArea.type(letter); + await this.retry.waitFor('autocomplete to be visible', () => + this.monaco.isAutocompleteVisible() + ); + }, + isAutocompleteVisible: async () => { + const element = await this.find.byClassName('suggest-widget').catch(() => null); + if (!element) return false; + + const attribute = await element.getAttribute('style'); + return !attribute?.includes('display: none;'); + }, + getAutocompleteSuggestion: async (index: number) => { + const suggestionsWidget = await this.find.byClassName('suggest-widget'); + const suggestions = await suggestionsWidget.findAllByClassName('monaco-list-row'); + const label = await suggestions[index].findByClassName('label-name'); + return label.getVisibleText(); + }, + pressUp: async (shift: boolean = false) => { + const textArea = await this.monaco.getTextArea(); + await textArea.pressKeys(shift ? [Key.SHIFT, Key.UP] : Key.UP); + }, + pressDown: async (shift: boolean = false) => { + const textArea = await this.monaco.getTextArea(); + await textArea.pressKeys(shift ? [Key.SHIFT, Key.DOWN] : Key.DOWN); + }, + pressRight: async (shift: boolean = false) => { + const textArea = await this.monaco.getTextArea(); + await textArea.pressKeys(shift ? [Key.SHIFT, Key.RIGHT] : Key.RIGHT); + }, + pressLeft: async (shift: boolean = false) => { + const textArea = await this.monaco.getTextArea(); + await textArea.pressKeys(shift ? [Key.SHIFT, Key.LEFT] : Key.LEFT); + }, + pressCtrlSpace: async () => { + const textArea = await this.monaco.getTextArea(); + await textArea.pressKeys([ + Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], + Key.SPACE, + ]); + }, + pressCtrlEnter: async () => { + const textArea = await this.monaco.getTextArea(); + await textArea.pressKeys([ + Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], + Key.ENTER, + ]); + }, + pressCtrlI: async () => { + const textArea = await this.monaco.getTextArea(); + await textArea.pressKeys([Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], 'i']); + }, + pressCtrlUp: async () => { + const textArea = await this.monaco.getTextArea(); + await textArea.pressKeys([ + Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], + Key.UP, + ]); + }, + pressCtrlDown: async () => { + const textArea = await this.monaco.getTextArea(); + await textArea.pressKeys([ + Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], + Key.DOWN, + ]); + }, + pressCtrlL: async () => { + const textArea = await this.monaco.getTextArea(); + await textArea.pressKeys([Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], 'l']); + }, + pressCtrlSlash: async () => { + const textArea = await this.monaco.getTextArea(); + await textArea.pressKeys([Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], '/']); + }, + pressEscape: async () => { + const textArea = await this.monaco.getTextArea(); + await textArea.pressKeys(Key.ESCAPE); + }, + selectAllRequests: async () => { + const textArea = await this.monaco.getTextArea(); + const selectionKey = Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL']; + await textArea.pressKeys([selectionKey, 'a']); + }, + getEditor: async () => { + return await this.testSubjects.find('consoleMonacoEditor'); + }, + hasInvalidSyntax: async () => { + return await this.find.existsByCssSelector('.squiggly-error'); + }, + responseHasDeprecationWarning: async () => { + const response = await this.monaco.getOutputText(); + return response.trim().startsWith('#!'); + }, + selectCurrentRequest: async () => { + const textArea = await this.monaco.getTextArea(); + await textArea.clickMouseButton(); + }, + getFontSize: async () => { + const codeEditor = await this.testSubjects.find('consoleMonacoEditor'); + const editorViewDiv = await codeEditor.findByClassName('view-line'); + return await editorViewDiv.getComputedStyle('font-size'); + }, + pasteClipboardValue: async () => { + const textArea = await this.monaco.getTextArea(); + await textArea.pressKeys([Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], 'v']); + }, + copyRequestsToClipboard: async () => { + const textArea = await this.monaco.getTextArea(); + await textArea.pressKeys([Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], 'a']); + await textArea.pressKeys([Key[process.platform === 'darwin' ? 'COMMAND' : 'CONTROL'], 'c']); + }, + isA11yOverlayVisible: async () => { + return await this.testSubjects.exists('codeEditorAccessibilityOverlay'); + }, + }; + public async getVisibleTextFromAceEditor(editor: WebElementWrapper) { const lines = await editor.findAllByClassName('ace_line_group'); const linesText = await Promise.all(lines.map(async (line) => await line.getVisibleText())); diff --git a/test/functional/services/data_grid.ts b/test/functional/services/data_grid.ts index 18282442696ff..cb225c45d1ea0 100644 --- a/test/functional/services/data_grid.ts +++ b/test/functional/services/data_grid.ts @@ -268,7 +268,7 @@ export class DataGridService extends FtrService { } public async getDetailsRows(): Promise { - return await this.testSubjects.findAll('docTableDetailsFlyout'); + return await this.testSubjects.findAll('docViewerFlyout'); } public async closeFlyout() { @@ -452,17 +452,30 @@ export class DataGridService extends FtrService { return await tableDocViewRow.findByTestSubject(`~removeInclusiveFilterButton`); } + public async showFieldCellActionInFlyout(fieldName: string, actionName: string): Promise { + const cellSelector = ['addFilterForValueButton', 'addFilterOutValueButton'].includes(actionName) + ? `tableDocViewRow-${fieldName}-value` + : `tableDocViewRow-${fieldName}-name`; + await this.testSubjects.click(cellSelector); + await this.retry.waitFor('grid cell actions to appear', async () => { + return this.testSubjects.exists(`${actionName}-${fieldName}`); + }); + } + public async clickFieldActionInFlyout(fieldName: string, actionName: string): Promise { - const openPopoverButtonSelector = `openFieldActionsButton-${fieldName}`; - const inlineButtonsGroupSelector = `fieldActionsGroup-${fieldName}`; - if (await this.testSubjects.exists(openPopoverButtonSelector)) { - await this.testSubjects.click(openPopoverButtonSelector); - } else { - await this.testSubjects.existOrFail(inlineButtonsGroupSelector); - } + await this.showFieldCellActionInFlyout(fieldName, actionName); await this.testSubjects.click(`${actionName}-${fieldName}`); } + public async expandFieldNameCellInFlyout(fieldName: string): Promise { + const buttonSelector = 'euiDataGridCellExpandButton'; + await this.testSubjects.click(`tableDocViewRow-${fieldName}-name`); + await this.retry.waitFor('grid cell actions to appear', async () => { + return this.testSubjects.exists(buttonSelector); + }); + await this.testSubjects.click(buttonSelector); + } + public async hasNoResults() { return await this.find.existsByCssSelector('.euiDataGrid__noResults'); } diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 7ccbc90d8760e..5a16b46a366e2 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -126,6 +126,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'data_views.scriptedFieldsEnabled (any)', // It's a boolean (any because schema.conditional) 'data_visualizer.resultLinks.fileBeat.enabled (boolean)', 'dev_tools.deeplinks.navLinkStatus (string)', + 'discover.experimental.enabledProfiles (array)', 'enterpriseSearch.canDeployEntSearch (boolean)', 'enterpriseSearch.host (string)', 'enterpriseSearch.ui.enabled (boolean)', @@ -313,6 +314,8 @@ export default function ({ getService }: PluginFunctionalProviderContext) { // 'xpack.reporting.poll.jobsRefresh.intervalErrorMultiplier (number)', 'xpack.rollup.ui.enabled (boolean)', 'xpack.saved_object_tagging.cache_refresh_interval (duration)', + 'xpack.search.homepage.ui.enabled (boolean)', + 'xpack.searchInferenceEndpoints.ui.enabled (boolean)', 'xpack.searchPlayground.ui.enabled (boolean)', 'xpack.security.loginAssistanceMessage (string)', 'xpack.security.sameSiteCookies (alternatives)', diff --git a/test/tsconfig.json b/test/tsconfig.json index 5028acbc0d830..0f120b831abe7 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -61,7 +61,6 @@ "@kbn/core-application-browser", "@kbn/screenshot-mode-plugin", "@kbn/dev-utils", - "@kbn/analytics-client", "@kbn/utility-types", "@kbn/dev-proc-runner", "@kbn/enterprise-search-plugin", @@ -73,5 +72,6 @@ "@kbn/ftr-common-functional-ui-services", "@kbn/monaco", "@kbn/search-types", + "@kbn/console-plugin", ] } diff --git a/tsconfig.base.json b/tsconfig.base.json index 31f05377e4d4c..0f8d9a11563e0 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -64,22 +64,12 @@ "@kbn/ambient-ui-types/*": ["packages/kbn-ambient-ui-types/*"], "@kbn/analytics": ["packages/kbn-analytics"], "@kbn/analytics/*": ["packages/kbn-analytics/*"], - "@kbn/analytics-client": ["packages/analytics/client"], - "@kbn/analytics-client/*": ["packages/analytics/client/*"], "@kbn/analytics-collection-utils": ["packages/analytics/utils/analytics_collection_utils"], "@kbn/analytics-collection-utils/*": ["packages/analytics/utils/analytics_collection_utils/*"], "@kbn/analytics-ftr-helpers-plugin": ["test/analytics/plugins/analytics_ftr_helpers"], "@kbn/analytics-ftr-helpers-plugin/*": ["test/analytics/plugins/analytics_ftr_helpers/*"], "@kbn/analytics-plugin-a-plugin": ["test/analytics/plugins/analytics_plugin_a"], "@kbn/analytics-plugin-a-plugin/*": ["test/analytics/plugins/analytics_plugin_a/*"], - "@kbn/analytics-shippers-elastic-v3-browser": ["packages/analytics/shippers/elastic_v3/browser"], - "@kbn/analytics-shippers-elastic-v3-browser/*": ["packages/analytics/shippers/elastic_v3/browser/*"], - "@kbn/analytics-shippers-elastic-v3-common": ["packages/analytics/shippers/elastic_v3/common"], - "@kbn/analytics-shippers-elastic-v3-common/*": ["packages/analytics/shippers/elastic_v3/common/*"], - "@kbn/analytics-shippers-elastic-v3-server": ["packages/analytics/shippers/elastic_v3/server"], - "@kbn/analytics-shippers-elastic-v3-server/*": ["packages/analytics/shippers/elastic_v3/server/*"], - "@kbn/analytics-shippers-fullstory": ["packages/analytics/shippers/fullstory"], - "@kbn/analytics-shippers-fullstory/*": ["packages/analytics/shippers/fullstory/*"], "@kbn/apm-config-loader": ["packages/kbn-apm-config-loader"], "@kbn/apm-config-loader/*": ["packages/kbn-apm-config-loader/*"], "@kbn/apm-data-access-plugin": ["x-pack/plugins/observability_solution/apm_data_access"], @@ -206,6 +196,8 @@ "@kbn/content-management-table-list-view-common/*": ["packages/content-management/table_list_view_common/*"], "@kbn/content-management-table-list-view-table": ["packages/content-management/table_list_view_table"], "@kbn/content-management-table-list-view-table/*": ["packages/content-management/table_list_view_table/*"], + "@kbn/content-management-user-profiles": ["packages/content-management/user_profiles"], + "@kbn/content-management-user-profiles/*": ["packages/content-management/user_profiles/*"], "@kbn/content-management-utils": ["packages/kbn-content-management-utils"], "@kbn/content-management-utils/*": ["packages/kbn-content-management-utils/*"], "@kbn/controls-example-plugin": ["examples/controls_example"], @@ -750,6 +742,8 @@ "@kbn/docs-utils/*": ["packages/kbn-docs-utils/*"], "@kbn/dom-drag-drop": ["packages/kbn-dom-drag-drop"], "@kbn/dom-drag-drop/*": ["packages/kbn-dom-drag-drop/*"], + "@kbn/ebt": ["packages/analytics/ebt"], + "@kbn/ebt/*": ["packages/analytics/ebt/*"], "@kbn/ebt-tools": ["packages/kbn-ebt-tools"], "@kbn/ebt-tools/*": ["packages/kbn-ebt-tools/*"], "@kbn/ecs-data-quality-dashboard": ["x-pack/packages/security-solution/ecs_data_quality_dashboard"], @@ -1000,6 +994,8 @@ "@kbn/input-control-vis-plugin/*": ["src/plugins/input_control_vis/*"], "@kbn/inspector-plugin": ["src/plugins/inspector"], "@kbn/inspector-plugin/*": ["src/plugins/inspector/*"], + "@kbn/integration-assistant-plugin": ["x-pack/plugins/integration_assistant"], + "@kbn/integration-assistant-plugin/*": ["x-pack/plugins/integration_assistant/*"], "@kbn/interactive-setup-plugin": ["src/plugins/interactive_setup"], "@kbn/interactive-setup-plugin/*": ["src/plugins/interactive_setup/*"], "@kbn/interactive-setup-test-endpoints-plugin": ["test/interactive_setup_api_integration/plugins/test_endpoints"], @@ -1242,6 +1238,8 @@ "@kbn/open-telemetry-instrumented-plugin/*": ["test/common/plugins/otel_metrics/*"], "@kbn/openapi-bundler": ["packages/kbn-openapi-bundler"], "@kbn/openapi-bundler/*": ["packages/kbn-openapi-bundler/*"], + "@kbn/openapi-common": ["packages/kbn-openapi-common"], + "@kbn/openapi-common/*": ["packages/kbn-openapi-common/*"], "@kbn/openapi-generator": ["packages/kbn-openapi-generator"], "@kbn/openapi-generator/*": ["packages/kbn-openapi-generator/*"], "@kbn/optimizer": ["packages/kbn-optimizer"], @@ -1356,6 +1354,8 @@ "@kbn/resizable-layout-examples-plugin/*": ["examples/resizable_layout_examples/*"], "@kbn/resolver-test-plugin": ["x-pack/test/plugin_functional/plugins/resolver_test"], "@kbn/resolver-test-plugin/*": ["x-pack/test/plugin_functional/plugins/resolver_test/*"], + "@kbn/response-ops-feature-flag-service": ["packages/response-ops/feature_flag_service"], + "@kbn/response-ops-feature-flag-service/*": ["packages/response-ops/feature_flag_service/*"], "@kbn/response-stream-plugin": ["examples/response_stream"], "@kbn/response-stream-plugin/*": ["examples/response_stream/*"], "@kbn/rison": ["packages/kbn-rison"], @@ -1382,6 +1382,8 @@ "@kbn/saml-provider-plugin/*": ["x-pack/test/security_api_integration/plugins/saml_provider/*"], "@kbn/sample-task-plugin": ["x-pack/test/plugin_api_integration/plugins/sample_task_plugin"], "@kbn/sample-task-plugin/*": ["x-pack/test/plugin_api_integration/plugins/sample_task_plugin/*"], + "@kbn/sample-task-plugin-mget": ["x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget"], + "@kbn/sample-task-plugin-mget/*": ["x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget/*"], "@kbn/saved-object-export-transforms-plugin": ["test/plugin_functional/plugins/saved_object_export_transforms"], "@kbn/saved-object-export-transforms-plugin/*": ["test/plugin_functional/plugins/saved_object_export_transforms/*"], "@kbn/saved-object-import-warnings-plugin": ["test/plugin_functional/plugins/saved_object_import_warnings"], @@ -1424,8 +1426,12 @@ "@kbn/search-errors/*": ["packages/kbn-search-errors/*"], "@kbn/search-examples-plugin": ["examples/search_examples"], "@kbn/search-examples-plugin/*": ["examples/search_examples/*"], + "@kbn/search-homepage": ["x-pack/plugins/search_homepage"], + "@kbn/search-homepage/*": ["x-pack/plugins/search_homepage/*"], "@kbn/search-index-documents": ["packages/kbn-search-index-documents"], "@kbn/search-index-documents/*": ["packages/kbn-search-index-documents/*"], + "@kbn/search-inference-endpoints": ["x-pack/plugins/search_inference_endpoints"], + "@kbn/search-inference-endpoints/*": ["x-pack/plugins/search_inference_endpoints/*"], "@kbn/search-notebooks": ["x-pack/plugins/search_notebooks"], "@kbn/search-notebooks/*": ["x-pack/plugins/search_notebooks/*"], "@kbn/search-playground": ["x-pack/plugins/search_playground"], @@ -1496,6 +1502,8 @@ "@kbn/securitysolution-list-hooks/*": ["packages/kbn-securitysolution-list-hooks/*"], "@kbn/securitysolution-list-utils": ["packages/kbn-securitysolution-list-utils"], "@kbn/securitysolution-list-utils/*": ["packages/kbn-securitysolution-list-utils/*"], + "@kbn/securitysolution-lists-common": ["packages/kbn-securitysolution-lists-common"], + "@kbn/securitysolution-lists-common/*": ["packages/kbn-securitysolution-lists-common/*"], "@kbn/securitysolution-rules": ["packages/kbn-securitysolution-rules"], "@kbn/securitysolution-rules/*": ["packages/kbn-securitysolution-rules/*"], "@kbn/securitysolution-t-grid": ["packages/kbn-securitysolution-t-grid"], diff --git a/versions.json b/versions.json index b789d4463f918..f43e07cfdf2e2 100644 --- a/versions.json +++ b/versions.json @@ -8,19 +8,13 @@ "currentMinor": true }, { - "version": "8.14.1", + "version": "8.14.2", "branch": "8.14", "currentMajor": true, "previousMinor": true }, { - "version": "8.13.5", - "branch": "8.13", - "currentMajor": true, - "previousMinor": true - }, - { - "version": "7.17.22", + "version": "7.17.23", "branch": "7.17", "previousMajor": true } diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 8059502946861..1f224ca164e52 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -92,8 +92,10 @@ "xpack.rollupJobs": ["plugins/rollup"], "xpack.runtimeFields": "plugins/runtime_fields", "xpack.screenshotting": "plugins/screenshotting", + "xpack.searchHomepage": "plugins/search_homepage", "xpack.searchNotebooks": "plugins/search_notebooks", "xpack.searchPlayground": "plugins/search_playground", + "xpack.searchInferenceEndpoints": "plugins/search_inference_endpoints", "xpack.searchProfiler": "plugins/searchprofiler", "xpack.security": "plugins/security", "xpack.server": "legacy/server", diff --git a/x-pack/packages/kbn-elastic-assistant-common/constants.ts b/x-pack/packages/kbn-elastic-assistant-common/constants.ts index f30cb053d4ce1..74da6ab2476e2 100755 --- a/x-pack/packages/kbn-elastic-assistant-common/constants.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/constants.ts @@ -27,5 +27,6 @@ export const ELASTIC_AI_ASSISTANT_ANONYMIZATION_FIELDS_URL_FIND = `${ELASTIC_AI_ // TODO: Update existing 'status' endpoint to take resource as query param as to not conflict with 'entries' export const ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_URL = `${ELASTIC_AI_ASSISTANT_INTERNAL_URL}/knowledge_base/{resource?}`; -export const ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL = `${ELASTIC_AI_ASSISTANT_URL}/knowledge_base/entries`; -export const ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_BULK_ACTION = `${ELASTIC_AI_ASSISTANT_URL}/knowledge_base/_bulk_action`; +export const ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL = `${ELASTIC_AI_ASSISTANT_INTERNAL_URL}/knowledge_base/entries`; +export const ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_FIND = `${ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL}/_find`; +export const ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_BULK_ACTION = `${ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL}/_bulk_action`; diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/capabilities/index.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/capabilities/index.ts index 9c734cc4b3c13..c1c101fd74cd8 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/capabilities/index.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/capabilities/index.ts @@ -10,6 +10,11 @@ */ export type AssistantFeatures = { [K in keyof typeof defaultAssistantFeatures]: boolean }; +/** + * Type for keys of the assistant features + */ +export type AssistantFeatureKey = keyof AssistantFeatures; + /** * Default features available to the elastic assistant */ diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/anonymization_fields/find_anonymization_fields_route.gen.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/anonymization_fields/find_anonymization_fields_route.gen.ts index b51ea84170643..486df4547b429 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/anonymization_fields/find_anonymization_fields_route.gen.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/anonymization_fields/find_anonymization_fields_route.gen.ts @@ -17,6 +17,7 @@ import { z } from 'zod'; import { ArrayFromString } from '@kbn/zod-helpers'; +import { SortOrder } from '../common_attributes.gen'; import { AnonymizationFieldResponse } from './bulk_crud_anonymization_fields_route.gen'; export type FindAnonymizationFieldsSortField = z.infer; @@ -30,11 +31,6 @@ export const FindAnonymizationFieldsSortField = z.enum([ export type FindAnonymizationFieldsSortFieldEnum = typeof FindAnonymizationFieldsSortField.enum; export const FindAnonymizationFieldsSortFieldEnum = FindAnonymizationFieldsSortField.enum; -export type SortOrder = z.infer; -export const SortOrder = z.enum(['asc', 'desc']); -export type SortOrderEnum = typeof SortOrder.enum; -export const SortOrderEnum = SortOrder.enum; - export type FindAnonymizationFieldsRequestQuery = z.infer< typeof FindAnonymizationFieldsRequestQuery >; diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/anonymization_fields/find_anonymization_fields_route.schema.yaml b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/anonymization_fields/find_anonymization_fields_route.schema.yaml index b9b2d1e9e2097..3541c3a1c649b 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/anonymization_fields/find_anonymization_fields_route.schema.yaml +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/anonymization_fields/find_anonymization_fields_route.schema.yaml @@ -36,7 +36,7 @@ paths: description: Sort order required: false schema: - $ref: '#/components/schemas/SortOrder' + $ref: '../common_attributes.schema.yaml#/components/schemas/SortOrder' - name: 'page' in: query description: Page number @@ -101,9 +101,3 @@ components: - 'allowed' - 'field' - 'updated_at' - - SortOrder: - type: string - enum: - - 'asc' - - 'desc' diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts index d4d4ce5657f62..613d54fa080fb 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/common_attributes.gen.ts @@ -45,3 +45,8 @@ export const User = z.object({ */ name: z.string().optional(), }); + +export type SortOrder = z.infer; +export const SortOrder = z.enum(['asc', 'desc']); +export type SortOrderEnum = typeof SortOrder.enum; +export const SortOrderEnum = SortOrder.enum; diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/common_attributes.schema.yaml b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/common_attributes.schema.yaml index 5c580c52281ad..348868746fb6c 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/common_attributes.schema.yaml +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/common_attributes.schema.yaml @@ -28,3 +28,9 @@ components: type: string description: User name + SortOrder: + type: string + enum: + - 'asc' + - 'desc' + diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.gen.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.gen.ts index 556dca3db8214..6f8607640e262 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.gen.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.gen.ts @@ -17,6 +17,7 @@ import { z } from 'zod'; import { ArrayFromString } from '@kbn/zod-helpers'; +import { SortOrder } from '../common_attributes.gen'; import { ConversationResponse } from './common_attributes.gen'; export type FindConversationsSortField = z.infer; @@ -29,11 +30,6 @@ export const FindConversationsSortField = z.enum([ export type FindConversationsSortFieldEnum = typeof FindConversationsSortField.enum; export const FindConversationsSortFieldEnum = FindConversationsSortField.enum; -export type SortOrder = z.infer; -export const SortOrder = z.enum(['asc', 'desc']); -export type SortOrderEnum = typeof SortOrder.enum; -export const SortOrderEnum = SortOrder.enum; - export type FindConversationsRequestQuery = z.infer; export const FindConversationsRequestQuery = z.object({ fields: ArrayFromString(z.string()).optional(), diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.schema.yaml b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.schema.yaml index 44cec1a169e51..fcb4c0a013eaa 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.schema.yaml +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/conversations/find_conversations_route.schema.yaml @@ -36,7 +36,7 @@ paths: description: Sort order required: false schema: - $ref: '#/components/schemas/SortOrder' + $ref: '../common_attributes.schema.yaml#/components/schemas/SortOrder' - name: 'page' in: query description: Page number @@ -124,7 +124,7 @@ paths: description: Sort order required: false schema: - $ref: '#/components/schemas/SortOrder' + $ref: '../common_attributes.schema.yaml#/components/schemas/SortOrder' - name: 'page' in: query description: Page number @@ -188,9 +188,3 @@ components: - 'is_default' - 'title' - 'updated_at' - - SortOrder: - type: string - enum: - - 'asc' - - 'desc' diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/index.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/index.ts index c9c2d2a8be3c0..ae66432af3076 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/index.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/index.ts @@ -45,3 +45,4 @@ export * from './knowledge_base/crud_kb_route.gen'; export * from './knowledge_base/bulk_crud_knowledge_base_route.gen'; export * from './knowledge_base/common_attributes.gen'; export * from './knowledge_base/crud_knowledge_base_route.gen'; +export * from './knowledge_base/find_knowledge_base_entries_route.gen'; diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/common_attributes.gen.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/common_attributes.gen.ts index 09e24b3ab18d3..d1cc9b57460c2 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/common_attributes.gen.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/common_attributes.gen.ts @@ -33,11 +33,11 @@ export const KnowledgeBaseEntryErrorSchema = z export type Metadata = z.infer; export const Metadata = z.object({ /** - * Knowledge Base resource name + * Knowledge Base resource name for grouping entries, e.g. 'esql', 'lens-docs', etc */ kbResource: z.string(), /** - * Original text content source + * Source document name or filepath */ source: z.string(), /** diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/common_attributes.schema.yaml b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/common_attributes.schema.yaml index a9a9794852953..53c69426b69bd 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/common_attributes.schema.yaml +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/common_attributes.schema.yaml @@ -32,10 +32,10 @@ components: properties: kbResource: type: string - description: Knowledge Base resource name + description: Knowledge Base resource name for grouping entries, e.g. 'esql', 'lens-docs', etc source: type: string - description: Original text content source + description: Source document name or filepath required: type: boolean description: Whether or not this resource should always be included @@ -121,4 +121,3 @@ components: type: string description: Knowledge Base Entry content - diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/find_knowledge_base_entries_route.gen.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/find_knowledge_base_entries_route.gen.ts new file mode 100644 index 0000000000000..25db35693c3dc --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/find_knowledge_base_entries_route.gen.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Find Knowledge Base Entries API endpoint + * version: 1 + */ + +import { z } from 'zod'; +import { ArrayFromString } from '@kbn/zod-helpers'; + +import { SortOrder } from '../common_attributes.gen'; +import { KnowledgeBaseEntryResponse } from './common_attributes.gen'; + +export type FindKnowledgeBaseEntriesSortField = z.infer; +export const FindKnowledgeBaseEntriesSortField = z.enum([ + 'created_at', + 'is_default', + 'title', + 'updated_at', +]); +export type FindKnowledgeBaseEntriesSortFieldEnum = typeof FindKnowledgeBaseEntriesSortField.enum; +export const FindKnowledgeBaseEntriesSortFieldEnum = FindKnowledgeBaseEntriesSortField.enum; + +export type FindKnowledgeBaseEntriesRequestQuery = z.infer< + typeof FindKnowledgeBaseEntriesRequestQuery +>; +export const FindKnowledgeBaseEntriesRequestQuery = z.object({ + fields: ArrayFromString(z.string()).optional(), + /** + * Search query + */ + filter: z.string().optional(), + /** + * Field to sort by + */ + sort_field: FindKnowledgeBaseEntriesSortField.optional(), + /** + * Sort order + */ + sort_order: SortOrder.optional(), + /** + * Page number + */ + page: z.coerce.number().int().min(1).optional().default(1), + /** + * Knowledge Base Entries per page + */ + per_page: z.coerce.number().int().min(0).optional().default(20), +}); +export type FindKnowledgeBaseEntriesRequestQueryInput = z.input< + typeof FindKnowledgeBaseEntriesRequestQuery +>; + +export type FindKnowledgeBaseEntriesResponse = z.infer; +export const FindKnowledgeBaseEntriesResponse = z.object({ + page: z.number().int(), + perPage: z.number().int(), + total: z.number().int(), + data: z.array(KnowledgeBaseEntryResponse), +}); diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/find_knowledge_base_entries_route.schema.yaml b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/find_knowledge_base_entries_route.schema.yaml new file mode 100644 index 0000000000000..d5298ff2ccbdc --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/knowledge_base/find_knowledge_base_entries_route.schema.yaml @@ -0,0 +1,102 @@ +openapi: 3.0.0 +info: + title: Find Knowledge Base Entries API endpoint + version: '1' +paths: + /internal/elastic_assistant/knowledge_base/entries/_find: + get: + operationId: FindKnowledgeBaseEntries + x-codegen-enabled: true + description: Finds Knowledge Base Entries that match the given query. + summary: Finds Knowledge Base Entries that match the given query. + tags: + - Knowledge Base Entries 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/FindKnowledgeBaseEntriesSortField' + - name: 'sort_order' + in: query + description: Sort order + required: false + schema: + $ref: '../common_attributes.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: Knowledge Base Entries 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: './common_attributes.schema.yaml#/components/schemas/KnowledgeBaseEntryResponse' + required: + - page + - perPage + - total + - data + 400: + description: Generic Error + content: + application/json: + schema: + type: object + properties: + statusCode: + type: number + error: + type: string + message: + type: string + +components: + schemas: + FindKnowledgeBaseEntriesSortField: + type: string + enum: + - 'created_at' + - 'is_default' + - 'title' + - 'updated_at' diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/prompts/find_prompts_route.gen.ts b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/prompts/find_prompts_route.gen.ts index bd050f5c8260d..49f4c75029581 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/prompts/find_prompts_route.gen.ts +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/prompts/find_prompts_route.gen.ts @@ -17,6 +17,7 @@ import { z } from 'zod'; import { ArrayFromString } from '@kbn/zod-helpers'; +import { SortOrder } from '../common_attributes.gen'; import { PromptResponse } from './bulk_crud_prompts_route.gen'; export type FindPromptsSortField = z.infer; @@ -24,11 +25,6 @@ export const FindPromptsSortField = z.enum(['created_at', 'is_default', 'name', export type FindPromptsSortFieldEnum = typeof FindPromptsSortField.enum; export const FindPromptsSortFieldEnum = FindPromptsSortField.enum; -export type SortOrder = z.infer; -export const SortOrder = z.enum(['asc', 'desc']); -export type SortOrderEnum = typeof SortOrder.enum; -export const SortOrderEnum = SortOrder.enum; - export type FindPromptsRequestQuery = z.infer; export const FindPromptsRequestQuery = z.object({ fields: ArrayFromString(z.string()).optional(), diff --git a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/prompts/find_prompts_route.schema.yaml b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/prompts/find_prompts_route.schema.yaml index 8e85194811dbc..1902f4e9ae3d9 100644 --- a/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/prompts/find_prompts_route.schema.yaml +++ b/x-pack/packages/kbn-elastic-assistant-common/impl/schemas/prompts/find_prompts_route.schema.yaml @@ -36,7 +36,7 @@ paths: description: Sort order required: false schema: - $ref: '#/components/schemas/SortOrder' + $ref: '../common_attributes.schema.yaml#/components/schemas/SortOrder' - name: 'page' in: query description: Page number @@ -100,9 +100,3 @@ components: - 'is_default' - 'name' - 'updated_at' - - SortOrder: - type: string - enum: - - 'asc' - - 'desc' diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/index.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/index.test.tsx index c1c6f8e6a67aa..0e89bc7f5738a 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/index.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/index.test.tsx @@ -9,13 +9,7 @@ import { HttpSetup } from '@kbn/core-http-browser'; import { ApiConfig } from '@kbn/elastic-assistant-common'; import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/public/common'; -import { - deleteKnowledgeBase, - fetchConnectorExecuteAction, - FetchConnectorExecuteAction, - getKnowledgeBaseStatus, - postKnowledgeBase, -} from '.'; +import { fetchConnectorExecuteAction, FetchConnectorExecuteAction } from '.'; import { API_ERROR } from '../translations'; jest.mock('@kbn/core-http-browser'); @@ -134,7 +128,7 @@ describe('API tests', () => { ); }); - it('calls the non-stream API when assistantStreamingEnabled is true and actionTypeId is gemini and isEnabledKnowledgeBase is true', async () => { + it('calls the stream API when assistantStreamingEnabled is true and actionTypeId is gemini and isEnabledKnowledgeBase is true', async () => { const testProps: FetchConnectorExecuteAction = { ...fetchConnectorArgs, apiConfig: apiConfig.gemini, @@ -145,13 +139,13 @@ describe('API tests', () => { expect(mockHttp.fetch).toHaveBeenCalledWith( '/internal/elastic_assistant/actions/connector/foo/_execute', { - ...staticDefaults, - body: '{"message":"This is a test","subAction":"invokeAI","conversationId":"test","actionTypeId":".gemini","replacements":{},"isEnabledKnowledgeBase":true,"isEnabledRAGAlerts":false}', + ...streamingDefaults, + body: '{"message":"This is a test","subAction":"invokeStream","conversationId":"test","actionTypeId":".gemini","replacements":{},"isEnabledKnowledgeBase":true,"isEnabledRAGAlerts":false}', } ); }); - it('calls the non-stream API when assistantStreamingEnabled is true and actionTypeId is gemini and isEnabledKnowledgeBase is false and isEnabledRAGAlerts is true', async () => { + it('calls the stream API when assistantStreamingEnabled is true and actionTypeId is gemini and isEnabledKnowledgeBase is false and isEnabledRAGAlerts is true', async () => { const testProps: FetchConnectorExecuteAction = { ...fetchConnectorArgs, apiConfig: apiConfig.gemini, @@ -164,8 +158,8 @@ describe('API tests', () => { expect(mockHttp.fetch).toHaveBeenCalledWith( '/internal/elastic_assistant/actions/connector/foo/_execute', { - ...staticDefaults, - body: '{"message":"This is a test","subAction":"invokeAI","conversationId":"test","actionTypeId":".gemini","replacements":{},"isEnabledKnowledgeBase":false,"isEnabledRAGAlerts":true}', + ...streamingDefaults, + body: '{"message":"This is a test","subAction":"invokeStream","conversationId":"test","actionTypeId":".gemini","replacements":{},"isEnabledKnowledgeBase":false,"isEnabledRAGAlerts":true}', } ); }); @@ -303,79 +297,4 @@ describe('API tests', () => { expect(result).toEqual({ response, isStream: false, isError: false }); }); }); - - const knowledgeBaseArgs = { - resource: 'a-resource', - http: mockHttp, - }; - describe('getKnowledgeBaseStatus', () => { - it('calls the knowledge base API when correct resource path', async () => { - await getKnowledgeBaseStatus(knowledgeBaseArgs); - - expect(mockHttp.fetch).toHaveBeenCalledWith( - '/internal/elastic_assistant/knowledge_base/a-resource', - { - method: 'GET', - signal: undefined, - version: '1', - } - ); - }); - it('returns error when error is an error', async () => { - const error = 'simulated error'; - (mockHttp.fetch as jest.Mock).mockImplementation(() => { - throw new Error(error); - }); - - await expect(getKnowledgeBaseStatus(knowledgeBaseArgs)).resolves.toThrowError( - 'simulated error' - ); - }); - }); - - describe('postKnowledgeBase', () => { - it('calls the knowledge base API when correct resource path', async () => { - await postKnowledgeBase(knowledgeBaseArgs); - - expect(mockHttp.fetch).toHaveBeenCalledWith( - '/internal/elastic_assistant/knowledge_base/a-resource', - { - method: 'POST', - signal: undefined, - version: '1', - } - ); - }); - it('returns error when error is an error', async () => { - const error = 'simulated error'; - (mockHttp.fetch as jest.Mock).mockImplementation(() => { - throw new Error(error); - }); - - await expect(postKnowledgeBase(knowledgeBaseArgs)).resolves.toThrowError('simulated error'); - }); - }); - - describe('deleteKnowledgeBase', () => { - it('calls the knowledge base API when correct resource path', async () => { - await deleteKnowledgeBase(knowledgeBaseArgs); - - expect(mockHttp.fetch).toHaveBeenCalledWith( - '/internal/elastic_assistant/knowledge_base/a-resource', - { - method: 'DELETE', - signal: undefined, - version: '1', - } - ); - }); - it('returns error when error is an error', async () => { - const error = 'simulated error'; - (mockHttp.fetch as jest.Mock).mockImplementation(() => { - throw new Error(error); - }); - - await expect(deleteKnowledgeBase(knowledgeBaseArgs)).resolves.toThrowError('simulated error'); - }); - }); }); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/index.tsx index fa12841a96152..b8c42a787621b 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/index.tsx @@ -6,19 +6,7 @@ */ import { HttpSetup } from '@kbn/core/public'; -import { IHttpFetchError } from '@kbn/core-http-browser'; -import { - API_VERSIONS, - ApiConfig, - CreateKnowledgeBaseRequestParams, - CreateKnowledgeBaseResponse, - DeleteKnowledgeBaseRequestParams, - DeleteKnowledgeBaseResponse, - ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_URL, - ReadKnowledgeBaseRequestParams, - ReadKnowledgeBaseResponse, - Replacements, -} from '@kbn/elastic-assistant-common'; +import { API_VERSIONS, ApiConfig, Replacements } from '@kbn/elastic-assistant-common'; import { API_ERROR } from '../translations'; import { getOptionalRequestParams } from '../helpers'; import { TraceOptions } from '../types'; @@ -64,13 +52,7 @@ export const fetchConnectorExecuteAction = async ({ traceOptions, }: FetchConnectorExecuteAction): Promise => { // TODO add streaming support for gemini with langchain on - const isStream = - assistantStreamingEnabled && - (apiConfig.actionTypeId === '.gen-ai' || - apiConfig.actionTypeId === '.bedrock' || - // TODO add streaming support for gemini with langchain on - // tracked here: https://github.com/elastic/security-team/issues/7363 - (apiConfig.actionTypeId === '.gemini' && !isEnabledRAGAlerts && !isEnabledKnowledgeBase)); + const isStream = assistantStreamingEnabled; const optionalRequestParams = getOptionalRequestParams({ isEnabledRAGAlerts, @@ -191,99 +173,3 @@ export const fetchConnectorExecuteAction = async ({ }; } }; - -/** - * API call for getting the status of the Knowledge Base. Provide - * a resource to include the status of that specific resource. - * - * @param {Object} options - The options object. - * @param {HttpSetup} options.http - HttpSetup - * @param {string} [options.resource] - Resource to get the status of, otherwise status of overall KB - * @param {AbortSignal} [options.signal] - AbortSignal - * - * @returns {Promise} - */ -export const getKnowledgeBaseStatus = async ({ - http, - resource, - signal, -}: ReadKnowledgeBaseRequestParams & { http: HttpSetup; signal?: AbortSignal | undefined }): Promise< - ReadKnowledgeBaseResponse | IHttpFetchError -> => { - try { - const path = ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_URL.replace('{resource?}', resource || ''); - const response = await http.fetch(path, { - method: 'GET', - signal, - version: API_VERSIONS.internal.v1, - }); - - return response as ReadKnowledgeBaseResponse; - } catch (error) { - return error as IHttpFetchError; - } -}; - -/** - * API call for setting up the Knowledge Base. Provide a resource to set up a specific resource. - * - * @param {Object} options - The options object. - * @param {HttpSetup} options.http - HttpSetup - * @param {string} [options.resource] - Resource to be added to the KB, otherwise sets up the base KB - * @param {AbortSignal} [options.signal] - AbortSignal - * - * @returns {Promise} - */ -export const postKnowledgeBase = async ({ - http, - resource, - signal, -}: CreateKnowledgeBaseRequestParams & { - http: HttpSetup; - signal?: AbortSignal | undefined; -}): Promise => { - try { - const path = ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_URL.replace('{resource?}', resource || ''); - const response = await http.fetch(path, { - method: 'POST', - signal, - version: API_VERSIONS.internal.v1, - }); - - return response as CreateKnowledgeBaseResponse; - } catch (error) { - return error as IHttpFetchError; - } -}; - -/** - * API call for deleting the Knowledge Base. Provide a resource to delete that specific resource. - * - * @param {Object} options - The options object. - * @param {HttpSetup} options.http - HttpSetup - * @param {string} [options.resource] - Resource to be deleted from the KB, otherwise delete the entire KB - * @param {AbortSignal} [options.signal] - AbortSignal - * - * @returns {Promise} - */ -export const deleteKnowledgeBase = async ({ - http, - resource, - signal, -}: DeleteKnowledgeBaseRequestParams & { - http: HttpSetup; - signal?: AbortSignal | undefined; -}): Promise => { - try { - const path = ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_URL.replace('{resource?}', resource || ''); - const response = await http.fetch(path, { - method: 'DELETE', - signal, - version: API_VERSIONS.internal.v1, - }); - - return response as DeleteKnowledgeBaseResponse; - } catch (error) { - return error as IHttpFetchError; - } -}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/api.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/api.test.tsx new file mode 100644 index 0000000000000..06ba7d875b64f --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/api.test.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { HttpSetup } from '@kbn/core-http-browser'; + +import { deleteKnowledgeBase, getKnowledgeBaseStatus, postKnowledgeBase } from './api'; + +jest.mock('@kbn/core-http-browser'); + +const mockHttp = { + fetch: jest.fn(), +} as unknown as HttpSetup; + +describe('API tests', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const knowledgeBaseArgs = { + resource: 'a-resource', + http: mockHttp, + }; + describe('getKnowledgeBaseStatus', () => { + it('calls the knowledge base API when correct resource path', async () => { + await getKnowledgeBaseStatus(knowledgeBaseArgs); + + expect(mockHttp.fetch).toHaveBeenCalledWith( + '/internal/elastic_assistant/knowledge_base/a-resource', + { + method: 'GET', + signal: undefined, + version: '1', + } + ); + }); + it('returns error when error is an error', async () => { + const error = 'simulated error'; + (mockHttp.fetch as jest.Mock).mockImplementation(() => { + throw new Error(error); + }); + + await expect(getKnowledgeBaseStatus(knowledgeBaseArgs)).resolves.toThrowError( + 'simulated error' + ); + }); + }); + + describe('postKnowledgeBase', () => { + it('calls the knowledge base API when correct resource path', async () => { + await postKnowledgeBase(knowledgeBaseArgs); + + expect(mockHttp.fetch).toHaveBeenCalledWith( + '/internal/elastic_assistant/knowledge_base/a-resource', + { + method: 'POST', + signal: undefined, + version: '1', + } + ); + }); + it('returns error when error is an error', async () => { + const error = 'simulated error'; + (mockHttp.fetch as jest.Mock).mockImplementation(() => { + throw new Error(error); + }); + + await expect(postKnowledgeBase(knowledgeBaseArgs)).resolves.toThrowError('simulated error'); + }); + }); + + describe('deleteKnowledgeBase', () => { + it('calls the knowledge base API when correct resource path', async () => { + await deleteKnowledgeBase(knowledgeBaseArgs); + + expect(mockHttp.fetch).toHaveBeenCalledWith( + '/internal/elastic_assistant/knowledge_base/a-resource', + { + method: 'DELETE', + signal: undefined, + version: '1', + } + ); + }); + it('returns error when error is an error', async () => { + const error = 'simulated error'; + (mockHttp.fetch as jest.Mock).mockImplementation(() => { + throw new Error(error); + }); + + await expect(deleteKnowledgeBase(knowledgeBaseArgs)).resolves.toThrowError('simulated error'); + }); + }); +}); diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/api.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/api.tsx new file mode 100644 index 0000000000000..65fa1f72064e1 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/api.tsx @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + API_VERSIONS, + CreateKnowledgeBaseRequestParams, + CreateKnowledgeBaseResponse, + DeleteKnowledgeBaseRequestParams, + DeleteKnowledgeBaseResponse, + ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_URL, + ReadKnowledgeBaseRequestParams, + ReadKnowledgeBaseResponse, +} from '@kbn/elastic-assistant-common'; +import { HttpSetup, IHttpFetchError } from '@kbn/core-http-browser'; + +/** + * API call for getting the status of the Knowledge Base. Provide + * a resource to include the status of that specific resource. + * + * @param {Object} options - The options object. + * @param {HttpSetup} options.http - HttpSetup + * @param {string} [options.resource] - Resource to get the status of, otherwise status of overall KB + * @param {AbortSignal} [options.signal] - AbortSignal + * + * @returns {Promise} + */ +export const getKnowledgeBaseStatus = async ({ + http, + resource, + signal, +}: ReadKnowledgeBaseRequestParams & { http: HttpSetup; signal?: AbortSignal | undefined }): Promise< + ReadKnowledgeBaseResponse | IHttpFetchError +> => { + try { + const path = ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_URL.replace('{resource?}', resource || ''); + const response = await http.fetch(path, { + method: 'GET', + signal, + version: API_VERSIONS.internal.v1, + }); + + return response as ReadKnowledgeBaseResponse; + } catch (error) { + return error as IHttpFetchError; + } +}; + +/** + * API call for setting up the Knowledge Base. Provide a resource to set up a specific resource. + * + * @param {Object} options - The options object. + * @param {HttpSetup} options.http - HttpSetup + * @param {string} [options.resource] - Resource to be added to the KB, otherwise sets up the base KB + * @param {AbortSignal} [options.signal] - AbortSignal + * + * @returns {Promise} + */ +export const postKnowledgeBase = async ({ + http, + resource, + signal, +}: CreateKnowledgeBaseRequestParams & { + http: HttpSetup; + signal?: AbortSignal | undefined; +}): Promise => { + try { + const path = ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_URL.replace('{resource?}', resource || ''); + const response = await http.fetch(path, { + method: 'POST', + signal, + version: API_VERSIONS.internal.v1, + }); + + return response as CreateKnowledgeBaseResponse; + } catch (error) { + return error as IHttpFetchError; + } +}; + +/** + * API call for deleting the Knowledge Base. Provide a resource to delete that specific resource. + * + * @param {Object} options - The options object. + * @param {HttpSetup} options.http - HttpSetup + * @param {string} [options.resource] - Resource to be deleted from the KB, otherwise delete the entire KB + * @param {AbortSignal} [options.signal] - AbortSignal + * + * @returns {Promise} + */ +export const deleteKnowledgeBase = async ({ + http, + resource, + signal, +}: DeleteKnowledgeBaseRequestParams & { + http: HttpSetup; + signal?: AbortSignal | undefined; +}): Promise => { + try { + const path = ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_URL.replace('{resource?}', resource || ''); + const response = await http.fetch(path, { + method: 'DELETE', + signal, + version: API_VERSIONS.internal.v1, + }); + + return response as DeleteKnowledgeBaseResponse; + } catch (error) { + return error as IHttpFetchError; + } +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/entries/use_create_knowledge_base_entry.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/entries/use_create_knowledge_base_entry.tsx new file mode 100644 index 0000000000000..eaf9a32fde81a --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/entries/use_create_knowledge_base_entry.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMutation } from '@tanstack/react-query'; +import type { HttpSetup, IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; +import type { IToasts } from '@kbn/core-notifications-browser'; +import { i18n } from '@kbn/i18n'; + +import { + API_VERSIONS, + ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL, + KnowledgeBaseEntryCreateProps, + KnowledgeBaseEntryResponse, +} from '@kbn/elastic-assistant-common'; +import { useInvalidateKnowledgeBaseEntries } from './use_knowledge_base_entries'; + +const CREATE_KNOWLEDGE_BASE_ENTRY_MUTATION_KEY = [ + ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL, + API_VERSIONS.internal.v1, +]; + +export interface UseCreateKnowledgeBaseEntryParams { + http: HttpSetup; + signal?: AbortSignal; + toasts?: IToasts; +} + +/** + * Hook for creating a Knowledge Base Entry + * + * @param {Object} options - The options object + * @param {HttpSetup} options.http - HttpSetup + * @param {AbortSignal} [options.signal] - AbortSignal + * @param {IToasts} [options.toasts] - IToasts + * + * @returns mutation hook for creating a Knowledge Base Entry + * + */ +export const useCreateKnowledgeBaseEntry = ({ + http, + signal, + toasts, +}: UseCreateKnowledgeBaseEntryParams) => { + const invalidateKnowledgeBaseEntries = useInvalidateKnowledgeBaseEntries(); + + return useMutation( + CREATE_KNOWLEDGE_BASE_ENTRY_MUTATION_KEY, + (entry: KnowledgeBaseEntryCreateProps) => { + return http.post( + ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL, + { + body: JSON.stringify(entry), + version: API_VERSIONS.internal.v1, + signal, + } + ); + }, + { + onError: (error: IHttpFetchError) => { + if (error.name !== 'AbortError') { + toasts?.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { + title: i18n.translate( + 'xpack.elasticAssistant.knowledgeBase.entries.createErrorTitle', + { + defaultMessage: 'Error creating Knowledge Base Entry', + } + ), + } + ); + } + }, + onSettled: () => { + invalidateKnowledgeBaseEntries(); + }, + } + ); +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/entries/use_delete_knowledge_base_entries.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/entries/use_delete_knowledge_base_entries.tsx new file mode 100644 index 0000000000000..0cfce8f576b24 --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/entries/use_delete_knowledge_base_entries.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useMutation } from '@tanstack/react-query'; +import type { HttpSetup, IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; +import type { IToasts } from '@kbn/core-notifications-browser'; +import { i18n } from '@kbn/i18n'; + +import { + API_VERSIONS, + ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_BULK_ACTION, + KnowledgeBaseEntryBulkActionBase, + KnowledgeBaseEntryBulkCrudActionResponse, + PerformKnowledgeBaseEntryBulkActionRequestBody, +} from '@kbn/elastic-assistant-common'; +import { useInvalidateKnowledgeBaseEntries } from './use_knowledge_base_entries'; + +const DELETE_KNOWLEDGE_BASE_ENTRIES_MUTATION_KEY = [ + ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_BULK_ACTION, + API_VERSIONS.internal.v1, +]; + +export interface UseDeleteKnowledgeEntriesParams { + http: HttpSetup; + signal?: AbortSignal; + toasts?: IToasts; +} + +/** + * Hook for deleting Knowledge Base Entries by id or query. + * + * @param {Object} options - The options object + * @param {HttpSetup} options.http - HttpSetup + * @param {AbortSignal} [options.signal] - AbortSignal + * @param {IToasts} [options.toasts] - IToasts + * + * @returns mutation hook for deleting Knowledge Base Entries + * + */ +export const useDeleteKnowledgeBaseEntries = ({ + http, + signal, + toasts, +}: UseDeleteKnowledgeEntriesParams) => { + const invalidateKnowledgeBaseEntries = useInvalidateKnowledgeBaseEntries(); + + return useMutation( + DELETE_KNOWLEDGE_BASE_ENTRIES_MUTATION_KEY, + ({ ids, query }: KnowledgeBaseEntryBulkActionBase) => { + const body: PerformKnowledgeBaseEntryBulkActionRequestBody = { + delete: { + query, + ids, + }, + }; + return http.post( + ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_BULK_ACTION, + { + body: JSON.stringify(body), + version: API_VERSIONS.internal.v1, + signal, + } + ); + }, + { + onError: (error: IHttpFetchError) => { + if (error.name !== 'AbortError') { + toasts?.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { + title: i18n.translate( + 'xpack.elasticAssistant.knowledgeBase.entries.deleteErrorTitle', + { + defaultMessage: 'Error deleting Knowledge Base Entries', + } + ), + } + ); + } + }, + onSettled: () => { + invalidateKnowledgeBaseEntries(); + }, + } + ); +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/entries/use_knowledge_base_entries.ts b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/entries/use_knowledge_base_entries.ts new file mode 100644 index 0000000000000..aa1d72d755ffa --- /dev/null +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/entries/use_knowledge_base_entries.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { HttpSetup } from '@kbn/core/public'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { + API_VERSIONS, + ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_FIND, + FindKnowledgeBaseEntriesResponse, +} from '@kbn/elastic-assistant-common'; + +import { useCallback } from 'react'; + +export interface UseKnowledgeBaseEntriesParams { + http: HttpSetup; + signal?: AbortSignal | undefined; +} + +/** + * Hook for fetching Knowledge Base Entries. + * + * Note: RBAC is handled at kbDataClient layer, so unless user has KB feature privileges, this will only return system and their own user KB entries. + * + * @param {Object} options - The options object. + * @param {HttpSetup} options.http - HttpSetup + * @param {Function} [options.onFetch] - transformation function for kb entries fetch result + * @param {AbortSignal} [options.signal] - AbortSignal + * + * @returns {useQuery} hook for fetching Knowledge Base Entries + */ +const query = { + page: 1, + perPage: 100, +}; + +export const KNOWLEDGE_BASE_ENTRY_QUERY_KEY = [ + ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_FIND, + query.page, + query.perPage, + API_VERSIONS.internal.v1, +]; + +export const useKnowledgeBaseEntries = ({ http, signal }: UseKnowledgeBaseEntriesParams) => + useQuery( + KNOWLEDGE_BASE_ENTRY_QUERY_KEY, + async () => + http.fetch( + ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_FIND, + { + method: 'GET', + version: API_VERSIONS.internal.v1, + query, + signal, + } + ), + { + keepPreviousData: true, + initialData: { page: 1, perPage: 100, total: 0, data: [] }, + } + ); + +/** + * Use this hook to invalidate the Knowledge Base Entries cache. For example, adding, + * editing, or deleting any Knowledge Base entries should lead to cache invalidation. + * + * @returns {Function} - Function to invalidate the Knowledge Base Entries cache + */ +export const useInvalidateKnowledgeBaseEntries = () => { + const queryClient = useQueryClient(); + + return useCallback(() => { + queryClient.invalidateQueries(KNOWLEDGE_BASE_ENTRY_QUERY_KEY, { + refetchType: 'active', + }); + }, [queryClient]); +}; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_delete_knowledge_base.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/use_delete_knowledge_base.test.tsx similarity index 94% rename from x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_delete_knowledge_base.test.tsx rename to x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/use_delete_knowledge_base.test.tsx index 06933c4ebeff9..b50c345edb3b3 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_delete_knowledge_base.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/use_delete_knowledge_base.test.tsx @@ -7,14 +7,14 @@ import { act, renderHook } from '@testing-library/react-hooks'; import { useDeleteKnowledgeBase, UseDeleteKnowledgeBaseParams } from './use_delete_knowledge_base'; -import { deleteKnowledgeBase as _deleteKnowledgeBase } from '../assistant/api'; +import { deleteKnowledgeBase as _deleteKnowledgeBase } from './api'; import { useMutation as _useMutation } from '@tanstack/react-query'; const useMutationMock = _useMutation as jest.Mock; const deleteKnowledgeBaseMock = _deleteKnowledgeBase as jest.Mock; -jest.mock('../assistant/api', () => { - const actual = jest.requireActual('../assistant/api'); +jest.mock('./api', () => { + const actual = jest.requireActual('./api'); return { ...actual, deleteKnowledgeBase: jest.fn((...args) => actual.deleteKnowledgeBase(...args)), diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_delete_knowledge_base.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/use_delete_knowledge_base.tsx similarity index 97% rename from x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_delete_knowledge_base.tsx rename to x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/use_delete_knowledge_base.tsx index 3266bc20b8cdd..5e4ce82bde3bd 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_delete_knowledge_base.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/use_delete_knowledge_base.tsx @@ -9,7 +9,7 @@ import { useMutation } from '@tanstack/react-query'; import type { IToasts } from '@kbn/core-notifications-browser'; import type { HttpSetup, IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; import { i18n } from '@kbn/i18n'; -import { deleteKnowledgeBase } from '../assistant/api'; +import { deleteKnowledgeBase } from './api'; import { useInvalidateKnowledgeBaseStatus } from './use_knowledge_base_status'; const DELETE_KNOWLEDGE_BASE_MUTATION_KEY = ['elastic-assistant', 'delete-knowledge-base']; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_knowledge_base_status.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/use_knowledge_base_status.test.tsx similarity index 96% rename from x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_knowledge_base_status.test.tsx rename to x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/use_knowledge_base_status.test.tsx index a999666845378..aaad50afacd91 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_knowledge_base_status.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/use_knowledge_base_status.test.tsx @@ -7,12 +7,12 @@ import { act, renderHook } from '@testing-library/react-hooks'; import { useKnowledgeBaseStatus, UseKnowledgeBaseStatusParams } from './use_knowledge_base_status'; -import { getKnowledgeBaseStatus as _getKnowledgeBaseStatus } from '../assistant/api'; +import { getKnowledgeBaseStatus as _getKnowledgeBaseStatus } from './api'; const getKnowledgeBaseStatusMock = _getKnowledgeBaseStatus as jest.Mock; -jest.mock('../assistant/api', () => { - const actual = jest.requireActual('../assistant/api'); +jest.mock('./api', () => { + const actual = jest.requireActual('./api'); return { ...actual, getKnowledgeBaseStatus: jest.fn((...args) => actual.getKnowledgeBaseStatus(...args)), diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_knowledge_base_status.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/use_knowledge_base_status.tsx similarity index 97% rename from x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_knowledge_base_status.tsx rename to x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/use_knowledge_base_status.tsx index c03eb31581e42..ba6317329d350 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_knowledge_base_status.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/use_knowledge_base_status.tsx @@ -12,7 +12,7 @@ import type { IToasts } from '@kbn/core-notifications-browser'; import { i18n } from '@kbn/i18n'; import { useCallback } from 'react'; import { ReadKnowledgeBaseResponse } from '@kbn/elastic-assistant-common'; -import { getKnowledgeBaseStatus } from '../assistant/api'; +import { getKnowledgeBaseStatus } from './api'; const KNOWLEDGE_BASE_STATUS_QUERY_KEY = ['elastic-assistant', 'knowledge-base-status']; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_setup_knowledge_base.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/use_setup_knowledge_base.test.tsx similarity index 94% rename from x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_setup_knowledge_base.test.tsx rename to x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/use_setup_knowledge_base.test.tsx index 8e0b084e9beed..a252700ba744f 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_setup_knowledge_base.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/use_setup_knowledge_base.test.tsx @@ -7,13 +7,13 @@ import { act, renderHook } from '@testing-library/react-hooks'; import { useSetupKnowledgeBase, UseSetupKnowledgeBaseParams } from './use_setup_knowledge_base'; -import { postKnowledgeBase as _postKnowledgeBase } from '../assistant/api'; +import { postKnowledgeBase as _postKnowledgeBase } from './api'; import { useMutation as _useMutation } from '@tanstack/react-query'; const postKnowledgeBaseMock = _postKnowledgeBase as jest.Mock; const useMutationMock = _useMutation as jest.Mock; -jest.mock('../assistant/api', () => { - const actual = jest.requireActual('../assistant/api'); +jest.mock('./api', () => { + const actual = jest.requireActual('./api'); return { ...actual, postKnowledgeBase: jest.fn((...args) => actual.postKnowledgeBase(...args)), diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_setup_knowledge_base.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/use_setup_knowledge_base.tsx similarity index 97% rename from x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_setup_knowledge_base.tsx rename to x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/use_setup_knowledge_base.tsx index 34533683e7921..c27c97976e989 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/use_setup_knowledge_base.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/api/knowledge_base/use_setup_knowledge_base.tsx @@ -9,7 +9,7 @@ import { useMutation } from '@tanstack/react-query'; import type { HttpSetup, IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; import type { IToasts } from '@kbn/core-notifications-browser'; import { i18n } from '@kbn/i18n'; -import { postKnowledgeBase } from '../assistant/api'; +import { postKnowledgeBase } from './api'; import { useInvalidateKnowledgeBaseStatus } from './use_knowledge_base_status'; const SETUP_KNOWLEDGE_BASE_MUTATION_KEY = ['elastic-assistant', 'post-knowledge-base']; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx index defe0d90e8c8c..88f00de57f444 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/system_prompt_modal/system_prompt_settings.tsx @@ -340,7 +340,6 @@ export const SystemPromptSettings: React.FC = React.memo( } checked={isNewConversationDefault} onChange={handleNewConversationDefaultChange} - compressed /> diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/use_load_connectors/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/use_load_connectors/index.tsx index b93c166a9c5d7..293993e82fde6 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/use_load_connectors/index.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/use_load_connectors/index.tsx @@ -30,6 +30,7 @@ export interface Props { const actionTypeKey = { bedrock: '.bedrock', openai: '.gen-ai', + gemini: '.gemini', }; export const useLoadConnectors = ({ @@ -44,7 +45,9 @@ export const useLoadConnectors = ({ (acc: AIConnector[], connector) => [ ...acc, ...(!connector.isMissingSecrets && - [actionTypeKey.bedrock, actionTypeKey.openai].includes(connector.actionTypeId) + [actionTypeKey.bedrock, actionTypeKey.openai, actionTypeKey.gemini].includes( + connector.actionTypeId + ) ? [ { ...connector, diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/install_knowledge_base_button.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/install_knowledge_base_button.tsx index f5a82fd02c55d..32e34eb59fa93 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/install_knowledge_base_button.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/install_knowledge_base_button.tsx @@ -10,8 +10,8 @@ import { EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useAssistantContext } from '../..'; -import { useSetupKnowledgeBase } from './use_setup_knowledge_base'; -import { useKnowledgeBaseStatus } from './use_knowledge_base_status'; +import { useSetupKnowledgeBase } from '../assistant/api/knowledge_base/use_setup_knowledge_base'; +import { useKnowledgeBaseStatus } from '../assistant/api/knowledge_base/use_knowledge_base_status'; const ESQL_RESOURCE = 'esql'; diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings.test.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings.test.tsx index 56f6796ac16fa..4d39504075c7e 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings.test.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings.test.tsx @@ -11,7 +11,7 @@ import { fireEvent, render } from '@testing-library/react'; import { DEFAULT_LATEST_ALERTS } from '../assistant_context/constants'; import { KnowledgeBaseSettings } from './knowledge_base_settings'; import { TestProviders } from '../mock/test_providers/test_providers'; -import { useKnowledgeBaseStatus } from './use_knowledge_base_status'; +import { useKnowledgeBaseStatus } from '../assistant/api/knowledge_base/use_knowledge_base_status'; import { mockSystemPrompts } from '../mock/system_prompt'; import { defaultAssistantFeatures } from '@kbn/elastic-assistant-common'; @@ -47,7 +47,7 @@ const defaultProps = { setUpdatedKnowledgeBaseSettings, }; const mockDelete = jest.fn(); -jest.mock('./use_delete_knowledge_base', () => ({ +jest.mock('../assistant/api/knowledge_base/use_delete_knowledge_base', () => ({ useDeleteKnowledgeBase: jest.fn(() => { return { mutate: mockDelete, @@ -57,7 +57,7 @@ jest.mock('./use_delete_knowledge_base', () => ({ })); const mockSetup = jest.fn(); -jest.mock('./use_setup_knowledge_base', () => ({ +jest.mock('../assistant/api/knowledge_base/use_setup_knowledge_base', () => ({ useSetupKnowledgeBase: jest.fn(() => { return { mutate: mockSetup, @@ -66,7 +66,7 @@ jest.mock('./use_setup_knowledge_base', () => ({ }), })); -jest.mock('./use_knowledge_base_status', () => ({ +jest.mock('../assistant/api/knowledge_base/use_knowledge_base_status', () => ({ useKnowledgeBaseStatus: jest.fn(() => { return { data: { diff --git a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings.tsx b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings.tsx index 8c83d1f3403e8..fc152d1e5a3da 100644 --- a/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings.tsx +++ b/x-pack/packages/kbn-elastic-assistant/impl/knowledge_base/knowledge_base_settings.tsx @@ -30,9 +30,9 @@ import { AlertsSettings } from '../alerts/settings/alerts_settings'; import { useAssistantContext } from '../assistant_context'; import type { KnowledgeBaseConfig } from '../assistant/types'; import * as i18n from './translations'; -import { useDeleteKnowledgeBase } from './use_delete_knowledge_base'; -import { useKnowledgeBaseStatus } from './use_knowledge_base_status'; -import { useSetupKnowledgeBase } from './use_setup_knowledge_base'; +import { useDeleteKnowledgeBase } from '../assistant/api/knowledge_base/use_delete_knowledge_base'; +import { useKnowledgeBaseStatus } from '../assistant/api/knowledge_base/use_knowledge_base_status'; +import { useSetupKnowledgeBase } from '../assistant/api/knowledge_base/use_setup_knowledge_base'; const ESQL_RESOURCE = 'esql'; const KNOWLEDGE_BASE_INDEX_PATTERN_OLD = '.kibana-elastic-ai-assistant-kb'; diff --git a/x-pack/packages/kbn-langchain/server/index.ts b/x-pack/packages/kbn-langchain/server/index.ts index 12c32c86563a8..1d52159951809 100644 --- a/x-pack/packages/kbn-langchain/server/index.ts +++ b/x-pack/packages/kbn-langchain/server/index.ts @@ -9,10 +9,12 @@ import { ActionsClientChatOpenAI } from './language_models/chat_openai'; import { ActionsClientLlm } from './language_models/llm'; import { ActionsClientSimpleChatModel } from './language_models/simple_chat_model'; import { parseBedrockStream } from './utils/bedrock'; +import { parseGeminiResponse } from './utils/gemini'; import { getDefaultArguments } from './language_models/constants'; export { parseBedrockStream, + parseGeminiResponse, getDefaultArguments, ActionsClientChatOpenAI, ActionsClientLlm, diff --git a/x-pack/packages/kbn-langchain/server/language_models/chat_openai.ts b/x-pack/packages/kbn-langchain/server/language_models/chat_openai.ts index 7675e2442e598..c2dada0dafa3b 100644 --- a/x-pack/packages/kbn-langchain/server/language_models/chat_openai.ts +++ b/x-pack/packages/kbn-langchain/server/language_models/chat_openai.ts @@ -27,6 +27,7 @@ export interface ActionsClientChatOpenAIParams { streaming?: boolean; traceId?: string; maxRetries?: number; + maxTokens?: number; model?: string; temperature?: number; signal?: AbortSignal; @@ -75,9 +76,11 @@ export class ActionsClientChatOpenAI extends ChatOpenAI { streaming = true, temperature, timeout, + maxTokens, }: ActionsClientChatOpenAIParams) { super({ maxRetries, + maxTokens, streaming, // matters only for the LangSmith logs (Metadata > Invocation Params), which are misleading if this is not set modelName: model ?? DEFAULT_OPEN_AI_MODEL, diff --git a/x-pack/packages/kbn-langchain/server/language_models/constants.ts b/x-pack/packages/kbn-langchain/server/language_models/constants.ts index d4d38d172f975..be9866023120e 100644 --- a/x-pack/packages/kbn-langchain/server/language_models/constants.ts +++ b/x-pack/packages/kbn-langchain/server/language_models/constants.ts @@ -11,6 +11,10 @@ export const getDefaultArguments = (llmType?: string, temperature?: number, stop temperature: temperature ?? DEFAULT_BEDROCK_TEMPERATURE, stopSequences: stop ?? DEFAULT_BEDROCK_STOP_SEQUENCES, } + : llmType === 'gemini' + ? { + temperature: temperature ?? DEFAULT_GEMINI_TEMPERATURE, + } : { n: 1, stop: stop ?? null, temperature: temperature ?? DEFAULT_OPEN_AI_TEMPERATURE }; export const DEFAULT_OPEN_AI_TEMPERATURE = 0.2; @@ -19,4 +23,5 @@ export const DEFAULT_OPEN_AI_TEMPERATURE = 0.2; export const DEFAULT_OPEN_AI_MODEL = 'gpt-4'; const DEFAULT_BEDROCK_TEMPERATURE = 0; const DEFAULT_BEDROCK_STOP_SEQUENCES = ['\n\nHuman:', '\nObservation:']; +const DEFAULT_GEMINI_TEMPERATURE = 0; export const DEFAULT_TIMEOUT = 180000; diff --git a/x-pack/packages/kbn-langchain/server/language_models/simple_chat_model.test.ts b/x-pack/packages/kbn-langchain/server/language_models/simple_chat_model.test.ts index 6a11466f9faa0..a9d2142fc3963 100644 --- a/x-pack/packages/kbn-langchain/server/language_models/simple_chat_model.test.ts +++ b/x-pack/packages/kbn-langchain/server/language_models/simple_chat_model.test.ts @@ -14,6 +14,7 @@ import { mockActionResponse } from './mocks'; import { BaseMessage } from '@langchain/core/messages'; import { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager'; import { parseBedrockStream } from '../utils/bedrock'; +import { parseGeminiStream } from '../utils/gemini'; const connectorId = 'mock-connector-id'; @@ -94,6 +95,7 @@ const defaultArgs = { streaming: false, }; jest.mock('../utils/bedrock'); +jest.mock('../utils/gemini'); describe('ActionsClientSimpleChatModel', () => { beforeEach(() => { @@ -216,6 +218,7 @@ describe('ActionsClientSimpleChatModel', () => { describe('_call streaming: true', () => { beforeEach(() => { (parseBedrockStream as jest.Mock).mockResolvedValue(mockActionResponse.message); + (parseGeminiStream as jest.Mock).mockResolvedValue(mockActionResponse.message); }); it('returns the expected content when _call is invoked with streaming and llmType is Bedrock', async () => { const actionsClientSimpleChatModel = new ActionsClientSimpleChatModel({ @@ -238,7 +241,7 @@ describe('ActionsClientSimpleChatModel', () => { it('returns the expected content when _call is invoked with streaming and llmType is Gemini', async () => { const actionsClientSimpleChatModel = new ActionsClientSimpleChatModel({ ...defaultArgs, - actions: mockActions, + actions: mockStreamActions, llmType: 'gemini', streaming: true, }); @@ -248,8 +251,8 @@ describe('ActionsClientSimpleChatModel', () => { callOptions, callRunManager ); - const subAction = mockExecute.mock.calls[0][0].params.subAction; - expect(subAction).toEqual('invokeAI'); + const subAction = mockStreamExecute.mock.calls[0][0].params.subAction; + expect(subAction).toEqual('invokeStream'); expect(result).toEqual(mockActionResponse.message); }); diff --git a/x-pack/packages/kbn-langchain/server/language_models/simple_chat_model.ts b/x-pack/packages/kbn-langchain/server/language_models/simple_chat_model.ts index f13b0a53611ef..161e3601ee3ae 100644 --- a/x-pack/packages/kbn-langchain/server/language_models/simple_chat_model.ts +++ b/x-pack/packages/kbn-langchain/server/language_models/simple_chat_model.ts @@ -17,6 +17,7 @@ import { KibanaRequest } from '@kbn/core-http-server'; import { v4 as uuidv4 } from 'uuid'; import { get } from 'lodash/fp'; import { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager'; +import { parseGeminiStream } from '../utils/gemini'; import { parseBedrockStream } from '../utils/bedrock'; import { getDefaultArguments } from './constants'; @@ -35,6 +36,7 @@ export interface CustomChatModelInput extends BaseChatModelParams { temperature?: number; request: KibanaRequest; streaming: boolean; + maxTokens?: number; } export class ActionsClientSimpleChatModel extends SimpleChatModel { @@ -44,6 +46,7 @@ export class ActionsClientSimpleChatModel extends SimpleChatModel { #request: KibanaRequest; #traceId: string; #signal?: AbortSignal; + #maxTokens?: number; llmType: string; streaming: boolean; model?: string; @@ -59,6 +62,7 @@ export class ActionsClientSimpleChatModel extends SimpleChatModel { temperature, signal, streaming, + maxTokens, }: CustomChatModelInput) { super({}); @@ -68,11 +72,11 @@ export class ActionsClientSimpleChatModel extends SimpleChatModel { this.#logger = logger; this.#signal = signal; this.#request = request; + this.#maxTokens = maxTokens; this.llmType = llmType ?? 'ActionsClientSimpleChatModel'; this.model = model; this.temperature = temperature; - // only enable streaming for bedrock - this.streaming = streaming && llmType === 'bedrock'; + this.streaming = streaming; } _llmType() { @@ -95,7 +99,7 @@ export class ActionsClientSimpleChatModel extends SimpleChatModel { throw new Error('No messages provided.'); } const formattedMessages = []; - if (messages.length === 2) { + if (messages.length >= 2) { messages.forEach((message, i) => { if (typeof message.content !== 'string') { throw new Error('Multimodal messages are not supported.'); @@ -121,6 +125,7 @@ export class ActionsClientSimpleChatModel extends SimpleChatModel { subActionParams: { model: this.model, messages: formattedMessages, + maxTokens: this.#maxTokens, ...getDefaultArguments(this.llmType, this.temperature, options.stop), }, }, @@ -149,7 +154,6 @@ export class ActionsClientSimpleChatModel extends SimpleChatModel { return content; // per the contact of _call, return a string } - // Bedrock streaming const readable = get('data', actionResult) as Readable; if (typeof readable?.read !== 'function') { @@ -177,13 +181,9 @@ export class ActionsClientSimpleChatModel extends SimpleChatModel { } } }; + const streamParser = this.llmType === 'bedrock' ? parseBedrockStream : parseGeminiStream; - const parsed = await parseBedrockStream( - readable, - this.#logger, - this.#signal, - handleLLMNewToken - ); + const parsed = await streamParser(readable, this.#logger, this.#signal, handleLLMNewToken); return parsed; // per the contact of _call, return a string } diff --git a/x-pack/packages/kbn-langchain/server/utils/bedrock.ts b/x-pack/packages/kbn-langchain/server/utils/bedrock.ts index 4b0b6ca245ff2..08e884ef01da2 100644 --- a/x-pack/packages/kbn-langchain/server/utils/bedrock.ts +++ b/x-pack/packages/kbn-langchain/server/utils/bedrock.ts @@ -6,17 +6,10 @@ */ import { finished } from 'stream/promises'; -import { Readable } from 'stream'; import { Logger } from '@kbn/core/server'; import { EventStreamCodec } from '@smithy/eventstream-codec'; import { fromUtf8, toUtf8 } from '@smithy/util-utf8'; - -type StreamParser = ( - responseStream: Readable, - logger: Logger, - abortSignal?: AbortSignal, - tokenHandler?: (token: string) => void -) => Promise; +import { StreamParser } from './types'; export const parseBedrockStream: StreamParser = async ( responseStream, diff --git a/x-pack/packages/kbn-langchain/server/utils/gemini.test.ts b/x-pack/packages/kbn-langchain/server/utils/gemini.test.ts new file mode 100644 index 0000000000000..3fcdb87b24551 --- /dev/null +++ b/x-pack/packages/kbn-langchain/server/utils/gemini.test.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Readable } from 'stream'; +import { parseGeminiStream, parseGeminiResponse } from './gemini'; +import { loggerMock } from '@kbn/logging-mocks'; + +describe('parseGeminiStream', () => { + const mockLogger = loggerMock.create(); + let mockStream: Readable; + + beforeEach(() => { + jest.clearAllMocks(); + mockStream = new Readable({ + read() {}, + }); + }); + + it('should parse the stream correctly', async () => { + const data = + 'data: {"candidates":[{"content":{"role":"system","parts":[{"text":"Hello"}]},"finishReason":"stop","safetyRatings":[{"category":"safe","probability":"low"}]}],"usageMetadata":{"promptTokenCount":10,"candidatesTokenCount":10,"totalTokenCount":20}}\n'; + mockStream.push(data); + mockStream.push(null); + + const result = await parseGeminiStream(mockStream, mockLogger); + expect(result).toBe('Hello'); + }); + + it('should handle abort signal correctly', async () => { + const abortSignal = new AbortController().signal; + setTimeout(() => { + abortSignal.dispatchEvent(new Event('abort')); + }, 100); + + const result = parseGeminiStream(mockStream, mockLogger, abortSignal); + + await expect(result).resolves.toBe(''); + expect(mockLogger.info).toHaveBeenCalledWith('Bedrock stream parsing was aborted.'); + }); + + it('should call tokenHandler with correct tokens', async () => { + const data = + 'data: {"candidates":[{"content":{"role":"system","parts":[{"text":"Hello world"}]},"finishReason":"stop","safetyRatings":[{"category":"safe","probability":"low"}]}],"usageMetadata":{"promptTokenCount":10,"candidatesTokenCount":10,"totalTokenCount":20}}\n'; + mockStream.push(data); + mockStream.push(null); + + const tokenHandler = jest.fn(); + await parseGeminiStream(mockStream, mockLogger, undefined, tokenHandler); + + expect(tokenHandler).toHaveBeenCalledWith('Hello '); + expect(tokenHandler).toHaveBeenCalledWith('world '); + }); + + it('should handle stream error correctly', async () => { + const error = new Error('Stream error'); + const resultPromise = parseGeminiStream(mockStream, mockLogger); + + mockStream.emit('error', error); + + await expect(resultPromise).rejects.toThrow('Stream error'); + }); +}); + +describe('parseGeminiResponse', () => { + it('should parse response correctly', () => { + const response = + 'data: {"candidates":[{"content":{"role":"system","parts":[{"text":"Hello"}]},"finishReason":"stop","safetyRatings":[{"category":"safe","probability":"low"}]}],"usageMetadata":{"promptTokenCount":10,"candidatesTokenCount":10,"totalTokenCount":20}}\n'; + const result = parseGeminiResponse(response); + expect(result).toBe('Hello'); + }); + + it('should ignore lines that do not start with data: ', () => { + const response = + 'invalid line\ndata: {"candidates":[{"content":{"role":"system","parts":[{"text":"Hello"}]},"finishReason":"stop","safetyRatings":[{"category":"safe","probability":"low"}]}],"usageMetadata":{"promptTokenCount":10,"candidatesTokenCount":10,"totalTokenCount":20}}\n'; + const result = parseGeminiResponse(response); + expect(result).toBe('Hello'); + }); + + it('should ignore lines that end with [DONE]', () => { + const response = + 'data: {"candidates":[{"content":{"role":"system","parts":[{"text":"Hello"}]},"finishReason":"stop","safetyRatings":[{"category":"safe","probability":"low"}]}],"usageMetadata":{"promptTokenCount":10,"candidatesTokenCount":10,"totalTokenCount":20}}\ndata: [DONE]'; + const result = parseGeminiResponse(response); + expect(result).toBe('Hello'); + }); +}); diff --git a/x-pack/packages/kbn-langchain/server/utils/gemini.ts b/x-pack/packages/kbn-langchain/server/utils/gemini.ts new file mode 100644 index 0000000000000..68fa4c0363e13 --- /dev/null +++ b/x-pack/packages/kbn-langchain/server/utils/gemini.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { StreamParser } from './types'; + +export const parseGeminiStream: StreamParser = async ( + stream, + logger, + abortSignal, + tokenHandler +) => { + let responseBody = ''; + stream.on('data', (chunk) => { + const decoded = chunk.toString(); + const parsed = parseGeminiResponse(decoded); + if (tokenHandler) { + const splitByQuotes = parsed.split(`"`); + splitByQuotes.forEach((chunkk, index) => { + // add quote back on except for last chunk + const splitBySpace = `${chunkk}${index === splitByQuotes.length - 1 ? '' : '"'}`.split(` `); + + for (const char of splitBySpace) { + tokenHandler(`${char} `); + } + }); + } + responseBody += parsed; + }); + return new Promise((resolve, reject) => { + stream.on('end', () => { + resolve(responseBody); + }); + stream.on('error', (err) => { + reject(err); + }); + if (abortSignal) { + abortSignal.addEventListener('abort', () => { + logger.info('Bedrock stream parsing was aborted.'); + stream.destroy(); + resolve(responseBody); + }); + } + }); +}; + +/** Parse Gemini stream response body */ +export const parseGeminiResponse = (responseBody: string) => { + return responseBody + .split('\n') + .filter((line) => line.startsWith('data: ') && !line.endsWith('[DONE]')) + .map((line) => JSON.parse(line.replace('data: ', ''))) + .filter( + ( + line + ): line is { + candidates: Array<{ + content: { role: string; parts: Array<{ text: string }> }; + finishReason: string; + safetyRatings: Array<{ category: string; probability: string }>; + }>; + usageMetadata: { + promptTokenCount: number; + candidatesTokenCount: number; + totalTokenCount: number; + }; + } => 'candidates' in line + ) + .reduce((prev, line) => { + if (line.candidates[0] && line.candidates[0].content) { + const parts = line.candidates[0].content.parts; + const text = parts.map((part) => part.text).join(''); + return prev + text; + } + return prev; + }, ''); +}; diff --git a/x-pack/packages/kbn-langchain/server/utils/types.ts b/x-pack/packages/kbn-langchain/server/utils/types.ts new file mode 100644 index 0000000000000..d88adb4045e87 --- /dev/null +++ b/x-pack/packages/kbn-langchain/server/utils/types.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 { Readable } from 'stream'; +import { Logger } from '@kbn/logging'; + +export type StreamParser = ( + responseStream: Readable, + logger: Logger, + abortSignal?: AbortSignal, + tokenHandler?: (token: string) => void +) => Promise; + +export interface GeminiResponseSchema { + candidates: Candidate[]; + usageMetadata: { + promptTokenCount: number; + candidatesTokenCount: number; + totalTokenCount: number; + }; +} +interface Part { + text: string; +} + +interface Candidate { + content: Content; + finishReason: string; +} + +interface Content { + role: string; + parts: Part[]; +} diff --git a/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/fetch_historical_summary.ts b/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/fetch_historical_summary.ts index 7912c3ccbedc1..84efa2e3b2a5b 100644 --- a/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/fetch_historical_summary.ts +++ b/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/fetch_historical_summary.ts @@ -16,7 +16,8 @@ import { allOrAnyStringOrArray, dateRangeSchema, dateType, - summarySchema, + errorBudgetSchema, + statusSchema, } from '../../schema/common'; const fetchHistoricalSummaryParamsSchema = t.type({ @@ -41,12 +42,12 @@ const fetchHistoricalSummaryParamsSchema = t.type({ }), }); -const historicalSummarySchema = t.intersection([ - t.type({ - date: dateType, - }), - summarySchema, -]); +const historicalSummarySchema = t.type({ + date: dateType, + status: statusSchema, + sliValue: t.number, + errorBudget: errorBudgetSchema, +}); const fetchHistoricalSummaryResponseSchema = t.array( t.type({ diff --git a/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/find.ts b/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/find.ts index 31e8c5852a1e1..be7e063ecc80f 100644 --- a/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/find.ts +++ b/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/find.ts @@ -14,6 +14,9 @@ const sortBySchema = t.union([ t.literal('error_budget_remaining'), t.literal('sli_value'), t.literal('status'), + t.literal('burn_rate_5m'), + t.literal('burn_rate_1h'), + t.literal('burn_rate_1d'), ]); const findSLOParamsSchema = t.partial({ diff --git a/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/find_group.ts b/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/find_group.ts index 3c04134c83312..7c3f5e576cea5 100644 --- a/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/find_group.ts +++ b/x-pack/packages/kbn-slo-schema/src/rest_specs/routes/find_group.ts @@ -12,6 +12,7 @@ const groupBySchema = t.union([ t.literal('slo.tags'), t.literal('status'), t.literal('slo.indicator.type'), + t.literal('slo.instanceId'), t.literal('_index'), ]); diff --git a/x-pack/packages/kbn-slo-schema/src/schema/common.ts b/x-pack/packages/kbn-slo-schema/src/schema/common.ts index e2b6d5af0a023..ff30ae5f25483 100644 --- a/x-pack/packages/kbn-slo-schema/src/schema/common.ts +++ b/x-pack/packages/kbn-slo-schema/src/schema/common.ts @@ -48,6 +48,9 @@ const summarySchema = t.intersection([ status: statusSchema, sliValue: t.number, errorBudget: errorBudgetSchema, + fiveMinuteBurnRate: t.number, + oneHourBurnRate: t.number, + oneDayBurnRate: t.number, }), t.partial({ summaryUpdatedAt: t.union([t.string, t.null]), @@ -78,6 +81,7 @@ const groupSummarySchema = t.type({ id: t.string, instanceId: t.string, name: t.string, + groupings: t.record(t.string, t.unknown), }), }), violated: t.number, diff --git a/x-pack/packages/ml/aiops_components/index.ts b/x-pack/packages/ml/aiops_components/index.ts index 3a640c1d0cb44..c5798bc4b0ae0 100644 --- a/x-pack/packages/ml/aiops_components/index.ts +++ b/x-pack/packages/ml/aiops_components/index.ts @@ -9,15 +9,7 @@ export { DualBrush, DualBrushAnnotation } from './src/dual_brush'; export { ProgressControls } from './src/progress_controls'; export { DocumentCountChart, - DocumentCountChartWithAutoAnalysisStart, + DocumentCountChartRedux, type BrushSettings, - type BrushSelectionUpdateHandler, } from './src/document_count_chart'; export type { DocumentCountChartProps } from './src/document_count_chart'; -export { - useLogRateAnalysisStateContext, - LogRateAnalysisStateProvider, - type GroupTableItem, - type GroupTableItemGroup, - type TableItemAction, -} from './src/log_rate_analysis_state_provider'; diff --git a/x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart.tsx b/x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart.tsx index 08dcaa824866c..fbfece79b3d77 100644 --- a/x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart.tsx +++ b/x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart.tsx @@ -29,10 +29,10 @@ import { getSnappedWindowParameters, getWindowParametersForTrigger, type DocumentCountStatsChangePoint, - type LogRateAnalysisType, type LogRateHistogramItem, type WindowParameters, } from '@kbn/aiops-log-rate-analysis'; +import { type BrushSelectionUpdatePayload } from '@kbn/aiops-log-rate-analysis/state'; import { MULTILAYER_TIME_AXIS_STYLE } from '@kbn/charts-plugin/common'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; @@ -40,8 +40,6 @@ import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import { DualBrush, DualBrushAnnotation } from '../..'; -import { useLogRateAnalysisStateContext } from '../log_rate_analysis_state_provider'; - import { BrushBadge } from './brush_badge'; declare global { @@ -77,22 +75,17 @@ export interface BrushSettings { } /** - * Callback function which gets called when the brush selection has changed - * - * @param windowParameters Baseline and deviation time ranges. - * @param force Force update - * @param logRateAnalysisType `spike` or `dip` based on median log rate bucket size + * Callback to set the autoRunAnalysis flag */ -export type BrushSelectionUpdateHandler = ( - windowParameters: WindowParameters, - force: boolean, - logRateAnalysisType: LogRateAnalysisType -) => void; +type SetAutoRunAnalysisFn = (isAutoRun: boolean) => void; /** - * Callback to set the autoRunAnalysis flag + * Brush selection update handler */ -type SetAutoRunAnalysisFn = (isAutoRun: boolean) => void; +type BrushSelectionUpdateHandler = ( + /** Payload for the brush selection update */ + d: BrushSelectionUpdatePayload +) => void; /** * Props for document count chart @@ -126,7 +119,7 @@ export interface DocumentCountChartProps { /** Whether or not brush has been reset */ isBrushCleared: boolean; /** Callback to set the autoRunAnalysis flag */ - setAutoRunAnalysis?: SetAutoRunAnalysisFn; + setAutoRunAnalysisFn?: SetAutoRunAnalysisFn; /** Timestamp for start of initial analysis */ autoAnalysisStart?: number | WindowParameters; /** Optional style to override bar chart */ @@ -190,7 +183,7 @@ export const DocumentCountChart: FC = (props) => { interval, chartPointsSplitLabel, isBrushCleared, - setAutoRunAnalysis, + setAutoRunAnalysisFn, autoAnalysisStart, barColorOverride, barStyleAccessor, @@ -315,7 +308,7 @@ export const DocumentCountChart: FC = (props) => { windowParameters === undefined && adjustedChartPoints !== undefined ) { - if (setAutoRunAnalysis) { + if (setAutoRunAnalysisFn) { const autoRun = typeof startRange !== 'number' || (typeof startRange === 'number' && @@ -323,7 +316,7 @@ export const DocumentCountChart: FC = (props) => { startRange >= changePoint.startTs && startRange <= changePoint.endTs); - setAutoRunAnalysis(autoRun); + setAutoRunAnalysisFn(autoRun); } const wp = getWindowParametersForTrigger( @@ -338,11 +331,11 @@ export const DocumentCountChart: FC = (props) => { setWindowParameters(wpSnap); if (brushSelectionUpdateHandler !== undefined) { - brushSelectionUpdateHandler( - wpSnap, - true, - getLogRateAnalysisType(adjustedChartPoints, wpSnap) - ); + brushSelectionUpdateHandler({ + windowParameters: wpSnap, + force: true, + analysisType: getLogRateAnalysisType(adjustedChartPoints, wpSnap), + }); } } } @@ -354,7 +347,7 @@ export const DocumentCountChart: FC = (props) => { timeRangeLatest, snapTimestamps, originalWindowParameters, - setAutoRunAnalysis, + setAutoRunAnalysisFn, setWindowParameters, brushSelectionUpdateHandler, adjustedChartPoints, @@ -395,7 +388,11 @@ export const DocumentCountChart: FC = (props) => { } setWindowParameters(wp); setWindowParametersAsPixels(wpPx); - brushSelectionUpdateHandler(wp, false, getLogRateAnalysisType(adjustedChartPoints, wp)); + brushSelectionUpdateHandler({ + windowParameters: wp, + force: false, + analysisType: getLogRateAnalysisType(adjustedChartPoints, wp), + }); } const [mlBrushWidth, setMlBrushWidth] = useState(); @@ -556,24 +553,3 @@ export const DocumentCountChart: FC = (props) => { ); }; - -/** - * Functional component that renders a `DocumentCountChart` with additional properties - * managed by the log rate analysis state. It leverages the `useLogRateAnalysisStateContext` - * to acquire state variables like `initialAnalysisStart` and functions such as - * `setAutoRunAnalysis`. These values are then passed as props to the `DocumentCountChart`. - * - * @param props - The properties passed to the DocumentCountChart component. - * @returns The DocumentCountChart component enhanced with automatic analysis start capabilities. - */ -export const DocumentCountChartWithAutoAnalysisStart: FC = (props) => { - const { initialAnalysisStart, setAutoRunAnalysis } = useLogRateAnalysisStateContext(); - - return ( - - ); -}; diff --git a/x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart_redux.tsx b/x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart_redux.tsx new file mode 100644 index 0000000000000..b74d02a54b161 --- /dev/null +++ b/x-pack/packages/ml/aiops_components/src/document_count_chart/document_count_chart_redux.tsx @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo, type FC } from 'react'; + +import { i18n } from '@kbn/i18n'; +import type { LogRateHistogramItem } from '@kbn/aiops-log-rate-analysis'; +import { + brushSelectionUpdate, + setAutoRunAnalysis, + useAppSelector, + useAppDispatch, + useCurrentSelectedGroup, + useCurrentSelectedSignificantItem, + type GroupTableItem, +} from '@kbn/aiops-log-rate-analysis/state'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; + +import { DocumentCountChart, type DocumentCountChartProps } from './document_count_chart'; + +function getDocumentCountStatsSplitLabel( + significantItem?: SignificantItem, + group?: GroupTableItem +): string { + if (significantItem) { + return `${significantItem?.fieldName}:${significantItem?.fieldValue}`; + } else if (group) { + return i18n.translate('xpack.aiops.logRateAnalysis.page.documentCountStatsSplitGroupLabel', { + defaultMessage: 'Selected group', + }); + } else { + return ''; + } +} + +type DocumentCountChartReduxProps = Omit< + DocumentCountChartProps, + | 'chartPointsSplitLabel' + | 'autoAnalysisStart' + | 'chartPoints' + | 'chartPointsSplit' + | 'documentStats' + | 'isBrushCleared' + | 'brushSelectionUpdateHandler' + | 'timeRangeEarliest' + | 'timeRangeLatest' + | 'interval' +>; + +/** + * Functional component that renders a `DocumentCountChart` with additional properties + * managed by the log rate analysis state. It leverages the `LogRateAnalysisReduxProvider` + * to acquire state variables like `initialAnalysisStart` and functions such as + * `setAutoRunAnalysis`. These values are then passed as props to the `DocumentCountChart`. + * This wrapper component is necessary since the `DocumentCountChart` component is + * also used for log pattern analysis which doesn't use redux. + * + * @param props - The properties passed to the DocumentCountChart component. + * @returns The DocumentCountChart component enhanced with automatic analysis start capabilities. + */ +export const DocumentCountChartRedux: FC = memo((props) => { + const dispatch = useAppDispatch(); + const currentSelectedGroup = useCurrentSelectedGroup(); + const currentSelectedSignificantItem = useCurrentSelectedSignificantItem(); + const { documentStats, initialAnalysisStart, isBrushCleared } = useAppSelector( + (s) => s.logRateAnalysis + ); + const { documentCountStats, documentCountStatsCompare } = documentStats; + + const bucketTimestamps = Object.keys(documentCountStats?.buckets ?? {}).map((time) => +time); + const splitBucketTimestamps = Object.keys(documentCountStatsCompare?.buckets ?? {}).map( + (time) => +time + ); + const timeRangeEarliest = Math.min(...[...bucketTimestamps, ...splitBucketTimestamps]); + const timeRangeLatest = Math.max(...[...bucketTimestamps, ...splitBucketTimestamps]); + + if ( + documentCountStats === undefined || + documentCountStats.buckets === undefined || + documentCountStats.interval === undefined || + timeRangeEarliest === undefined || + timeRangeLatest === undefined + ) { + return null; + } + + const documentCountStatsSplitLabel = getDocumentCountStatsSplitLabel( + currentSelectedSignificantItem, + currentSelectedGroup + ); + + const chartPoints: LogRateHistogramItem[] = Object.entries(documentCountStats.buckets).map( + ([time, value]) => ({ + time: +time, + value, + }) + ); + + let chartPointsSplit: LogRateHistogramItem[] | undefined; + if (documentCountStatsCompare?.buckets !== undefined) { + chartPointsSplit = Object.entries(documentCountStatsCompare?.buckets).map(([time, value]) => ({ + time: +time, + value, + })); + } + + return ( + dispatch(brushSelectionUpdate(d))} + isBrushCleared={isBrushCleared} + setAutoRunAnalysisFn={(d: boolean) => dispatch(setAutoRunAnalysis(d))} + /> + ); +}); diff --git a/x-pack/packages/ml/aiops_components/src/document_count_chart/index.ts b/x-pack/packages/ml/aiops_components/src/document_count_chart/index.ts index efde8b0a83cba..0a0993d3757aa 100644 --- a/x-pack/packages/ml/aiops_components/src/document_count_chart/index.ts +++ b/x-pack/packages/ml/aiops_components/src/document_count_chart/index.ts @@ -5,12 +5,6 @@ * 2.0. */ -export { - DocumentCountChart, - DocumentCountChartWithAutoAnalysisStart, -} from './document_count_chart'; -export type { - BrushSelectionUpdateHandler, - BrushSettings, - DocumentCountChartProps, -} from './document_count_chart'; +export { DocumentCountChart } from './document_count_chart'; +export { DocumentCountChartRedux } from './document_count_chart_redux'; +export type { BrushSettings, DocumentCountChartProps } from './document_count_chart'; diff --git a/x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/log_rate_analysis_state_provider.tsx b/x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/log_rate_analysis_state_provider.tsx deleted file mode 100644 index 72289fe0b75cf..0000000000000 --- a/x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/log_rate_analysis_state_provider.tsx +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { - createContext, - useContext, - useMemo, - useState, - type FC, - type PropsWithChildren, - type Dispatch, - type SetStateAction, -} from 'react'; - -import type { SignificantItem } from '@kbn/ml-agg-utils'; -import type { WindowParameters } from '@kbn/aiops-log-rate-analysis'; - -import type { GroupTableItem } from './types'; - -type InitialAnalysisStart = number | WindowParameters | undefined; -type SignificantItemOrNull = SignificantItem | null; -type GroupOrNull = GroupTableItem | null; - -interface LogRateAnalysisState { - autoRunAnalysis: boolean; - setAutoRunAnalysis: Dispatch>; - initialAnalysisStart: InitialAnalysisStart; - setInitialAnalysisStart: Dispatch>; - pinnedSignificantItem: SignificantItemOrNull; - setPinnedSignificantItem: Dispatch>; - pinnedGroup: GroupOrNull; - setPinnedGroup: Dispatch>; - selectedSignificantItem: SignificantItemOrNull; - setSelectedSignificantItem: Dispatch>; - selectedGroup: GroupOrNull; - setSelectedGroup: Dispatch>; - currentSelectedSignificantItem?: SignificantItem; - currentSelectedGroup?: GroupTableItem; - clearAllRowState: () => void; -} - -const LogRateAnalysisStateContext = createContext(undefined); - -/** - * Props for LogRateAnalysisStateProvider. - */ -interface LogRateAnalysisStateProviderProps { - /** The parameters to be used to trigger an analysis. */ - initialAnalysisStart?: InitialAnalysisStart; -} - -/** - * Context provider component that manages and provides global state for Log Rate Analysis. - * This provider handles several pieces of state important for controlling and displaying - * log rate analysis data, such as the control of automatic analysis runs, and the management - * of both pinned and selected significant items and groups. - * - * The state includes mechanisms for setting initial analysis parameters, toggling analysis, - * and managing the current selection and pinned state of significant items and groups. - * - * @param props - Props object containing initial settings for the analysis, - * including children components to be wrapped by the Provider. - * @returns A context provider wrapping children with access to log rate analysis state. - */ -export const LogRateAnalysisStateProvider: FC< - PropsWithChildren -> = (props) => { - const { children, initialAnalysisStart: incomingInitialAnalysisStart } = props; - - const [autoRunAnalysis, setAutoRunAnalysis] = useState(true); - const [initialAnalysisStart, setInitialAnalysisStart] = useState< - number | WindowParameters | undefined - >(incomingInitialAnalysisStart); - - // Row state that will be shared with all components - const [pinnedSignificantItem, setPinnedSignificantItem] = useState(null); - const [pinnedGroup, setPinnedGroup] = 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 currentSelectedSignificantItem = useMemo(() => { - if (selectedSignificantItem) { - return selectedSignificantItem; - } else if (pinnedSignificantItem) { - return pinnedSignificantItem; - } - }, [pinnedSignificantItem, selectedSignificantItem]); - - // If a group is pinned, still overrule with a potentially hovered group. - const currentSelectedGroup = useMemo(() => { - if (selectedGroup) { - return selectedGroup; - } else if (pinnedGroup) { - return pinnedGroup; - } - }, [selectedGroup, pinnedGroup]); - - const contextValue: LogRateAnalysisState = useMemo( - () => ({ - autoRunAnalysis, - setAutoRunAnalysis, - initialAnalysisStart, - setInitialAnalysisStart, - pinnedSignificantItem, - setPinnedSignificantItem, - pinnedGroup, - setPinnedGroup, - selectedSignificantItem, - setSelectedSignificantItem, - selectedGroup, - setSelectedGroup, - currentSelectedSignificantItem, - currentSelectedGroup, - clearAllRowState: () => { - setPinnedSignificantItem(null); - setPinnedGroup(null); - setSelectedSignificantItem(null); - setSelectedGroup(null); - }, - }), - [ - autoRunAnalysis, - setAutoRunAnalysis, - initialAnalysisStart, - setInitialAnalysisStart, - pinnedSignificantItem, - setPinnedSignificantItem, - pinnedGroup, - setPinnedGroup, - selectedSignificantItem, - setSelectedSignificantItem, - selectedGroup, - setSelectedGroup, - currentSelectedSignificantItem, - currentSelectedGroup, - ] - ); - - return ( - // Provider managing the state - - {children} - - ); -}; - -/** - * Custom hook for accessing the state of log rate analysis from the LogRateAnalysisStateContext. - * This hook must be used within a component that is a descendant of the LogRateAnalysisStateContext provider. - * - * @returns The current state of the log rate analysis. - * @throws Throws an error if the hook is used outside of its Provider context. - */ -export const useLogRateAnalysisStateContext = () => { - const logRateAnalysisState = useContext(LogRateAnalysisStateContext); - - // If `undefined`, throw an error. - if (logRateAnalysisState === undefined) { - throw new Error('useLogRateAnalysisStateContext was used outside of its Provider'); - } - - return logRateAnalysisState; -}; diff --git a/x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx b/x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx index 03ebe7b57ed03..4f08a259bbac2 100644 --- a/x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx +++ b/x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx @@ -30,12 +30,12 @@ import { useAnimatedProgressBarBackground } from './use_animated_progress_bar_ba * Props for ProgressControlProps */ interface ProgressControlProps { - isBrushCleared: boolean; progress: number; progressMessage: string; onRefresh: () => void; onCancel: () => void; onReset: () => void; + isBrushCleared: boolean; isRunning: boolean; shouldRerunAnalysis: boolean; runAnalysisDisabled?: boolean; @@ -43,7 +43,7 @@ interface ProgressControlProps { /** * ProgressControls React Component - * Component with ability to Run & cancel analysis + * Component with ability to run & cancel analysis * by default uses `Baseline` and `Deviation` for the badge name * * @param props ProgressControls component props @@ -52,12 +52,12 @@ interface ProgressControlProps { export const ProgressControls: FC> = (props) => { const { children, - isBrushCleared, progress, progressMessage, onRefresh, onCancel, onReset, + isBrushCleared, isRunning, shouldRerunAnalysis, runAnalysisDisabled = false, @@ -66,6 +66,7 @@ export const ProgressControls: FC> = (pr const progressOutput = Math.round(progress * 100); const { euiTheme } = useEuiTheme(); + const runningProgressBarStyles = useAnimatedProgressBarBackground(euiTheme.colors.success); const analysisCompleteStyle = { display: 'none' }; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/api/actions.ts b/x-pack/packages/ml/aiops_log_rate_analysis/api/actions.ts deleted file mode 100644 index 4c17f28c932b3..0000000000000 --- a/x-pack/packages/ml/aiops_log_rate_analysis/api/actions.ts +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { - SignificantItem, - SignificantItemHistogram, - SignificantItemGroup, - SignificantItemGroupHistogram, -} from '@kbn/ml-agg-utils'; - -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', - ADD_SIGNIFICANT_TERMS_GROUP_HISTOGRAM: 'add_significant_terms_group_histogram', - ADD_ERROR: 'add_error', - PING: 'ping', - RESET_ALL: 'reset_all', - RESET_ERRORS: 'reset_errors', - RESET_GROUPS: 'reset_groups', - SET_ZERO_DOCS_FALLBACK: 'set_zero_docs_fallback', - UPDATE_LOADING_STATE: 'update_loading_state', -} as const; -export type ApiActionName = typeof API_ACTION_NAME[keyof typeof API_ACTION_NAME]; - -interface ApiActionAddSignificantItems { - type: typeof API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS; - payload: SignificantItem[]; -} - -export function addSignificantItemsAction( - payload: ApiActionAddSignificantItems['payload'] -): ApiActionAddSignificantItems { - return { - type: API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS, - payload, - }; -} - -interface ApiActionAddSignificantItemsHistogram { - type: typeof API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_HISTOGRAM; - payload: SignificantItemHistogram[]; -} - -export function addSignificantItemsHistogramAction( - payload: ApiActionAddSignificantItemsHistogram['payload'] -): ApiActionAddSignificantItemsHistogram { - return { - type: API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_HISTOGRAM, - payload, - }; -} - -interface ApiActionAddSignificantItemsGroup { - type: typeof API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_GROUP; - payload: SignificantItemGroup[]; -} - -export function addSignificantItemsGroupAction( - payload: ApiActionAddSignificantItemsGroup['payload'] -): ApiActionAddSignificantItemsGroup { - return { - type: API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_GROUP, - payload, - }; -} - -interface ApiActionAddSignificantItemsGroupHistogram { - type: typeof API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_GROUP_HISTOGRAM; - payload: SignificantItemGroupHistogram[]; -} - -export function addSignificantItemsGroupHistogramAction( - payload: ApiActionAddSignificantItemsGroupHistogram['payload'] -): ApiActionAddSignificantItemsGroupHistogram { - return { - type: API_ACTION_NAME.ADD_SIGNIFICANT_ITEMS_GROUP_HISTOGRAM, - payload, - }; -} - -interface ApiActionAddError { - type: typeof API_ACTION_NAME.ADD_ERROR; - payload: string; -} - -export function addErrorAction(payload: ApiActionAddError['payload']): ApiActionAddError { - return { - type: API_ACTION_NAME.ADD_ERROR, - payload, - }; -} - -interface ApiActionResetErrors { - type: typeof API_ACTION_NAME.RESET_ERRORS; -} - -export function resetErrorsAction() { - return { - type: API_ACTION_NAME.RESET_ERRORS, - }; -} - -interface ApiActionPing { - type: typeof API_ACTION_NAME.PING; -} - -export function pingAction(): ApiActionPing { - return { type: API_ACTION_NAME.PING }; -} - -interface ApiActionResetAll { - type: typeof API_ACTION_NAME.RESET_ALL; -} - -export function resetAllAction(): ApiActionResetAll { - return { type: API_ACTION_NAME.RESET_ALL }; -} - -interface ApiActionResetGroups { - type: typeof API_ACTION_NAME.RESET_GROUPS; -} - -export function resetGroupsAction(): ApiActionResetGroups { - return { type: API_ACTION_NAME.RESET_GROUPS }; -} - -interface ApiActionUpdateLoadingState { - type: typeof API_ACTION_NAME.UPDATE_LOADING_STATE; - payload: { - ccsWarning: boolean; - loaded: number; - loadingState: string; - remainingFieldCandidates?: string[]; - groupsMissing?: boolean; - }; -} - -export function updateLoadingStateAction( - payload: ApiActionUpdateLoadingState['payload'] -): ApiActionUpdateLoadingState { - return { - type: API_ACTION_NAME.UPDATE_LOADING_STATE, - payload, - }; -} - -interface ApiActionSetZeroDocsFallback { - type: typeof API_ACTION_NAME.SET_ZERO_DOCS_FALLBACK; - payload: boolean; -} - -export function setZeroDocsFallback( - payload: ApiActionSetZeroDocsFallback['payload'] -): ApiActionSetZeroDocsFallback { - return { - type: API_ACTION_NAME.SET_ZERO_DOCS_FALLBACK, - payload, - }; -} - -export type AiopsLogRateAnalysisApiAction = - | ApiActionAddSignificantItems - | ApiActionAddSignificantItemsGroup - | ApiActionAddSignificantItemsHistogram - | ApiActionAddSignificantItemsGroupHistogram - | ApiActionAddError - | ApiActionPing - | ApiActionResetAll - | ApiActionResetErrors - | ApiActionResetGroups - | ApiActionUpdateLoadingState - | ApiActionSetZeroDocsFallback; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/api/stream_reducer.test.ts b/x-pack/packages/ml/aiops_log_rate_analysis/api/stream_reducer.test.ts index ea1c33e569fce..42a522853e443 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/api/stream_reducer.test.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/api/stream_reducer.test.ts @@ -9,19 +9,20 @@ import { significantTerms } from '@kbn/aiops-test-utils/artificial_logs/signific import { finalSignificantItemGroups } from '@kbn/aiops-test-utils/artificial_logs/final_significant_item_groups'; import { - addSignificantItemsAction, - addSignificantItemsGroupAction, - resetAllAction, - resetGroupsAction, - updateLoadingStateAction, -} from './actions'; -import { initialState, streamReducer } from './stream_reducer'; + addSignificantItems, + addSignificantItemsGroup, + resetAll, + resetGroups, + updateLoadingState, + getDefaultState, + streamReducer, +} from './stream_reducer'; describe('streamReducer', () => { it('updates loading state', () => { const state = streamReducer( - initialState, - updateLoadingStateAction({ ccsWarning: true, loaded: 50, loadingState: 'Loaded 50%' }) + getDefaultState(), + updateLoadingState({ ccsWarning: true, loaded: 50, loadingState: 'Loaded 50%' }) ); expect(state).toEqual({ @@ -37,8 +38,8 @@ describe('streamReducer', () => { it('adds significant item, then resets all state again', () => { const state1 = streamReducer( - initialState, - addSignificantItemsAction([ + getDefaultState(), + addSignificantItems([ { key: 'the-field-name:the-field-value', type: 'keyword', @@ -57,26 +58,23 @@ describe('streamReducer', () => { expect(state1.significantItems).toHaveLength(1); - const state2 = streamReducer(state1, resetAllAction()); + const state2 = streamReducer(state1, resetAll()); expect(state2.significantItems).toHaveLength(0); }); it('adds significant items and groups, then resets groups only', () => { - const state1 = streamReducer(initialState, addSignificantItemsAction(significantTerms)); + const state1 = streamReducer(getDefaultState(), addSignificantItems(significantTerms)); expect(state1.significantItems).toHaveLength(4); expect(state1.significantItemsGroups).toHaveLength(0); - const state2 = streamReducer( - state1, - addSignificantItemsGroupAction(finalSignificantItemGroups) - ); + const state2 = streamReducer(state1, addSignificantItemsGroup(finalSignificantItemGroups)); expect(state2.significantItems).toHaveLength(4); expect(state2.significantItemsGroups).toHaveLength(4); - const state3 = streamReducer(state2, resetGroupsAction()); + const state3 = streamReducer(state2, resetGroups()); expect(state3.significantItems).toHaveLength(4); expect(state3.significantItemsGroups).toHaveLength(0); diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/api/stream_reducer.ts b/x-pack/packages/ml/aiops_log_rate_analysis/api/stream_reducer.ts index c4bde0b90c0fd..93da216fe59ae 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/api/stream_reducer.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/api/stream_reducer.ts @@ -5,10 +5,15 @@ * 2.0. */ -import type { SignificantItem, SignificantItemGroup } from '@kbn/ml-agg-utils'; +import type { PayloadAction } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; -import type { AiopsLogRateAnalysisApiAction } from './actions'; -import { API_ACTION_NAME } from './actions'; +import type { + SignificantItem, + SignificantItemGroup, + SignificantItemHistogram, + SignificantItemGroupHistogram, +} from '@kbn/ml-agg-utils'; export interface StreamState { ccsWarning: boolean; @@ -22,7 +27,7 @@ export interface StreamState { zeroDocsFallback: boolean; } -export const initialState: StreamState = { +export const getDefaultState = (): StreamState => ({ ccsWarning: false, significantItems: [], significantItemsGroups: [], @@ -30,50 +35,87 @@ export const initialState: StreamState = { loaded: 0, loadingState: '', zeroDocsFallback: false, -}; +}); -export function streamReducer( - state: StreamState, - action: AiopsLogRateAnalysisApiAction -): StreamState { - switch (action.type) { - 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) => { +export const logRateAnalysisResultsSlice = createSlice({ + name: 'logRateAnalysisResults', + initialState: getDefaultState(), + reducers: { + addSignificantItems: (state, action: PayloadAction) => { + state.significantItems.push(...action.payload); + }, + addSignificantItemsHistogram: (state, action: PayloadAction) => { + state.significantItems = state.significantItems.map((cp) => { const cpHistogram = action.payload.find( (h) => h.fieldName === cp.fieldName && h.fieldValue === cp.fieldValue ); - if (cpHistogram) { - cp.histogram = cpHistogram.histogram; - } - return cp; + return { + ...cp, + ...(cpHistogram ? { histogram: cpHistogram.histogram } : {}), + }; }); - 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) => { + }, + addSignificantItemsGroup: (state, action: PayloadAction) => { + state.significantItemsGroups = action.payload; + }, + addSignificantItemsGroupHistogram: ( + state, + action: PayloadAction + ) => { + state.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, 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, significantItemsGroups: [] }; - case API_ACTION_NAME.RESET_ALL: - return initialState; - case API_ACTION_NAME.UPDATE_LOADING_STATE: + }, + addError: (state, action: PayloadAction) => { + state.errors.push(action.payload); + }, + ping: () => {}, + resetErrors: (state) => { + state.errors = []; + }, + resetGroups: (state) => { + state.significantItemsGroups = []; + }, + resetAll: () => getDefaultState(), + updateLoadingState: ( + state, + action: PayloadAction<{ + ccsWarning: boolean; + loaded: number; + loadingState: string; + remainingFieldCandidates?: string[]; + groupsMissing?: boolean; + }> + ) => { return { ...state, ...action.payload }; - case API_ACTION_NAME.SET_ZERO_DOCS_FALLBACK: - return { ...state, zeroDocsFallback: action.payload }; - default: - return state; - } -} + }, + setZeroDocsFallback: (state, action: PayloadAction) => { + state.zeroDocsFallback = action.payload; + }, + }, +}); + +export const streamReducer = logRateAnalysisResultsSlice.reducer; +export const streamReducerActions = logRateAnalysisResultsSlice.actions; + +type StreamReducerActions = typeof streamReducerActions; +export type ApiActionName = keyof StreamReducerActions; +export type AiopsLogRateAnalysisApiAction = ReturnType; + +export const { + addError, + addSignificantItems, + addSignificantItemsGroup, + addSignificantItemsGroupHistogram, + addSignificantItemsHistogram, + ping, + resetAll, + resetErrors, + resetGroups, + setZeroDocsFallback, + updateLoadingState, +} = logRateAnalysisResultsSlice.actions; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/index.ts b/x-pack/packages/ml/aiops_log_rate_analysis/index.ts index 8ae028101917d..c9abbfdb625b8 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/index.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/index.ts @@ -9,7 +9,7 @@ export { LOG_RATE_ANALYSIS_HIGHLIGHT_COLOR } from './constants'; export { getLogRateAnalysisType } from './get_log_rate_analysis_type'; export { LOG_RATE_ANALYSIS_TYPE, type LogRateAnalysisType } from './log_rate_analysis_type'; export type { LogRateHistogramItem } from './log_rate_histogram_item'; -export type { DocumentCountStatsChangePoint } from './types'; +export type { DocumentCountStats, DocumentStats, DocumentCountStatsChangePoint } from './types'; export type { WindowParameters } from './window_parameters'; export { getSnappedTimestamps } from './get_snapped_timestamps'; export { getSnappedWindowParameters } from './get_snapped_window_parameters'; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/field_caps_pgbench.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/field_caps_pgbench.ts index a4d85d8673971..bc3aec796ebe6 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/field_caps_pgbench.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/__mocks__/field_caps_pgbench.ts @@ -8,6 +8,16 @@ export const fieldCapsPgBenchMock = { indices: ['.ds-filebeat-8.2.0-2022.06.07-000082'], fields: { + // The next two fields are not in the original field caps response, + // but are added here to test the logic to ignore fields that are not + // in the white list. It's based on a real world example where the mapping + // included a double mapping of text+integer. + ignore_this_text_field: { + text: { type: 'text', metadata_field: false, searchable: true, aggregatable: false }, + }, + 'ignore_this_text_field.int': { + integer: { type: 'integer', metadata_field: false, searchable: true, aggregatable: true }, + }, 'kubernetes.node.uid': { keyword: { type: 'keyword', metadata_field: false, searchable: true, aggregatable: true }, }, diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_index_info.ts b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_index_info.ts index 1bb5b701fdd17..458fc630b5009 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_index_info.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/queries/fetch_index_info.ts @@ -17,6 +17,12 @@ import { getTotalDocCountRequest } from './get_total_doc_count_request'; // TODO Consolidate with duplicate `fetchPValues` in // `x-pack/plugins/observability_solution/apm/server/routes/correlations/queries/fetch_duration_field_candidates.ts` +// Supported field names for text fields for log rate analysis. +// If we analyse all detected text fields, we might run into performance +// issues with the `categorize_text` aggregation. Until this is resolved, we +// rely on a predefined white list of supported text fields. +const TEXT_FIELD_WHITE_LIST = ['message', 'error.message']; + const SUPPORTED_ES_FIELD_TYPES = [ ES_FIELD_TYPES.KEYWORD, ES_FIELD_TYPES.IP, @@ -76,7 +82,7 @@ export const fetchIndexInfo = async ( acceptableFields.add(key); } - if (isTextField) { + if (isTextField && TEXT_FIELD_WHITE_LIST.includes(key)) { acceptableTextFields.add(key); } diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/hooks.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/hooks.ts new file mode 100644 index 0000000000000..4652d604c5d61 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/hooks.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { TypedUseSelectorHook } from 'react-redux'; +import { useDispatch, useSelector, useStore } from 'react-redux'; +import type { AppDispatch, AppStore, RootState } from './store'; + +// Improves TypeScript support compared to plain `useDispatch` and `useSelector` +export const useAppDispatch: () => AppDispatch = useDispatch; +export const useAppSelector: TypedUseSelectorHook = useSelector; +export const useAppStore: () => AppStore = useStore; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/index.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/index.ts new file mode 100644 index 0000000000000..8808a9311e2c8 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/index.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { + brushSelectionUpdate, + clearSelection, + setAnalysisType, + setAutoRunAnalysis, + setDocumentCountChartData, + setInitialAnalysisStart, + setIsBrushCleared, + setStickyHistogram, + setWindowParameters, + type BrushSelectionUpdatePayload, +} from './log_rate_analysis_slice'; +export { + clearAllRowState, + setPinnedGroup, + setPinnedSignificantItem, + setSelectedGroup, + setSelectedSignificantItem, +} from './log_rate_analysis_table_row_slice'; +export { LogRateAnalysisReduxProvider } from './store'; +export { useAppDispatch, useAppSelector, useAppStore } from './hooks'; +export { useCurrentSelectedGroup } from './use_current_selected_group'; +export { useCurrentSelectedSignificantItem } from './use_current_selected_significant_item'; +export type { GroupTableItem, GroupTableItemGroup, TableItemAction } from './types'; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_slice.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_slice.ts new file mode 100644 index 0000000000000..30cc7b0c30fb4 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_slice.ts @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PayloadAction } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; + +import type { WindowParameters } from '../window_parameters'; + +import type { LogRateAnalysisType } from '../log_rate_analysis_type'; +import { LOG_RATE_ANALYSIS_TYPE } from '../log_rate_analysis_type'; + +import type { DocumentStats } from '../types'; + +export type InitialAnalysisStart = number | WindowParameters | undefined; + +/** + * Payload for brushSelectionUpdate action + */ +export interface BrushSelectionUpdatePayload { + /** The window parameters to update the analysis with */ + windowParameters: WindowParameters; + /** Flag to force the update */ + force: boolean; + + analysisType: LogRateAnalysisType; +} + +export interface LogRateAnalysisState { + analysisType: LogRateAnalysisType; + autoRunAnalysis: boolean; + initialAnalysisStart: InitialAnalysisStart; + isBrushCleared: boolean; + stickyHistogram: boolean; + windowParameters?: WindowParameters; + earliest?: number; + latest?: number; + intervalMs?: number; + documentStats: DocumentStats; +} + +function getDefaultState(): LogRateAnalysisState { + return { + analysisType: LOG_RATE_ANALYSIS_TYPE.SPIKE, + autoRunAnalysis: true, + initialAnalysisStart: undefined, + isBrushCleared: true, + documentStats: { + sampleProbability: 1, + totalCount: 0, + }, + // Default to false for now, until page restructure work to enable smooth sticky histogram is done + stickyHistogram: false, + }; +} + +export const logRateAnalysisSlice = createSlice({ + name: 'logRateAnalysis', + initialState: getDefaultState(), + reducers: { + brushSelectionUpdate: ( + state: LogRateAnalysisState, + action: PayloadAction + ) => { + if (!state.isBrushCleared || action.payload.force) { + state.windowParameters = action.payload.windowParameters; + } + if (action.payload.force) { + state.isBrushCleared = false; + } + state.analysisType = action.payload.analysisType; + }, + clearSelection: (state: LogRateAnalysisState) => { + state.windowParameters = undefined; + state.isBrushCleared = true; + state.initialAnalysisStart = undefined; + }, + setAnalysisType: (state: LogRateAnalysisState, action: PayloadAction) => { + state.analysisType = action.payload; + }, + setAutoRunAnalysis: (state: LogRateAnalysisState, action: PayloadAction) => { + state.autoRunAnalysis = action.payload; + }, + setDocumentCountChartData: ( + state: LogRateAnalysisState, + action: PayloadAction<{ + earliest?: number; + latest?: number; + intervalMs?: number; + documentStats: DocumentStats; + }> + ) => { + state.earliest = action.payload.earliest; + state.latest = action.payload.latest; + state.intervalMs = action.payload.intervalMs; + state.documentStats = action.payload.documentStats; + }, + setInitialAnalysisStart: ( + state: LogRateAnalysisState, + action: PayloadAction + ) => { + state.initialAnalysisStart = action.payload; + }, + setIsBrushCleared: (state: LogRateAnalysisState, action: PayloadAction) => { + state.isBrushCleared = action.payload; + }, + setStickyHistogram: (state: LogRateAnalysisState, action: PayloadAction) => { + state.stickyHistogram = action.payload; + }, + setWindowParameters: ( + state: LogRateAnalysisState, + action: PayloadAction + ) => { + state.windowParameters = action.payload; + state.isBrushCleared = action.payload === undefined; + }, + }, +}); + +// Action creators are generated for each case reducer function +export const { + brushSelectionUpdate, + clearSelection, + setAnalysisType, + setAutoRunAnalysis, + setDocumentCountChartData, + setInitialAnalysisStart, + setIsBrushCleared, + setStickyHistogram, + setWindowParameters, +} = logRateAnalysisSlice.actions; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_row_slice.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_row_slice.ts new file mode 100644 index 0000000000000..3da98e4cc80ff --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/log_rate_analysis_table_row_slice.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PayloadAction } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; + +import type { SignificantItem } from '@kbn/ml-agg-utils'; + +import type { GroupTableItem } from './types'; + +type SignificantItemOrNull = SignificantItem | null; +type GroupOrNull = GroupTableItem | null; + +export interface LogRateAnalysisTableRowState { + pinnedGroup: GroupOrNull; + pinnedSignificantItem: SignificantItemOrNull; + selectedGroup: GroupOrNull; + selectedSignificantItem: SignificantItemOrNull; +} + +function getDefaultState(): LogRateAnalysisTableRowState { + return { + pinnedGroup: null, + pinnedSignificantItem: null, + selectedGroup: null, + selectedSignificantItem: null, + }; +} + +export const logRateAnalysisTableRowSlice = createSlice({ + name: 'logRateAnalysisTableRow', + initialState: getDefaultState(), + reducers: { + clearAllRowState: (state: LogRateAnalysisTableRowState) => { + state.pinnedGroup = null; + state.pinnedSignificantItem = null; + state.selectedGroup = null; + state.selectedSignificantItem = null; + }, + setPinnedGroup: (state: LogRateAnalysisTableRowState, action: PayloadAction) => { + state.pinnedGroup = action.payload; + }, + setPinnedSignificantItem: ( + state: LogRateAnalysisTableRowState, + action: PayloadAction + ) => { + state.pinnedSignificantItem = action.payload; + }, + setSelectedGroup: (state: LogRateAnalysisTableRowState, action: PayloadAction) => { + state.selectedGroup = action.payload; + }, + setSelectedSignificantItem: ( + state: LogRateAnalysisTableRowState, + action: PayloadAction + ) => { + state.selectedSignificantItem = action.payload; + }, + }, +}); + +// Action creators are generated for each case reducer function +export const { + clearAllRowState, + setPinnedGroup, + setPinnedSignificantItem, + setSelectedGroup, + setSelectedSignificantItem, +} = logRateAnalysisTableRowSlice.actions; diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx b/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx new file mode 100644 index 0000000000000..439c80da5ac33 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/store.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo, type FC, type PropsWithChildren } from 'react'; +import { configureStore } from '@reduxjs/toolkit'; +import { Provider } from 'react-redux'; +import useMount from 'react-use/lib/useMount'; + +import { streamSlice } from '@kbn/ml-response-stream/client'; + +import { logRateAnalysisResultsSlice } from '../api/stream_reducer'; + +import { logRateAnalysisSlice } from './log_rate_analysis_slice'; +import { logRateAnalysisTableRowSlice } from './log_rate_analysis_table_row_slice'; +import type { InitialAnalysisStart } from './log_rate_analysis_slice'; + +const getReduxStore = () => + configureStore({ + reducer: { + // General page state + logRateAnalysis: logRateAnalysisSlice.reducer, + // Analysis results + logRateAnalysisResults: logRateAnalysisResultsSlice.reducer, + // Handles running the analysis + logRateAnalysisStream: streamSlice.reducer, + // Handles hovering and pinning table rows + logRateAnalysisTableRow: logRateAnalysisTableRowSlice.reducer, + }, + }); + +interface LogRateAnalysisReduxProviderProps { + initialAnalysisStart?: InitialAnalysisStart; +} + +export const LogRateAnalysisReduxProvider: FC< + PropsWithChildren +> = ({ children, initialAnalysisStart }) => { + const store = useMemo(getReduxStore, []); + + useMount(() => { + if (initialAnalysisStart) { + store.dispatch(logRateAnalysisSlice.actions.setInitialAnalysisStart(initialAnalysisStart)); + } + }); + + return {children}; +}; + +// Infer the `RootState` and `AppDispatch` types from the store itself +export type AppStore = ReturnType; +export type RootState = ReturnType; +export type AppDispatch = AppStore['dispatch']; diff --git a/x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/types.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/types.ts similarity index 100% rename from x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/types.ts rename to x-pack/packages/ml/aiops_log_rate_analysis/state/types.ts diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_group.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_group.ts new file mode 100644 index 0000000000000..9653691d3efd4 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_group.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createSelector } from '@reduxjs/toolkit'; + +import type { RootState } from './store'; +import { useAppSelector } from './hooks'; + +const selectSelectedGroup = (s: RootState) => s.logRateAnalysisTableRow.selectedGroup; +const selectPinnedGroup = (s: RootState) => s.logRateAnalysisTableRow.pinnedGroup; +const selectCurrentSelectedGroup = createSelector( + selectSelectedGroup, + selectPinnedGroup, + (selectedGroup, pinnedGroup) => { + if (selectedGroup) { + return selectedGroup; + } else if (pinnedGroup) { + return pinnedGroup; + } + } +); + +export const useCurrentSelectedGroup = () => useAppSelector(selectCurrentSelectedGroup); diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_significant_item.ts b/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_significant_item.ts new file mode 100644 index 0000000000000..d189d16fc2fa0 --- /dev/null +++ b/x-pack/packages/ml/aiops_log_rate_analysis/state/use_current_selected_significant_item.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 { createSelector } from '@reduxjs/toolkit'; + +import type { RootState } from './store'; +import { useAppSelector } from './hooks'; + +const selectSelectedSignificantItem = (s: RootState) => + s.logRateAnalysisTableRow.selectedSignificantItem; +const selectPinnedSignificantItem = (s: RootState) => + s.logRateAnalysisTableRow.pinnedSignificantItem; +const selectCurrentSelectedSignificantItem = createSelector( + selectSelectedSignificantItem, + selectPinnedSignificantItem, + (selectedSignificantItem, pinnedSignificantItem) => { + if (selectedSignificantItem) { + return selectedSignificantItem; + } else if (pinnedSignificantItem) { + return pinnedSignificantItem; + } + } +); + +export const useCurrentSelectedSignificantItem = () => + useAppSelector(selectCurrentSelectedSignificantItem); diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/tsconfig.json b/x-pack/packages/ml/aiops_log_rate_analysis/tsconfig.json index 97a23d3d64c2e..66a4a2affbae1 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/tsconfig.json +++ b/x-pack/packages/ml/aiops_log_rate_analysis/tsconfig.json @@ -29,5 +29,6 @@ "@kbn/field-types", "@kbn/ml-chi2test", "@kbn/ml-string-hash", + "@kbn/ml-response-stream", ] } diff --git a/x-pack/packages/ml/aiops_log_rate_analysis/types.ts b/x-pack/packages/ml/aiops_log_rate_analysis/types.ts index af8d92ad321df..3cd2f5967a0e9 100644 --- a/x-pack/packages/ml/aiops_log_rate_analysis/types.ts +++ b/x-pack/packages/ml/aiops_log_rate_analysis/types.ts @@ -55,3 +55,37 @@ export interface DocumentCountStatsChangePoint { /** The type of change point. */ type: string; } + +/** + * Represents the document count statistics for a given time range. + */ +export interface DocumentCountStats { + /** The time interval in milliseconds. */ + interval?: number; + /** The document count per time bucket. */ + buckets?: { [key: string]: number }; + /** The change point in the document count statistics. */ + changePoint?: DocumentCountStatsChangePoint; + /** The earliest timestamp in the time range. */ + timeRangeEarliest?: number; + /** The latest timestamp in the time range. */ + timeRangeLatest?: number; + /** The total document count. */ + totalCount: number; + /** The timestamp of the last document in the time range. */ + lastDocTimeStampMs?: number; +} + +/** + * Represents the overall document stats. + */ +export interface DocumentStats { + /** The probability of sampling. */ + sampleProbability: number; + /** The total document count. */ + totalCount: number; + /** The document count statistics. */ + documentCountStats?: DocumentCountStats; + /** The document count statistics for comparison. */ + documentCountStatsCompare?: DocumentCountStats; +} diff --git a/x-pack/packages/ml/inference_integration_flyout/components/inference_flyout_wrapper.tsx b/x-pack/packages/ml/inference_integration_flyout/components/inference_flyout_wrapper.tsx index d5e17de1ef4c7..d66540feccff1 100644 --- a/x-pack/packages/ml/inference_integration_flyout/components/inference_flyout_wrapper.tsx +++ b/x-pack/packages/ml/inference_integration_flyout/components/inference_flyout_wrapper.tsx @@ -78,7 +78,7 @@ export interface SaveMappingOnClick { taskType: InferenceTaskType, modelConfig: ModelConfig ) => void; - isCreateInferenceApiLoading: boolean; + isCreateInferenceApiLoading?: boolean; } export interface DocumentationProps { elserv2documentationUrl?: string; diff --git a/x-pack/packages/ml/inference_integration_flyout/types.ts b/x-pack/packages/ml/inference_integration_flyout/types.ts index 849706b12a84f..900778e254686 100644 --- a/x-pack/packages/ml/inference_integration_flyout/types.ts +++ b/x-pack/packages/ml/inference_integration_flyout/types.ts @@ -54,7 +54,7 @@ export interface ElasticsearchService { export enum Service { cohere = 'cohere', elser = 'elser', - huggingFace = 'huggingFace', + huggingFace = 'hugging_face', openai = 'openai', elasticsearch = 'elasticsearch', } diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.test.tsx index 78d5ed68787bf..1dad2a79d9651 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.test.tsx @@ -12,6 +12,7 @@ import React from 'react'; import { SAME_FAMILY } from '../../data_quality_panel/same_family/translations'; import { eventCategory, + someField, eventCategoryWithUnallowedValues, } from '../../mock/enriched_field_metadata/mock_enriched_field_metadata'; import { TestProviders } from '../../mock/test_providers/test_providers'; @@ -261,15 +262,9 @@ describe('getCommonTableColumns', () => { const columns = getCommonTableColumns(); const descriptionolumnRender = columns[5].render; - const withDescription: EnrichedFieldMetadata = { - ...eventCategory, - description: undefined, - }; - render( - {descriptionolumnRender != null && - descriptionolumnRender(withDescription.description, withDescription)} + {descriptionolumnRender != null && descriptionolumnRender(undefined, someField)} ); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.tsx index cc3ccf395bc9e..2e0d2506d6e0f 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_common_table_columns/index.tsx @@ -44,19 +44,25 @@ export const getCommonTableColumns = (): Array< { field: 'indexFieldType', name: i18n.INDEX_MAPPING_TYPE_ACTUAL, - render: (_, x) => - x.type != null && x.indexFieldType !== x.type ? ( - getIsInSameFamily({ ecsExpectedType: x.type, type: x.indexFieldType }) ? ( + render: (_, x) => { + // if custom field or ecs based field with mapping match + if (!x.hasEcsMetadata || x.indexFieldType === x.type) { + return {x.indexFieldType}; + } + + // mapping mismatch due to same family + if (getIsInSameFamily({ ecsExpectedType: x.type, type: x.indexFieldType })) { + return (
{x.indexFieldType}
- ) : ( - {x.indexFieldType} - ) - ) : ( - {x.indexFieldType} - ), + ); + } + + // mapping mismatch + return {x.indexFieldType}; + }, sortable: true, truncateText: false, width: '15%', diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.test.tsx index bbc026ba0b364..367ff38fa1093 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.test.tsx @@ -12,8 +12,8 @@ import React from 'react'; import { SAME_FAMILY } from '../../data_quality_panel/same_family/translations'; import { TestProviders } from '../../mock/test_providers/test_providers'; import { eventCategory } from '../../mock/enriched_field_metadata/mock_enriched_field_metadata'; -import { EnrichedFieldMetadata } from '../../types'; -import { EMPTY_PLACEHOLDER, getIncompatibleMappingsTableColumns } from '.'; +import { EcsBasedFieldMetadata } from '../../types'; +import { getIncompatibleMappingsTableColumns } from '.'; describe('getIncompatibleMappingsTableColumns', () => { test('it returns the expected column configuration', () => { @@ -65,19 +65,6 @@ describe('getIncompatibleMappingsTableColumns', () => { expect(screen.getByTestId('codeSuccess')).toHaveTextContent(expected); }); - - test('it renders an empty placeholder when type is undefined', () => { - const columns = getIncompatibleMappingsTableColumns(); - const typeColumnRender = columns[1].render; - - render( - - {typeColumnRender != null && typeColumnRender(undefined, eventCategory)} - - ); - - expect(screen.getByTestId('codeSuccess')).toHaveTextContent(EMPTY_PLACEHOLDER); - }); }); describe('indexFieldType column render()', () => { @@ -88,7 +75,7 @@ describe('getIncompatibleMappingsTableColumns', () => { const columns = getIncompatibleMappingsTableColumns(); const indexFieldTypeColumnRender = columns[2].render; - const withTypeMismatchSameFamily: EnrichedFieldMetadata = { + const withTypeMismatchSameFamily: EcsBasedFieldMetadata = { ...eventCategory, // `event.category` is a `keyword` per the ECS spec indexFieldType, // this index has a mapping of `wildcard` instead of `keyword` isInSameFamily: true, // `wildcard` and `keyword` are in the same family @@ -121,7 +108,7 @@ describe('getIncompatibleMappingsTableColumns', () => { const columns = getIncompatibleMappingsTableColumns(); const indexFieldTypeColumnRender = columns[2].render; - const withTypeMismatchDifferentFamily: EnrichedFieldMetadata = { + const withTypeMismatchDifferentFamily: EcsBasedFieldMetadata = { ...eventCategory, // `event.category` is a `keyword` per the ECS spec indexFieldType, // this index has a mapping of `text` instead of `keyword` isInSameFamily: false, // `text` and `wildcard` are not in the same family diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.tsx index 4a6c042f67e96..70078ab7ccc16 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/get_incompatible_mappings_table_columns/index.tsx @@ -11,12 +11,12 @@ import React from 'react'; import { SameFamily } from '../../data_quality_panel/same_family'; import { CodeDanger, CodeSuccess } from '../../styles'; import * as i18n from '../translations'; -import type { EnrichedFieldMetadata } from '../../types'; +import type { EcsBasedFieldMetadata } from '../../types'; export const EMPTY_PLACEHOLDER = '--'; export const getIncompatibleMappingsTableColumns = (): Array< - EuiTableFieldDataColumnType + EuiTableFieldDataColumnType > => [ { field: 'indexFieldName', @@ -28,11 +28,7 @@ export const getIncompatibleMappingsTableColumns = (): Array< { field: 'type', name: i18n.ECS_MAPPING_TYPE_EXPECTED, - render: (type: string) => ( - - {type != null ? type : EMPTY_PLACEHOLDER} - - ), + render: (type: string) => {type}, sortable: true, truncateText: false, width: '25%', diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/helpers.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/helpers.test.tsx index 7c72289290942..d9897cfa3d399 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/helpers.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/helpers.test.tsx @@ -10,7 +10,6 @@ import { omit } from 'lodash/fp'; import React from 'react'; import { - EMPTY_PLACEHOLDER, getCustomTableColumns, getEcsCompliantTableColumns, getIncompatibleValuesTableColumns, @@ -117,31 +116,6 @@ describe('helpers', () => { expect(screen.queryByTestId('typePlaceholder')).not.toBeInTheDocument(); }); }); - - describe('when `type` is undefined', () => { - beforeEach(() => { - const withUndefinedType = { - ...eventCategory, - type: undefined, // <-- - }; - const columns = getEcsCompliantTableColumns(); - const typeRender = columns[1].render; - - render( - - <>{typeRender != null && typeRender(withUndefinedType.type, withUndefinedType)} - - ); - }); - - test('it does NOT render the `type`', () => { - expect(screen.queryByTestId('type')).not.toBeInTheDocument(); - }); - - test('it renders the placeholder', () => { - expect(screen.getByTestId('typePlaceholder')).toHaveTextContent(EMPTY_PLACEHOLDER); - }); - }); }); describe('allowed values render()', () => { @@ -230,35 +204,6 @@ describe('helpers', () => { expect(screen.queryByTestId('emptyPlaceholder')).not.toBeInTheDocument(); }); }); - - describe('when `description` is undefined', () => { - const withUndefinedDescription = { - ...eventCategory, - description: undefined, // <-- - }; - - beforeEach(() => { - const columns = getEcsCompliantTableColumns(); - const descriptionRender = columns[3].render; - - render( - - <> - {descriptionRender != null && - descriptionRender(withUndefinedDescription.description, withUndefinedDescription)} - - - ); - }); - - test('it does NOT render the `description`', () => { - expect(screen.queryByTestId('description')).not.toBeInTheDocument(); - }); - - test('it renders the placeholder', () => { - expect(screen.getByTestId('emptyPlaceholder')).toBeInTheDocument(); - }); - }); }); }); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/helpers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/helpers.tsx index 8153380c140c3..a9f5c17034833 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/helpers.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/helpers.tsx @@ -13,12 +13,17 @@ import { EcsAllowedValues } from './ecs_allowed_values'; import { IndexInvalidValues } from './index_invalid_values'; import { CodeSuccess } from '../styles'; import * as i18n from './translations'; -import type { AllowedValue, EnrichedFieldMetadata, UnallowedValueCount } from '../types'; +import type { + AllowedValue, + CustomFieldMetadata, + EcsBasedFieldMetadata, + UnallowedValueCount, +} from '../types'; export const EMPTY_PLACEHOLDER = '--'; export const getCustomTableColumns = (): Array< - EuiTableFieldDataColumnType + EuiTableFieldDataColumnType > => [ { field: 'indexFieldName', @@ -40,7 +45,7 @@ export const getCustomTableColumns = (): Array< ]; export const getEcsCompliantTableColumns = (): Array< - EuiTableFieldDataColumnType + EuiTableFieldDataColumnType > => [ { field: 'indexFieldName', @@ -52,12 +57,7 @@ export const getEcsCompliantTableColumns = (): Array< { field: 'type', name: i18n.ECS_MAPPING_TYPE, - render: (type: string | undefined) => - type != null ? ( - {type} - ) : ( - {EMPTY_PLACEHOLDER} - ), + render: (type: string) => {type}, sortable: true, truncateText: false, width: '25%', @@ -75,12 +75,7 @@ export const getEcsCompliantTableColumns = (): Array< { field: 'description', name: i18n.ECS_DESCRIPTION, - render: (description: string | undefined) => - description != null ? ( - {description} - ) : ( - {EMPTY_PLACEHOLDER} - ), + render: (description: string) => {description}, sortable: false, truncateText: false, width: '25%', @@ -88,7 +83,7 @@ export const getEcsCompliantTableColumns = (): Array< ]; export const getIncompatibleValuesTableColumns = (): Array< - EuiTableFieldDataColumnType + EuiTableFieldDataColumnType > => [ { field: 'indexFieldName', diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index.tsx index 145686785cafa..460663fb28790 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/compare_fields_table/index.tsx @@ -20,17 +20,17 @@ const search: Search = { }, }; -interface Props { - enrichedFieldMetadata: EnrichedFieldMetadata[]; - getTableColumns: () => Array>; +interface Props { + enrichedFieldMetadata: T[]; + getTableColumns: () => Array>; title: string; } -const CompareFieldsTableComponent: React.FC = ({ +const CompareFieldsTableComponent = ({ enrichedFieldMetadata, getTableColumns, title, -}) => { +}: Props): React.ReactElement => { const columns = useMemo(() => getTableColumns(), [getTableColumns]); return ( @@ -53,4 +53,8 @@ const CompareFieldsTableComponent: React.FC = ({ CompareFieldsTableComponent.displayName = 'CompareFieldsTableComponent'; -export const CompareFieldsTable = React.memo(CompareFieldsTableComponent); +export const CompareFieldsTable = React.memo( + CompareFieldsTableComponent + // React.memo doesn't pass generics through so + // this is a cheap fix without sacrificing type safety +) as typeof CompareFieldsTableComponent; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/constants.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/constants.ts new file mode 100644 index 0000000000000..a53c50edc1084 --- /dev/null +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/constants.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EcsFlat } from '@elastic/ecs'; +import { EcsFieldMetadata } from './types'; + +export const EcsFlatTyped = EcsFlat as unknown as Record; +export type EcsFlatTyped = typeof EcsFlatTyped; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.test.tsx index 43c05f5757dd0..7fd0a3f3b133d 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.test.tsx @@ -5,20 +5,15 @@ * 2.0. */ -import { EcsFlat } from '@elastic/ecs'; -import { omit } from 'lodash/fp'; - +import { EcsFlatTyped } from '../../constants'; import { getUnallowedValueRequestItems, getValidValues, hasAllowedValues } from './helpers'; -import { AllowedValue, EcsMetadata } from '../../types'; - -const ecsMetadata: Record = EcsFlat as unknown as Record; describe('helpers', () => { describe('hasAllowedValues', () => { test('it returns true for a field that has `allowed_values`', () => { expect( hasAllowedValues({ - ecsMetadata, + ecsMetadata: EcsFlatTyped, fieldName: 'event.category', }) ).toBe(true); @@ -27,7 +22,7 @@ describe('helpers', () => { test('it returns false for a field that does NOT have `allowed_values`', () => { expect( hasAllowedValues({ - ecsMetadata, + ecsMetadata: EcsFlatTyped, fieldName: 'host.name', }) ).toBe(false); @@ -36,25 +31,16 @@ describe('helpers', () => { test('it returns false for a field that does NOT exist in `ecsMetadata`', () => { expect( hasAllowedValues({ - ecsMetadata, + ecsMetadata: EcsFlatTyped, fieldName: 'does.NOT.exist', }) ).toBe(false); }); - - test('it returns false when `ecsMetadata` is null', () => { - expect( - hasAllowedValues({ - ecsMetadata: null, // <-- - fieldName: 'event.category', - }) - ).toBe(false); - }); }); describe('getValidValues', () => { test('it returns the expected valid values', () => { - expect(getValidValues(ecsMetadata['event.category'])).toEqual( + expect(getValidValues(EcsFlatTyped['event.category'])).toEqual( expect.arrayContaining([ 'authentication', 'configuration', @@ -79,60 +65,19 @@ describe('helpers', () => { }); test('it returns an empty array when the `field` does NOT have `allowed_values`', () => { - expect(getValidValues(ecsMetadata['host.name'])).toEqual([]); + expect(getValidValues(EcsFlatTyped['host.name'])).toEqual([]); }); test('it returns an empty array when `field` is undefined', () => { expect(getValidValues(undefined)).toEqual([]); }); - - test('it skips `allowed_values` where `name` is undefined', () => { - // omit the `name` property from the `database` `AllowedValue`: - const missingDatabase = - ecsMetadata['event.category'].allowed_values?.map((x) => - x.name === 'database' ? omit('name', x) : x - ) ?? []; - - const field = { - ...ecsMetadata['event.category'], - allowed_values: missingDatabase, - }; - - expect(getValidValues(field)).toEqual( - expect.arrayContaining([ - 'authentication', - 'configuration', - 'driver', - 'email', - 'file', - 'host', - 'iam', - 'intrusion_detection', - 'malware', - 'network', - 'package', - 'process', - 'registry', - 'session', - 'threat', - 'vulnerability', - 'web', - ]) - ); - expect(getValidValues(field)).not.toEqual( - expect.arrayContaining([ - // there should be no entry for 'database' - 'database', - ]) - ); - }); }); describe('getUnallowedValueRequestItems', () => { test('it returns the expected request items', () => { expect( getUnallowedValueRequestItems({ - ecsMetadata, + ecsMetadata: EcsFlatTyped, indexName: 'auditbeat-*', }) ).toEqual([ @@ -203,14 +148,5 @@ describe('helpers', () => { }, ]); }); - - test('it returns an empty array when `ecsMetadata` is null', () => { - expect( - getUnallowedValueRequestItems({ - ecsMetadata: null, // <-- - indexName: 'auditbeat-*', - }) - ).toEqual([]); - }); }); }); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.tsx index 707e83fd0df52..fd356b9fe60d5 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/allowed_values/helpers.tsx @@ -5,40 +5,38 @@ * 2.0. */ -import type { EcsMetadata, UnallowedValueRequestItem } from '../../types'; +import type { EcsFlatTyped } from '../../constants'; +import type { EcsFieldMetadata, UnallowedValueRequestItem } from '../../types'; export const hasAllowedValues = ({ ecsMetadata, fieldName, }: { - ecsMetadata: Record | null; + ecsMetadata: EcsFlatTyped; fieldName: string; -}): boolean => - ecsMetadata != null ? (ecsMetadata[fieldName]?.allowed_values?.length ?? 0) > 0 : false; +}): boolean => (ecsMetadata[fieldName]?.allowed_values?.length ?? 0) > 0; -export const getValidValues = (field: EcsMetadata | undefined): string[] => - field?.allowed_values?.flatMap(({ name }) => (name != null ? name : [])) ?? []; +export const getValidValues = (field?: EcsFieldMetadata): string[] => + field?.allowed_values?.flatMap(({ name }) => name) ?? []; export const getUnallowedValueRequestItems = ({ ecsMetadata, indexName, }: { - ecsMetadata: Record | null; + ecsMetadata: EcsFlatTyped; indexName: string; }): UnallowedValueRequestItem[] => - ecsMetadata != null - ? Object.keys(ecsMetadata).reduce( - (acc, fieldName) => - hasAllowedValues({ ecsMetadata, fieldName }) - ? [ - ...acc, - { - indexName, - indexFieldName: fieldName, - allowedValues: getValidValues(ecsMetadata[fieldName]), - }, - ] - : acc, - [] - ) - : []; + Object.keys(ecsMetadata).reduce( + (acc, fieldName) => + hasAllowedValues({ ecsMetadata, fieldName }) + ? [ + ...acc, + { + indexName, + indexFieldName: fieldName, + allowedValues: getValidValues(ecsMetadata[fieldName]), + }, + ] + : acc, + [] + ); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.test.ts index c97a8f4fb9a50..70d522f18177a 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.test.ts @@ -5,15 +5,14 @@ * 2.0. */ -import { EcsFlat, EcsVersion } from '@elastic/ecs'; +import { EcsVersion } from '@elastic/ecs'; import { checkIndex, EMPTY_PARTITIONED_FIELD_METADATA } from './check_index'; import { EMPTY_STAT } from '../../../../helpers'; import { mockMappingsResponse } from '../../../../mock/mappings_response/mock_mappings_response'; import { mockUnallowedValuesResponse } from '../../../../mock/unallowed_values/mock_unallowed_values'; -import { EcsMetadata, UnallowedValueRequestItem } from '../../../../types'; - -const ecsMetadata = EcsFlat as unknown as Record; +import { UnallowedValueRequestItem } from '../../../../types'; +import { EcsFlatTyped } from '../../../../constants'; let mockFetchMappings = jest.fn( ({ @@ -99,7 +98,7 @@ describe('checkIndex', () => { abortController: new AbortController(), batchId: 'batch-id', checkAllStartTime: Date.now(), - ecsMetadata, + ecsMetadata: EcsFlatTyped, formatBytes, formatNumber, httpFetch, @@ -149,7 +148,7 @@ describe('checkIndex', () => { abortController, batchId: 'batch-id', checkAllStartTime: Date.now(), - ecsMetadata, + ecsMetadata: EcsFlatTyped, formatBytes, formatNumber, httpFetch, @@ -164,51 +163,6 @@ describe('checkIndex', () => { }); }); - describe('when `ecsMetadata` is null', () => { - const onCheckCompleted = jest.fn(); - - beforeEach(async () => { - jest.clearAllMocks(); - - await checkIndex({ - abortController: new AbortController(), - batchId: 'batch-id', - checkAllStartTime: Date.now(), - ecsMetadata: null, // <-- - formatBytes, - formatNumber, - httpFetch, - indexName, - isLastCheck: false, - onCheckCompleted, - pattern, - version: EcsVersion, - }); - }); - - test('it invokes onCheckCompleted with a null `error`', () => { - expect(onCheckCompleted.mock.calls[0][0].error).toBeNull(); - }); - - test('it invokes onCheckCompleted with the expected `indexName`', () => { - expect(onCheckCompleted.mock.calls[0][0].indexName).toEqual(indexName); - }); - - test('it invokes onCheckCompleted with the default `partitionedFieldMetadata`', () => { - expect(onCheckCompleted.mock.calls[0][0].partitionedFieldMetadata).toEqual( - EMPTY_PARTITIONED_FIELD_METADATA - ); - }); - - test('it invokes onCheckCompleted with the expected `pattern`', () => { - expect(onCheckCompleted.mock.calls[0][0].pattern).toEqual(pattern); - }); - - test('it invokes onCheckCompleted with the expected `version`', () => { - expect(onCheckCompleted.mock.calls[0][0].version).toEqual(EcsVersion); - }); - }); - describe('when an error occurs', () => { const onCheckCompleted = jest.fn(); const error = 'simulated fetch mappings error'; @@ -230,7 +184,7 @@ describe('checkIndex', () => { abortController: new AbortController(), batchId: 'batch-id', checkAllStartTime: Date.now(), - ecsMetadata, + ecsMetadata: EcsFlatTyped, formatBytes, formatNumber, httpFetch, @@ -284,7 +238,7 @@ describe('checkIndex', () => { abortController: new AbortController(), batchId: 'batch-id', checkAllStartTime: Date.now(), - ecsMetadata, + ecsMetadata: EcsFlatTyped, formatBytes, formatNumber, httpFetch, @@ -346,7 +300,7 @@ describe('checkIndex', () => { abortController, batchId: 'batch-id', checkAllStartTime: Date.now(), - ecsMetadata, + ecsMetadata: EcsFlatTyped, formatBytes, formatNumber, httpFetch, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.ts index 914f244ec39bc..a9216b9d09fdd 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/check_index.ts @@ -12,9 +12,10 @@ import { getSortedPartitionedFieldMetadata, } from '../../../index_properties/helpers'; import * as i18n from './translations'; -import type { EcsMetadata, OnCheckCompleted, PartitionedFieldMetadata } from '../../../../types'; +import type { OnCheckCompleted, PartitionedFieldMetadata } from '../../../../types'; import { fetchMappings } from '../../../../use_mappings/helpers'; import { fetchUnallowedValues, getUnallowedValues } from '../../../../use_unallowed_values/helpers'; +import { EcsFlatTyped } from '../../../../constants'; export const EMPTY_PARTITIONED_FIELD_METADATA: PartitionedFieldMetadata = { all: [], @@ -41,7 +42,7 @@ export async function checkIndex({ abortController: AbortController; batchId: string; checkAllStartTime: number; - ecsMetadata: Record | null; + ecsMetadata: EcsFlatTyped; formatBytes: (value: number | undefined) => string; formatNumber: (value: number | undefined) => string; httpFetch: HttpHandler; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.tsx index dafa39492ab6e..5155c2a07f9cf 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/data_quality_summary/summary_actions/check_all/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EcsFlat, EcsVersion } from '@elastic/ecs'; +import { EcsVersion } from '@elastic/ecs'; import { EuiButton } from '@elastic/eui'; import React, { useCallback, useEffect, useRef, useState } from 'react'; @@ -16,7 +16,8 @@ import { checkIndex } from './check_index'; import { useDataQualityContext } from '../../../data_quality_context'; import { getAllIndicesToCheck } from './helpers'; import * as i18n from '../../../../translations'; -import type { EcsMetadata, IndexToCheck, OnCheckCompleted } from '../../../../types'; +import type { IndexToCheck, OnCheckCompleted } from '../../../../types'; +import { EcsFlatTyped } from '../../../../constants'; const CheckAllButton = styled(EuiButton)` width: 112px; @@ -97,7 +98,7 @@ const CheckAllComponent: React.FC = ({ abortController: abortController.current, batchId, checkAllStartTime: startTime, - ecsMetadata: EcsFlat as unknown as Record, + ecsMetadata: EcsFlatTyped, formatBytes, formatNumber, httpFetch, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.test.ts index a4c2bf01f3b41..2ef3eb9c1c190 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.test.ts @@ -5,8 +5,6 @@ * 2.0. */ -import { EcsFlat } from '@elastic/ecs'; - import { getMappingsProperties, getSortedPartitionedFieldMetadata, @@ -14,16 +12,14 @@ import { } from './helpers'; import { mockIndicesGetMappingIndexMappingRecords } from '../../mock/indices_get_mapping_index_mapping_record/mock_indices_get_mapping_index_mapping_record'; import { mockMappingsProperties } from '../../mock/mappings_properties/mock_mappings_properties'; -import { EcsMetadata } from '../../types'; - -const ecsMetadata: Record = EcsFlat as unknown as Record; +import { EcsFlatTyped } from '../../constants'; describe('helpers', () => { describe('getSortedPartitionedFieldMetadata', () => { test('it returns null when mappings are loading', () => { expect( getSortedPartitionedFieldMetadata({ - ecsMetadata, + ecsMetadata: EcsFlatTyped, loadingMappings: true, // <-- mappingsProperties: mockMappingsProperties, unallowedValues: {}, @@ -31,21 +27,10 @@ describe('helpers', () => { ).toBeNull(); }); - test('it returns null when `ecsMetadata` is null', () => { - expect( - getSortedPartitionedFieldMetadata({ - ecsMetadata: null, // <-- - loadingMappings: false, - mappingsProperties: mockMappingsProperties, - unallowedValues: {}, - }) - ).toBeNull(); - }); - test('it returns null when `unallowedValues` is null', () => { expect( getSortedPartitionedFieldMetadata({ - ecsMetadata, + ecsMetadata: EcsFlatTyped, loadingMappings: false, mappingsProperties: mockMappingsProperties, unallowedValues: null, // <-- @@ -54,30 +39,27 @@ describe('helpers', () => { }); describe('when `mappingsProperties` is unknown', () => { + const incompatibleFieldMetadata = { + ...EcsFlatTyped['@timestamp'], + hasEcsMetadata: true, + indexFieldName: '@timestamp', + indexFieldType: '-', + indexInvalidValues: [], + isEcsCompliant: false, + isInSameFamily: false, + }; const expected = { - all: [], + all: [incompatibleFieldMetadata], custom: [], ecsCompliant: [], - incompatible: [ - { - description: - 'Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events.', - hasEcsMetadata: true, - indexFieldName: '@timestamp', - indexFieldType: '-', - indexInvalidValues: [], - isEcsCompliant: false, - isInSameFamily: false, - type: 'date', - }, - ], + incompatible: [incompatibleFieldMetadata], sameFamily: [], }; test('it returns a `PartitionedFieldMetadata` with an `incompatible` `@timestamp` when `mappingsProperties` is undefined', () => { expect( getSortedPartitionedFieldMetadata({ - ecsMetadata, + ecsMetadata: EcsFlatTyped, loadingMappings: false, mappingsProperties: undefined, // <-- unallowedValues: {}, @@ -88,7 +70,7 @@ describe('helpers', () => { test('it returns a `PartitionedFieldMetadata` with an `incompatible` `@timestamp` when `mappingsProperties` is null', () => { expect( getSortedPartitionedFieldMetadata({ - ecsMetadata, + ecsMetadata: EcsFlatTyped, loadingMappings: false, mappingsProperties: null, // <-- unallowedValues: {}, @@ -116,7 +98,7 @@ describe('helpers', () => { expect( getSortedPartitionedFieldMetadata({ - ecsMetadata, + ecsMetadata: EcsFlatTyped, loadingMappings: false, mappingsProperties: mockMappingsProperties, unallowedValues, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.ts index 5c19b83dd0dfd..e47d18685615f 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/helpers.ts @@ -10,6 +10,7 @@ import type { MappingProperty, } from '@elastic/elasticsearch/lib/api/types'; import { sortBy } from 'lodash/fp'; +import { EcsFlatTyped } from '../../constants'; import { getEnrichedFieldMetadata, @@ -17,7 +18,7 @@ import { getMissingTimestampFieldMetadata, getPartitionedFieldMetadata, } from '../../helpers'; -import type { EcsMetadata, PartitionedFieldMetadata, UnallowedValueCount } from '../../types'; +import type { PartitionedFieldMetadata, UnallowedValueCount } from '../../types'; export const ALL_TAB_ID = 'allTab'; export const ECS_COMPLIANT_TAB_ID = 'ecsCompliantTab'; @@ -40,19 +41,26 @@ export const getSortedPartitionedFieldMetadata = ({ mappingsProperties, unallowedValues, }: { - ecsMetadata: Record | null; + ecsMetadata: EcsFlatTyped; loadingMappings: boolean; mappingsProperties: Record | null | undefined; unallowedValues: Record | null; }): PartitionedFieldMetadata | null => { - if (loadingMappings || ecsMetadata == null || unallowedValues == null) { + if (loadingMappings || unallowedValues == null) { return null; } + // this covers scenario when we try to check an empty index + // or index without required @timestamp field in the mapping + // + // we create an artifical incompatible timestamp field metadata + // so that we can signal to user that the incompatibility is due to missing timestamp if (mappingsProperties == null) { + const missingTimestampFieldMetadata = getMissingTimestampFieldMetadata(); return { ...EMPTY_METADATA, - incompatible: [getMissingTimestampFieldMetadata()], + all: [missingTimestampFieldMetadata], + incompatible: [missingTimestampFieldMetadata], }; } diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx index 9a00f588a15d7..a9dce4dafc741 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EcsFlat, EcsVersion } from '@elastic/ecs'; +import { EcsVersion } from '@elastic/ecs'; import type { FlameElementEvent, HeatmapElementEvent, @@ -39,12 +39,13 @@ import { getSameFamilyFields, } from '../tabs/incompatible_tab/helpers'; import * as i18n from './translations'; -import type { EcsMetadata, IlmPhase, PartitionedFieldMetadata, PatternRollup } from '../../types'; +import type { IlmPhase, PartitionedFieldMetadata, PatternRollup } from '../../types'; import { useAddToNewCase } from '../../use_add_to_new_case'; import { useMappings } from '../../use_mappings'; import { useUnallowedValues } from '../../use_unallowed_values'; import { useDataQualityContext } from '../data_quality_context'; import { formatStorageResult, postStorageResult, getSizeInBytes } from '../../helpers'; +import { EcsFlatTyped } from '../../constants'; const EMPTY_MARKDOWN_COMMENTS: string[] = []; @@ -109,7 +110,7 @@ const IndexPropertiesComponent: React.FC = ({ const requestItems = useMemo( () => getUnallowedValueRequestItems({ - ecsMetadata: EcsFlat as unknown as Record, + ecsMetadata: EcsFlatTyped, indexName, }), [indexName] @@ -134,7 +135,7 @@ const IndexPropertiesComponent: React.FC = ({ const partitionedFieldMetadata: PartitionedFieldMetadata | null = useMemo( () => getSortedPartitionedFieldMetadata({ - ecsMetadata: EcsFlat as unknown as Record, + ecsMetadata: EcsFlatTyped, loadingMappings, mappingsProperties, unallowedValues, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.test.ts index 098c66370f1c9..8c14a214cabf3 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.test.ts @@ -57,7 +57,7 @@ import { import { SAME_FAMILY } from '../../same_family/translations'; import { INCOMPATIBLE_FIELD_MAPPINGS_TABLE_TITLE } from '../../tabs/incompatible_tab/translations'; import { - EnrichedFieldMetadata, + EcsBasedFieldMetadata, ErrorSummary, PatternRollup, UnallowedValueCount, @@ -230,17 +230,6 @@ describe('helpers', () => { '| host.name.keyword | `keyword` | `--` |\n| some.field | `text` | `--` |\n| some.field.keyword | `keyword` | `--` |\n| source.ip.keyword | `keyword` | `--` |' ); }); - - test('it returns the expected table rows when some have allowed values', () => { - const withAllowedValues = [ - ...mockCustomFields, - eventCategory, // note: this is not a real-world use case, because custom fields don't have allowed values - ]; - - expect(getCustomMarkdownTableRows(withAllowedValues)).toEqual( - '| host.name.keyword | `keyword` | `--` |\n| some.field | `text` | `--` |\n| some.field.keyword | `keyword` | `--` |\n| source.ip.keyword | `keyword` | `--` |\n| event.category | `keyword` | `authentication`, `configuration`, `database`, `driver`, `email`, `file`, `host`, `iam`, `intrusion_detection`, `malware`, `network`, `package`, `process`, `registry`, `session`, `threat`, `vulnerability`, `web` |' - ); - }); }); describe('getSameFamilyBadge', () => { @@ -265,7 +254,7 @@ describe('helpers', () => { describe('getIncompatibleMappingsMarkdownTableRows', () => { test('it returns the expected table rows when the field is in the same family', () => { - const eventCategoryWithWildcard: EnrichedFieldMetadata = { + const eventCategoryWithWildcard: EcsBasedFieldMetadata = { ...eventCategory, // `event.category` is a `keyword` per the ECS spec indexFieldType: 'wildcard', // this index has a mapping of `wildcard` instead of `keyword` isInSameFamily: true, // `wildcard` and `keyword` are in the same family @@ -282,7 +271,7 @@ describe('helpers', () => { }); test('it returns the expected table rows when the field is NOT in the same family', () => { - const eventCategoryWithText: EnrichedFieldMetadata = { + const eventCategoryWithText: EcsBasedFieldMetadata = { ...eventCategory, // `event.category` is a `keyword` per the ECS spec indexFieldType: 'text', // this index has a mapping of `text` instead of `keyword` isInSameFamily: false, // `text` and `keyword` are NOT in the same family diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.ts index 850986e3959f1..bdbfdba4ddb35 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/index_properties/markdown/helpers.ts @@ -25,6 +25,8 @@ import { HOT, WARM, COLD, FROZEN, UNMANAGED } from '../../../ilm_phases_empty_pr import * as i18n from '../translations'; import type { AllowedValue, + CustomFieldMetadata, + EcsBasedFieldMetadata, EnrichedFieldMetadata, ErrorSummary, IlmExplainPhaseCounts, @@ -85,23 +87,21 @@ export const getIndexInvalidValues = (indexInvalidValues: UnallowedValueCount[]) .map(({ fieldName, count }) => `${getCodeFormattedValue(escape(fieldName))} (${count})`) .join(', '); // newlines are instead joined with spaces -export const getCustomMarkdownTableRows = ( - enrichedFieldMetadata: EnrichedFieldMetadata[] -): string => - enrichedFieldMetadata +export const getCustomMarkdownTableRows = (customFieldMetadata: CustomFieldMetadata[]): string => + customFieldMetadata .map( (x) => `| ${escape(x.indexFieldName)} | ${getCodeFormattedValue( x.indexFieldType - )} | ${getAllowedValues(x.allowed_values)} |` + )} | ${getAllowedValues(undefined)} |` ) .join('\n'); -export const getSameFamilyBadge = (enrichedFieldMetadata: EnrichedFieldMetadata): string => - enrichedFieldMetadata.isInSameFamily ? getCodeFormattedValue(SAME_FAMILY) : ''; +export const getSameFamilyBadge = (ecsBasedFieldMetadata: EcsBasedFieldMetadata): string => + ecsBasedFieldMetadata.isInSameFamily ? getCodeFormattedValue(SAME_FAMILY) : ''; export const getIncompatibleMappingsMarkdownTableRows = ( - incompatibleMappings: EnrichedFieldMetadata[] + incompatibleMappings: EcsBasedFieldMetadata[] ): string => incompatibleMappings .map( @@ -113,7 +113,7 @@ export const getIncompatibleMappingsMarkdownTableRows = ( .join('\n'); export const getIncompatibleValuesMarkdownTableRows = ( - incompatibleValues: EnrichedFieldMetadata[] + incompatibleValues: EcsBasedFieldMetadata[] ): string => incompatibleValues .map( @@ -173,14 +173,14 @@ ${getMarkdownTableRows(errorSummary)} ` : ''; -export const getMarkdownTable = ({ +export const getMarkdownTable = ({ enrichedFieldMetadata, getMarkdownTableRows, headerNames, title, }: { - enrichedFieldMetadata: EnrichedFieldMetadata[]; - getMarkdownTableRows: (enrichedFieldMetadata: EnrichedFieldMetadata[]) => string; + enrichedFieldMetadata: T; + getMarkdownTableRows: (enrichedFieldMetadata: T) => string; headerNames: string[]; title: string; }): string => diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.test.tsx index 41eb828b1be28..fc2ba0327062f 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.test.tsx @@ -23,7 +23,7 @@ describe('CustomCallout', () => { beforeEach(() => { render( - +
{content}
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.tsx index 74cf0dd6ce068..8d1bc0c20a72c 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/custom_callout/index.tsx @@ -9,27 +9,27 @@ import { EcsVersion } from '@elastic/ecs'; import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import React, { useMemo } from 'react'; -import type { EnrichedFieldMetadata } from '../../../../types'; +import type { CustomFieldMetadata } from '../../../../types'; import * as i18n from '../../../index_properties/translations'; interface Props { children?: React.ReactNode; - enrichedFieldMetadata: EnrichedFieldMetadata[]; + customFieldMetadata: CustomFieldMetadata[]; } -const CustomCalloutComponent: React.FC = ({ children, enrichedFieldMetadata }) => { +const CustomCalloutComponent: React.FC = ({ children, customFieldMetadata }) => { const title = useMemo( () => ( - {i18n.CUSTOM_CALLOUT_TITLE(enrichedFieldMetadata.length)} + {i18n.CUSTOM_CALLOUT_TITLE(customFieldMetadata.length)} ), - [enrichedFieldMetadata.length] + [customFieldMetadata.length] ); return (
- {i18n.CUSTOM_CALLOUT({ fieldCount: enrichedFieldMetadata.length, version: EcsVersion })} + {i18n.CUSTOM_CALLOUT({ fieldCount: customFieldMetadata.length, version: EcsVersion })}
{i18n.ECS_IS_A_PERMISSIVE_SCHEMA}
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/helpers.test.ts deleted file mode 100644 index b059c444f4c60..0000000000000 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/helpers.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getIncompatiableFieldsInSameFamilyCount } from './helpers'; -import { - eventCategory, - eventCategoryWithUnallowedValues, - hostNameWithTextMapping, - someField, - sourceIpWithTextMapping, -} from '../../../../mock/enriched_field_metadata/mock_enriched_field_metadata'; -import { EnrichedFieldMetadata } from '../../../../types'; - -const sameFamily: EnrichedFieldMetadata = { - ...eventCategory, // `event.category` is a `keyword` per the ECS spec - indexFieldType: 'wildcard', // this index has a mapping of `wildcard` instead of `keyword` - isInSameFamily: true, // `wildcard` and `keyword` are in the same family - isEcsCompliant: false, // wildcard !== keyword -}; - -describe('helpers', () => { - describe('getFieldsInSameFamilyCount', () => { - test('it filters out fields that are ECS compliant', () => { - expect( - getIncompatiableFieldsInSameFamilyCount([ - eventCategory, // isEcsCompliant: true, indexInvalidValues.length: 0, isInSameFamily: true, `keyword` and `keyword` are in the same family - ]) - ).toEqual(0); - }); - - test('it filters out fields with unallowed values', () => { - expect( - getIncompatiableFieldsInSameFamilyCount([ - eventCategoryWithUnallowedValues, // isEcsCompliant: false, indexInvalidValues.length: 2, isInSameFamily: true, `keyword` and `keyword` are in the same family - ]) - ).toEqual(0); - }); - - test('it filters out fields that are not in the same family', () => { - expect( - getIncompatiableFieldsInSameFamilyCount([ - hostNameWithTextMapping, // isEcsCompliant: false, indexInvalidValues.length: 0, isInSameFamily: false, `keyword` and `text` are not in the family - ]) - ).toEqual(0); - }); - - test('it returns 1 for an incompatible field in the same family', () => { - expect( - getIncompatiableFieldsInSameFamilyCount([ - sameFamily, // isEcsCompliant: false, indexInvalidValues.length: 0, isInSameFamily: true, `wildcard` and `keyword` are in the same family - ]) - ).toEqual(1); - }); - - test('it returns the expected count when some of the input should be counted', () => { - expect( - getIncompatiableFieldsInSameFamilyCount([ - sameFamily, - eventCategoryWithUnallowedValues, // isEcsCompliant: false, indexInvalidValues.length: 2, isInSameFamily: true, `keyword` and `keyword` are in the same family - hostNameWithTextMapping, // isEcsCompliant: false, indexInvalidValues.length, isInSameFamily: false, `text` and `keyword` not in the same family - someField, // isEcsCompliant: false, indexInvalidValues.length: 0, isInSameFamily: false, custom fields are never in the same family - sourceIpWithTextMapping, // isEcsCompliant: false, indexInvalidValues.length: 0, isInSameFamily: false, `ip` is not a member of any families - ]) - ).toEqual(1); - }); - - test('it returns zero when none of the input should be counted', () => { - expect( - getIncompatiableFieldsInSameFamilyCount([ - eventCategoryWithUnallowedValues, // isEcsCompliant: false, indexInvalidValues.length: 2, isInSameFamily: true, `keyword` and `keyword` are in the same family - hostNameWithTextMapping, // isEcsCompliant: false, indexInvalidValues.length, isInSameFamily: false, `text` and `keyword` not in the same family - someField, // isEcsCompliant: false, indexInvalidValues.length: 0, isInSameFamily: false, custom fields are never in the same family - sourceIpWithTextMapping, // isEcsCompliant: false, indexInvalidValues.length: 0, isInSameFamily: false, `ip` is not a member of any families - ]) - ).toEqual(0); - }); - }); -}); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/helpers.ts deleted file mode 100644 index ca0e6c42098b9..0000000000000 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/helpers.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EnrichedFieldMetadata } from '../../../../types'; - -export const getIncompatiableFieldsInSameFamilyCount = ( - enrichedFieldMetadata: EnrichedFieldMetadata[] -): number => - enrichedFieldMetadata.filter( - (x) => !x.isEcsCompliant && x.indexInvalidValues.length === 0 && x.isInSameFamily - ).length; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.test.tsx index 522162fff699b..590dff6d64a0b 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/incompatible_callout/index.test.tsx @@ -21,12 +21,12 @@ import { sourceIpWithTextMapping, } from '../../../../mock/enriched_field_metadata/mock_enriched_field_metadata'; import { TestProviders } from '../../../../mock/test_providers/test_providers'; -import { EnrichedFieldMetadata } from '../../../../types'; +import { EcsBasedFieldMetadata } from '../../../../types'; import { IncompatibleCallout } from '.'; const content = 'Is your name Michael?'; -const eventCategoryWithWildcard: EnrichedFieldMetadata = { +const eventCategoryWithWildcard: EcsBasedFieldMetadata = { ...eventCategory, // `event.category` is a `keyword` per the ECS spec indexFieldType: 'wildcard', // this index has a mapping of `wildcard` instead of `keyword` isInSameFamily: true, // `wildcard` and `keyword` are in the same family @@ -38,7 +38,7 @@ describe('IncompatibleCallout', () => { render( = ({ children, enrichedFieldMetadata }) => { - const fieldCount = enrichedFieldMetadata.length; +const IncompatibleCalloutComponent: React.FC = ({ children, ecsBasedFieldMetadata }) => { + const fieldCount = ecsBasedFieldMetadata.length; const title = useMemo( () => {i18n.INCOMPATIBLE_CALLOUT_TITLE(fieldCount)}, [fieldCount] diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/same_family_callout/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/same_family_callout/index.test.tsx index 89494a2c91b03..774b0a04ec174 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/same_family_callout/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/same_family_callout/index.test.tsx @@ -21,7 +21,7 @@ describe('SameFamilyCallout', () => { render(
{content}
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/same_family_callout/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/same_family_callout/index.tsx index 961f9a43aa781..ffec880687c8c 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/same_family_callout/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/callouts/same_family_callout/index.tsx @@ -10,28 +10,28 @@ import { EuiCallOut, EuiSpacer, EuiText } from '@elastic/eui'; import React, { useMemo } from 'react'; import * as i18n from '../../../index_properties/translations'; -import type { EnrichedFieldMetadata } from '../../../../types'; +import type { EcsBasedFieldMetadata } from '../../../../types'; interface Props { children?: React.ReactNode; - enrichedFieldMetadata: EnrichedFieldMetadata[]; + ecsBasedFieldMetadata: EcsBasedFieldMetadata[]; } -const SameFamilyCalloutComponent: React.FC = ({ children, enrichedFieldMetadata }) => { +const SameFamilyCalloutComponent: React.FC = ({ children, ecsBasedFieldMetadata }) => { const title = useMemo( () => ( - {i18n.SAME_FAMILY_CALLOUT_TITLE(enrichedFieldMetadata.length)} + {i18n.SAME_FAMILY_CALLOUT_TITLE(ecsBasedFieldMetadata.length)} ), - [enrichedFieldMetadata.length] + [ecsBasedFieldMetadata.length] ); return (
{i18n.SAME_FAMILY_CALLOUT({ - fieldCount: enrichedFieldMetadata.length, + fieldCount: ecsBasedFieldMetadata.length, version: EcsVersion, })}
diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.test.ts index 30aae990fc7dd..31dd9644bbc1d 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.test.ts @@ -35,7 +35,7 @@ const formatNumber = (value: number | undefined) => describe('helpers', () => { describe('getCustomMarkdownComment', () => { test('it returns a comment for custom fields with the expected field counts and ECS version', () => { - expect(getCustomMarkdownComment({ enrichedFieldMetadata: [hostNameKeyword, someField] })) + expect(getCustomMarkdownComment({ customFieldMetadata: [hostNameKeyword, someField] })) .toEqual(`#### 2 Custom field mappings These fields are not defined by the Elastic Common Schema (ECS), version ${EcsVersion}. diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.ts index 0d659ac6570f5..f5b6ca7220809 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/helpers.ts @@ -19,26 +19,26 @@ import { } from '../../index_properties/markdown/helpers'; import * as i18n from '../../index_properties/translations'; import { getFillColor } from '../summary_tab/helpers'; -import type { EnrichedFieldMetadata, IlmPhase, PartitionedFieldMetadata } from '../../../types'; +import type { CustomFieldMetadata, IlmPhase, PartitionedFieldMetadata } from '../../../types'; export const getCustomMarkdownComment = ({ - enrichedFieldMetadata, + customFieldMetadata, }: { - enrichedFieldMetadata: EnrichedFieldMetadata[]; + customFieldMetadata: CustomFieldMetadata[]; }): string => getMarkdownComment({ suggestedAction: `${i18n.CUSTOM_CALLOUT({ - fieldCount: enrichedFieldMetadata.length, + fieldCount: customFieldMetadata.length, version: EcsVersion, })} ${i18n.ECS_IS_A_PERMISSIVE_SCHEMA} `, - title: i18n.CUSTOM_CALLOUT_TITLE(enrichedFieldMetadata.length), + title: i18n.CUSTOM_CALLOUT_TITLE(customFieldMetadata.length), }); -export const showCustomCallout = (enrichedFieldMetadata: EnrichedFieldMetadata[]): boolean => - enrichedFieldMetadata.length > 0; +export const showCustomCallout = (customFieldMetadata: CustomFieldMetadata[]): boolean => + customFieldMetadata.length > 0; export const getCustomColor = (partitionedFieldMetadata: PartitionedFieldMetadata): string => showCustomCallout(partitionedFieldMetadata.custom) @@ -80,7 +80,7 @@ export const getAllCustomMarkdownComments = ({ }), getTabCountsMarkdownComment(partitionedFieldMetadata), getCustomMarkdownComment({ - enrichedFieldMetadata: partitionedFieldMetadata.custom, + customFieldMetadata: partitionedFieldMetadata.custom, }), getMarkdownTable({ enrichedFieldMetadata: partitionedFieldMetadata.custom, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/index.tsx index 019e65e891474..b976be6193087 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/custom_tab/index.tsx @@ -91,7 +91,7 @@ const CustomTabComponent: React.FC = ({ <> {showCustomCallout(partitionedFieldMetadata.custom) ? ( <> - + diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/helpers.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/helpers.test.tsx index c6386d7710bfc..3c04a6473facf 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/helpers.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/helpers.test.tsx @@ -11,7 +11,6 @@ import { omit } from 'lodash/fp'; import { eventCategory, - someField, timestamp, } from '../../mock/enriched_field_metadata/mock_enriched_field_metadata'; import { mockPartitionedFieldMetadata } from '../../mock/partitioned_field_metadata/mock_partitioned_field_metadata'; @@ -38,11 +37,11 @@ describe('helpers', () => { }); test('it returns false when `enrichedFieldMetadata` contains an @timestamp field', () => { - expect(showMissingTimestampCallout([timestamp, eventCategory, someField])).toBe(false); + expect(showMissingTimestampCallout([timestamp, eventCategory])).toBe(false); }); test('it returns true when `enrichedFieldMetadata` does NOT contain an @timestamp field', () => { - expect(showMissingTimestampCallout([eventCategory, someField])).toBe(true); + expect(showMissingTimestampCallout([eventCategory])).toBe(true); }); }); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/helpers.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/helpers.tsx index 0f59258949b2d..670357c3730f7 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/helpers.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/helpers.tsx @@ -39,7 +39,7 @@ import { SameFamilyTab } from './same_family_tab'; import { SummaryTab } from './summary_tab'; import { getFillColor } from './summary_tab/helpers'; import type { - EnrichedFieldMetadata, + EcsBasedFieldMetadata, IlmPhase, MeteringStatsIndex, PartitionedFieldMetadata, @@ -56,8 +56,8 @@ ${i18n.PAGES_MAY_NOT_DISPLAY_EVENTS} }); export const showMissingTimestampCallout = ( - enrichedFieldMetadata: EnrichedFieldMetadata[] -): boolean => !enrichedFieldMetadata.some((x) => x.name === '@timestamp'); + ecsBasedFieldMetadata: EcsBasedFieldMetadata[] +): boolean => !ecsBasedFieldMetadata.some((x) => x.name === '@timestamp'); export const getEcsCompliantColor = (partitionedFieldMetadata: PartitionedFieldMetadata): string => showMissingTimestampCallout(partitionedFieldMetadata.ecsCompliant) diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.ts index b2f5a5c430175..857b53589f163 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/helpers.ts @@ -19,7 +19,7 @@ import { } from '../../index_properties/markdown/helpers'; import { getFillColor } from '../summary_tab/helpers'; import * as i18n from '../../index_properties/translations'; -import type { EnrichedFieldMetadata, IlmPhase, PartitionedFieldMetadata } from '../../../types'; +import type { EcsBasedFieldMetadata, IlmPhase, PartitionedFieldMetadata } from '../../../types'; import { INCOMPATIBLE_FIELD_MAPPINGS_TABLE_TITLE, INCOMPATIBLE_FIELD_VALUES_TABLE_TITLE, @@ -44,17 +44,17 @@ ${i18n.MAPPINGS_THAT_CONFLICT_WITH_ECS} title: i18n.INCOMPATIBLE_CALLOUT_TITLE(incompatible), }); -export const showInvalidCallout = (enrichedFieldMetadata: EnrichedFieldMetadata[]): boolean => - enrichedFieldMetadata.length > 0; +export const showInvalidCallout = (ecsBasedFieldMetadata: EcsBasedFieldMetadata[]): boolean => + ecsBasedFieldMetadata.length > 0; export const getIncompatibleColor = (): string => getFillColor('incompatible'); export const getSameFamilyColor = (): string => getFillColor('same-family'); export const getIncompatibleMappings = ( - enrichedFieldMetadata: EnrichedFieldMetadata[] -): EnrichedFieldMetadata[] => - enrichedFieldMetadata.filter( + ecsBasedFieldMetadata: EcsBasedFieldMetadata[] +): EcsBasedFieldMetadata[] => + ecsBasedFieldMetadata.filter( (x) => !x.isEcsCompliant && x.type !== x.indexFieldType && @@ -62,9 +62,9 @@ export const getIncompatibleMappings = ( ); export const getIncompatibleMappingsFields = ( - enrichedFieldMetadata: EnrichedFieldMetadata[] + ecsBasedFieldMetadata: EcsBasedFieldMetadata[] ): string[] => - enrichedFieldMetadata.reduce((acc, x) => { + ecsBasedFieldMetadata.reduce((acc, x) => { if ( !x.isEcsCompliant && x.type !== x.indexFieldType && @@ -78,8 +78,8 @@ export const getIncompatibleMappingsFields = ( return acc; }, []); -export const getSameFamilyFields = (enrichedFieldMetadata: EnrichedFieldMetadata[]): string[] => - enrichedFieldMetadata.reduce((acc, x) => { +export const getSameFamilyFields = (ecsBasedFieldMetadata: EcsBasedFieldMetadata[]): string[] => + ecsBasedFieldMetadata.reduce((acc, x) => { if (!x.isEcsCompliant && x.type !== x.indexFieldType && x.isInSameFamily) { const field = escape(x.indexFieldName); if (field != null) { @@ -90,14 +90,14 @@ export const getSameFamilyFields = (enrichedFieldMetadata: EnrichedFieldMetadata }, []); export const getIncompatibleValues = ( - enrichedFieldMetadata: EnrichedFieldMetadata[] -): EnrichedFieldMetadata[] => - enrichedFieldMetadata.filter((x) => !x.isEcsCompliant && x.indexInvalidValues.length > 0); + ecsBasedFieldMetadata: EcsBasedFieldMetadata[] +): EcsBasedFieldMetadata[] => + ecsBasedFieldMetadata.filter((x) => !x.isEcsCompliant && x.indexInvalidValues.length > 0); export const getIncompatibleValuesFields = ( - enrichedFieldMetadata: EnrichedFieldMetadata[] + ecsBasedFieldMetadata: EcsBasedFieldMetadata[] ): string[] => - enrichedFieldMetadata.reduce((acc, x) => { + ecsBasedFieldMetadata.reduce((acc, x) => { if (!x.isEcsCompliant && x.indexInvalidValues.length > 0) { const field = escape(x.indexFieldName); if (field != null) { @@ -112,8 +112,8 @@ export const getIncompatibleFieldsMarkdownTablesComment = ({ incompatibleValues, indexName, }: { - incompatibleMappings: EnrichedFieldMetadata[]; - incompatibleValues: EnrichedFieldMetadata[]; + incompatibleMappings: EcsBasedFieldMetadata[]; + incompatibleValues: EcsBasedFieldMetadata[]; indexName: string; }): string => ` ${ diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/index.tsx index aa6866555a3b3..94333d57da0b2 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/incompatible_tab/index.tsx @@ -127,7 +127,7 @@ const IncompatibleTabComponent: React.FC = ({
{showInvalidCallout(partitionedFieldMetadata.incompatible) ? ( <> - + getMarkdownComment({ @@ -37,14 +37,14 @@ ${i18n.FIELDS_WITH_MAPPINGS_SAME_FAMILY} }); export const getSameFamilyMappings = ( - enrichedFieldMetadata: EnrichedFieldMetadata[] -): EnrichedFieldMetadata[] => enrichedFieldMetadata.filter((x) => x.isInSameFamily); + enrichedFieldMetadata: EcsBasedFieldMetadata[] +): EcsBasedFieldMetadata[] => enrichedFieldMetadata.filter((x) => x.isInSameFamily); export const getSameFamilyMarkdownTablesComment = ({ sameFamilyMappings, indexName, }: { - sameFamilyMappings: EnrichedFieldMetadata[]; + sameFamilyMappings: EcsBasedFieldMetadata[]; indexName: string; }): string => ` ${ diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/same_family_tab/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/same_family_tab/index.tsx index a02ff03d24d4d..26295bbbc2ee8 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/same_family_tab/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/same_family_tab/index.tsx @@ -83,7 +83,7 @@ const SameFamilyTabComponent: React.FC = ({ return (
- + {i18n.COPY_TO_CLIPBOARD} diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/callout_summary/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/callout_summary/index.tsx index aa32baafecf19..96b110a05807e 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/callout_summary/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/callout_summary/index.tsx @@ -109,7 +109,7 @@ const CalloutSummaryComponent: React.FC = ({ <> {showInvalidCallout(partitionedFieldMetadata.incompatible) && ( <> - + )} diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.test.ts index 3838a4dc1c168..c89cf1d5158ec 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/tabs/summary_tab/helpers.test.ts @@ -35,6 +35,7 @@ import { getSummaryData, getTabId, } from './helpers'; +import { EcsFlatTyped } from '../../../constants'; describe('helpers', () => { describe('getSummaryData', () => { @@ -216,15 +217,13 @@ describe('helpers', () => { custom: [], incompatible: [ { - description: - 'Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events.', + ...EcsFlatTyped['@timestamp'], hasEcsMetadata: true, indexFieldName: '@timestamp', indexFieldType: '-', indexInvalidValues: [], isEcsCompliant: false, isInSameFamily: false, - type: 'date', }, ], sameFamily: [], diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.test.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.test.ts index f4d44e65309f0..bfe3ffc0c5e55 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.test.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.test.ts @@ -7,7 +7,6 @@ import { IlmExplainLifecycleLifecycleExplain } from '@elastic/elasticsearch/lib/api/types'; import { euiThemeVars } from '@kbn/ui-theme'; -import { EcsFlat } from '@elastic/ecs'; import { omit } from 'lodash/fp'; import { @@ -33,11 +32,11 @@ import { getTotalPatternIndicesChecked, getTotalPatternSameFamily, getTotalSizeInBytes, - hasValidTimestampMapping, isMappingCompatible, postStorageResult, getStorageResults, StorageResult, + formatStorageResult, } from './helpers'; import { hostNameWithTextMapping, @@ -68,13 +67,11 @@ import { COLD_DESCRIPTION, FROZEN_DESCRIPTION, HOT_DESCRIPTION, - TIMESTAMP_DESCRIPTION, UNMANAGED_DESCRIPTION, WARM_DESCRIPTION, } from './translations'; import { DataQualityCheckResult, - EcsMetadata, EnrichedFieldMetadata, PartitionedFieldMetadata, PatternRollup, @@ -82,8 +79,8 @@ import { } from './types'; import { httpServiceMock } from '@kbn/core-http-browser-mocks'; import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; - -const ecsMetadata: Record = EcsFlat as unknown as Record; +import { EcsFlatTyped } from './constants'; +import { mockPartitionedFieldMetadataWithSameFamily } from './mock/partitioned_field_metadata/mock_partitioned_field_metadata_with_same_family'; describe('helpers', () => { describe('getTotalPatternSameFamily', () => { @@ -485,7 +482,7 @@ describe('helpers', () => { test('it returns the happy path result when the index has no mapping conflicts, and no unallowed values', () => { expect( getEnrichedFieldMetadata({ - ecsMetadata, + ecsMetadata: EcsFlatTyped, fieldMetadata: fieldMetadataCorrectMappingType, // no mapping conflicts for `event.category` in this index unallowedValues: noUnallowedValues, // no unallowed values for `event.category` in this index }) @@ -501,7 +498,7 @@ describe('helpers', () => { expect( getEnrichedFieldMetadata({ - ecsMetadata, + ecsMetadata: EcsFlatTyped, fieldMetadata: fieldMetadataCorrectMappingType, // no mapping conflicts for `event.category` in this index unallowedValues: noEntryForEventCategory, // a lookup in this map for the `event.category` field will return undefined }) @@ -511,7 +508,7 @@ describe('helpers', () => { test('it returns a result with the expected `indexInvalidValues` and `isEcsCompliant` when the index has no mapping conflict, but it has unallowed values', () => { expect( getEnrichedFieldMetadata({ - ecsMetadata, + ecsMetadata: EcsFlatTyped, fieldMetadata: fieldMetadataCorrectMappingType, // no mapping conflicts for `event.category` in this index unallowedValues, // this index has unallowed values for the event.category field }) @@ -537,7 +534,7 @@ describe('helpers', () => { expect( getEnrichedFieldMetadata({ - ecsMetadata, + ecsMetadata: EcsFlatTyped, fieldMetadata: { field: 'event.category', // `event.category` is a `keyword`, per the ECS spec type: indexFieldType, // this index has a mapping of `text` instead @@ -558,7 +555,7 @@ describe('helpers', () => { expect( getEnrichedFieldMetadata({ - ecsMetadata, + ecsMetadata: EcsFlatTyped, fieldMetadata: { field: 'event.category', // `event.category` is a `keyword` per the ECS spec type: indexFieldType, // this index has a mapping of `wildcard` instead @@ -579,7 +576,7 @@ describe('helpers', () => { expect( getEnrichedFieldMetadata({ - ecsMetadata, + ecsMetadata: EcsFlatTyped, fieldMetadata: { field: 'event.category', // `event.category` is a `keyword` per the ECS spec type: indexFieldType, // this index has a mapping of `text` instead @@ -611,7 +608,7 @@ describe('helpers', () => { expect( getEnrichedFieldMetadata({ - ecsMetadata, + ecsMetadata: EcsFlatTyped, fieldMetadata: { field, type: indexFieldType, // no mapping conflict, because ECS doesn't define this field @@ -632,14 +629,13 @@ describe('helpers', () => { describe('getMissingTimestampFieldMetadata', () => { test('it returns the expected `EnrichedFieldMetadata`', () => { expect(getMissingTimestampFieldMetadata()).toEqual({ - description: TIMESTAMP_DESCRIPTION, + ...EcsFlatTyped['@timestamp'], hasEcsMetadata: true, indexFieldName: '@timestamp', indexFieldType: '-', // the index did NOT define a mapping for @timestamp indexInvalidValues: [], isEcsCompliant: false, // an index must define the @timestamp mapping isInSameFamily: false, // `date` is not a member of any families - type: 'date', }); }); }); @@ -717,37 +713,6 @@ describe('helpers', () => { }); }); - describe('hasValidTimestampMapping', () => { - test('it returns true when the `enrichedFieldMetadata` has a valid @timestamp', () => { - const enrichedFieldMetadata: EnrichedFieldMetadata[] = [timestamp, sourcePort]; - - expect(hasValidTimestampMapping(enrichedFieldMetadata)).toBe(true); - }); - - test('it returns false when the `enrichedFieldMetadata` collection does NOT include a valid @timestamp', () => { - const enrichedFieldMetadata: EnrichedFieldMetadata[] = [sourcePort]; - - expect(hasValidTimestampMapping(enrichedFieldMetadata)).toBe(false); - }); - - test('it returns false when the `enrichedFieldMetadata` has an @timestamp with an invalid mapping', () => { - const timestampWithInvalidMapping: EnrichedFieldMetadata = { - ...timestamp, - indexFieldType: 'text', // invalid mapping, should be "date" - }; - const enrichedFieldMetadata: EnrichedFieldMetadata[] = [ - timestampWithInvalidMapping, - sourcePort, - ]; - - expect(hasValidTimestampMapping(enrichedFieldMetadata)).toBe(false); - }); - - test('it returns false when `enrichedFieldMetadata` is empty', () => { - expect(hasValidTimestampMapping([])).toBe(false); - }); - }); - describe('getDocsCount', () => { test('it returns the expected docs count when `stats` contains the `indexName`', () => { const indexName = '.ds-packetbeat-8.6.1-2023.02.04-000001'; @@ -1429,6 +1394,120 @@ describe('helpers', () => { }); }); + describe('formatStorageResult', () => { + it('should correctly format the input data into a StorageResult object', () => { + const inputData: Parameters[number] = { + result: { + indexName: 'testIndex', + pattern: 'testPattern', + checkedAt: 1627545600000, + docsCount: 100, + incompatible: 3, + sameFamily: 1, + ilmPhase: 'hot', + markdownComments: ['test comments'], + error: null, + }, + report: { + batchId: 'testBatch', + isCheckAll: true, + sameFamilyFields: ['agent.type'], + unallowedMappingFields: ['event.category', 'host.name', 'source.ip'], + unallowedValueFields: ['event.category'], + sizeInBytes: 5000, + ecsVersion: '1.0.0', + indexName: 'testIndex', + indexId: 'testIndexId', + }, + partitionedFieldMetadata: mockPartitionedFieldMetadataWithSameFamily, + }; + + const expectedResult: StorageResult = { + batchId: 'testBatch', + indexName: 'testIndex', + indexPattern: 'testPattern', + isCheckAll: true, + checkedAt: 1627545600000, + docsCount: 100, + totalFieldCount: 10, + ecsFieldCount: 2, + customFieldCount: 4, + incompatibleFieldCount: 3, + incompatibleFieldMappingItems: [ + { + fieldName: 'event.category', + expectedValue: 'keyword', + actualValue: 'constant_keyword', + description: + 'This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy.\n`event.category` represents the "big buckets" of ECS categories. For example, filtering on `event.category:process` yields all events relating to process activity. This field is closely related to `event.type`, which is used as a subcategory.\nThis field is an array. This will allow proper categorization of some events that fall in multiple categories.', + }, + { + fieldName: 'host.name', + expectedValue: 'keyword', + actualValue: 'text', + description: + 'Name of the host.\nIt can contain what `hostname` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.', + }, + { + fieldName: 'source.ip', + expectedValue: 'ip', + actualValue: 'text', + description: 'IP address of the source (IPv4 or IPv6).', + }, + ], + incompatibleFieldValueItems: [ + { + fieldName: 'event.category', + expectedValues: [ + 'authentication', + 'configuration', + 'database', + 'driver', + 'email', + 'file', + 'host', + 'iam', + 'intrusion_detection', + 'malware', + 'network', + 'package', + 'process', + 'registry', + 'session', + 'threat', + 'vulnerability', + 'web', + ], + actualValues: [{ name: 'an_invalid_category', count: 2 }], + description: + 'This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy.\n`event.category` represents the "big buckets" of ECS categories. For example, filtering on `event.category:process` yields all events relating to process activity. This field is closely related to `event.type`, which is used as a subcategory.\nThis field is an array. This will allow proper categorization of some events that fall in multiple categories.', + }, + ], + sameFamilyFieldCount: 1, + sameFamilyFields: ['agent.type'], + sameFamilyFieldItems: [ + { + fieldName: 'agent.type', + expectedValue: 'keyword', + actualValue: 'constant_keyword', + description: + 'Type of the agent.\nThe agent type always stays the same and should be given by the agent used. In case of Filebeat the agent would always be Filebeat also if two Filebeat instances are run on the same machine.', + }, + ], + unallowedMappingFields: ['event.category', 'host.name', 'source.ip'], + unallowedValueFields: ['event.category'], + sizeInBytes: 5000, + ilmPhase: 'hot', + markdownComments: ['test comments'], + ecsVersion: '1.0.0', + indexId: 'testIndexId', + error: null, + }; + + expect(formatStorageResult(inputData)).toEqual(expectedResult); + }); + }); + describe('postStorageResult', () => { const { fetch } = httpServiceMock.createStartContract(); const { toasts } = notificationServiceMock.createStartContract(); diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts index 48dd12688838c..0980ac3763347 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/helpers.ts @@ -17,16 +17,20 @@ import * as i18n from './translations'; import type { DataQualityCheckResult, DataQualityIndexCheckedParams, - EcsMetadata, + EcsBasedFieldMetadata, EnrichedFieldMetadata, ErrorSummary, IlmPhase, + IncompatibleFieldMappingItem, + IncompatibleFieldValueItem, MeteringStatsIndex, PartitionedFieldMetadata, PartitionedFieldMetadataStats, PatternRollup, + SameFamilyFieldItem, UnallowedValueCount, } from './types'; +import { EcsFlatTyped } from './constants'; const EMPTY_INDEX_NAMES: string[] = []; export const INTERNAL_API_VERSION = '1'; @@ -172,7 +176,7 @@ export const getEnrichedFieldMetadata = ({ fieldMetadata, unallowedValues, }: { - ecsMetadata: Record; + ecsMetadata: EcsFlatTyped; fieldMetadata: FieldType; unallowedValues: Record; }): EnrichedFieldMetadata => { @@ -202,7 +206,7 @@ export const getEnrichedFieldMetadata = ({ return { indexFieldName: field, indexFieldType: type, - indexInvalidValues, + indexInvalidValues: [], hasEcsMetadata: false, isEcsCompliant: false, isInSameFamily: false, // custom fields are never in the same family @@ -210,15 +214,14 @@ export const getEnrichedFieldMetadata = ({ } }; -export const getMissingTimestampFieldMetadata = (): EnrichedFieldMetadata => ({ - description: i18n.TIMESTAMP_DESCRIPTION, +export const getMissingTimestampFieldMetadata = (): EcsBasedFieldMetadata => ({ + ...EcsFlatTyped['@timestamp'], hasEcsMetadata: true, indexFieldName: '@timestamp', indexFieldType: '-', indexInvalidValues: [], isEcsCompliant: false, isInSameFamily: false, // `date` is not a member of any families - type: 'date', }); export const getPartitionedFieldMetadata = ( @@ -258,11 +261,6 @@ export const getPartitionedFieldMetadataStats = ( }; }; -export const hasValidTimestampMapping = (enrichedFieldMetadata: EnrichedFieldMetadata[]): boolean => - enrichedFieldMetadata.some( - (x) => x.indexFieldName === '@timestamp' && x.indexFieldType === 'date' - ); - export const getDocsCount = ({ indexName, stats, @@ -471,8 +469,11 @@ export interface StorageResult { ecsFieldCount: number; customFieldCount: number; incompatibleFieldCount: number; + incompatibleFieldMappingItems: IncompatibleFieldMappingItem[]; + incompatibleFieldValueItems: IncompatibleFieldValueItem[]; sameFamilyFieldCount: number; sameFamilyFields: string[]; + sameFamilyFieldItems: SameFamilyFieldItem[]; unallowedMappingFields: string[]; unallowedValueFields: string[]; sizeInBytes: number; @@ -491,28 +492,66 @@ export const formatStorageResult = ({ result: DataQualityCheckResult; report: DataQualityIndexCheckedParams; partitionedFieldMetadata: PartitionedFieldMetadata; -}): StorageResult => ({ - batchId: report.batchId, - indexName: result.indexName, - indexPattern: result.pattern, - isCheckAll: report.isCheckAll, - checkedAt: result.checkedAt ?? Date.now(), - docsCount: result.docsCount ?? 0, - totalFieldCount: partitionedFieldMetadata.all.length, - ecsFieldCount: partitionedFieldMetadata.ecsCompliant.length, - customFieldCount: partitionedFieldMetadata.custom.length, - incompatibleFieldCount: partitionedFieldMetadata.incompatible.length, - sameFamilyFieldCount: partitionedFieldMetadata.sameFamily.length, - sameFamilyFields: report.sameFamilyFields ?? [], - unallowedMappingFields: report.unallowedMappingFields ?? [], - unallowedValueFields: report.unallowedValueFields ?? [], - sizeInBytes: report.sizeInBytes ?? 0, - ilmPhase: result.ilmPhase, - markdownComments: result.markdownComments, - ecsVersion: report.ecsVersion, - indexId: report.indexId ?? '', // ---> we don't have this field when isILMAvailable is false - error: result.error, -}); +}): StorageResult => { + const incompatibleFieldMappingItems: IncompatibleFieldMappingItem[] = []; + const incompatibleFieldValueItems: IncompatibleFieldValueItem[] = []; + const sameFamilyFieldItems: SameFamilyFieldItem[] = []; + + partitionedFieldMetadata.incompatible.forEach((field) => { + if (field.type !== field.indexFieldType) { + incompatibleFieldMappingItems.push({ + fieldName: field.indexFieldName, + expectedValue: field.type, + actualValue: field.indexFieldType, + description: field.description, + }); + } + + if (field.indexInvalidValues.length > 0) { + incompatibleFieldValueItems.push({ + fieldName: field.indexFieldName, + expectedValues: field.allowed_values?.map((x) => x.name) ?? [], + actualValues: field.indexInvalidValues.map((v) => ({ name: v.fieldName, count: v.count })), + description: field.description, + }); + } + }); + + partitionedFieldMetadata.sameFamily.forEach((field) => { + sameFamilyFieldItems.push({ + fieldName: field.indexFieldName, + expectedValue: field.type, + actualValue: field.indexFieldType, + description: field.description, + }); + }); + + return { + batchId: report.batchId, + indexName: result.indexName, + indexPattern: result.pattern, + isCheckAll: report.isCheckAll, + checkedAt: result.checkedAt ?? Date.now(), + docsCount: result.docsCount ?? 0, + totalFieldCount: partitionedFieldMetadata.all.length, + ecsFieldCount: partitionedFieldMetadata.ecsCompliant.length, + customFieldCount: partitionedFieldMetadata.custom.length, + incompatibleFieldCount: partitionedFieldMetadata.incompatible.length, + incompatibleFieldMappingItems, + incompatibleFieldValueItems, + sameFamilyFieldCount: partitionedFieldMetadata.sameFamily.length, + sameFamilyFields: report.sameFamilyFields ?? [], + sameFamilyFieldItems, + unallowedMappingFields: report.unallowedMappingFields ?? [], + unallowedValueFields: report.unallowedValueFields ?? [], + sizeInBytes: report.sizeInBytes ?? 0, + ilmPhase: result.ilmPhase, + markdownComments: result.markdownComments, + ecsVersion: report.ecsVersion, + indexId: report.indexId ?? '', + error: result.error, + }; +}; export const formatResultFromStorage = ({ storageResult, diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/enriched_field_metadata/mock_enriched_field_metadata.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/enriched_field_metadata/mock_enriched_field_metadata.ts index 1f5ea0ce4d4d4..351d0bb06310f 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/enriched_field_metadata/mock_enriched_field_metadata.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/mock/enriched_field_metadata/mock_enriched_field_metadata.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { EnrichedFieldMetadata } from '../../types'; +import { CustomFieldMetadata, EcsBasedFieldMetadata } from '../../types'; -export const timestamp: EnrichedFieldMetadata = { +export const timestamp: EcsBasedFieldMetadata = { dashed_name: 'timestamp', description: 'Date/time when the event originated.\nThis is the date/time extracted from the event, typically representing when the event was generated by the source.\nIf the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline.\nRequired field for all events.', @@ -27,7 +27,7 @@ export const timestamp: EnrichedFieldMetadata = { isInSameFamily: false, // `date` is not a member of any families }; -export const eventCategory: EnrichedFieldMetadata = { +export const eventCategory: EcsBasedFieldMetadata = { allowed_values: [ { description: @@ -166,7 +166,7 @@ export const eventCategory: EnrichedFieldMetadata = { isInSameFamily: false, }; -export const eventCategoryWithUnallowedValues: EnrichedFieldMetadata = { +export const eventCategoryWithUnallowedValues: EcsBasedFieldMetadata = { ...eventCategory, indexInvalidValues: [ { @@ -181,7 +181,7 @@ export const eventCategoryWithUnallowedValues: EnrichedFieldMetadata = { isEcsCompliant: false, // because this index has unallowed values }; -export const hostNameWithTextMapping: EnrichedFieldMetadata = { +export const hostNameWithTextMapping: EcsBasedFieldMetadata = { dashed_name: 'host-name', description: 'Name of the host.\nIt can contain what `hostname` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.', @@ -200,7 +200,7 @@ export const hostNameWithTextMapping: EnrichedFieldMetadata = { isInSameFamily: false, // `keyword` and `text` are not in the family }; -export const hostNameKeyword: EnrichedFieldMetadata = { +export const hostNameKeyword: CustomFieldMetadata = { indexFieldName: 'host.name.keyword', indexFieldType: 'keyword', indexInvalidValues: [], @@ -209,7 +209,7 @@ export const hostNameKeyword: EnrichedFieldMetadata = { isInSameFamily: false, // custom fields are never in the same family }; -export const someField: EnrichedFieldMetadata = { +export const someField: CustomFieldMetadata = { indexFieldName: 'some.field', indexFieldType: 'text', indexInvalidValues: [], @@ -218,7 +218,7 @@ export const someField: EnrichedFieldMetadata = { isInSameFamily: false, // custom fields are never in the same family }; -export const someFieldKeyword: EnrichedFieldMetadata = { +export const someFieldKeyword: CustomFieldMetadata = { indexFieldName: 'some.field.keyword', indexFieldType: 'keyword', indexInvalidValues: [], @@ -227,7 +227,7 @@ export const someFieldKeyword: EnrichedFieldMetadata = { isInSameFamily: false, // custom fields are never in the same family }; -export const sourceIpWithTextMapping: EnrichedFieldMetadata = { +export const sourceIpWithTextMapping: EcsBasedFieldMetadata = { dashed_name: 'source-ip', description: 'IP address of the source (IPv4 or IPv6).', flat_name: 'source.ip', @@ -244,7 +244,7 @@ export const sourceIpWithTextMapping: EnrichedFieldMetadata = { isInSameFamily: false, // `ip` is not a member of any families }; -export const sourceIpKeyword: EnrichedFieldMetadata = { +export const sourceIpKeyword: CustomFieldMetadata = { indexFieldName: 'source.ip.keyword', indexFieldType: 'keyword', indexInvalidValues: [], @@ -253,7 +253,7 @@ export const sourceIpKeyword: EnrichedFieldMetadata = { isInSameFamily: false, // custom fields are never in the same family }; -export const sourcePort: EnrichedFieldMetadata = { +export const sourcePort: EcsBasedFieldMetadata = { dashed_name: 'source-port', description: 'Port of the source.', flat_name: 'source.port', @@ -271,7 +271,7 @@ export const sourcePort: EnrichedFieldMetadata = { isInSameFamily: false, // `long` is not a member of any families }; -export const mockCustomFields: EnrichedFieldMetadata[] = [ +export const mockCustomFields: CustomFieldMetadata[] = [ { indexFieldName: 'host.name.keyword', indexFieldType: 'keyword', @@ -306,7 +306,7 @@ export const mockCustomFields: EnrichedFieldMetadata[] = [ }, ]; -export const mockIncompatibleMappings: EnrichedFieldMetadata[] = [ +export const mockIncompatibleMappings: EcsBasedFieldMetadata[] = [ { dashed_name: 'host-name', description: diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts index bd551d80f531b..ed20efc3fd959 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/types.ts @@ -16,43 +16,73 @@ export interface Mappings { indexes: Record; } -export interface AllowedValue { - description?: string; - expected_event_types?: string[]; - name?: string; -} +export interface EcsFieldMetadata { + dashed_name: string; + description: string; + flat_name: string; + level: string; + name: string; + normalize: string[]; + short: string; + type: string; -export interface EcsMetadata { allowed_values?: AllowedValue[]; - dashed_name?: string; - description?: string; - example?: string; - flat_name?: string; + beta?: string; + doc_values?: boolean; + example?: string | number | boolean; + expected_values?: string[]; format?: string; ignore_above?: number; - level?: string; - name?: string; - normalize?: string[]; + index?: boolean; + input_format?: string; + multi_fields?: MultiField[]; + object_type?: string; + original_fieldset?: string; + output_format?: string; + output_precision?: number; + pattern?: string; required?: boolean; - short?: string; - type?: string; + scaling_factor?: number; +} + +export interface AllowedValue { + description: string; + name: string; + expected_event_types?: string[]; + beta?: string; +} + +export interface MultiField { + flat_name: string; + name: string; + type: string; } -export type EnrichedFieldMetadata = EcsMetadata & { - hasEcsMetadata: boolean; +export interface CustomFieldMetadata { + hasEcsMetadata: false; + indexFieldName: string; + indexFieldType: string; + indexInvalidValues: []; + isEcsCompliant: false; + isInSameFamily: false; +} +export interface EcsBasedFieldMetadata extends EcsFieldMetadata { + hasEcsMetadata: true; indexFieldName: string; indexFieldType: string; indexInvalidValues: UnallowedValueCount[]; isEcsCompliant: boolean; isInSameFamily: boolean; -}; +} + +export type EnrichedFieldMetadata = EcsBasedFieldMetadata | CustomFieldMetadata; export interface PartitionedFieldMetadata { all: EnrichedFieldMetadata[]; - custom: EnrichedFieldMetadata[]; - ecsCompliant: EnrichedFieldMetadata[]; - incompatible: EnrichedFieldMetadata[]; - sameFamily: EnrichedFieldMetadata[]; + custom: CustomFieldMetadata[]; + ecsCompliant: EcsBasedFieldMetadata[]; + incompatible: EcsBasedFieldMetadata[]; + sameFamily: EcsBasedFieldMetadata[]; } export interface PartitionedFieldMetadataStats { @@ -89,6 +119,32 @@ export interface UnallowedValueSearchResult { export type IlmPhase = 'hot' | 'warm' | 'cold' | 'frozen' | 'unmanaged'; +export interface IncompatibleFieldMappingItem { + fieldName: string; + expectedValue: string; + actualValue: string; + description: string; +} + +export interface ActualIncompatibleValue { + name: string; + count: number; +} + +export interface IncompatibleFieldValueItem { + fieldName: string; + expectedValues: string[]; + actualValues: ActualIncompatibleValue[]; + description: string; +} + +export interface SameFamilyFieldItem { + fieldName: string; + expectedValue: string; + actualValue: string; + description: string; +} + export interface IlmExplainPhaseCounts { hot: number; warm: number; diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.test.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.test.tsx index 0983308815464..fcbb8aae9337f 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.test.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/use_unallowed_values/index.test.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { EcsFlat } from '@elastic/ecs'; import { renderHook } from '@testing-library/react-hooks'; import React, { FC, PropsWithChildren } from 'react'; @@ -13,9 +12,10 @@ import { getUnallowedValueRequestItems } from '../data_quality_panel/allowed_val import { DataQualityProvider } from '../data_quality_panel/data_quality_context'; import { mockUnallowedValuesResponse } from '../mock/unallowed_values/mock_unallowed_values'; import { ERROR_LOADING_UNALLOWED_VALUES } from '../translations'; -import { EcsMetadata, UnallowedValueRequestItem } from '../types'; +import { UnallowedValueRequestItem } from '../types'; import { useUnallowedValues, UseUnallowedValues } from '.'; import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; +import { EcsFlatTyped } from '../constants'; const mockHttpFetch = jest.fn(); const mockReportDataQualityIndexChecked = jest.fn(); @@ -37,10 +37,9 @@ const ContextWrapper: FC> = ({ children }) => ( ); -const ecsMetadata = EcsFlat as unknown as Record; const indexName = 'auditbeat-custom-index-1'; const requestItems = getUnallowedValueRequestItems({ - ecsMetadata, + ecsMetadata: EcsFlatTyped, indexName, }); diff --git a/x-pack/packages/security-solution/features/src/product_features_keys.ts b/x-pack/packages/security-solution/features/src/product_features_keys.ts index 5f6cd5f93b54b..be60a0edfdb3d 100644 --- a/x-pack/packages/security-solution/features/src/product_features_keys.ts +++ b/x-pack/packages/security-solution/features/src/product_features_keys.ts @@ -67,6 +67,11 @@ export enum ProductFeatureSecurityKey { * enables all rule actions */ externalRuleActions = 'external_rule_actions', + + /** + * enables Cloud Security Posture - CSPM, KSPM, CNVM + */ + cloudSecurityPosture = 'cloud_security_posture', } export enum ProductFeatureCasesKey { diff --git a/x-pack/packages/security-solution/features/src/security/product_feature_config.ts b/x-pack/packages/security-solution/features/src/security/product_feature_config.ts index 7fc2413e85e89..46ca114943795 100644 --- a/x-pack/packages/security-solution/features/src/security/product_feature_config.ts +++ b/x-pack/packages/security-solution/features/src/security/product_feature_config.ts @@ -121,4 +121,5 @@ export const securityDefaultProductFeaturesConfig: DefaultSecurityProductFeature [ProductFeatureSecurityKey.endpointProtectionUpdates]: {}, [ProductFeatureSecurityKey.endpointAgentTamperProtection]: {}, [ProductFeatureSecurityKey.externalRuleActions]: {}, + [ProductFeatureSecurityKey.cloudSecurityPosture]: {}, }; diff --git a/x-pack/packages/security-solution/upselling/service/types.ts b/x-pack/packages/security-solution/upselling/service/types.ts index 0b0d5b6e930f5..fdbd840429fec 100644 --- a/x-pack/packages/security-solution/upselling/service/types.ts +++ b/x-pack/packages/security-solution/upselling/service/types.ts @@ -17,6 +17,7 @@ export type UpsellingSectionId = | 'osquery_automated_response_actions' | 'endpoint_protection_updates' | 'endpoint_agent_tamper_protection' + | 'cloud_security_posture_integration_installation' | 'ruleDetailsEndpointExceptions'; export type UpsellingMessageId = diff --git a/x-pack/plugins/actions/docs/openapi/bundled.json b/x-pack/plugins/actions/docs/openapi/bundled.json index 81033683ee038..df93f77a5ab20 100644 --- a/x-pack/plugins/actions/docs/openapi/bundled.json +++ b/x-pack/plugins/actions/docs/openapi/bundled.json @@ -1,5 +1,5 @@ { - "openapi": "3.1.0", + "openapi": "3.0.3", "info": { "title": "Connectors", "description": "OpenAPI schema for Connectors endpoints", @@ -32,16 +32,20 @@ } ], "paths": { - "/api/actions/connector": { + "/s/{spaceId}/api/actions/connector": { "post": { - "summary": "Creates a connector.", - "operationId": "createConnector", + "summary": "Create a connector", + "operationId": "createConnectorWithSpaceId", + "description": "You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.\n", "tags": [ "connectors" ], "parameters": [ { "$ref": "#/components/parameters/kbn_xsrf" + }, + { + "$ref": "#/components/parameters/space_id" } ], "requestBody": { @@ -99,16 +103,20 @@ } } }, - "/api/actions/connector/{connectorId}": { + "/s/{spaceId}/api/actions/connector/{connectorId}": { "get": { - "summary": "Retrieves a connector by ID.", - "operationId": "getConnector", + "summary": "Get connector information", + "operationId": "getConnectorWithSpaceId", + "description": "You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.\n", "tags": [ "connectors" ], "parameters": [ { "$ref": "#/components/parameters/connector_id" + }, + { + "$ref": "#/components/parameters/space_id" } ], "responses": { @@ -136,8 +144,9 @@ } }, "delete": { - "summary": "Deletes a connector.", - "operationId": "deleteConnector", + "summary": "Delete a connector", + "operationId": "deleteConnectorWithSpaceId", + "description": "You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. WARNING: When you delete a connector, it cannot be recovered.\n", "tags": [ "connectors" ], @@ -147,6 +156,9 @@ }, { "$ref": "#/components/parameters/connector_id" + }, + { + "$ref": "#/components/parameters/space_id" } ], "responses": { @@ -162,8 +174,9 @@ } }, "post": { - "summary": "Creates a connector.", - "operationId": "createConnectorId", + "summary": "Create a connector", + "operationId": "createConnectorIdWithSpaceId", + "description": "You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.\n", "tags": [ "connectors" ], @@ -171,16 +184,17 @@ { "$ref": "#/components/parameters/kbn_xsrf" }, + { + "$ref": "#/components/parameters/space_id" + }, { "in": "path", "name": "connectorId", - "description": "A UUID v1 or v4 identifier for the connector. If you omit this parameter, an identifier is randomly generated.\n", + "description": "A UUID v1 or v4 identifier for the connector. If you omit this parameter, an identifier is randomly generated.", "required": true, "schema": { "type": "string", - "examples": [ - "ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74" - ] + "example": "ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74" } } ], @@ -221,8 +235,9 @@ } }, "put": { - "summary": "Updates the attributes for a connector.", - "operationId": "updateConnector", + "summary": "Update a connector", + "operationId": "updateConnectorWithSpaceId", + "description": "You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.\n", "tags": [ "connectors" ], @@ -232,6 +247,9 @@ }, { "$ref": "#/components/parameters/connector_id" + }, + { + "$ref": "#/components/parameters/space_id" } ], "requestBody": { @@ -272,10 +290,138 @@ } } }, - "/api/actions/connector/{connectorId}/_execute": { + "/s/{spaceId}/api/actions/connectors": { + "get": { + "summary": "Get all connectors", + "operationId": "getConnectorsWithSpaceId", + "description": "You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.\n", + "tags": [ + "connectors" + ], + "parameters": [ + { + "$ref": "#/components/parameters/space_id" + } + ], + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/connector_response_properties" + } + }, + "examples": { + "getConnectorsResponse": { + "$ref": "#/components/examples/get_connectors_response" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/401" + } + } + } + }, + "/s/{spaceId}/api/actions/connector_types": { + "get": { + "summary": "Get all connector types", + "operationId": "getConnectorTypesWithSpaceId", + "description": "You do not need any Kibana feature privileges to run this API.\n", + "tags": [ + "connectors" + ], + "parameters": [ + { + "$ref": "#/components/parameters/space_id" + }, + { + "in": "query", + "name": "feature_id", + "description": "A filter to limit the retrieved connector types to those that support a specific feature (such as alerting or cases).", + "schema": { + "$ref": "#/components/schemas/features" + } + } + ], + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "title": "Get connector types response body properties", + "description": "The properties vary for each connector type.", + "type": "array", + "items": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Indicates whether the connector type is enabled in Kibana.", + "example": true + }, + "enabled_in_config": { + "type": "boolean", + "description": "Indicates whether the connector type is enabled in the Kibana `.yml` file.", + "example": true + }, + "enabled_in_license": { + "type": "boolean", + "description": "Indicates whether the connector is enabled in the license.", + "example": true + }, + "id": { + "$ref": "#/components/schemas/connector_types" + }, + "minimum_license_required": { + "type": "string", + "description": "The license that is required to use the connector type.", + "example": "basic" + }, + "name": { + "type": "string", + "description": "The name of the connector type.", + "example": "Index" + }, + "supported_feature_ids": { + "type": "array", + "description": "The Kibana features that are supported by the connector type.", + "items": { + "$ref": "#/components/schemas/features" + }, + "example": [ + "alerting", + "uptime", + "siem" + ] + } + } + } + }, + "examples": { + "getConnectorTypesResponse": { + "$ref": "#/components/examples/get_connector_types_response" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/401" + } + } + } + }, + "/s/{spaceId}/api/actions/connector/{connectorId}/_execute": { "post": { - "summary": "Runs a connector.", - "operationId": "runConnector", + "summary": "Run a connector", + "operationId": "runConnectorWithSpaceId", "description": "You can use this API to test an action that involves interaction with Kibana services or integrations with third-party systems. You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. If you use an index connector, you must also have `all`, `create`, `index`, or `write` indices privileges.\n", "tags": [ "connectors" @@ -286,6 +432,9 @@ }, { "$ref": "#/components/parameters/connector_id" + }, + { + "$ref": "#/components/parameters/space_id" } ], "requestBody": { @@ -296,21 +445,12 @@ "$ref": "#/components/schemas/run_connector_request" }, "examples": { - "runCasesWebhookConnectorRequest": { - "$ref": "#/components/examples/run_cases_webhook_connector_request" - }, - "runEmailConnectorRequest": { - "$ref": "#/components/examples/run_email_connector_request" - }, "runIndexConnectorRequest": { "$ref": "#/components/examples/run_index_connector_request" }, "runJiraConnectorRequest": { "$ref": "#/components/examples/run_jira_connector_request" }, - "runPagerDutyConnectorRequest": { - "$ref": "#/components/examples/run_pagerduty_connector_request" - }, "runServerLogConnectorRequest": { "$ref": "#/components/examples/run_server_log_connector_request" }, @@ -359,6 +499,12 @@ } ] }, + "message": { + "type": "string" + }, + "service_message": { + "type": "string" + }, "status": { "type": "string", "description": "The status of the action.", @@ -370,21 +516,12 @@ } }, "examples": { - "runCasesWebhookConnectorResponse": { - "$ref": "#/components/examples/run_cases_webhook_connector_response" - }, - "runEmailConnectorResponse": { - "$ref": "#/components/examples/run_email_connector_response" - }, "runIndexConnectorResponse": { "$ref": "#/components/examples/run_index_connector_response" }, "runJiraConnectorResponse": { "$ref": "#/components/examples/run_jira_connector_response" }, - "runPagerDutyConnectorResponse": { - "$ref": "#/components/examples/run_pagerduty_connector_response" - }, "runServerLogConnectorResponse": { "$ref": "#/components/examples/run_server_log_connector_response" }, @@ -407,27 +544,62 @@ } } }, - "/api/actions/connectors": { - "get": { - "summary": "Retrieves all connectors.", - "operationId": "getConnectors", + "/api/actions/connector": { + "post": { + "summary": "Create a connector", + "operationId": "createConnector", "tags": [ "connectors" ], + "parameters": [ + { + "$ref": "#/components/parameters/kbn_xsrf" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/create_connector_request" + }, + "examples": { + "createEmailConnectorRequest": { + "$ref": "#/components/examples/create_email_connector_request" + }, + "createIndexConnectorRequest": { + "$ref": "#/components/examples/create_index_connector_request" + }, + "createWebhookConnectorRequest": { + "$ref": "#/components/examples/create_webhook_connector_request" + }, + "createXmattersConnectorRequest": { + "$ref": "#/components/examples/create_xmatters_connector_request" + } + } + } + } + }, "responses": { "200": { "description": "Indicates a successful call.", "content": { "application/json": { "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/connector_response_properties" - } + "$ref": "#/components/schemas/connector_response_properties" }, "examples": { - "getConnectorsResponse": { - "$ref": "#/components/examples/get_connectors_response" + "createEmailConnectorResponse": { + "$ref": "#/components/examples/create_email_connector_response" + }, + "createIndexConnectorResponse": { + "$ref": "#/components/examples/create_index_connector_response" + }, + "createWebhookConnectorResponse": { + "$ref": "#/components/examples/create_webhook_connector_response" + }, + "createXmattersConnectorResponse": { + "$ref": "#/components/examples/create_xmatters_connector_response" } } } @@ -439,195 +611,16 @@ } } }, - "/api/actions/connector_types": { + "/api/actions/connector/{connectorId}": { "get": { - "summary": "Retrieves a list of all connector types.", - "operationId": "getConnectorTypes", + "summary": "Get a connector information", + "operationId": "getConnector", "tags": [ "connectors" ], "parameters": [ { - "in": "query", - "name": "feature_id", - "description": "A filter to limit the retrieved connector types to those that support a specific feature (such as alerting or cases).", - "schema": { - "$ref": "#/components/schemas/features" - } - } - ], - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json": { - "schema": { - "title": "Get connector types response body properties", - "description": "The properties vary for each connector type.", - "type": "array", - "items": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Indicates whether the connector type is enabled in Kibana.", - "examples": [ - true - ] - }, - "enabled_in_config": { - "type": "boolean", - "description": "Indicates whether the connector type is enabled in the Kibana configuration file.", - "examples": [ - true - ] - }, - "enabled_in_license": { - "type": "boolean", - "description": "Indicates whether the connector is enabled in the license.", - "examples": [ - true - ] - }, - "id": { - "$ref": "#/components/schemas/connector_types" - }, - "is_system_action_type": { - "type": "boolean", - "examples": [ - false - ] - }, - "minimum_license_required": { - "type": "string", - "description": "The license that is required to use the connector type.", - "examples": [ - "basic" - ] - }, - "name": { - "type": "string", - "description": "The name of the connector type.", - "examples": [ - "Index" - ] - }, - "supported_feature_ids": { - "type": "array", - "description": "The features that are supported by the connector type.", - "items": { - "$ref": "#/components/schemas/features" - }, - "examples": [ - [ - "alerting", - "cases", - "siem" - ] - ] - } - } - } - }, - "examples": { - "getConnectorTypesServerlessResponse": { - "$ref": "#/components/examples/get_connector_types_generativeai_response" - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/401" - } - } - } - }, - "/s/{spaceId}/api/actions/connector": { - "post": { - "summary": "Creates a connector.", - "operationId": "createConnectorWithSpaceId", - "description": "You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.\n", - "tags": [ - "connectors" - ], - "parameters": [ - { - "$ref": "#/components/parameters/kbn_xsrf" - }, - { - "$ref": "#/components/parameters/space_id" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/create_connector_request" - }, - "examples": { - "createEmailConnectorRequest": { - "$ref": "#/components/examples/create_email_connector_request" - }, - "createIndexConnectorRequest": { - "$ref": "#/components/examples/create_index_connector_request" - }, - "createWebhookConnectorRequest": { - "$ref": "#/components/examples/create_webhook_connector_request" - }, - "createXmattersConnectorRequest": { - "$ref": "#/components/examples/create_xmatters_connector_request" - } - } - } - } - }, - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/connector_response_properties" - }, - "examples": { - "createEmailConnectorResponse": { - "$ref": "#/components/examples/create_email_connector_response" - }, - "createIndexConnectorResponse": { - "$ref": "#/components/examples/create_index_connector_response" - }, - "createWebhookConnectorResponse": { - "$ref": "#/components/examples/create_webhook_connector_response" - }, - "createXmattersConnectorResponse": { - "$ref": "#/components/examples/create_xmatters_connector_response" - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/401" - } - } - } - }, - "/s/{spaceId}/api/actions/connector/{connectorId}": { - "get": { - "summary": "Retrieves a connector by ID.", - "operationId": "getConnectorWithSpaceId", - "description": "You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.\n", - "tags": [ - "connectors" - ], - "parameters": [ - { - "$ref": "#/components/parameters/connector_id" - }, - { - "$ref": "#/components/parameters/space_id" + "$ref": "#/components/parameters/connector_id" } ], "responses": { @@ -655,9 +648,8 @@ } }, "delete": { - "summary": "Deletes a connector.", - "operationId": "deleteConnectorWithSpaceId", - "description": "You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. WARNING: When you delete a connector, it cannot be recovered.\n", + "summary": "Delete a connector", + "operationId": "deleteConnector", "tags": [ "connectors" ], @@ -667,9 +659,6 @@ }, { "$ref": "#/components/parameters/connector_id" - }, - { - "$ref": "#/components/parameters/space_id" } ], "responses": { @@ -685,9 +674,8 @@ } }, "post": { - "summary": "Creates a connector.", - "operationId": "createConnectorIdWithSpaceId", - "description": "You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.\n", + "summary": "Create a connector", + "operationId": "createConnectorId", "tags": [ "connectors" ], @@ -695,19 +683,14 @@ { "$ref": "#/components/parameters/kbn_xsrf" }, - { - "$ref": "#/components/parameters/space_id" - }, { "in": "path", "name": "connectorId", - "description": "A UUID v1 or v4 identifier for the connector. If you omit this parameter, an identifier is randomly generated.", + "description": "A UUID v1 or v4 identifier for the connector. If you omit this parameter, an identifier is randomly generated.\n", "required": true, "schema": { "type": "string", - "examples": [ - "ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74" - ] + "example": "ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74" } } ], @@ -748,9 +731,8 @@ } }, "put": { - "summary": "Updates the attributes for a connector.", - "operationId": "updateConnectorWithSpaceId", - "description": "You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.\n", + "summary": "Update a connector", + "operationId": "updateConnector", "tags": [ "connectors" ], @@ -760,9 +742,6 @@ }, { "$ref": "#/components/parameters/connector_id" - }, - { - "$ref": "#/components/parameters/space_id" } ], "requestBody": { @@ -803,150 +782,10 @@ } } }, - "/s/{spaceId}/api/actions/connectors": { - "get": { - "summary": "Retrieves all connectors.", - "operationId": "getConnectorsWithSpaceId", - "description": "You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.\n", - "tags": [ - "connectors" - ], - "parameters": [ - { - "$ref": "#/components/parameters/space_id" - } - ], - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/connector_response_properties" - } - }, - "examples": { - "getConnectorsResponse": { - "$ref": "#/components/examples/get_connectors_response" - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/401" - } - } - } - }, - "/s/{spaceId}/api/actions/connector_types": { - "get": { - "summary": "Retrieves a list of all connector types.", - "operationId": "getConnectorTypesWithSpaceId", - "description": "You do not need any Kibana feature privileges to run this API.\n", - "tags": [ - "connectors" - ], - "parameters": [ - { - "$ref": "#/components/parameters/space_id" - }, - { - "in": "query", - "name": "feature_id", - "description": "A filter to limit the retrieved connector types to those that support a specific feature (such as alerting or cases).", - "schema": { - "$ref": "#/components/schemas/features" - } - } - ], - "responses": { - "200": { - "description": "Indicates a successful call.", - "content": { - "application/json": { - "schema": { - "title": "Get connector types response body properties", - "description": "The properties vary for each connector type.", - "type": "array", - "items": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Indicates whether the connector type is enabled in Kibana.", - "examples": [ - true - ] - }, - "enabled_in_config": { - "type": "boolean", - "description": "Indicates whether the connector type is enabled in the Kibana `.yml` file.", - "examples": [ - true - ] - }, - "enabled_in_license": { - "type": "boolean", - "description": "Indicates whether the connector is enabled in the license.", - "examples": [ - true - ] - }, - "id": { - "$ref": "#/components/schemas/connector_types" - }, - "minimum_license_required": { - "type": "string", - "description": "The license that is required to use the connector type.", - "examples": [ - "basic" - ] - }, - "name": { - "type": "string", - "description": "The name of the connector type.", - "examples": [ - "Index" - ] - }, - "supported_feature_ids": { - "type": "array", - "description": "The Kibana features that are supported by the connector type.", - "items": { - "$ref": "#/components/schemas/features" - }, - "examples": [ - [ - "alerting", - "uptime", - "siem" - ] - ] - } - } - } - }, - "examples": { - "getConnectorTypesResponse": { - "$ref": "#/components/examples/get_connector_types_response" - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/401" - } - } - } - }, - "/s/{spaceId}/api/actions/connector/{connectorId}/_execute": { + "/api/actions/connector/{connectorId}/_execute": { "post": { - "summary": "Runs a connector.", - "operationId": "runConnectorWithSpaceId", + "summary": "Run a connector", + "operationId": "runConnector", "description": "You can use this API to test an action that involves interaction with Kibana services or integrations with third-party systems. You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. If you use an index connector, you must also have `all`, `create`, `index`, or `write` indices privileges.\n", "tags": [ "connectors" @@ -957,9 +796,6 @@ }, { "$ref": "#/components/parameters/connector_id" - }, - { - "$ref": "#/components/parameters/space_id" } ], "requestBody": { @@ -970,12 +806,21 @@ "$ref": "#/components/schemas/run_connector_request" }, "examples": { + "runCasesWebhookConnectorRequest": { + "$ref": "#/components/examples/run_cases_webhook_connector_request" + }, + "runEmailConnectorRequest": { + "$ref": "#/components/examples/run_email_connector_request" + }, "runIndexConnectorRequest": { "$ref": "#/components/examples/run_index_connector_request" }, "runJiraConnectorRequest": { "$ref": "#/components/examples/run_jira_connector_request" }, + "runPagerDutyConnectorRequest": { + "$ref": "#/components/examples/run_pagerduty_connector_request" + }, "runServerLogConnectorRequest": { "$ref": "#/components/examples/run_server_log_connector_request" }, @@ -1024,12 +869,6 @@ } ] }, - "message": { - "type": "string" - }, - "service_message": { - "type": "string" - }, "status": { "type": "string", "description": "The status of the action.", @@ -1041,23 +880,154 @@ } }, "examples": { - "runIndexConnectorResponse": { - "$ref": "#/components/examples/run_index_connector_response" - }, - "runJiraConnectorResponse": { - "$ref": "#/components/examples/run_jira_connector_response" - }, - "runServerLogConnectorResponse": { - "$ref": "#/components/examples/run_server_log_connector_response" - }, - "runServiceNowITOMConnectorResponse": { - "$ref": "#/components/examples/run_servicenow_itom_connector_response" - }, - "runSlackConnectorResponse": { - "$ref": "#/components/examples/run_slack_api_connector_response" - }, - "runSwimlaneConnectorResponse": { - "$ref": "#/components/examples/run_swimlane_connector_response" + "runCasesWebhookConnectorResponse": { + "$ref": "#/components/examples/run_cases_webhook_connector_response" + }, + "runEmailConnectorResponse": { + "$ref": "#/components/examples/run_email_connector_response" + }, + "runIndexConnectorResponse": { + "$ref": "#/components/examples/run_index_connector_response" + }, + "runJiraConnectorResponse": { + "$ref": "#/components/examples/run_jira_connector_response" + }, + "runPagerDutyConnectorResponse": { + "$ref": "#/components/examples/run_pagerduty_connector_response" + }, + "runServerLogConnectorResponse": { + "$ref": "#/components/examples/run_server_log_connector_response" + }, + "runServiceNowITOMConnectorResponse": { + "$ref": "#/components/examples/run_servicenow_itom_connector_response" + }, + "runSlackConnectorResponse": { + "$ref": "#/components/examples/run_slack_api_connector_response" + }, + "runSwimlaneConnectorResponse": { + "$ref": "#/components/examples/run_swimlane_connector_response" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/401" + } + } + } + }, + "/api/actions/connectors": { + "get": { + "summary": "Get all connectors", + "operationId": "getConnectors", + "tags": [ + "connectors" + ], + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/connector_response_properties" + } + }, + "examples": { + "getConnectorsResponse": { + "$ref": "#/components/examples/get_connectors_response" + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/401" + } + } + } + }, + "/api/actions/connector_types": { + "get": { + "summary": "Get all connector types", + "operationId": "getConnectorTypes", + "tags": [ + "connectors" + ], + "parameters": [ + { + "in": "query", + "name": "feature_id", + "description": "A filter to limit the retrieved connector types to those that support a specific feature (such as alerting or cases).", + "schema": { + "$ref": "#/components/schemas/features" + } + } + ], + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json": { + "schema": { + "title": "Get connector types response body properties", + "description": "The properties vary for each connector type.", + "type": "array", + "items": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Indicates whether the connector type is enabled in Kibana.", + "example": true + }, + "enabled_in_config": { + "type": "boolean", + "description": "Indicates whether the connector type is enabled in the Kibana configuration file.", + "example": true + }, + "enabled_in_license": { + "type": "boolean", + "description": "Indicates whether the connector is enabled in the license.", + "example": true + }, + "id": { + "$ref": "#/components/schemas/connector_types" + }, + "is_system_action_type": { + "type": "boolean", + "example": false + }, + "minimum_license_required": { + "type": "string", + "description": "The license that is required to use the connector type.", + "example": "basic" + }, + "name": { + "type": "string", + "description": "The name of the connector type.", + "example": "Index" + }, + "supported_feature_ids": { + "type": "array", + "description": "The features that are supported by the connector type.", + "items": { + "$ref": "#/components/schemas/features" + }, + "example": [ + "alerting", + "cases", + "siem" + ] + } + } + } + }, + "examples": { + "getConnectorTypesServerlessResponse": { + "$ref": "#/components/examples/get_connector_types_generativeai_response" } } } @@ -1071,7 +1041,7 @@ }, "/s/{spaceId}/api/actions/action/{actionId}": { "delete": { - "summary": "Deletes a connector.", + "summary": "Delete a connector", "operationId": "legacyDeleteConnector", "deprecated": true, "description": "Deprecated in 7.13.0. Use the delete connector API instead. WARNING: When you delete a connector, it cannot be recovered.\n", @@ -1099,7 +1069,7 @@ } }, "get": { - "summary": "Retrieves a connector by ID.", + "summary": "Get connector information", "operationId": "legacyGetConnector", "description": "Deprecated in 7.13.0. Use the get connector API instead.", "deprecated": true, @@ -1124,7 +1094,7 @@ } }, "put": { - "summary": "Updates the attributes for a connector.", + "summary": "Update a connector", "operationId": "legacyUpdateConnector", "deprecated": true, "description": "Deprecated in 7.13.0. Use the update connector API instead.", @@ -1180,7 +1150,7 @@ }, "/s/{spaceId}/api/actions": { "get": { - "summary": "Retrieves all connectors.", + "summary": "Get all connectors", "operationId": "legacyGetConnectors", "deprecated": true, "description": "Deprecated in 7.13.0. Use the get all connectors API instead.", @@ -1212,7 +1182,7 @@ } }, "post": { - "summary": "Creates a connector.", + "summary": "Create a connector", "operationId": "legacyCreateConnector", "deprecated": true, "description": "Deprecated in 7.13.0. Use the create connector API instead.", @@ -1268,7 +1238,7 @@ }, "/s/{spaceId}/api/actions/list_action_types": { "get": { - "summary": "Retrieves a list of all connector types.", + "summary": "Get connector types", "operationId": "legacyGetConnectorTypes", "deprecated": true, "description": "Deprecated in 7.13.0. Use the get all connector types API instead.", @@ -1303,9 +1273,7 @@ "enabledInLicense": { "type": "boolean", "description": "Indicates whether the connector is enabled in the license.", - "examples": [ - true - ] + "example": true }, "id": { "type": "string", @@ -1333,7 +1301,7 @@ }, "/s/{spaceId}/api/actions/action/{actionId}/_execute": { "post": { - "summary": "Runs a connector.", + "summary": "Run a connector", "operationId": "legacyRunConnector", "deprecated": true, "description": "Deprecated in 7.13.0. Use the run connector API instead.", @@ -1438,28 +1406,24 @@ "description": "Cross-site request forgery protection", "required": true }, - "connector_id": { + "space_id": { "in": "path", - "name": "connectorId", - "description": "An identifier for the connector.", + "name": "spaceId", + "description": "An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used.", "required": true, "schema": { "type": "string", - "examples": [ - "df770e30-8b8b-11ed-a780-3b746c987a81" - ] + "example": "default" } }, - "space_id": { + "connector_id": { "in": "path", - "name": "spaceId", - "description": "An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used.", + "name": "connectorId", + "description": "An identifier for the connector.", "required": true, "schema": { "type": "string", - "examples": [ - "default" - ] + "example": "df770e30-8b8b-11ed-a780-3b746c987a81" } }, "action_id": { @@ -1469,9 +1433,7 @@ "required": true, "schema": { "type": "string", - "examples": [ - "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad" - ] + "example": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad" } } }, @@ -1496,22 +1458,50 @@ "enum": [ ".bedrock" ], - "examples": [ - ".bedrock" - ] + "example": ".bedrock" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_bedrock" } } }, + "create_connector_request_gemini": { + "title": "Create Google Gemini connector request", + "description": "The Google Gemini connector uses axios to send a POST request to Google Gemini.", + "type": "object", + "required": [ + "config", + "connector_type_id", + "name", + "secrets" + ], + "properties": { + "config": { + "$ref": "#/components/schemas/config_properties_gemini" + }, + "connector_type_id": { + "type": "string", + "description": "The type of connector.", + "enum": [ + ".gemini" + ], + "example": ".gemini" + }, + "name": { + "type": "string", + "description": "The display name for the connector.", + "example": "my-connector" + }, + "secrets": { + "$ref": "#/components/schemas/secrets_properties_gemini" + } + } + }, "create_connector_request_cases_webhook": { "title": "Create Webhook - Case Managment connector request", "description": "The Webhook - Case Management connector uses axios to send POST, PUT, and GET requests to a case management RESTful API web service.\n", @@ -1531,16 +1521,12 @@ "enum": [ ".cases-webhook" ], - "examples": [ - ".cases-webhook" - ] + "example": ".cases-webhook" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_cases_webhook" @@ -1567,16 +1553,12 @@ "enum": [ ".d3security" ], - "examples": [ - ".d3security" - ] + "example": ".d3security" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_d3security" @@ -1603,16 +1585,12 @@ "enum": [ ".email" ], - "examples": [ - ".email" - ] + "example": ".email" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_email" @@ -1639,16 +1617,12 @@ "enum": [ ".gen-ai" ], - "examples": [ - ".gen-ai" - ] + "example": ".gen-ai" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_genai" @@ -1674,16 +1648,12 @@ "enum": [ ".index" ], - "examples": [ - ".index" - ] + "example": ".index" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" } } }, @@ -1707,16 +1677,12 @@ "enum": [ ".jira" ], - "examples": [ - ".jira" - ] + "example": ".jira" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_jira" @@ -1743,16 +1709,12 @@ "enum": [ ".opsgenie" ], - "examples": [ - ".opsgenie" - ] + "example": ".opsgenie" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_opsgenie" @@ -1779,16 +1741,12 @@ "enum": [ ".pagerduty" ], - "examples": [ - ".pagerduty" - ] + "example": ".pagerduty" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_pagerduty" @@ -1812,9 +1770,7 @@ "connector_type_id": { "description": "The type of connector.", "type": "string", - "examples": [ - ".resilient" - ], + "example": ".resilient", "enum": [ ".resilient" ] @@ -1822,9 +1778,7 @@ "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_resilient" @@ -1852,16 +1806,12 @@ "enum": [ ".sentinelone" ], - "examples": [ - ".sentinelone" - ] + "example": ".sentinelone" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_sentinelone" @@ -1883,16 +1833,12 @@ "enum": [ ".server-log" ], - "examples": [ - ".server-log" - ] + "example": ".server-log" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" } } }, @@ -1916,16 +1862,12 @@ "enum": [ ".servicenow" ], - "examples": [ - ".servicenow" - ] + "example": ".servicenow" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_servicenow" @@ -1952,16 +1894,12 @@ "enum": [ ".servicenow-itom" ], - "examples": [ - ".servicenow-itom" - ] + "example": ".servicenow-itom" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_servicenow" @@ -1988,16 +1926,12 @@ "enum": [ ".servicenow-sir" ], - "examples": [ - ".servicenow-sir" - ] + "example": ".servicenow-sir" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_servicenow" @@ -2023,16 +1957,12 @@ "enum": [ ".slack_api" ], - "examples": [ - ".slack_api" - ] + "example": ".slack_api" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_slack_api" @@ -2055,16 +1985,12 @@ "enum": [ ".slack" ], - "examples": [ - ".slack" - ] + "example": ".slack" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_slack_webhook" @@ -2091,16 +2017,12 @@ "enum": [ ".swimlane" ], - "examples": [ - ".swimlane" - ] + "example": ".swimlane" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_swimlane" @@ -2123,16 +2045,12 @@ "enum": [ ".teams" ], - "examples": [ - ".teams" - ] + "example": ".teams" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_teams" @@ -2159,16 +2077,12 @@ "enum": [ ".tines" ], - "examples": [ - ".tines" - ] + "example": ".tines" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_tines" @@ -2195,16 +2109,12 @@ "enum": [ ".torq" ], - "examples": [ - ".torq" - ] + "example": ".torq" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_torq" @@ -2231,16 +2141,12 @@ "enum": [ ".webhook" ], - "examples": [ - ".webhook" - ] + "example": ".webhook" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_webhook" @@ -2267,16 +2173,12 @@ "enum": [ ".xmatters" ], - "examples": [ - ".xmatters" - ] + "example": ".xmatters" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_xmatters" @@ -2321,6 +2223,49 @@ } } }, + "config_properties_gemini": { + "title": "Connector request properties for an Google Gemini connector", + "description": "Defines properties for connectors when type is `.gemini`.", + "type": "object", + "required": [ + "apiUrl", + "gcpRegion", + "gcpProjectID" + ], + "properties": { + "apiUrl": { + "type": "string", + "description": "The Google Gemini request URL." + }, + "defaultModel": { + "type": "string", + "description": "The generative artificial intelligence model for Google Gemini to use.", + "default": "gemini-1.5-pro-preview-0409" + }, + "gcpRegion": { + "type": "string", + "description": "The GCP region where the Vertex AI endpoint enabled." + }, + "gcpProjectID": { + "type": "string", + "description": "The Google ProjectID that has Vertex AI endpoint enabled." + } + } + }, + "secrets_properties_gemini": { + "title": "Connector secrets properties for a Google Gemini connector", + "description": "Defines secrets for connectors when type is `.gemini`.", + "type": "object", + "required": [ + "credentialsJSON" + ], + "properties": { + "credentialsJSON": { + "type": "string", + "description": "The service account credentials JSON file. The service account should have Vertex AI user IAM role assigned to it." + } + } + }, "config_properties_cases_webhook": { "title": "Connector request properties for Webhook - Case Management connector", "required": [ @@ -2339,9 +2284,7 @@ "createCommentJson": { "type": "string", "description": "A JSON payload sent to the create comment URL to create a case comment. You can use variables to add Kibana Cases data to the payload. The required variable is `case.comment`. Due to Mustache template variables (the text enclosed in triple braces, for example, `{{{case.title}}}`), the JSON is not validated when you create the connector. The JSON is validated once the Mustache variables have been placed when the REST method runs. Manually ensure that the JSON is valid, disregarding the Mustache variables, so the later validation will pass.\n", - "examples": [ - "{\"body\": {{{case.comment}}}}" - ] + "example": "{\"body\": {{{case.comment}}}}" }, "createCommentMethod": { "type": "string", @@ -2356,16 +2299,12 @@ "createCommentUrl": { "type": "string", "description": "The REST API URL to create a case comment by ID in the third-party system. You can use a variable to add the external system ID to the URL. If you are using the `xpack.actions.allowedHosts setting`, add the hostname to the allowed hosts.\n", - "examples": [ - "https://example.com/issue/{{{external.system.id}}}/comment" - ] + "example": "https://example.com/issue/{{{external.system.id}}}/comment" }, "createIncidentJson": { "type": "string", "description": "A JSON payload sent to the create case URL to create a case. You can use variables to add case data to the payload. Required variables are `case.title` and `case.description`. Due to Mustache template variables (which is the text enclosed in triple braces, for example, `{{{case.title}}}`), the JSON is not validated when you create the connector. The JSON is validated after the Mustache variables have been placed when REST method runs. Manually ensure that the JSON is valid to avoid future validation errors; disregard Mustache variables during your review.\n", - "examples": [ - "{\"fields\": {\"summary\": {{{case.title}}},\"description\": {{{case.description}}},\"labels\": {{{case.tags}}}}}" - ] + "example": "{\"fields\": {\"summary\": {{{case.title}}},\"description\": {{{case.description}}},\"labels\": {{{case.tags}}}}}" }, "createIncidentMethod": { "type": "string", @@ -2392,9 +2331,7 @@ "getIncidentUrl": { "type": "string", "description": "The REST API URL to get the case by ID from the third-party system. If you are using the `xpack.actions.allowedHosts` setting, add the hostname to the allowed hosts. You can use a variable to add the external system ID to the URL. Due to Mustache template variables (the text enclosed in triple braces, for example, `{{{case.title}}}`), the JSON is not validated when you create the connector. The JSON is validated after the Mustache variables have been placed when REST method runs. Manually ensure that the JSON is valid, disregarding the Mustache variables, so the later validation will pass.\n", - "examples": [ - "https://example.com/issue/{{{external.system.id}}}" - ] + "example": "https://example.com/issue/{{{external.system.id}}}" }, "hasAuth": { "type": "boolean", @@ -2408,9 +2345,7 @@ "updateIncidentJson": { "type": "string", "description": "The JSON payload sent to the update case URL to update the case. You can use variables to add Kibana Cases data to the payload. Required variables are `case.title` and `case.description`. Due to Mustache template variables (which is the text enclosed in triple braces, for example, `{{{case.title}}}`), the JSON is not validated when you create the connector. The JSON is validated after the Mustache variables have been placed when REST method runs. Manually ensure that the JSON is valid to avoid future validation errors; disregard Mustache variables during your review.\n", - "examples": [ - "{\"fields\": {\"summary\": {{{case.title}}},\"description\": {{{case.description}}},\"labels\": {{{case.tags}}}}}" - ] + "example": "{\"fields\": {\"summary\": {{{case.title}}},\"description\": {{{case.description}}},\"labels\": {{{case.tags}}}}}" }, "updateIncidentMethod": { "type": "string", @@ -2425,16 +2360,12 @@ "updateIncidentUrl": { "type": "string", "description": "The REST API URL to update the case by ID in the third-party system. You can use a variable to add the external system ID to the URL. If you are using the `xpack.actions.allowedHosts` setting, add the hostname to the allowed hosts.\n", - "examples": [ - "https://example.com/issue/{{{external.system.ID}}}" - ] + "example": "https://example.com/issue/{{{external.system.ID}}}" }, "viewIncidentUrl": { "type": "string", "description": "The URL to view the case in the external system. You can use variables to add the external system ID or external system title to the URL.\n", - "examples": [ - "https://testing-jira.atlassian.net/browse/{{{external.system.title}}}" - ] + "example": "https://testing-jira.atlassian.net/browse/{{{external.system.title}}}" } } }, @@ -2490,10 +2421,8 @@ "properties": { "clientId": { "description": "The client identifier, which is a part of OAuth 2.0 client credentials authentication, in GUID format. If `service` is `exchange_server`, this property is required.\n", - "type": [ - "string", - "null" - ] + "type": "string", + "nullable": true }, "from": { "description": "The from address for all emails sent by the connector. It must be specified in `user@host-name` format.\n", @@ -2509,10 +2438,8 @@ "type": "string" }, "oauthTokenUrl": { - "type": [ - "string", - "null" - ] + "type": "string", + "nullable": true }, "port": { "description": "The port to connect to on the service provider. If the `service` is `elastic_cloud` (for Elastic Cloud notifications) or one of Nodemailer's well-known email service providers, this property is ignored. If `service` is `other`, this property must be defined. \n", @@ -2536,10 +2463,8 @@ }, "tenantId": { "description": "The tenant identifier, which is part of OAuth 2.0 client credentials authentication, in GUID format. If `service` is `exchange_server`, this property is required.\n", - "type": [ - "string", - "null" - ] + "type": "string", + "nullable": true } } }, @@ -2651,10 +2576,8 @@ "executionTimeField": { "description": "A field that indicates when the document was indexed.", "default": null, - "type": [ - "string", - "null" - ] + "type": "string", + "nullable": true }, "index": { "description": "The Elasticsearch index to be written to.", @@ -2740,13 +2663,9 @@ "properties": { "apiUrl": { "description": "The PagerDuty event URL.", - "type": [ - "string", - "null" - ], - "examples": [ - "https://events.pagerduty.com/v2/enqueue" - ] + "type": "string", + "nullable": true, + "example": "https://events.pagerduty.com/v2/enqueue" } } }, @@ -3316,10 +3235,10 @@ "properties": { "authType": { "type": "string", + "nullable": true, "enum": [ "webhook-authentication-basic", - "webhook-authentication-ssl", - "null" + "webhook-authentication-ssl" ], "description": "The type of authentication to use: basic, SSL, or none.\n" }, @@ -3340,10 +3259,8 @@ "description": "If `true`, a user name and password must be provided for login type authentication.\n" }, "headers": { - "type": [ - "object", - "null" - ], + "type": "object", + "nullable": true, "description": "A set of key-value pairs sent as headers with the request." }, "method": { @@ -3405,10 +3322,8 @@ "properties": { "configUrl": { "description": "The request URL for the Elastic Alerts trigger in xMatters. It is applicable only when `usesBasic` is `true`.\n", - "type": [ - "string", - "null" - ] + "type": "string", + "nullable": true }, "usesBasic": { "description": "Specifies whether the connector uses HTTP basic authentication (`true`) or URL authentication (`false`).", @@ -3443,6 +3358,9 @@ { "$ref": "#/components/schemas/create_connector_request_bedrock" }, + { + "$ref": "#/components/schemas/create_connector_request_gemini" + }, { "$ref": "#/components/schemas/create_connector_request_cases_webhook" }, @@ -3514,6 +3432,7 @@ "propertyName": "connector_type_id", "mapping": { ".bedrock": "#/components/schemas/create_connector_request_bedrock", + ".gemini": "#/components/schemas/create_connector_request_gemini", ".cases-webhook": "#/components/schemas/create_connector_request_cases_webhook", ".d3security": "#/components/schemas/create_connector_request_d3security", ".email": "#/components/schemas/create_connector_request_email", @@ -3583,6 +3502,52 @@ } } }, + "connector_response_properties_gemini": { + "title": "Connector response properties for a Google Gemini connector", + "type": "object", + "required": [ + "connector_type_id", + "id", + "is_deprecated", + "is_preconfigured", + "name" + ], + "properties": { + "config": { + "$ref": "#/components/schemas/config_properties_gemini" + }, + "connector_type_id": { + "type": "string", + "description": "The type of connector.", + "enum": [ + ".gemini" + ] + }, + "id": { + "type": "string", + "description": "The identifier for the connector." + }, + "is_deprecated": { + "$ref": "#/components/schemas/is_deprecated" + }, + "is_missing_secrets": { + "$ref": "#/components/schemas/is_missing_secrets" + }, + "is_preconfigured": { + "$ref": "#/components/schemas/is_preconfigured" + }, + "is_system_action": { + "$ref": "#/components/schemas/is_system_action" + }, + "name": { + "type": "string", + "description": "The display name for the connector." + }, + "referenced_by_count": { + "$ref": "#/components/schemas/referenced_by_count" + } + } + }, "connector_response_properties_cases_webhook": { "title": "Connector request properties for a Webhook - Case Management connector", "type": "object", @@ -4055,10 +4020,8 @@ ], "properties": { "config": { - "type": [ - "object", - "null" - ] + "type": "object", + "nullable": true }, "connector_type_id": { "type": "string", @@ -4598,37 +4561,27 @@ "is_deprecated": { "type": "boolean", "description": "Indicates whether the connector type is deprecated.", - "examples": [ - false - ] + "example": false }, "is_missing_secrets": { "type": "boolean", "description": "Indicates whether secrets are missing for the connector. Secrets configuration properties vary depending on the connector type.", - "examples": [ - false - ] + "example": false }, "is_preconfigured": { "type": "boolean", "description": "Indicates whether it is a preconfigured connector. If true, the `config` and `is_missing_secrets` properties are omitted from the response. \n", - "examples": [ - false - ] + "example": false }, "is_system_action": { "type": "boolean", "description": "Indicates whether the connector is used for system actions.", - "examples": [ - false - ] + "example": false }, "referenced_by_count": { "type": "integer", "description": "Indicates the number of saved objects that reference the connector. If `is_preconfigured` is true, this value is not calculated. This property is returned only by the get all connectors API.\n", - "examples": [ - 2 - ] + "example": 2 }, "connector_response_properties": { "title": "Connector response properties", @@ -4637,6 +4590,9 @@ { "$ref": "#/components/schemas/connector_response_properties_bedrock" }, + { + "$ref": "#/components/schemas/connector_response_properties_gemini" + }, { "$ref": "#/components/schemas/connector_response_properties_cases_webhook" }, @@ -4708,6 +4664,7 @@ "propertyName": "connector_type_id", "mapping": { ".bedrock": "#/components/schemas/connector_response_properties_bedrock", + ".gemini": "#/components/schemas/connector_response_properties_gemini", ".cases-webhook": "#/components/schemas/connector_response_properties_cases_webhook", ".d3security": "#/components/schemas/connector_response_properties_d3security", ".email": "#/components/schemas/connector_response_properties_email", @@ -4753,6 +4710,26 @@ } } }, + "update_connector_request_gemini": { + "title": "Update Google Gemini connector request", + "type": "object", + "required": [ + "config", + "name" + ], + "properties": { + "config": { + "$ref": "#/components/schemas/config_properties_gemini" + }, + "name": { + "type": "string", + "description": "The display name for the connector." + }, + "secrets": { + "$ref": "#/components/schemas/secrets_properties_gemini" + } + } + }, "update_connector_request_cases_webhook": { "title": "Update Webhook - Case Managment connector request", "type": "object", @@ -4767,9 +4744,7 @@ "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_cases_webhook" @@ -5046,9 +5021,7 @@ "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_swimlane" @@ -5163,6 +5136,9 @@ { "$ref": "#/components/schemas/update_connector_request_bedrock" }, + { + "$ref": "#/components/schemas/update_connector_request_gemini" + }, { "$ref": "#/components/schemas/update_connector_request_cases_webhook" }, @@ -5228,6 +5204,51 @@ } ] }, + "features": { + "type": "string", + "description": "The feature that uses the connector.\n", + "enum": [ + "alerting", + "cases", + "generativeAIForSecurity", + "generativeAIForObservability", + "generativeAIForSearchPlayground", + "siem", + "uptime" + ] + }, + "connector_types": { + "title": "Connector types", + "type": "string", + "description": "The type of connector. For example, `.email`, `.index`, `.jira`, `.opsgenie`, or `.server-log`.", + "enum": [ + ".bedrock", + ".gemini", + ".cases-webhook", + ".d3security", + ".email", + ".gen-ai", + ".index", + ".jira", + ".opsgenie", + ".pagerduty", + ".resilient", + ".sentinelone", + ".servicenow", + ".servicenow-itom", + ".servicenow-sir", + ".server-log", + ".slack", + ".slack_api", + ".swimlane", + ".teams", + ".tines", + ".torq", + ".webhook", + ".xmatters" + ], + "example": ".server-log" + }, "run_connector_params_acknowledge_resolve_pagerduty": { "title": "PagerDuty connector parameters", "description": "Test an action that acknowledges or resolves a PagerDuty alert.", @@ -5381,16 +5402,12 @@ "class": { "description": "The class or type of the event.", "type": "string", - "examples": [ - "cpu load" - ] + "example": "cpu load" }, "component": { "description": "The component of the source machine that is responsible for the event.", "type": "string", - "examples": [ - "eth0" - ] + "example": "eth0" }, "customDetails": { "description": "Additional details to add to the event.", @@ -5411,9 +5428,7 @@ "group": { "description": "The logical grouping of components of a service.", "type": "string", - "examples": [ - "app-stack" - ] + "example": "app-stack" }, "links": { "description": "A list of links to add to the event.", @@ -5606,19 +5621,15 @@ ], "properties": { "correlation_id": { - "type": [ - "null", - "string" - ], + "type": "string", + "nullable": true, "description": "An identifier that is assigned to the incident when it is created by the connector. NOTE: If you use the default value and the rule generates multiple alerts that use the same alert IDs, the latest open incident for this correlation ID is closed unless you specify the external ID.\n", "maxLength": 100, "default": "{{rule.id}}:{{alert.id}}" }, "externalId": { - "type": [ - "null", - "string" - ], + "type": "string", + "nullable": true, "description": "The unique identifier (`incidentId`) for the incident in ServiceNow." } } @@ -5668,12 +5679,10 @@ "type": "object", "description": "The custom properties of the alert.", "additionalProperties": true, - "examples": [ - { - "key1": "value1", - "key2": "value2" - } - ] + "example": { + "key1": "value1", + "key2": "value2" + } }, "entity": { "type": "string", @@ -5805,9 +5814,7 @@ "id": { "type": "string", "description": "The Jira issue type identifier.", - "examples": [ - 10024 - ] + "example": 10024 } } } @@ -5889,9 +5896,7 @@ "externalId": { "type": "string", "description": "The Jira, ServiceNow ITSM, or ServiceNow SecOps issue identifier.", - "examples": [ - 71778 - ] + "example": 71778 } } } @@ -5921,9 +5926,7 @@ "id": { "type": "string", "description": "The Jira issue identifier.", - "examples": [ - 71778 - ] + "example": 71778 } } } @@ -6358,50 +6361,6 @@ } } }, - "features": { - "type": "string", - "description": "The feature that uses the connector.\n", - "enum": [ - "alerting", - "cases", - "generativeAI", - "siem", - "uptime" - ] - }, - "connector_types": { - "title": "Connector types", - "type": "string", - "description": "The type of connector. For example, `.email`, `.index`, `.jira`, `.opsgenie`, or `.server-log`.", - "enum": [ - ".bedrock", - ".cases-webhook", - ".d3security", - ".email", - ".gen-ai", - ".index", - ".jira", - ".opsgenie", - ".pagerduty", - ".resilient", - ".sentinelone", - ".servicenow", - ".servicenow-itom", - ".servicenow-sir", - ".server-log", - ".slack", - ".slack_api", - ".swimlane", - ".teams", - ".tines", - ".torq", - ".webhook", - ".xmatters" - ], - "examples": [ - ".server-log" - ] - }, "action_response_properties": { "title": "Action response properties", "description": "The properties vary depending on the action type.", @@ -6594,51 +6553,77 @@ } } }, - "run_cases_webhook_connector_request": { - "summary": "Run a Webhook - Case Management connector to create a case.", - "value": { - "params": { - "subAction": "pushToService", - "subActionParams": { - "comments": [ - { - "commentId": 1, - "comment": "A comment about the incident." - } - ], - "incident": { - "title": "Case title", - "description": "Description of the incident.", - "tags": [ - "tag1", - "tag2" - ], - "severity": "low", - "status": "open", - "id": "caseID" - } - } + "get_connectors_response": { + "summary": "A list of connectors", + "value": [ + { + "id": "preconfigured-email-connector", + "name": "my-preconfigured-email-notification", + "connector_type_id": ".email", + "is_preconfigured": true, + "is_deprecated": false, + "referenced_by_count": 0, + "is_system_action": false + }, + { + "id": "e07d0c80-8b8b-11ed-a780-3b746c987a81", + "name": "my-index-connector", + "config": { + "index": "test-index", + "refresh": false, + "executionTimeField": null + }, + "connector_type_id": ".index", + "is_preconfigured": false, + "is_deprecated": false, + "referenced_by_count": 2, + "is_missing_secrets": false, + "is_system_action": false } - } + ] }, - "run_email_connector_request": { - "summary": "Send an email message from an email connector.", - "value": { - "params": { - "bcc": [ - "user1@example.com" - ], - "cc": [ - "user2@example.com", - "user3@example.com" - ], - "message": "Test email message.", - "subject": "Test message subject", - "to": [ - "user4@example.com" + "get_connector_types_response": { + "summary": "A list of connector types", + "value": [ + { + "id": ".swimlane", + "name": "Swimlane", + "enabled": true, + "enabled_in_config": true, + "enabled_in_license": true, + "minimum_license_required": "gold", + "supported_feature_ids": [ + "alerting", + "cases", + "siem" + ] + }, + { + "id": ".index", + "name": "Index", + "enabled": true, + "enabled_in_config": true, + "enabled_in_license": true, + "minimum_license_required": "basic", + "supported_feature_ids": [ + "alerting", + "uptime", + "siem" + ] + }, + { + "id": ".server-log", + "name": "Server log", + "enabled": true, + "enabled_in_config": true, + "enabled_in_license": true, + "minimum_license_required": "basic", + "supported_feature_ids": [ + "alerting", + "uptime" ] } - } + ] }, "run_index_connector_request": { "summary": "Run an index connector.", @@ -6662,24 +6647,6 @@ } } }, - "run_pagerduty_connector_request": { - "summary": "Run a PagerDuty connector to trigger an alert.", - "value": { - "params": { - "eventAction": "trigger", - "summary": "A brief event summary", - "links": [ - { - "href": "http://example.com/pagerduty", - "text": "An example link" - } - ], - "customDetails": { - "my_data_1": "test data" - } - } - } - }, "run_server_log_connector_request": { "summary": "Run a server log connector.", "value": { @@ -6713,78 +6680,29 @@ "C123ABC456" ], "text": "A test message." - } - } - } - }, - "run_swimlane_connector_request": { - "summary": "Run a Swimlane connector to create an incident.", - "value": { - "params": { - "subAction": "pushToService", - "subActionParams": { - "comments": [ - { - "commentId": 1, - "comment": "A comment about the incident." - } - ], - "incident": { - "caseId": "1000", - "caseName": "Case name", - "description": "Description of the incident." - } - } - } - } - }, - "run_cases_webhook_connector_response": { - "summary": "Response from a pushToService action for a Webhook - Case Management connector.", - "value": { - "connector_id": "1824b5b8-c005-4dcc-adac-57f92db46459", - "data": { - "id": 100665, - "title": "TEST-29034", - "url": "https://example.com/browse/TEST-29034", - "pushedDate": "2023-12-05T19:43:36.360Z", - "comments": [ - { - "commentId": 1, - "pushedDate": "2023-12-05T19:43:36.360Z" - } - ] - }, - "status": "ok" + } + } } }, - "run_email_connector_response": { - "summary": "Response for sending a message from an email connector.", + "run_swimlane_connector_request": { + "summary": "Run a Swimlane connector to create an incident.", "value": { - "connector_id": "7fc7b9a0-ecc9-11ec-8736-e7d63118c907", - "data": { - "accepted": [ - "user1@example.com", - "user2@example.com", - "user3@example.com", - "user4@example.com" - ], - "envelope": { - "from": "tester@example.com", - "to": [ - "user1@example.com", - "user2@example.com", - "user3@example.com", - "user4@example.com" - ] - }, - "envelopeTime": 8, - "messageTime": 3, - "messageSize": 729, - "response": "250 Message queued as QzEXKcGJ", - "messageId": "<08a92d29-642a-0706-750c-de5996bd5cf3@example.com>", - "rejected": [] - }, - "status": "ok" + "params": { + "subAction": "pushToService", + "subActionParams": { + "comments": [ + { + "commentId": 1, + "comment": "A comment about the incident." + } + ], + "incident": { + "caseId": "1000", + "caseName": "Case name", + "description": "Description of the incident." + } + } + } } }, "run_index_connector_response": { @@ -6849,18 +6767,6 @@ "status": "ok" } }, - "run_pagerduty_connector_response": { - "summary": "Response from running a PagerDuty connector.", - "value": { - "connector_id": "45de9f70-954f-4608-b12a-db7cf808e49d", - "data": { - "dedup_key": "5115e138b26b484a81eaea779faa6016", - "message": "Event processed", - "status": "success" - }, - "status": "ok" - } - }, "run_server_log_connector_response": { "summary": "Response from running a server log connector.", "value": { @@ -6999,34 +6905,130 @@ "status": "ok" } }, - "get_connectors_response": { - "summary": "A list of connectors", - "value": [ - { - "id": "preconfigured-email-connector", - "name": "my-preconfigured-email-notification", - "connector_type_id": ".email", - "is_preconfigured": true, - "is_deprecated": false, - "referenced_by_count": 0, - "is_system_action": false + "run_cases_webhook_connector_request": { + "summary": "Run a Webhook - Case Management connector to create a case.", + "value": { + "params": { + "subAction": "pushToService", + "subActionParams": { + "comments": [ + { + "commentId": 1, + "comment": "A comment about the incident." + } + ], + "incident": { + "title": "Case title", + "description": "Description of the incident.", + "tags": [ + "tag1", + "tag2" + ], + "severity": "low", + "status": "open", + "id": "caseID" + } + } + } + } + }, + "run_email_connector_request": { + "summary": "Send an email message from an email connector.", + "value": { + "params": { + "bcc": [ + "user1@example.com" + ], + "cc": [ + "user2@example.com", + "user3@example.com" + ], + "message": "Test email message.", + "subject": "Test message subject", + "to": [ + "user4@example.com" + ] + } + } + }, + "run_pagerduty_connector_request": { + "summary": "Run a PagerDuty connector to trigger an alert.", + "value": { + "params": { + "eventAction": "trigger", + "summary": "A brief event summary", + "links": [ + { + "href": "http://example.com/pagerduty", + "text": "An example link" + } + ], + "customDetails": { + "my_data_1": "test data" + } + } + } + }, + "run_cases_webhook_connector_response": { + "summary": "Response from a pushToService action for a Webhook - Case Management connector.", + "value": { + "connector_id": "1824b5b8-c005-4dcc-adac-57f92db46459", + "data": { + "id": 100665, + "title": "TEST-29034", + "url": "https://example.com/browse/TEST-29034", + "pushedDate": "2023-12-05T19:43:36.360Z", + "comments": [ + { + "commentId": 1, + "pushedDate": "2023-12-05T19:43:36.360Z" + } + ] }, - { - "id": "e07d0c80-8b8b-11ed-a780-3b746c987a81", - "name": "my-index-connector", - "config": { - "index": "test-index", - "refresh": false, - "executionTimeField": null + "status": "ok" + } + }, + "run_email_connector_response": { + "summary": "Response for sending a message from an email connector.", + "value": { + "connector_id": "7fc7b9a0-ecc9-11ec-8736-e7d63118c907", + "data": { + "accepted": [ + "user1@example.com", + "user2@example.com", + "user3@example.com", + "user4@example.com" + ], + "envelope": { + "from": "tester@example.com", + "to": [ + "user1@example.com", + "user2@example.com", + "user3@example.com", + "user4@example.com" + ] }, - "connector_type_id": ".index", - "is_preconfigured": false, - "is_deprecated": false, - "referenced_by_count": 2, - "is_missing_secrets": false, - "is_system_action": false - } - ] + "envelopeTime": 8, + "messageTime": 3, + "messageSize": 729, + "response": "250 Message queued as QzEXKcGJ", + "messageId": "<08a92d29-642a-0706-750c-de5996bd5cf3@example.com>", + "rejected": [] + }, + "status": "ok" + } + }, + "run_pagerduty_connector_response": { + "summary": "Response from running a PagerDuty connector.", + "value": { + "connector_id": "45de9f70-954f-4608-b12a-db7cf808e49d", + "data": { + "dedup_key": "5115e138b26b484a81eaea779faa6016", + "message": "Event processed", + "status": "success" + }, + "status": "ok" + } }, "get_connector_types_generativeai_response": { "summary": "A list of connector types for the `generativeAI` feature.", @@ -7039,7 +7041,9 @@ "enabled_in_license": true, "minimum_license_required": "enterprise", "supported_feature_ids": [ - "generativeAI" + "generativeAIForSecurity", + "generativeAIForObservability", + "generativeAIForSearchPlayground" ], "is_system_action_type": false }, @@ -7051,52 +7055,23 @@ "enabled_in_license": true, "minimum_license_required": "enterprise", "supported_feature_ids": [ - "generativeAI" + "generativeAIForSecurity", + "generativeAIForObservability", + "generativeAIForSearchPlayground" ], "is_system_action_type": false - } - ] - }, - "get_connector_types_response": { - "summary": "A list of connector types", - "value": [ - { - "id": ".swimlane", - "name": "Swimlane", - "enabled": true, - "enabled_in_config": true, - "enabled_in_license": true, - "minimum_license_required": "gold", - "supported_feature_ids": [ - "alerting", - "cases", - "siem" - ] - }, - { - "id": ".index", - "name": "Index", - "enabled": true, - "enabled_in_config": true, - "enabled_in_license": true, - "minimum_license_required": "basic", - "supported_feature_ids": [ - "alerting", - "uptime", - "siem" - ] }, { - "id": ".server-log", - "name": "Server log", + "id": ".gemini", + "name": "Google Gemini", "enabled": true, "enabled_in_config": true, "enabled_in_license": true, - "minimum_license_required": "basic", + "minimum_license_required": "enterprise", "supported_feature_ids": [ - "alerting", - "uptime" - ] + "generativeAIForSecurity" + ], + "is_system_action_type": false } ] } @@ -7112,9 +7087,7 @@ "properties": { "error": { "type": "string", - "examples": [ - "Unauthorized" - ], + "example": "Unauthorized", "enum": [ "Unauthorized" ] @@ -7124,9 +7097,7 @@ }, "statusCode": { "type": "integer", - "examples": [ - 401 - ], + "example": 401, "enum": [ 401 ] @@ -7146,24 +7117,18 @@ "properties": { "error": { "type": "string", - "examples": [ - "Not Found" - ], + "example": "Not Found", "enum": [ "Not Found" ] }, "message": { "type": "string", - "examples": [ - "Saved object [action/baf33fc0-920c-11ed-b36a-874bd1548a00] not found" - ] + "example": "Saved object [action/baf33fc0-920c-11ed-b36a-874bd1548a00] not found" }, "statusCode": { "type": "integer", - "examples": [ - 404 - ], + "example": 404, "enum": [ 404 ] diff --git a/x-pack/plugins/actions/docs/openapi/bundled.yaml b/x-pack/plugins/actions/docs/openapi/bundled.yaml index 599567cf08150..95a0449bec192 100644 --- a/x-pack/plugins/actions/docs/openapi/bundled.yaml +++ b/x-pack/plugins/actions/docs/openapi/bundled.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.0 +openapi: 3.0.3 info: title: Connectors description: OpenAPI schema for Connectors endpoints @@ -17,14 +17,17 @@ tags: - name: connectors description: Connector APIs enable you to create and manage connectors. paths: - /api/actions/connector: + /s/{spaceId}/api/actions/connector: post: - summary: Creates a connector. - operationId: createConnector + summary: Create a connector + operationId: createConnectorWithSpaceId + description: | + You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. tags: - connectors parameters: - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/space_id' requestBody: required: true content: @@ -58,14 +61,17 @@ paths: $ref: '#/components/examples/create_xmatters_connector_response' '401': $ref: '#/components/responses/401' - /api/actions/connector/{connectorId}: + /s/{spaceId}/api/actions/connector/{connectorId}: get: - summary: Retrieves a connector by ID. - operationId: getConnector + summary: Get connector information + operationId: getConnectorWithSpaceId + description: | + You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. tags: - connectors parameters: - $ref: '#/components/parameters/connector_id' + - $ref: '#/components/parameters/space_id' responses: '200': description: Indicates a successful call. @@ -81,13 +87,16 @@ paths: '404': $ref: '#/components/responses/404' delete: - summary: Deletes a connector. - operationId: deleteConnector + summary: Delete a connector + operationId: deleteConnectorWithSpaceId + description: | + You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. WARNING: When you delete a connector, it cannot be recovered. tags: - connectors parameters: - $ref: '#/components/parameters/kbn_xsrf' - $ref: '#/components/parameters/connector_id' + - $ref: '#/components/parameters/space_id' responses: '204': description: Indicates a successful call. @@ -96,21 +105,22 @@ paths: '404': $ref: '#/components/responses/404' post: - summary: Creates a connector. - operationId: createConnectorId + summary: Create a connector + operationId: createConnectorIdWithSpaceId + description: | + You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. tags: - connectors parameters: - $ref: '#/components/parameters/kbn_xsrf' + - $ref: '#/components/parameters/space_id' - in: path name: connectorId - description: | - A UUID v1 or v4 identifier for the connector. If you omit this parameter, an identifier is randomly generated. + description: A UUID v1 or v4 identifier for the connector. If you omit this parameter, an identifier is randomly generated. required: true schema: type: string - examples: - - ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74 + example: ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74 requestBody: required: true content: @@ -133,13 +143,16 @@ paths: '401': $ref: '#/components/responses/401' put: - summary: Updates the attributes for a connector. - operationId: updateConnector + summary: Update a connector + operationId: updateConnectorWithSpaceId + description: | + You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. tags: - connectors parameters: - $ref: '#/components/parameters/kbn_xsrf' - $ref: '#/components/parameters/connector_id' + - $ref: '#/components/parameters/space_id' requestBody: required: true content: @@ -162,10 +175,97 @@ paths: $ref: '#/components/responses/401' '404': $ref: '#/components/responses/404' - /api/actions/connector/{connectorId}/_execute: + /s/{spaceId}/api/actions/connectors: + get: + summary: Get all connectors + operationId: getConnectorsWithSpaceId + description: | + You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. + tags: + - connectors + parameters: + - $ref: '#/components/parameters/space_id' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/connector_response_properties' + examples: + getConnectorsResponse: + $ref: '#/components/examples/get_connectors_response' + '401': + $ref: '#/components/responses/401' + /s/{spaceId}/api/actions/connector_types: + get: + summary: Get all connector types + operationId: getConnectorTypesWithSpaceId + description: | + You do not need any Kibana feature privileges to run this API. + tags: + - connectors + parameters: + - $ref: '#/components/parameters/space_id' + - in: query + name: feature_id + description: A filter to limit the retrieved connector types to those that support a specific feature (such as alerting or cases). + schema: + $ref: '#/components/schemas/features' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + title: Get connector types response body properties + description: The properties vary for each connector type. + type: array + items: + type: object + properties: + enabled: + type: boolean + description: Indicates whether the connector type is enabled in Kibana. + example: true + enabled_in_config: + type: boolean + description: Indicates whether the connector type is enabled in the Kibana `.yml` file. + example: true + enabled_in_license: + type: boolean + description: Indicates whether the connector is enabled in the license. + example: true + id: + $ref: '#/components/schemas/connector_types' + minimum_license_required: + type: string + description: The license that is required to use the connector type. + example: basic + name: + type: string + description: The name of the connector type. + example: Index + supported_feature_ids: + type: array + description: The Kibana features that are supported by the connector type. + items: + $ref: '#/components/schemas/features' + example: + - alerting + - uptime + - siem + examples: + getConnectorTypesResponse: + $ref: '#/components/examples/get_connector_types_response' + '401': + $ref: '#/components/responses/401' + /s/{spaceId}/api/actions/connector/{connectorId}/_execute: post: - summary: Runs a connector. - operationId: runConnector + summary: Run a connector + operationId: runConnectorWithSpaceId description: | You can use this API to test an action that involves interaction with Kibana services or integrations with third-party systems. You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. If you use an index connector, you must also have `all`, `create`, `index`, or `write` indices privileges. tags: @@ -173,6 +273,7 @@ paths: parameters: - $ref: '#/components/parameters/kbn_xsrf' - $ref: '#/components/parameters/connector_id' + - $ref: '#/components/parameters/space_id' requestBody: required: true content: @@ -180,16 +281,10 @@ paths: schema: $ref: '#/components/schemas/run_connector_request' examples: - runCasesWebhookConnectorRequest: - $ref: '#/components/examples/run_cases_webhook_connector_request' - runEmailConnectorRequest: - $ref: '#/components/examples/run_email_connector_request' runIndexConnectorRequest: $ref: '#/components/examples/run_index_connector_request' runJiraConnectorRequest: $ref: '#/components/examples/run_jira_connector_request' - runPagerDutyConnectorRequest: - $ref: '#/components/examples/run_pagerduty_connector_request' runServerLogConnectorRequest: $ref: '#/components/examples/run_server_log_connector_request' runServiceNowITOMConnectorRequest: @@ -221,6 +316,10 @@ paths: description: An array of information returned from the action. items: type: object + message: + type: string + service_message: + type: string status: type: string description: The status of the action. @@ -228,16 +327,10 @@ paths: - error - ok examples: - runCasesWebhookConnectorResponse: - $ref: '#/components/examples/run_cases_webhook_connector_response' - runEmailConnectorResponse: - $ref: '#/components/examples/run_email_connector_response' runIndexConnectorResponse: $ref: '#/components/examples/run_index_connector_response' runJiraConnectorResponse: $ref: '#/components/examples/run_jira_connector_response' - runPagerDutyConnectorResponse: - $ref: '#/components/examples/run_pagerduty_connector_response' runServerLogConnectorResponse: $ref: '#/components/examples/run_server_log_connector_response' runServiceNowITOMConnectorResponse: @@ -248,106 +341,14 @@ paths: $ref: '#/components/examples/run_swimlane_connector_response' '401': $ref: '#/components/responses/401' - /api/actions/connectors: - get: - summary: Retrieves all connectors. - operationId: getConnectors - tags: - - connectors - responses: - '200': - description: Indicates a successful call. - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/connector_response_properties' - examples: - getConnectorsResponse: - $ref: '#/components/examples/get_connectors_response' - '401': - $ref: '#/components/responses/401' - /api/actions/connector_types: - get: - summary: Retrieves a list of all connector types. - operationId: getConnectorTypes - tags: - - connectors - parameters: - - in: query - name: feature_id - description: A filter to limit the retrieved connector types to those that support a specific feature (such as alerting or cases). - schema: - $ref: '#/components/schemas/features' - responses: - '200': - description: Indicates a successful call. - content: - application/json: - schema: - title: Get connector types response body properties - description: The properties vary for each connector type. - type: array - items: - type: object - properties: - enabled: - type: boolean - description: Indicates whether the connector type is enabled in Kibana. - examples: - - true - enabled_in_config: - type: boolean - description: Indicates whether the connector type is enabled in the Kibana configuration file. - examples: - - true - enabled_in_license: - type: boolean - description: Indicates whether the connector is enabled in the license. - examples: - - true - id: - $ref: '#/components/schemas/connector_types' - is_system_action_type: - type: boolean - examples: - - false - minimum_license_required: - type: string - description: The license that is required to use the connector type. - examples: - - basic - name: - type: string - description: The name of the connector type. - examples: - - Index - supported_feature_ids: - type: array - description: The features that are supported by the connector type. - items: - $ref: '#/components/schemas/features' - examples: - - - alerting - - cases - - siem - examples: - getConnectorTypesServerlessResponse: - $ref: '#/components/examples/get_connector_types_generativeai_response' - '401': - $ref: '#/components/responses/401' - /s/{spaceId}/api/actions/connector: + /api/actions/connector: post: - summary: Creates a connector. - operationId: createConnectorWithSpaceId - description: | - You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. + summary: Create a connector + operationId: createConnector tags: - connectors parameters: - $ref: '#/components/parameters/kbn_xsrf' - - $ref: '#/components/parameters/space_id' requestBody: required: true content: @@ -381,17 +382,14 @@ paths: $ref: '#/components/examples/create_xmatters_connector_response' '401': $ref: '#/components/responses/401' - /s/{spaceId}/api/actions/connector/{connectorId}: + /api/actions/connector/{connectorId}: get: - summary: Retrieves a connector by ID. - operationId: getConnectorWithSpaceId - description: | - You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. + summary: Get a connector information + operationId: getConnector tags: - connectors parameters: - $ref: '#/components/parameters/connector_id' - - $ref: '#/components/parameters/space_id' responses: '200': description: Indicates a successful call. @@ -407,16 +405,13 @@ paths: '404': $ref: '#/components/responses/404' delete: - summary: Deletes a connector. - operationId: deleteConnectorWithSpaceId - description: | - You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. WARNING: When you delete a connector, it cannot be recovered. + summary: Delete a connector + operationId: deleteConnector tags: - connectors parameters: - $ref: '#/components/parameters/kbn_xsrf' - $ref: '#/components/parameters/connector_id' - - $ref: '#/components/parameters/space_id' responses: '204': description: Indicates a successful call. @@ -425,23 +420,20 @@ paths: '404': $ref: '#/components/responses/404' post: - summary: Creates a connector. - operationId: createConnectorIdWithSpaceId - description: | - You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. + summary: Create a connector + operationId: createConnectorId tags: - connectors parameters: - $ref: '#/components/parameters/kbn_xsrf' - - $ref: '#/components/parameters/space_id' - in: path name: connectorId - description: A UUID v1 or v4 identifier for the connector. If you omit this parameter, an identifier is randomly generated. + description: | + A UUID v1 or v4 identifier for the connector. If you omit this parameter, an identifier is randomly generated. required: true schema: type: string - examples: - - ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74 + example: ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74 requestBody: required: true content: @@ -464,16 +456,13 @@ paths: '401': $ref: '#/components/responses/401' put: - summary: Updates the attributes for a connector. - operationId: updateConnectorWithSpaceId - description: | - You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. + summary: Update a connector + operationId: updateConnector tags: - connectors parameters: - $ref: '#/components/parameters/kbn_xsrf' - $ref: '#/components/parameters/connector_id' - - $ref: '#/components/parameters/space_id' requestBody: required: true content: @@ -496,110 +485,17 @@ paths: $ref: '#/components/responses/401' '404': $ref: '#/components/responses/404' - /s/{spaceId}/api/actions/connectors: - get: - summary: Retrieves all connectors. - operationId: getConnectorsWithSpaceId + /api/actions/connector/{connectorId}/_execute: + post: + summary: Run a connector + operationId: runConnector description: | - You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. - tags: - - connectors - parameters: - - $ref: '#/components/parameters/space_id' - responses: - '200': - description: Indicates a successful call. - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/connector_response_properties' - examples: - getConnectorsResponse: - $ref: '#/components/examples/get_connectors_response' - '401': - $ref: '#/components/responses/401' - /s/{spaceId}/api/actions/connector_types: - get: - summary: Retrieves a list of all connector types. - operationId: getConnectorTypesWithSpaceId - description: | - You do not need any Kibana feature privileges to run this API. - tags: - - connectors - parameters: - - $ref: '#/components/parameters/space_id' - - in: query - name: feature_id - description: A filter to limit the retrieved connector types to those that support a specific feature (such as alerting or cases). - schema: - $ref: '#/components/schemas/features' - responses: - '200': - description: Indicates a successful call. - content: - application/json: - schema: - title: Get connector types response body properties - description: The properties vary for each connector type. - type: array - items: - type: object - properties: - enabled: - type: boolean - description: Indicates whether the connector type is enabled in Kibana. - examples: - - true - enabled_in_config: - type: boolean - description: Indicates whether the connector type is enabled in the Kibana `.yml` file. - examples: - - true - enabled_in_license: - type: boolean - description: Indicates whether the connector is enabled in the license. - examples: - - true - id: - $ref: '#/components/schemas/connector_types' - minimum_license_required: - type: string - description: The license that is required to use the connector type. - examples: - - basic - name: - type: string - description: The name of the connector type. - examples: - - Index - supported_feature_ids: - type: array - description: The Kibana features that are supported by the connector type. - items: - $ref: '#/components/schemas/features' - examples: - - - alerting - - uptime - - siem - examples: - getConnectorTypesResponse: - $ref: '#/components/examples/get_connector_types_response' - '401': - $ref: '#/components/responses/401' - /s/{spaceId}/api/actions/connector/{connectorId}/_execute: - post: - summary: Runs a connector. - operationId: runConnectorWithSpaceId - description: | - You can use this API to test an action that involves interaction with Kibana services or integrations with third-party systems. You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. If you use an index connector, you must also have `all`, `create`, `index`, or `write` indices privileges. + You can use this API to test an action that involves interaction with Kibana services or integrations with third-party systems. You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. If you use an index connector, you must also have `all`, `create`, `index`, or `write` indices privileges. tags: - connectors parameters: - $ref: '#/components/parameters/kbn_xsrf' - $ref: '#/components/parameters/connector_id' - - $ref: '#/components/parameters/space_id' requestBody: required: true content: @@ -607,10 +503,16 @@ paths: schema: $ref: '#/components/schemas/run_connector_request' examples: + runCasesWebhookConnectorRequest: + $ref: '#/components/examples/run_cases_webhook_connector_request' + runEmailConnectorRequest: + $ref: '#/components/examples/run_email_connector_request' runIndexConnectorRequest: $ref: '#/components/examples/run_index_connector_request' runJiraConnectorRequest: $ref: '#/components/examples/run_jira_connector_request' + runPagerDutyConnectorRequest: + $ref: '#/components/examples/run_pagerduty_connector_request' runServerLogConnectorRequest: $ref: '#/components/examples/run_server_log_connector_request' runServiceNowITOMConnectorRequest: @@ -642,10 +544,6 @@ paths: description: An array of information returned from the action. items: type: object - message: - type: string - service_message: - type: string status: type: string description: The status of the action. @@ -653,10 +551,16 @@ paths: - error - ok examples: + runCasesWebhookConnectorResponse: + $ref: '#/components/examples/run_cases_webhook_connector_response' + runEmailConnectorResponse: + $ref: '#/components/examples/run_email_connector_response' runIndexConnectorResponse: $ref: '#/components/examples/run_index_connector_response' runJiraConnectorResponse: $ref: '#/components/examples/run_jira_connector_response' + runPagerDutyConnectorResponse: + $ref: '#/components/examples/run_pagerduty_connector_response' runServerLogConnectorResponse: $ref: '#/components/examples/run_server_log_connector_response' runServiceNowITOMConnectorResponse: @@ -667,9 +571,92 @@ paths: $ref: '#/components/examples/run_swimlane_connector_response' '401': $ref: '#/components/responses/401' + /api/actions/connectors: + get: + summary: Get all connectors + operationId: getConnectors + tags: + - connectors + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/connector_response_properties' + examples: + getConnectorsResponse: + $ref: '#/components/examples/get_connectors_response' + '401': + $ref: '#/components/responses/401' + /api/actions/connector_types: + get: + summary: Get all connector types + operationId: getConnectorTypes + tags: + - connectors + parameters: + - in: query + name: feature_id + description: A filter to limit the retrieved connector types to those that support a specific feature (such as alerting or cases). + schema: + $ref: '#/components/schemas/features' + responses: + '200': + description: Indicates a successful call. + content: + application/json: + schema: + title: Get connector types response body properties + description: The properties vary for each connector type. + type: array + items: + type: object + properties: + enabled: + type: boolean + description: Indicates whether the connector type is enabled in Kibana. + example: true + enabled_in_config: + type: boolean + description: Indicates whether the connector type is enabled in the Kibana configuration file. + example: true + enabled_in_license: + type: boolean + description: Indicates whether the connector is enabled in the license. + example: true + id: + $ref: '#/components/schemas/connector_types' + is_system_action_type: + type: boolean + example: false + minimum_license_required: + type: string + description: The license that is required to use the connector type. + example: basic + name: + type: string + description: The name of the connector type. + example: Index + supported_feature_ids: + type: array + description: The features that are supported by the connector type. + items: + $ref: '#/components/schemas/features' + example: + - alerting + - cases + - siem + examples: + getConnectorTypesServerlessResponse: + $ref: '#/components/examples/get_connector_types_generativeai_response' + '401': + $ref: '#/components/responses/401' /s/{spaceId}/api/actions/action/{actionId}: delete: - summary: Deletes a connector. + summary: Delete a connector operationId: legacyDeleteConnector deprecated: true description: | @@ -686,7 +673,7 @@ paths: '401': $ref: '#/components/responses/401' get: - summary: Retrieves a connector by ID. + summary: Get connector information operationId: legacyGetConnector description: Deprecated in 7.13.0. Use the get connector API instead. deprecated: true @@ -701,7 +688,7 @@ paths: '401': $ref: '#/components/responses/401' put: - summary: Updates the attributes for a connector. + summary: Update a connector operationId: legacyUpdateConnector deprecated: true description: Deprecated in 7.13.0. Use the update connector API instead. @@ -736,7 +723,7 @@ paths: $ref: '#/components/responses/404' /s/{spaceId}/api/actions: get: - summary: Retrieves all connectors. + summary: Get all connectors operationId: legacyGetConnectors deprecated: true description: Deprecated in 7.13.0. Use the get all connectors API instead. @@ -756,7 +743,7 @@ paths: '401': $ref: '#/components/responses/401' post: - summary: Creates a connector. + summary: Create a connector operationId: legacyCreateConnector deprecated: true description: Deprecated in 7.13.0. Use the create connector API instead. @@ -793,7 +780,7 @@ paths: $ref: '#/components/responses/401' /s/{spaceId}/api/actions/list_action_types: get: - summary: Retrieves a list of all connector types. + summary: Get connector types operationId: legacyGetConnectorTypes deprecated: true description: Deprecated in 7.13.0. Use the get all connector types API instead. @@ -822,8 +809,7 @@ paths: enabledInLicense: type: boolean description: Indicates whether the connector is enabled in the license. - examples: - - true + example: true id: type: string description: The unique identifier for the connector type. @@ -837,7 +823,7 @@ paths: $ref: '#/components/responses/401' /s/{spaceId}/api/actions/action/{actionId}/_execute: post: - summary: Runs a connector. + summary: Run a connector operationId: legacyRunConnector deprecated: true description: Deprecated in 7.13.0. Use the run connector API instead. @@ -903,24 +889,22 @@ components: name: kbn-xsrf description: Cross-site request forgery protection required: true - connector_id: + space_id: in: path - name: connectorId - description: An identifier for the connector. + name: spaceId + description: An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used. required: true schema: type: string - examples: - - df770e30-8b8b-11ed-a780-3b746c987a81 - space_id: + example: default + connector_id: in: path - name: spaceId - description: An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used. + name: connectorId + description: An identifier for the connector. required: true schema: type: string - examples: - - default + example: df770e30-8b8b-11ed-a780-3b746c987a81 action_id: in: path name: actionId @@ -928,8 +912,7 @@ components: required: true schema: type: string - examples: - - c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad + example: c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad schemas: create_connector_request_bedrock: title: Create Amazon Bedrock connector request @@ -948,15 +931,37 @@ components: description: The type of connector. enum: - .bedrock - examples: - - .bedrock + example: .bedrock name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_bedrock' + create_connector_request_gemini: + title: Create Google Gemini connector request + description: The Google Gemini connector uses axios to send a POST request to Google Gemini. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/config_properties_gemini' + connector_type_id: + type: string + description: The type of connector. + enum: + - .gemini + example: .gemini + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/secrets_properties_gemini' create_connector_request_cases_webhook: title: Create Webhook - Case Managment connector request description: | @@ -974,13 +979,11 @@ components: description: The type of connector. enum: - .cases-webhook - examples: - - .cases-webhook + example: .cases-webhook name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_cases_webhook' create_connector_request_d3security: @@ -1001,13 +1004,11 @@ components: description: The type of connector. enum: - .d3security - examples: - - .d3security + example: .d3security name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_d3security' create_connector_request_email: @@ -1028,13 +1029,11 @@ components: description: The type of connector. enum: - .email - examples: - - .email + example: .email name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_email' create_connector_request_genai: @@ -1055,13 +1054,11 @@ components: description: The type of connector. enum: - .gen-ai - examples: - - .gen-ai + example: .gen-ai name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_genai' create_connector_request_index: @@ -1080,13 +1077,11 @@ components: description: The type of connector. enum: - .index - examples: - - .index + example: .index name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector create_connector_request_jira: title: Create Jira connector request description: The Jira connector uses the REST API v2 to create Jira issues. @@ -1104,13 +1099,11 @@ components: description: The type of connector. enum: - .jira - examples: - - .jira + example: .jira name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_jira' create_connector_request_opsgenie: @@ -1130,13 +1123,11 @@ components: description: The type of connector. enum: - .opsgenie - examples: - - .opsgenie + example: .opsgenie name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_opsgenie' create_connector_request_pagerduty: @@ -1157,13 +1148,11 @@ components: description: The type of connector. enum: - .pagerduty - examples: - - .pagerduty + example: .pagerduty name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_pagerduty' create_connector_request_resilient: @@ -1181,15 +1170,13 @@ components: connector_type_id: description: The type of connector. type: string - examples: - - .resilient + example: .resilient enum: - .resilient name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_resilient' create_connector_request_sentinelone: @@ -1211,13 +1198,11 @@ components: description: The type of connector. enum: - .sentinelone - examples: - - .sentinelone + example: .sentinelone name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_sentinelone' create_connector_request_serverlog: @@ -1233,13 +1218,11 @@ components: description: The type of connector. enum: - .server-log - examples: - - .server-log + example: .server-log name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector create_connector_request_servicenow: title: Create ServiceNow ITSM connector request description: | @@ -1258,13 +1241,11 @@ components: description: The type of connector. enum: - .servicenow - examples: - - .servicenow + example: .servicenow name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_servicenow' create_connector_request_servicenow_itom: @@ -1285,13 +1266,11 @@ components: description: The type of connector. enum: - .servicenow-itom - examples: - - .servicenow-itom + example: .servicenow-itom name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_servicenow' create_connector_request_servicenow_sir: @@ -1312,13 +1291,11 @@ components: description: The type of connector. enum: - .servicenow-sir - examples: - - .servicenow-sir + example: .servicenow-sir name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_servicenow' create_connector_request_slack_api: @@ -1337,13 +1314,11 @@ components: description: The type of connector. enum: - .slack_api - examples: - - .slack_api + example: .slack_api name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_slack_api' create_connector_request_slack_webhook: @@ -1360,13 +1335,11 @@ components: description: The type of connector. enum: - .slack - examples: - - .slack + example: .slack name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_slack_webhook' create_connector_request_swimlane: @@ -1386,13 +1359,11 @@ components: description: The type of connector. enum: - .swimlane - examples: - - .swimlane + example: .swimlane name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_swimlane' create_connector_request_teams: @@ -1409,13 +1380,11 @@ components: description: The type of connector. enum: - .teams - examples: - - .teams + example: .teams name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_teams' create_connector_request_tines: @@ -1436,13 +1405,11 @@ components: description: The type of connector. enum: - .tines - examples: - - .tines + example: .tines name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_tines' create_connector_request_torq: @@ -1463,13 +1430,11 @@ components: description: The type of connector. enum: - .torq - examples: - - .torq + example: .torq name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_torq' create_connector_request_webhook: @@ -1490,13 +1455,11 @@ components: description: The type of connector. enum: - .webhook - examples: - - .webhook + example: .webhook name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_webhook' create_connector_request_xmatters: @@ -1517,13 +1480,11 @@ components: description: The type of connector. enum: - .xmatters - examples: - - .xmatters + example: .xmatters name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_xmatters' config_properties_bedrock: @@ -1555,6 +1516,38 @@ components: secret: type: string description: The AWS secret for authentication. + config_properties_gemini: + title: Connector request properties for an Google Gemini connector + description: Defines properties for connectors when type is `.gemini`. + type: object + required: + - apiUrl + - gcpRegion + - gcpProjectID + properties: + apiUrl: + type: string + description: The Google Gemini request URL. + defaultModel: + type: string + description: The generative artificial intelligence model for Google Gemini to use. + default: gemini-1.5-pro-preview-0409 + gcpRegion: + type: string + description: The GCP region where the Vertex AI endpoint enabled. + gcpProjectID: + type: string + description: The Google ProjectID that has Vertex AI endpoint enabled. + secrets_properties_gemini: + title: Connector secrets properties for a Google Gemini connector + description: Defines secrets for connectors when type is `.gemini`. + type: object + required: + - credentialsJSON + properties: + credentialsJSON: + type: string + description: The service account credentials JSON file. The service account should have Vertex AI user IAM role assigned to it. config_properties_cases_webhook: title: Connector request properties for Webhook - Case Management connector required: @@ -1573,8 +1566,7 @@ components: type: string description: | A JSON payload sent to the create comment URL to create a case comment. You can use variables to add Kibana Cases data to the payload. The required variable is `case.comment`. Due to Mustache template variables (the text enclosed in triple braces, for example, `{{{case.title}}}`), the JSON is not validated when you create the connector. The JSON is validated once the Mustache variables have been placed when the REST method runs. Manually ensure that the JSON is valid, disregarding the Mustache variables, so the later validation will pass. - examples: - - '{"body": {{{case.comment}}}}' + example: '{"body": {{{case.comment}}}}' createCommentMethod: type: string description: | @@ -1588,14 +1580,12 @@ components: type: string description: | The REST API URL to create a case comment by ID in the third-party system. You can use a variable to add the external system ID to the URL. If you are using the `xpack.actions.allowedHosts setting`, add the hostname to the allowed hosts. - examples: - - https://example.com/issue/{{{external.system.id}}}/comment + example: https://example.com/issue/{{{external.system.id}}}/comment createIncidentJson: type: string description: | A JSON payload sent to the create case URL to create a case. You can use variables to add case data to the payload. Required variables are `case.title` and `case.description`. Due to Mustache template variables (which is the text enclosed in triple braces, for example, `{{{case.title}}}`), the JSON is not validated when you create the connector. The JSON is validated after the Mustache variables have been placed when REST method runs. Manually ensure that the JSON is valid to avoid future validation errors; disregard Mustache variables during your review. - examples: - - '{"fields": {"summary": {{{case.title}}},"description": {{{case.description}}},"labels": {{{case.tags}}}}}' + example: '{"fields": {"summary": {{{case.title}}},"description": {{{case.description}}},"labels": {{{case.tags}}}}}' createIncidentMethod: type: string description: | @@ -1619,8 +1609,7 @@ components: type: string description: | The REST API URL to get the case by ID from the third-party system. If you are using the `xpack.actions.allowedHosts` setting, add the hostname to the allowed hosts. You can use a variable to add the external system ID to the URL. Due to Mustache template variables (the text enclosed in triple braces, for example, `{{{case.title}}}`), the JSON is not validated when you create the connector. The JSON is validated after the Mustache variables have been placed when REST method runs. Manually ensure that the JSON is valid, disregarding the Mustache variables, so the later validation will pass. - examples: - - https://example.com/issue/{{{external.system.id}}} + example: https://example.com/issue/{{{external.system.id}}} hasAuth: type: boolean description: If true, a username and password for login type authentication must be provided. @@ -1633,8 +1622,7 @@ components: type: string description: | The JSON payload sent to the update case URL to update the case. You can use variables to add Kibana Cases data to the payload. Required variables are `case.title` and `case.description`. Due to Mustache template variables (which is the text enclosed in triple braces, for example, `{{{case.title}}}`), the JSON is not validated when you create the connector. The JSON is validated after the Mustache variables have been placed when REST method runs. Manually ensure that the JSON is valid to avoid future validation errors; disregard Mustache variables during your review. - examples: - - '{"fields": {"summary": {{{case.title}}},"description": {{{case.description}}},"labels": {{{case.tags}}}}}' + example: '{"fields": {"summary": {{{case.title}}},"description": {{{case.description}}},"labels": {{{case.tags}}}}}' updateIncidentMethod: type: string description: | @@ -1648,14 +1636,12 @@ components: type: string description: | The REST API URL to update the case by ID in the third-party system. You can use a variable to add the external system ID to the URL. If you are using the `xpack.actions.allowedHosts` setting, add the hostname to the allowed hosts. - examples: - - https://example.com/issue/{{{external.system.ID}}} + example: https://example.com/issue/{{{external.system.ID}}} viewIncidentUrl: type: string description: | The URL to view the case in the external system. You can use variables to add the external system ID or external system title to the URL. - examples: - - https://testing-jira.atlassian.net/browse/{{{external.system.title}}} + example: https://testing-jira.atlassian.net/browse/{{{external.system.title}}} secrets_properties_cases_webhook: title: Connector secrets properties for Webhook - Case Management connector type: object @@ -1697,9 +1683,8 @@ components: clientId: description: | The client identifier, which is a part of OAuth 2.0 client credentials authentication, in GUID format. If `service` is `exchange_server`, this property is required. - type: - - string - - 'null' + type: string + nullable: true from: description: | The from address for all emails sent by the connector. It must be specified in `user@host-name` format. @@ -1714,9 +1699,8 @@ components: The host name of the service provider. If the `service` is `elastic_cloud` (for Elastic Cloud notifications) or one of Nodemailer's well-known email service providers, this property is ignored. If `service` is `other`, this property must be defined. type: string oauthTokenUrl: - type: - - string - - 'null' + type: string + nullable: true port: description: | The port to connect to on the service provider. If the `service` is `elastic_cloud` (for Elastic Cloud notifications) or one of Nodemailer's well-known email service providers, this property is ignored. If `service` is `other`, this property must be defined. @@ -1739,9 +1723,8 @@ components: tenantId: description: | The tenant identifier, which is part of OAuth 2.0 client credentials authentication, in GUID format. If `service` is `exchange_server`, this property is required. - type: - - string - - 'null' + type: string + nullable: true secrets_properties_email: title: Connector secrets properties for an email connector description: Defines secrets for connectors when type is `.email`. @@ -1825,9 +1808,8 @@ components: executionTimeField: description: A field that indicates when the document was indexed. default: null - type: - - string - - 'null' + type: string + nullable: true index: description: The Elasticsearch index to be written to. type: string @@ -1892,11 +1874,9 @@ components: properties: apiUrl: description: The PagerDuty event URL. - type: - - string - - 'null' - examples: - - https://events.pagerduty.com/v2/enqueue + type: string + nullable: true + example: https://events.pagerduty.com/v2/enqueue secrets_properties_pagerduty: title: Connector secrets properties for a PagerDuty connector description: Defines secrets for connectors when type is `.pagerduty`. @@ -2332,10 +2312,10 @@ components: properties: authType: type: string + nullable: true enum: - webhook-authentication-basic - webhook-authentication-ssl - - 'null' description: | The type of authentication to use: basic, SSL, or none. ca: @@ -2354,9 +2334,8 @@ components: description: | If `true`, a user name and password must be provided for login type authentication. headers: - type: - - object - - 'null' + type: object + nullable: true description: A set of key-value pairs sent as headers with the request. method: type: string @@ -2409,9 +2388,8 @@ components: configUrl: description: | The request URL for the Elastic Alerts trigger in xMatters. It is applicable only when `usesBasic` is `true`. - type: - - string - - 'null' + type: string + nullable: true usesBasic: description: Specifies whether the connector uses HTTP basic authentication (`true`) or URL authentication (`false`). type: boolean @@ -2438,6 +2416,7 @@ components: description: The properties vary depending on the connector type. oneOf: - $ref: '#/components/schemas/create_connector_request_bedrock' + - $ref: '#/components/schemas/create_connector_request_gemini' - $ref: '#/components/schemas/create_connector_request_cases_webhook' - $ref: '#/components/schemas/create_connector_request_d3security' - $ref: '#/components/schemas/create_connector_request_email' @@ -2464,6 +2443,7 @@ components: propertyName: connector_type_id mapping: .bedrock: '#/components/schemas/create_connector_request_bedrock' + .gemini: '#/components/schemas/create_connector_request_gemini' .cases-webhook: '#/components/schemas/create_connector_request_cases_webhook' .d3security: '#/components/schemas/create_connector_request_d3security' .email: '#/components/schemas/create_connector_request_email' @@ -2518,6 +2498,39 @@ components: name: type: string description: The display name for the connector. + connector_response_properties_gemini: + title: Connector response properties for a Google Gemini connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/config_properties_gemini' + connector_type_id: + type: string + description: The type of connector. + enum: + - .gemini + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/is_preconfigured' + is_system_action: + $ref: '#/components/schemas/is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/referenced_by_count' connector_response_properties_cases_webhook: title: Connector request properties for a Webhook - Case Management connector type: object @@ -2859,9 +2872,8 @@ components: - name properties: config: - type: - - object - - 'null' + type: object + nullable: true connector_type_id: type: string description: The type of connector. @@ -3247,35 +3259,31 @@ components: is_deprecated: type: boolean description: Indicates whether the connector type is deprecated. - examples: - - false + example: false is_missing_secrets: type: boolean description: Indicates whether secrets are missing for the connector. Secrets configuration properties vary depending on the connector type. - examples: - - false + example: false is_preconfigured: type: boolean description: | Indicates whether it is a preconfigured connector. If true, the `config` and `is_missing_secrets` properties are omitted from the response. - examples: - - false + example: false is_system_action: type: boolean description: Indicates whether the connector is used for system actions. - examples: - - false + example: false referenced_by_count: type: integer description: | Indicates the number of saved objects that reference the connector. If `is_preconfigured` is true, this value is not calculated. This property is returned only by the get all connectors API. - examples: - - 2 + example: 2 connector_response_properties: title: Connector response properties description: The properties vary depending on the connector type. oneOf: - $ref: '#/components/schemas/connector_response_properties_bedrock' + - $ref: '#/components/schemas/connector_response_properties_gemini' - $ref: '#/components/schemas/connector_response_properties_cases_webhook' - $ref: '#/components/schemas/connector_response_properties_d3security' - $ref: '#/components/schemas/connector_response_properties_email' @@ -3302,6 +3310,7 @@ components: propertyName: connector_type_id mapping: .bedrock: '#/components/schemas/connector_response_properties_bedrock' + .gemini: '#/components/schemas/connector_response_properties_gemini' .cases-webhook: '#/components/schemas/connector_response_properties_cases_webhook' .d3security: '#/components/schemas/connector_response_properties_d3security' .email: '#/components/schemas/connector_response_properties_email' @@ -3338,22 +3347,35 @@ components: description: The display name for the connector. secrets: $ref: '#/components/schemas/secrets_properties_bedrock' - update_connector_request_cases_webhook: - title: Update Webhook - Case Managment connector request + update_connector_request_gemini: + title: Update Google Gemini connector request type: object required: - config - name properties: config: - $ref: '#/components/schemas/config_properties_cases_webhook' + $ref: '#/components/schemas/config_properties_gemini' name: type: string description: The display name for the connector. - examples: - - my-connector secrets: - $ref: '#/components/schemas/secrets_properties_cases_webhook' + $ref: '#/components/schemas/secrets_properties_gemini' + update_connector_request_cases_webhook: + title: Update Webhook - Case Managment connector request + type: object + required: + - config + - name + properties: + config: + $ref: '#/components/schemas/config_properties_cases_webhook' + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/secrets_properties_cases_webhook' update_connector_request_d3security: title: Update D3 Security connector request type: object @@ -3548,8 +3570,7 @@ components: name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_swimlane' update_connector_request_teams: @@ -3629,6 +3650,7 @@ components: description: The properties vary depending on the connector type. oneOf: - $ref: '#/components/schemas/update_connector_request_bedrock' + - $ref: '#/components/schemas/update_connector_request_gemini' - $ref: '#/components/schemas/update_connector_request_cases_webhook' - $ref: '#/components/schemas/update_connector_request_d3security' - $ref: '#/components/schemas/update_connector_request_email' @@ -3650,6 +3672,48 @@ components: - $ref: '#/components/schemas/update_connector_request_torq' - $ref: '#/components/schemas/update_connector_request_webhook' - $ref: '#/components/schemas/update_connector_request_xmatters' + features: + type: string + description: | + The feature that uses the connector. + enum: + - alerting + - cases + - generativeAIForSecurity + - generativeAIForObservability + - generativeAIForSearchPlayground + - siem + - uptime + connector_types: + title: Connector types + type: string + description: The type of connector. For example, `.email`, `.index`, `.jira`, `.opsgenie`, or `.server-log`. + enum: + - .bedrock + - .gemini + - .cases-webhook + - .d3security + - .email + - .gen-ai + - .index + - .jira + - .opsgenie + - .pagerduty + - .resilient + - .sentinelone + - .servicenow + - .servicenow-itom + - .servicenow-sir + - .server-log + - .slack + - .slack_api + - .swimlane + - .teams + - .tines + - .torq + - .webhook + - .xmatters + example: .server-log run_connector_params_acknowledge_resolve_pagerduty: title: PagerDuty connector parameters description: Test an action that acknowledges or resolves a PagerDuty alert. @@ -3766,13 +3830,11 @@ components: class: description: The class or type of the event. type: string - examples: - - cpu load + example: cpu load component: description: The component of the source machine that is responsible for the event. type: string - examples: - - eth0 + example: eth0 customDetails: description: Additional details to add to the event. type: object @@ -3789,8 +3851,7 @@ components: group: description: The logical grouping of components of a service. type: string - examples: - - app-stack + example: app-stack links: description: A list of links to add to the event. type: array @@ -3930,17 +3991,15 @@ components: - externalId properties: correlation_id: - type: - - 'null' - - string + type: string + nullable: true description: | An identifier that is assigned to the incident when it is created by the connector. NOTE: If you use the default value and the rule generates multiple alerts that use the same alert IDs, the latest open incident for this correlation ID is closed unless you specify the external ID. maxLength: 100 default: '{{rule.id}}:{{alert.id}}' externalId: - type: - - 'null' - - string + type: string + nullable: true description: The unique identifier (`incidentId`) for the incident in ServiceNow. run_connector_subaction_createalert: title: The createAlert subaction @@ -3975,9 +4034,9 @@ components: type: object description: The custom properties of the alert. additionalProperties: true - examples: - - key1: value1 - key2: value2 + example: + key1: value1 + key2: value2 entity: type: string description: The domain of the alert. For example, the application or server name. @@ -4075,8 +4134,7 @@ components: id: type: string description: The Jira issue type identifier. - examples: - - 10024 + example: 10024 run_connector_subaction_getchoices: title: The getChoices subaction type: object @@ -4134,8 +4192,7 @@ components: externalId: type: string description: The Jira, ServiceNow ITSM, or ServiceNow SecOps issue identifier. - examples: - - 71778 + example: 71778 run_connector_subaction_issue: title: The issue subaction type: object @@ -4156,8 +4213,7 @@ components: id: type: string description: The Jira issue identifier. - examples: - - 71778 + example: 71778 run_connector_subaction_issues: title: The issues subaction type: object @@ -4446,46 +4502,6 @@ components: issues: '#/components/schemas/run_connector_subaction_issues' issueTypes: '#/components/schemas/run_connector_subaction_issuetypes' pushToService: '#/components/schemas/run_connector_subaction_pushtoservice' - features: - type: string - description: | - The feature that uses the connector. - enum: - - alerting - - cases - - generativeAI - - siem - - uptime - connector_types: - title: Connector types - type: string - description: The type of connector. For example, `.email`, `.index`, `.jira`, `.opsgenie`, or `.server-log`. - enum: - - .bedrock - - .cases-webhook - - .d3security - - .email - - .gen-ai - - .index - - .jira - - .opsgenie - - .pagerduty - - .resilient - - .sentinelone - - .servicenow - - .servicenow-itom - - .servicenow-sir - - .server-log - - .slack - - .slack_api - - .swimlane - - .teams - - .tines - - .torq - - .webhook - - .xmatters - examples: - - .server-log action_response_properties: title: Action response properties description: The properties vary depending on the action type. @@ -4636,37 +4652,60 @@ components: name: updated-connector config: index: updated-index - run_cases_webhook_connector_request: - summary: Run a Webhook - Case Management connector to create a case. + get_connectors_response: + summary: A list of connectors value: - params: - subAction: pushToService - subActionParams: - comments: - - commentId: 1 - comment: A comment about the incident. - incident: - title: Case title - description: Description of the incident. - tags: - - tag1 - - tag2 - severity: low - status: open - id: caseID - run_email_connector_request: - summary: Send an email message from an email connector. + - id: preconfigured-email-connector + name: my-preconfigured-email-notification + connector_type_id: .email + is_preconfigured: true + is_deprecated: false + referenced_by_count: 0 + is_system_action: false + - id: e07d0c80-8b8b-11ed-a780-3b746c987a81 + name: my-index-connector + config: + index: test-index + refresh: false + executionTimeField: null + connector_type_id: .index + is_preconfigured: false + is_deprecated: false + referenced_by_count: 2 + is_missing_secrets: false + is_system_action: false + get_connector_types_response: + summary: A list of connector types value: - params: - bcc: - - user1@example.com - cc: - - user2@example.com - - user3@example.com - message: Test email message. - subject: Test message subject - to: - - user4@example.com + - id: .swimlane + name: Swimlane + enabled: true + enabled_in_config: true + enabled_in_license: true + minimum_license_required: gold + supported_feature_ids: + - alerting + - cases + - siem + - id: .index + name: Index + enabled: true + enabled_in_config: true + enabled_in_license: true + minimum_license_required: basic + supported_feature_ids: + - alerting + - uptime + - siem + - id: .server-log + name: Server log + enabled: true + enabled_in_config: true + enabled_in_license: true + minimum_license_required: basic + supported_feature_ids: + - alerting + - uptime run_index_connector_request: summary: Run an index connector. value: @@ -4680,17 +4719,6 @@ components: value: params: subAction: issueTypes - run_pagerduty_connector_request: - summary: Run a PagerDuty connector to trigger an alert. - value: - params: - eventAction: trigger - summary: A brief event summary - links: - - href: http://example.com/pagerduty - text: An example link - customDetails: - my_data_1: test data run_server_log_connector_request: summary: Run a server log connector. value: @@ -4728,43 +4756,6 @@ components: caseId: '1000' caseName: Case name description: Description of the incident. - run_cases_webhook_connector_response: - summary: Response from a pushToService action for a Webhook - Case Management connector. - value: - connector_id: 1824b5b8-c005-4dcc-adac-57f92db46459 - data: - id: 100665 - title: TEST-29034 - url: https://example.com/browse/TEST-29034 - pushedDate: '2023-12-05T19:43:36.360Z' - comments: - - commentId: 1 - pushedDate: '2023-12-05T19:43:36.360Z' - status: ok - run_email_connector_response: - summary: Response for sending a message from an email connector. - value: - connector_id: 7fc7b9a0-ecc9-11ec-8736-e7d63118c907 - data: - accepted: - - user1@example.com - - user2@example.com - - user3@example.com - - user4@example.com - envelope: - from: tester@example.com - to: - - user1@example.com - - user2@example.com - - user3@example.com - - user4@example.com - envelopeTime: 8 - messageTime: 3 - messageSize: 729 - response: 250 Message queued as QzEXKcGJ - messageId: <08a92d29-642a-0706-750c-de5996bd5cf3@example.com> - rejected: [] - status: ok run_index_connector_response: summary: Response from running an index connector. value: @@ -4804,15 +4795,6 @@ components: - id: 10000 name: Epic status: ok - run_pagerduty_connector_response: - summary: Response from running a PagerDuty connector. - value: - connector_id: 45de9f70-954f-4608-b12a-db7cf808e49d - data: - dedup_key: 5115e138b26b484a81eaea779faa6016 - message: Event processed - status: success - status: ok run_server_log_connector_response: summary: Response from running a server log connector. value: @@ -4907,28 +4889,94 @@ components: - commentId: 1 pushedDate: '2022-09-08T16:52:27.865Z' status: ok - get_connectors_response: - summary: A list of connectors + run_cases_webhook_connector_request: + summary: Run a Webhook - Case Management connector to create a case. value: - - id: preconfigured-email-connector - name: my-preconfigured-email-notification - connector_type_id: .email - is_preconfigured: true - is_deprecated: false - referenced_by_count: 0 - is_system_action: false - - id: e07d0c80-8b8b-11ed-a780-3b746c987a81 - name: my-index-connector - config: - index: test-index - refresh: false - executionTimeField: null - connector_type_id: .index - is_preconfigured: false - is_deprecated: false - referenced_by_count: 2 - is_missing_secrets: false - is_system_action: false + params: + subAction: pushToService + subActionParams: + comments: + - commentId: 1 + comment: A comment about the incident. + incident: + title: Case title + description: Description of the incident. + tags: + - tag1 + - tag2 + severity: low + status: open + id: caseID + run_email_connector_request: + summary: Send an email message from an email connector. + value: + params: + bcc: + - user1@example.com + cc: + - user2@example.com + - user3@example.com + message: Test email message. + subject: Test message subject + to: + - user4@example.com + run_pagerduty_connector_request: + summary: Run a PagerDuty connector to trigger an alert. + value: + params: + eventAction: trigger + summary: A brief event summary + links: + - href: http://example.com/pagerduty + text: An example link + customDetails: + my_data_1: test data + run_cases_webhook_connector_response: + summary: Response from a pushToService action for a Webhook - Case Management connector. + value: + connector_id: 1824b5b8-c005-4dcc-adac-57f92db46459 + data: + id: 100665 + title: TEST-29034 + url: https://example.com/browse/TEST-29034 + pushedDate: '2023-12-05T19:43:36.360Z' + comments: + - commentId: 1 + pushedDate: '2023-12-05T19:43:36.360Z' + status: ok + run_email_connector_response: + summary: Response for sending a message from an email connector. + value: + connector_id: 7fc7b9a0-ecc9-11ec-8736-e7d63118c907 + data: + accepted: + - user1@example.com + - user2@example.com + - user3@example.com + - user4@example.com + envelope: + from: tester@example.com + to: + - user1@example.com + - user2@example.com + - user3@example.com + - user4@example.com + envelopeTime: 8 + messageTime: 3 + messageSize: 729 + response: 250 Message queued as QzEXKcGJ + messageId: <08a92d29-642a-0706-750c-de5996bd5cf3@example.com> + rejected: [] + status: ok + run_pagerduty_connector_response: + summary: Response from running a PagerDuty connector. + value: + connector_id: 45de9f70-954f-4608-b12a-db7cf808e49d + data: + dedup_key: 5115e138b26b484a81eaea779faa6016 + message: Event processed + status: success + status: ok get_connector_types_generativeai_response: summary: A list of connector types for the `generativeAI` feature. value: @@ -4939,7 +4987,9 @@ components: enabled_in_license: true minimum_license_required: enterprise supported_feature_ids: - - generativeAI + - generativeAIForSecurity + - generativeAIForObservability + - generativeAIForSearchPlayground is_system_action_type: false - id: .bedrock name: AWS Bedrock @@ -4948,40 +4998,19 @@ components: enabled_in_license: true minimum_license_required: enterprise supported_feature_ids: - - generativeAI + - generativeAIForSecurity + - generativeAIForObservability + - generativeAIForSearchPlayground is_system_action_type: false - get_connector_types_response: - summary: A list of connector types - value: - - id: .swimlane - name: Swimlane + - id: .gemini + name: Google Gemini enabled: true enabled_in_config: true enabled_in_license: true - minimum_license_required: gold - supported_feature_ids: - - alerting - - cases - - siem - - id: .index - name: Index - enabled: true - enabled_in_config: true - enabled_in_license: true - minimum_license_required: basic - supported_feature_ids: - - alerting - - uptime - - siem - - id: .server-log - name: Server log - enabled: true - enabled_in_config: true - enabled_in_license: true - minimum_license_required: basic + minimum_license_required: enterprise supported_feature_ids: - - alerting - - uptime + - generativeAIForSecurity + is_system_action_type: false responses: '401': description: Authorization information is missing or invalid. @@ -4993,16 +5022,14 @@ components: properties: error: type: string - examples: - - Unauthorized + example: Unauthorized enum: - Unauthorized message: type: string statusCode: type: integer - examples: - - 401 + example: 401 enum: - 401 '404': @@ -5015,18 +5042,15 @@ components: properties: error: type: string - examples: - - Not Found + example: Not Found enum: - Not Found message: type: string - examples: - - Saved object [action/baf33fc0-920c-11ed-b36a-874bd1548a00] not found + example: Saved object [action/baf33fc0-920c-11ed-b36a-874bd1548a00] not found statusCode: type: integer - examples: - - 404 + example: 404 enum: - 404 200_actions: diff --git a/x-pack/plugins/actions/docs/openapi/bundled_serverless.json b/x-pack/plugins/actions/docs/openapi/bundled_serverless.json index 821f74fb1424c..928dbbfd758d7 100644 --- a/x-pack/plugins/actions/docs/openapi/bundled_serverless.json +++ b/x-pack/plugins/actions/docs/openapi/bundled_serverless.json @@ -1,5 +1,5 @@ { - "openapi": "3.1.0", + "openapi": "3.0.3", "info": { "title": "Connectors", "description": "OpenAPI schema for connectors in Serverless projects", @@ -36,7 +36,7 @@ "paths": { "/api/actions/connector": { "post": { - "summary": "Creates a connector.", + "summary": "Create a connector", "operationId": "createConnector", "tags": [ "connectors" @@ -103,7 +103,7 @@ }, "/api/actions/connector/{connectorId}": { "get": { - "summary": "Retrieves a connector by ID.", + "summary": "Get a connector information", "operationId": "getConnector", "tags": [ "connectors" @@ -138,7 +138,7 @@ } }, "delete": { - "summary": "Deletes a connector.", + "summary": "Delete a connector", "operationId": "deleteConnector", "tags": [ "connectors" @@ -164,7 +164,7 @@ } }, "post": { - "summary": "Creates a connector.", + "summary": "Create a connector", "operationId": "createConnectorId", "tags": [ "connectors" @@ -180,9 +180,7 @@ "required": true, "schema": { "type": "string", - "examples": [ - "ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74" - ] + "example": "ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74" } } ], @@ -223,7 +221,7 @@ } }, "put": { - "summary": "Updates the attributes for a connector.", + "summary": "Update a connector", "operationId": "updateConnector", "tags": [ "connectors" @@ -276,7 +274,7 @@ }, "/api/actions/connectors": { "get": { - "summary": "Retrieves all connectors.", + "summary": "Get all connectors", "operationId": "getConnectors", "tags": [ "connectors" @@ -308,7 +306,7 @@ }, "/api/actions/connector_types": { "get": { - "summary": "Retrieves a list of all connector types.", + "summary": "Get all connector types", "operationId": "getConnectorTypes", "tags": [ "connectors" @@ -338,46 +336,34 @@ "enabled": { "type": "boolean", "description": "Indicates whether the connector type is enabled in Kibana.", - "examples": [ - true - ] + "example": true }, "enabled_in_config": { "type": "boolean", "description": "Indicates whether the connector type is enabled in the Kibana configuration file.", - "examples": [ - true - ] + "example": true }, "enabled_in_license": { "type": "boolean", "description": "Indicates whether the connector is enabled in the license.", - "examples": [ - true - ] + "example": true }, "id": { "$ref": "#/components/schemas/connector_types" }, "is_system_action_type": { "type": "boolean", - "examples": [ - false - ] + "example": false }, "minimum_license_required": { "type": "string", "description": "The license that is required to use the connector type.", - "examples": [ - "basic" - ] + "example": "basic" }, "name": { "type": "string", "description": "The name of the connector type.", - "examples": [ - "Index" - ] + "example": "Index" }, "supported_feature_ids": { "type": "array", @@ -385,12 +371,10 @@ "items": { "$ref": "#/components/schemas/features" }, - "examples": [ - [ - "alerting", - "cases", - "siem" - ] + "example": [ + "alerting", + "cases", + "siem" ] } } @@ -417,7 +401,7 @@ "type": "apiKey", "in": "header", "name": "Authorization", - "description": "e.g. Authorization: ApiKey base64AccessApiKey" + "description": "Serverless APIs support only key-based authentication. You must create an API key and use the encoded value in the request header. For example: 'Authorization: ApiKey base64AccessApiKey'.\n" } }, "parameters": { @@ -437,9 +421,7 @@ "required": true, "schema": { "type": "string", - "examples": [ - "df770e30-8b8b-11ed-a780-3b746c987a81" - ] + "example": "df770e30-8b8b-11ed-a780-3b746c987a81" } } }, @@ -464,16 +446,12 @@ "enum": [ ".bedrock" ], - "examples": [ - ".bedrock" - ] + "example": ".bedrock" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_bedrock" @@ -500,16 +478,12 @@ "enum": [ ".gemini" ], - "examples": [ - ".gemini" - ] + "example": ".gemini" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_gemini" @@ -535,16 +509,12 @@ "enum": [ ".cases-webhook" ], - "examples": [ - ".cases-webhook" - ] + "example": ".cases-webhook" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_cases_webhook" @@ -571,16 +541,12 @@ "enum": [ ".d3security" ], - "examples": [ - ".d3security" - ] + "example": ".d3security" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_d3security" @@ -607,16 +573,12 @@ "enum": [ ".email" ], - "examples": [ - ".email" - ] + "example": ".email" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_email" @@ -643,16 +605,12 @@ "enum": [ ".gen-ai" ], - "examples": [ - ".gen-ai" - ] + "example": ".gen-ai" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_genai" @@ -678,16 +636,12 @@ "enum": [ ".index" ], - "examples": [ - ".index" - ] + "example": ".index" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" } } }, @@ -711,16 +665,12 @@ "enum": [ ".jira" ], - "examples": [ - ".jira" - ] + "example": ".jira" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_jira" @@ -747,16 +697,12 @@ "enum": [ ".opsgenie" ], - "examples": [ - ".opsgenie" - ] + "example": ".opsgenie" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_opsgenie" @@ -783,16 +729,12 @@ "enum": [ ".pagerduty" ], - "examples": [ - ".pagerduty" - ] + "example": ".pagerduty" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_pagerduty" @@ -816,9 +758,7 @@ "connector_type_id": { "description": "The type of connector.", "type": "string", - "examples": [ - ".resilient" - ], + "example": ".resilient", "enum": [ ".resilient" ] @@ -826,9 +766,7 @@ "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_resilient" @@ -856,16 +794,12 @@ "enum": [ ".sentinelone" ], - "examples": [ - ".sentinelone" - ] + "example": ".sentinelone" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_sentinelone" @@ -887,16 +821,12 @@ "enum": [ ".server-log" ], - "examples": [ - ".server-log" - ] + "example": ".server-log" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" } } }, @@ -920,16 +850,12 @@ "enum": [ ".servicenow" ], - "examples": [ - ".servicenow" - ] + "example": ".servicenow" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_servicenow" @@ -956,16 +882,12 @@ "enum": [ ".servicenow-itom" ], - "examples": [ - ".servicenow-itom" - ] + "example": ".servicenow-itom" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_servicenow" @@ -992,16 +914,12 @@ "enum": [ ".servicenow-sir" ], - "examples": [ - ".servicenow-sir" - ] + "example": ".servicenow-sir" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_servicenow" @@ -1027,16 +945,12 @@ "enum": [ ".slack_api" ], - "examples": [ - ".slack_api" - ] + "example": ".slack_api" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_slack_api" @@ -1059,16 +973,12 @@ "enum": [ ".slack" ], - "examples": [ - ".slack" - ] + "example": ".slack" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_slack_webhook" @@ -1095,16 +1005,12 @@ "enum": [ ".swimlane" ], - "examples": [ - ".swimlane" - ] + "example": ".swimlane" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_swimlane" @@ -1127,16 +1033,12 @@ "enum": [ ".teams" ], - "examples": [ - ".teams" - ] + "example": ".teams" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_teams" @@ -1163,16 +1065,12 @@ "enum": [ ".tines" ], - "examples": [ - ".tines" - ] + "example": ".tines" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_tines" @@ -1199,16 +1097,12 @@ "enum": [ ".torq" ], - "examples": [ - ".torq" - ] + "example": ".torq" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_torq" @@ -1235,16 +1129,12 @@ "enum": [ ".webhook" ], - "examples": [ - ".webhook" - ] + "example": ".webhook" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_webhook" @@ -1271,16 +1161,12 @@ "enum": [ ".xmatters" ], - "examples": [ - ".xmatters" - ] + "example": ".xmatters" }, "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_xmatters" @@ -1341,12 +1227,12 @@ }, "defaultModel": { "type": "string", - "description": "The generative artificial intelligence model for Google Gemini to use.\n", + "description": "The generative artificial intelligence model for Google Gemini to use.", "default": "gemini-1.5-pro-preview-0409" }, "gcpRegion": { "type": "string", - "description": "The GCP region that has Vertex AI endpoint enabled." + "description": "The GCP region where the Vertex AI endpoint enabled." }, "gcpProjectID": { "type": "string", @@ -1355,7 +1241,7 @@ } }, "secrets_properties_gemini": { - "title": "Connector secrets properties for an Google Gemini connector", + "title": "Connector secrets properties for a Google Gemini connector", "description": "Defines secrets for connectors when type is `.gemini`.", "type": "object", "required": [ @@ -1386,9 +1272,7 @@ "createCommentJson": { "type": "string", "description": "A JSON payload sent to the create comment URL to create a case comment. You can use variables to add Kibana Cases data to the payload. The required variable is `case.comment`. Due to Mustache template variables (the text enclosed in triple braces, for example, `{{{case.title}}}`), the JSON is not validated when you create the connector. The JSON is validated once the Mustache variables have been placed when the REST method runs. Manually ensure that the JSON is valid, disregarding the Mustache variables, so the later validation will pass.\n", - "examples": [ - "{\"body\": {{{case.comment}}}}" - ] + "example": "{\"body\": {{{case.comment}}}}" }, "createCommentMethod": { "type": "string", @@ -1403,16 +1287,12 @@ "createCommentUrl": { "type": "string", "description": "The REST API URL to create a case comment by ID in the third-party system. You can use a variable to add the external system ID to the URL. If you are using the `xpack.actions.allowedHosts setting`, add the hostname to the allowed hosts.\n", - "examples": [ - "https://example.com/issue/{{{external.system.id}}}/comment" - ] + "example": "https://example.com/issue/{{{external.system.id}}}/comment" }, "createIncidentJson": { "type": "string", "description": "A JSON payload sent to the create case URL to create a case. You can use variables to add case data to the payload. Required variables are `case.title` and `case.description`. Due to Mustache template variables (which is the text enclosed in triple braces, for example, `{{{case.title}}}`), the JSON is not validated when you create the connector. The JSON is validated after the Mustache variables have been placed when REST method runs. Manually ensure that the JSON is valid to avoid future validation errors; disregard Mustache variables during your review.\n", - "examples": [ - "{\"fields\": {\"summary\": {{{case.title}}},\"description\": {{{case.description}}},\"labels\": {{{case.tags}}}}}" - ] + "example": "{\"fields\": {\"summary\": {{{case.title}}},\"description\": {{{case.description}}},\"labels\": {{{case.tags}}}}}" }, "createIncidentMethod": { "type": "string", @@ -1439,9 +1319,7 @@ "getIncidentUrl": { "type": "string", "description": "The REST API URL to get the case by ID from the third-party system. If you are using the `xpack.actions.allowedHosts` setting, add the hostname to the allowed hosts. You can use a variable to add the external system ID to the URL. Due to Mustache template variables (the text enclosed in triple braces, for example, `{{{case.title}}}`), the JSON is not validated when you create the connector. The JSON is validated after the Mustache variables have been placed when REST method runs. Manually ensure that the JSON is valid, disregarding the Mustache variables, so the later validation will pass.\n", - "examples": [ - "https://example.com/issue/{{{external.system.id}}}" - ] + "example": "https://example.com/issue/{{{external.system.id}}}" }, "hasAuth": { "type": "boolean", @@ -1455,9 +1333,7 @@ "updateIncidentJson": { "type": "string", "description": "The JSON payload sent to the update case URL to update the case. You can use variables to add Kibana Cases data to the payload. Required variables are `case.title` and `case.description`. Due to Mustache template variables (which is the text enclosed in triple braces, for example, `{{{case.title}}}`), the JSON is not validated when you create the connector. The JSON is validated after the Mustache variables have been placed when REST method runs. Manually ensure that the JSON is valid to avoid future validation errors; disregard Mustache variables during your review.\n", - "examples": [ - "{\"fields\": {\"summary\": {{{case.title}}},\"description\": {{{case.description}}},\"labels\": {{{case.tags}}}}}" - ] + "example": "{\"fields\": {\"summary\": {{{case.title}}},\"description\": {{{case.description}}},\"labels\": {{{case.tags}}}}}" }, "updateIncidentMethod": { "type": "string", @@ -1472,16 +1348,12 @@ "updateIncidentUrl": { "type": "string", "description": "The REST API URL to update the case by ID in the third-party system. You can use a variable to add the external system ID to the URL. If you are using the `xpack.actions.allowedHosts` setting, add the hostname to the allowed hosts.\n", - "examples": [ - "https://example.com/issue/{{{external.system.ID}}}" - ] + "example": "https://example.com/issue/{{{external.system.ID}}}" }, "viewIncidentUrl": { "type": "string", "description": "The URL to view the case in the external system. You can use variables to add the external system ID or external system title to the URL.\n", - "examples": [ - "https://testing-jira.atlassian.net/browse/{{{external.system.title}}}" - ] + "example": "https://testing-jira.atlassian.net/browse/{{{external.system.title}}}" } } }, @@ -1537,10 +1409,8 @@ "properties": { "clientId": { "description": "The client identifier, which is a part of OAuth 2.0 client credentials authentication, in GUID format. If `service` is `exchange_server`, this property is required.\n", - "type": [ - "string", - "null" - ] + "type": "string", + "nullable": true }, "from": { "description": "The from address for all emails sent by the connector. It must be specified in `user@host-name` format.\n", @@ -1556,10 +1426,8 @@ "type": "string" }, "oauthTokenUrl": { - "type": [ - "string", - "null" - ] + "type": "string", + "nullable": true }, "port": { "description": "The port to connect to on the service provider. If the `service` is `elastic_cloud` (for Elastic Cloud notifications) or one of Nodemailer's well-known email service providers, this property is ignored. If `service` is `other`, this property must be defined. \n", @@ -1583,10 +1451,8 @@ }, "tenantId": { "description": "The tenant identifier, which is part of OAuth 2.0 client credentials authentication, in GUID format. If `service` is `exchange_server`, this property is required.\n", - "type": [ - "string", - "null" - ] + "type": "string", + "nullable": true } } }, @@ -1698,10 +1564,8 @@ "executionTimeField": { "description": "A field that indicates when the document was indexed.", "default": null, - "type": [ - "string", - "null" - ] + "type": "string", + "nullable": true }, "index": { "description": "The Elasticsearch index to be written to.", @@ -1787,13 +1651,9 @@ "properties": { "apiUrl": { "description": "The PagerDuty event URL.", - "type": [ - "string", - "null" - ], - "examples": [ - "https://events.pagerduty.com/v2/enqueue" - ] + "type": "string", + "nullable": true, + "example": "https://events.pagerduty.com/v2/enqueue" } } }, @@ -2363,10 +2223,10 @@ "properties": { "authType": { "type": "string", + "nullable": true, "enum": [ "webhook-authentication-basic", - "webhook-authentication-ssl", - "null" + "webhook-authentication-ssl" ], "description": "The type of authentication to use: basic, SSL, or none.\n" }, @@ -2387,10 +2247,8 @@ "description": "If `true`, a user name and password must be provided for login type authentication.\n" }, "headers": { - "type": [ - "object", - "null" - ], + "type": "object", + "nullable": true, "description": "A set of key-value pairs sent as headers with the request." }, "method": { @@ -2452,10 +2310,8 @@ "properties": { "configUrl": { "description": "The request URL for the Elastic Alerts trigger in xMatters. It is applicable only when `usesBasic` is `true`.\n", - "type": [ - "string", - "null" - ] + "type": "string", + "nullable": true }, "usesBasic": { "description": "Specifies whether the connector uses HTTP basic authentication (`true`) or URL authentication (`false`).", @@ -2635,10 +2491,9 @@ } }, "connector_response_properties_gemini": { - "title": "Connector response properties for an Google Gemini connector", + "title": "Connector response properties for a Google Gemini connector", "type": "object", "required": [ - "config", "connector_type_id", "id", "is_deprecated", @@ -2675,6 +2530,9 @@ "name": { "type": "string", "description": "The display name for the connector." + }, + "referenced_by_count": { + "$ref": "#/components/schemas/referenced_by_count" } } }, @@ -3150,10 +3008,8 @@ ], "properties": { "config": { - "type": [ - "object", - "null" - ] + "type": "object", + "nullable": true }, "connector_type_id": { "type": "string", @@ -3693,37 +3549,27 @@ "is_deprecated": { "type": "boolean", "description": "Indicates whether the connector type is deprecated.", - "examples": [ - false - ] + "example": false }, "is_missing_secrets": { "type": "boolean", "description": "Indicates whether secrets are missing for the connector. Secrets configuration properties vary depending on the connector type.", - "examples": [ - false - ] + "example": false }, "is_preconfigured": { "type": "boolean", "description": "Indicates whether it is a preconfigured connector. If true, the `config` and `is_missing_secrets` properties are omitted from the response. \n", - "examples": [ - false - ] + "example": false }, "is_system_action": { "type": "boolean", "description": "Indicates whether the connector is used for system actions.", - "examples": [ - false - ] + "example": false }, "referenced_by_count": { "type": "integer", "description": "Indicates the number of saved objects that reference the connector. If `is_preconfigured` is true, this value is not calculated. This property is returned only by the get all connectors API.\n", - "examples": [ - 2 - ] + "example": 2 }, "connector_response_properties": { "title": "Connector response properties", @@ -3886,9 +3732,7 @@ "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_cases_webhook" @@ -4165,9 +4009,7 @@ "name": { "type": "string", "description": "The display name for the connector.", - "examples": [ - "my-connector" - ] + "example": "my-connector" }, "secrets": { "$ref": "#/components/schemas/secrets_properties_swimlane" @@ -4356,7 +4198,9 @@ "enum": [ "alerting", "cases", - "generativeAI", + "generativeAIForSecurity", + "generativeAIForObservability", + "generativeAIForSearchPlayground", "siem", "uptime" ] @@ -4391,9 +4235,7 @@ ".webhook", ".xmatters" ], - "examples": [ - ".server-log" - ] + "example": ".server-log" } }, "examples": { @@ -4596,7 +4438,9 @@ "enabled_in_license": true, "minimum_license_required": "enterprise", "supported_feature_ids": [ - "generativeAI" + "generativeAIForSecurity", + "generativeAIForObservability", + "generativeAIForSearchPlayground" ], "is_system_action_type": false }, @@ -4608,7 +4452,9 @@ "enabled_in_license": true, "minimum_license_required": "enterprise", "supported_feature_ids": [ - "generativeAI" + "generativeAIForSecurity", + "generativeAIForObservability", + "generativeAIForSearchPlayground" ], "is_system_action_type": false }, @@ -4620,7 +4466,7 @@ "enabled_in_license": true, "minimum_license_required": "enterprise", "supported_feature_ids": [ - "generativeAI" + "generativeAIForSecurity" ], "is_system_action_type": false } @@ -4638,9 +4484,7 @@ "properties": { "error": { "type": "string", - "examples": [ - "Unauthorized" - ], + "example": "Unauthorized", "enum": [ "Unauthorized" ] @@ -4650,9 +4494,7 @@ }, "statusCode": { "type": "integer", - "examples": [ - 401 - ], + "example": 401, "enum": [ 401 ] @@ -4672,24 +4514,18 @@ "properties": { "error": { "type": "string", - "examples": [ - "Not Found" - ], + "example": "Not Found", "enum": [ "Not Found" ] }, "message": { "type": "string", - "examples": [ - "Saved object [action/baf33fc0-920c-11ed-b36a-874bd1548a00] not found" - ] + "example": "Saved object [action/baf33fc0-920c-11ed-b36a-874bd1548a00] not found" }, "statusCode": { "type": "integer", - "examples": [ - 404 - ], + "example": 404, "enum": [ 404 ] diff --git a/x-pack/plugins/actions/docs/openapi/bundled_serverless.yaml b/x-pack/plugins/actions/docs/openapi/bundled_serverless.yaml index 047216dd7f6c4..4fdc184e3fb90 100644 --- a/x-pack/plugins/actions/docs/openapi/bundled_serverless.yaml +++ b/x-pack/plugins/actions/docs/openapi/bundled_serverless.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.0 +openapi: 3.0.3 info: title: Connectors description: OpenAPI schema for connectors in Serverless projects @@ -21,7 +21,7 @@ tags: paths: /api/actions/connector: post: - summary: Creates a connector. + summary: Create a connector operationId: createConnector tags: - connectors @@ -62,7 +62,7 @@ paths: $ref: '#/components/responses/401' /api/actions/connector/{connectorId}: get: - summary: Retrieves a connector by ID. + summary: Get a connector information operationId: getConnector tags: - connectors @@ -83,7 +83,7 @@ paths: '404': $ref: '#/components/responses/404' delete: - summary: Deletes a connector. + summary: Delete a connector operationId: deleteConnector tags: - connectors @@ -98,7 +98,7 @@ paths: '404': $ref: '#/components/responses/404' post: - summary: Creates a connector. + summary: Create a connector operationId: createConnectorId tags: - connectors @@ -111,8 +111,7 @@ paths: required: true schema: type: string - examples: - - ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74 + example: ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74 requestBody: required: true content: @@ -135,7 +134,7 @@ paths: '401': $ref: '#/components/responses/401' put: - summary: Updates the attributes for a connector. + summary: Update a connector operationId: updateConnector tags: - connectors @@ -166,7 +165,7 @@ paths: $ref: '#/components/responses/404' /api/actions/connectors: get: - summary: Retrieves all connectors. + summary: Get all connectors operationId: getConnectors tags: - connectors @@ -186,7 +185,7 @@ paths: $ref: '#/components/responses/401' /api/actions/connector_types: get: - summary: Retrieves a list of all connector types. + summary: Get all connector types operationId: getConnectorTypes tags: - connectors @@ -211,43 +210,37 @@ paths: enabled: type: boolean description: Indicates whether the connector type is enabled in Kibana. - examples: - - true + example: true enabled_in_config: type: boolean description: Indicates whether the connector type is enabled in the Kibana configuration file. - examples: - - true + example: true enabled_in_license: type: boolean description: Indicates whether the connector is enabled in the license. - examples: - - true + example: true id: $ref: '#/components/schemas/connector_types' is_system_action_type: type: boolean - examples: - - false + example: false minimum_license_required: type: string description: The license that is required to use the connector type. - examples: - - basic + example: basic name: type: string description: The name of the connector type. - examples: - - Index + example: Index supported_feature_ids: type: array description: The features that are supported by the connector type. items: $ref: '#/components/schemas/features' - examples: - - - alerting - - cases - - siem + example: + - alerting + - cases + - siem examples: getConnectorTypesServerlessResponse: $ref: '#/components/examples/get_connector_types_generativeai_response' @@ -259,7 +252,8 @@ components: type: apiKey in: header name: Authorization - description: 'e.g. Authorization: ApiKey base64AccessApiKey' + description: | + Serverless APIs support only key-based authentication. You must create an API key and use the encoded value in the request header. For example: 'Authorization: ApiKey base64AccessApiKey'. parameters: kbn_xsrf: schema: @@ -275,8 +269,7 @@ components: required: true schema: type: string - examples: - - df770e30-8b8b-11ed-a780-3b746c987a81 + example: df770e30-8b8b-11ed-a780-3b746c987a81 schemas: create_connector_request_bedrock: title: Create Amazon Bedrock connector request @@ -295,15 +288,37 @@ components: description: The type of connector. enum: - .bedrock - examples: - - .bedrock + example: .bedrock name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_bedrock' + create_connector_request_gemini: + title: Create Google Gemini connector request + description: The Google Gemini connector uses axios to send a POST request to Google Gemini. + type: object + required: + - config + - connector_type_id + - name + - secrets + properties: + config: + $ref: '#/components/schemas/config_properties_gemini' + connector_type_id: + type: string + description: The type of connector. + enum: + - .gemini + example: .gemini + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: '#/components/schemas/secrets_properties_gemini' create_connector_request_cases_webhook: title: Create Webhook - Case Managment connector request description: | @@ -321,13 +336,11 @@ components: description: The type of connector. enum: - .cases-webhook - examples: - - .cases-webhook + example: .cases-webhook name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_cases_webhook' create_connector_request_d3security: @@ -348,13 +361,11 @@ components: description: The type of connector. enum: - .d3security - examples: - - .d3security + example: .d3security name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_d3security' create_connector_request_email: @@ -375,13 +386,11 @@ components: description: The type of connector. enum: - .email - examples: - - .email + example: .email name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_email' create_connector_request_genai: @@ -402,13 +411,11 @@ components: description: The type of connector. enum: - .gen-ai - examples: - - .gen-ai + example: .gen-ai name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_genai' create_connector_request_index: @@ -427,13 +434,11 @@ components: description: The type of connector. enum: - .index - examples: - - .index + example: .index name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector create_connector_request_jira: title: Create Jira connector request description: The Jira connector uses the REST API v2 to create Jira issues. @@ -451,13 +456,11 @@ components: description: The type of connector. enum: - .jira - examples: - - .jira + example: .jira name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_jira' create_connector_request_opsgenie: @@ -477,13 +480,11 @@ components: description: The type of connector. enum: - .opsgenie - examples: - - .opsgenie + example: .opsgenie name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_opsgenie' create_connector_request_pagerduty: @@ -504,13 +505,11 @@ components: description: The type of connector. enum: - .pagerduty - examples: - - .pagerduty + example: .pagerduty name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_pagerduty' create_connector_request_resilient: @@ -528,15 +527,13 @@ components: connector_type_id: description: The type of connector. type: string - examples: - - .resilient + example: .resilient enum: - .resilient name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_resilient' create_connector_request_sentinelone: @@ -558,13 +555,11 @@ components: description: The type of connector. enum: - .sentinelone - examples: - - .sentinelone + example: .sentinelone name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_sentinelone' create_connector_request_serverlog: @@ -580,13 +575,11 @@ components: description: The type of connector. enum: - .server-log - examples: - - .server-log + example: .server-log name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector create_connector_request_servicenow: title: Create ServiceNow ITSM connector request description: | @@ -605,13 +598,11 @@ components: description: The type of connector. enum: - .servicenow - examples: - - .servicenow + example: .servicenow name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_servicenow' create_connector_request_servicenow_itom: @@ -632,13 +623,11 @@ components: description: The type of connector. enum: - .servicenow-itom - examples: - - .servicenow-itom + example: .servicenow-itom name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_servicenow' create_connector_request_servicenow_sir: @@ -659,13 +648,11 @@ components: description: The type of connector. enum: - .servicenow-sir - examples: - - .servicenow-sir + example: .servicenow-sir name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_servicenow' create_connector_request_slack_api: @@ -684,13 +671,11 @@ components: description: The type of connector. enum: - .slack_api - examples: - - .slack_api + example: .slack_api name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_slack_api' create_connector_request_slack_webhook: @@ -707,13 +692,11 @@ components: description: The type of connector. enum: - .slack - examples: - - .slack + example: .slack name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_slack_webhook' create_connector_request_swimlane: @@ -733,13 +716,11 @@ components: description: The type of connector. enum: - .swimlane - examples: - - .swimlane + example: .swimlane name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_swimlane' create_connector_request_teams: @@ -756,13 +737,11 @@ components: description: The type of connector. enum: - .teams - examples: - - .teams + example: .teams name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_teams' create_connector_request_tines: @@ -783,13 +762,11 @@ components: description: The type of connector. enum: - .tines - examples: - - .tines + example: .tines name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_tines' create_connector_request_torq: @@ -810,13 +787,11 @@ components: description: The type of connector. enum: - .torq - examples: - - .torq + example: .torq name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_torq' create_connector_request_webhook: @@ -837,13 +812,11 @@ components: description: The type of connector. enum: - .webhook - examples: - - .webhook + example: .webhook name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_webhook' create_connector_request_xmatters: @@ -864,13 +837,11 @@ components: description: The type of connector. enum: - .xmatters - examples: - - .xmatters + example: .xmatters name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_xmatters' config_properties_bedrock: @@ -902,6 +873,38 @@ components: secret: type: string description: The AWS secret for authentication. + config_properties_gemini: + title: Connector request properties for an Google Gemini connector + description: Defines properties for connectors when type is `.gemini`. + type: object + required: + - apiUrl + - gcpRegion + - gcpProjectID + properties: + apiUrl: + type: string + description: The Google Gemini request URL. + defaultModel: + type: string + description: The generative artificial intelligence model for Google Gemini to use. + default: gemini-1.5-pro-preview-0409 + gcpRegion: + type: string + description: The GCP region where the Vertex AI endpoint enabled. + gcpProjectID: + type: string + description: The Google ProjectID that has Vertex AI endpoint enabled. + secrets_properties_gemini: + title: Connector secrets properties for a Google Gemini connector + description: Defines secrets for connectors when type is `.gemini`. + type: object + required: + - credentialsJSON + properties: + credentialsJSON: + type: string + description: The service account credentials JSON file. The service account should have Vertex AI user IAM role assigned to it. config_properties_cases_webhook: title: Connector request properties for Webhook - Case Management connector required: @@ -920,8 +923,7 @@ components: type: string description: | A JSON payload sent to the create comment URL to create a case comment. You can use variables to add Kibana Cases data to the payload. The required variable is `case.comment`. Due to Mustache template variables (the text enclosed in triple braces, for example, `{{{case.title}}}`), the JSON is not validated when you create the connector. The JSON is validated once the Mustache variables have been placed when the REST method runs. Manually ensure that the JSON is valid, disregarding the Mustache variables, so the later validation will pass. - examples: - - '{"body": {{{case.comment}}}}' + example: '{"body": {{{case.comment}}}}' createCommentMethod: type: string description: | @@ -935,14 +937,12 @@ components: type: string description: | The REST API URL to create a case comment by ID in the third-party system. You can use a variable to add the external system ID to the URL. If you are using the `xpack.actions.allowedHosts setting`, add the hostname to the allowed hosts. - examples: - - https://example.com/issue/{{{external.system.id}}}/comment + example: https://example.com/issue/{{{external.system.id}}}/comment createIncidentJson: type: string description: | A JSON payload sent to the create case URL to create a case. You can use variables to add case data to the payload. Required variables are `case.title` and `case.description`. Due to Mustache template variables (which is the text enclosed in triple braces, for example, `{{{case.title}}}`), the JSON is not validated when you create the connector. The JSON is validated after the Mustache variables have been placed when REST method runs. Manually ensure that the JSON is valid to avoid future validation errors; disregard Mustache variables during your review. - examples: - - '{"fields": {"summary": {{{case.title}}},"description": {{{case.description}}},"labels": {{{case.tags}}}}}' + example: '{"fields": {"summary": {{{case.title}}},"description": {{{case.description}}},"labels": {{{case.tags}}}}}' createIncidentMethod: type: string description: | @@ -966,8 +966,7 @@ components: type: string description: | The REST API URL to get the case by ID from the third-party system. If you are using the `xpack.actions.allowedHosts` setting, add the hostname to the allowed hosts. You can use a variable to add the external system ID to the URL. Due to Mustache template variables (the text enclosed in triple braces, for example, `{{{case.title}}}`), the JSON is not validated when you create the connector. The JSON is validated after the Mustache variables have been placed when REST method runs. Manually ensure that the JSON is valid, disregarding the Mustache variables, so the later validation will pass. - examples: - - https://example.com/issue/{{{external.system.id}}} + example: https://example.com/issue/{{{external.system.id}}} hasAuth: type: boolean description: If true, a username and password for login type authentication must be provided. @@ -980,8 +979,7 @@ components: type: string description: | The JSON payload sent to the update case URL to update the case. You can use variables to add Kibana Cases data to the payload. Required variables are `case.title` and `case.description`. Due to Mustache template variables (which is the text enclosed in triple braces, for example, `{{{case.title}}}`), the JSON is not validated when you create the connector. The JSON is validated after the Mustache variables have been placed when REST method runs. Manually ensure that the JSON is valid to avoid future validation errors; disregard Mustache variables during your review. - examples: - - '{"fields": {"summary": {{{case.title}}},"description": {{{case.description}}},"labels": {{{case.tags}}}}}' + example: '{"fields": {"summary": {{{case.title}}},"description": {{{case.description}}},"labels": {{{case.tags}}}}}' updateIncidentMethod: type: string description: | @@ -995,14 +993,12 @@ components: type: string description: | The REST API URL to update the case by ID in the third-party system. You can use a variable to add the external system ID to the URL. If you are using the `xpack.actions.allowedHosts` setting, add the hostname to the allowed hosts. - examples: - - https://example.com/issue/{{{external.system.ID}}} + example: https://example.com/issue/{{{external.system.ID}}} viewIncidentUrl: type: string description: | The URL to view the case in the external system. You can use variables to add the external system ID or external system title to the URL. - examples: - - https://testing-jira.atlassian.net/browse/{{{external.system.title}}} + example: https://testing-jira.atlassian.net/browse/{{{external.system.title}}} secrets_properties_cases_webhook: title: Connector secrets properties for Webhook - Case Management connector type: object @@ -1044,9 +1040,8 @@ components: clientId: description: | The client identifier, which is a part of OAuth 2.0 client credentials authentication, in GUID format. If `service` is `exchange_server`, this property is required. - type: - - string - - 'null' + type: string + nullable: true from: description: | The from address for all emails sent by the connector. It must be specified in `user@host-name` format. @@ -1061,9 +1056,8 @@ components: The host name of the service provider. If the `service` is `elastic_cloud` (for Elastic Cloud notifications) or one of Nodemailer's well-known email service providers, this property is ignored. If `service` is `other`, this property must be defined. type: string oauthTokenUrl: - type: - - string - - 'null' + type: string + nullable: true port: description: | The port to connect to on the service provider. If the `service` is `elastic_cloud` (for Elastic Cloud notifications) or one of Nodemailer's well-known email service providers, this property is ignored. If `service` is `other`, this property must be defined. @@ -1086,9 +1080,8 @@ components: tenantId: description: | The tenant identifier, which is part of OAuth 2.0 client credentials authentication, in GUID format. If `service` is `exchange_server`, this property is required. - type: - - string - - 'null' + type: string + nullable: true secrets_properties_email: title: Connector secrets properties for an email connector description: Defines secrets for connectors when type is `.email`. @@ -1172,9 +1165,8 @@ components: executionTimeField: description: A field that indicates when the document was indexed. default: null - type: - - string - - 'null' + type: string + nullable: true index: description: The Elasticsearch index to be written to. type: string @@ -1239,11 +1231,9 @@ components: properties: apiUrl: description: The PagerDuty event URL. - type: - - string - - 'null' - examples: - - https://events.pagerduty.com/v2/enqueue + type: string + nullable: true + example: https://events.pagerduty.com/v2/enqueue secrets_properties_pagerduty: title: Connector secrets properties for a PagerDuty connector description: Defines secrets for connectors when type is `.pagerduty`. @@ -1679,10 +1669,10 @@ components: properties: authType: type: string + nullable: true enum: - webhook-authentication-basic - webhook-authentication-ssl - - 'null' description: | The type of authentication to use: basic, SSL, or none. ca: @@ -1701,9 +1691,8 @@ components: description: | If `true`, a user name and password must be provided for login type authentication. headers: - type: - - object - - 'null' + type: object + nullable: true description: A set of key-value pairs sent as headers with the request. method: type: string @@ -1756,9 +1745,8 @@ components: configUrl: description: | The request URL for the Elastic Alerts trigger in xMatters. It is applicable only when `usesBasic` is `true`. - type: - - string - - 'null' + type: string + nullable: true usesBasic: description: Specifies whether the connector uses HTTP basic authentication (`true`) or URL authentication (`false`). type: boolean @@ -1785,6 +1773,7 @@ components: description: The properties vary depending on the connector type. oneOf: - $ref: '#/components/schemas/create_connector_request_bedrock' + - $ref: '#/components/schemas/create_connector_request_gemini' - $ref: '#/components/schemas/create_connector_request_cases_webhook' - $ref: '#/components/schemas/create_connector_request_d3security' - $ref: '#/components/schemas/create_connector_request_email' @@ -1811,6 +1800,7 @@ components: propertyName: connector_type_id mapping: .bedrock: '#/components/schemas/create_connector_request_bedrock' + .gemini: '#/components/schemas/create_connector_request_gemini' .cases-webhook: '#/components/schemas/create_connector_request_cases_webhook' .d3security: '#/components/schemas/create_connector_request_d3security' .email: '#/components/schemas/create_connector_request_email' @@ -1865,6 +1855,39 @@ components: name: type: string description: The display name for the connector. + connector_response_properties_gemini: + title: Connector response properties for a Google Gemini connector + type: object + required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name + properties: + config: + $ref: '#/components/schemas/config_properties_gemini' + connector_type_id: + type: string + description: The type of connector. + enum: + - .gemini + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: '#/components/schemas/is_deprecated' + is_missing_secrets: + $ref: '#/components/schemas/is_missing_secrets' + is_preconfigured: + $ref: '#/components/schemas/is_preconfigured' + is_system_action: + $ref: '#/components/schemas/is_system_action' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: '#/components/schemas/referenced_by_count' connector_response_properties_cases_webhook: title: Connector request properties for a Webhook - Case Management connector type: object @@ -2206,9 +2229,8 @@ components: - name properties: config: - type: - - object - - 'null' + type: object + nullable: true connector_type_id: type: string description: The type of connector. @@ -2594,35 +2616,31 @@ components: is_deprecated: type: boolean description: Indicates whether the connector type is deprecated. - examples: - - false + example: false is_missing_secrets: type: boolean description: Indicates whether secrets are missing for the connector. Secrets configuration properties vary depending on the connector type. - examples: - - false + example: false is_preconfigured: type: boolean description: | Indicates whether it is a preconfigured connector. If true, the `config` and `is_missing_secrets` properties are omitted from the response. - examples: - - false + example: false is_system_action: type: boolean description: Indicates whether the connector is used for system actions. - examples: - - false + example: false referenced_by_count: type: integer description: | Indicates the number of saved objects that reference the connector. If `is_preconfigured` is true, this value is not calculated. This property is returned only by the get all connectors API. - examples: - - 2 + example: 2 connector_response_properties: title: Connector response properties description: The properties vary depending on the connector type. oneOf: - $ref: '#/components/schemas/connector_response_properties_bedrock' + - $ref: '#/components/schemas/connector_response_properties_gemini' - $ref: '#/components/schemas/connector_response_properties_cases_webhook' - $ref: '#/components/schemas/connector_response_properties_d3security' - $ref: '#/components/schemas/connector_response_properties_email' @@ -2649,6 +2667,7 @@ components: propertyName: connector_type_id mapping: .bedrock: '#/components/schemas/connector_response_properties_bedrock' + .gemini: '#/components/schemas/connector_response_properties_gemini' .cases-webhook: '#/components/schemas/connector_response_properties_cases_webhook' .d3security: '#/components/schemas/connector_response_properties_d3security' .email: '#/components/schemas/connector_response_properties_email' @@ -2685,6 +2704,20 @@ components: description: The display name for the connector. secrets: $ref: '#/components/schemas/secrets_properties_bedrock' + update_connector_request_gemini: + title: Update Google Gemini connector request + type: object + required: + - config + - name + properties: + config: + $ref: '#/components/schemas/config_properties_gemini' + name: + type: string + description: The display name for the connector. + secrets: + $ref: '#/components/schemas/secrets_properties_gemini' update_connector_request_cases_webhook: title: Update Webhook - Case Managment connector request type: object @@ -2697,8 +2730,7 @@ components: name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_cases_webhook' update_connector_request_d3security: @@ -2895,8 +2927,7 @@ components: name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: '#/components/schemas/secrets_properties_swimlane' update_connector_request_teams: @@ -2976,6 +3007,7 @@ components: description: The properties vary depending on the connector type. oneOf: - $ref: '#/components/schemas/update_connector_request_bedrock' + - $ref: '#/components/schemas/update_connector_request_gemini' - $ref: '#/components/schemas/update_connector_request_cases_webhook' - $ref: '#/components/schemas/update_connector_request_d3security' - $ref: '#/components/schemas/update_connector_request_email' @@ -3004,7 +3036,9 @@ components: enum: - alerting - cases - - generativeAI + - generativeAIForSecurity + - generativeAIForObservability + - generativeAIForSearchPlayground - siem - uptime connector_types: @@ -3013,6 +3047,7 @@ components: description: The type of connector. For example, `.email`, `.index`, `.jira`, `.opsgenie`, or `.server-log`. enum: - .bedrock + - .gemini - .cases-webhook - .d3security - .email @@ -3035,8 +3070,7 @@ components: - .torq - .webhook - .xmatters - examples: - - .server-log + example: .server-log examples: create_email_connector_request: summary: Create an email connector. @@ -3197,7 +3231,9 @@ components: enabled_in_license: true minimum_license_required: enterprise supported_feature_ids: - - generativeAI + - generativeAIForSecurity + - generativeAIForObservability + - generativeAIForSearchPlayground is_system_action_type: false - id: .bedrock name: AWS Bedrock @@ -3206,7 +3242,18 @@ components: enabled_in_license: true minimum_license_required: enterprise supported_feature_ids: - - generativeAI + - generativeAIForSecurity + - generativeAIForObservability + - generativeAIForSearchPlayground + is_system_action_type: false + - id: .gemini + name: Google Gemini + enabled: true + enabled_in_config: true + enabled_in_license: true + minimum_license_required: enterprise + supported_feature_ids: + - generativeAIForSecurity is_system_action_type: false responses: '401': @@ -3219,16 +3266,14 @@ components: properties: error: type: string - examples: - - Unauthorized + example: Unauthorized enum: - Unauthorized message: type: string statusCode: type: integer - examples: - - 401 + example: 401 enum: - 401 '404': @@ -3241,17 +3286,14 @@ components: properties: error: type: string - examples: - - Not Found + example: Not Found enum: - Not Found message: type: string - examples: - - Saved object [action/baf33fc0-920c-11ed-b36a-874bd1548a00] not found + example: Saved object [action/baf33fc0-920c-11ed-b36a-874bd1548a00] not found statusCode: type: integer - examples: - - 404 + example: 404 enum: - 404 diff --git a/x-pack/plugins/actions/docs/openapi/components/examples/get_connector_types_generativeai_response.yaml b/x-pack/plugins/actions/docs/openapi/components/examples/get_connector_types_generativeai_response.yaml index b271d7f0f3df3..a97199e0a3927 100644 --- a/x-pack/plugins/actions/docs/openapi/components/examples/get_connector_types_generativeai_response.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/examples/get_connector_types_generativeai_response.yaml @@ -7,7 +7,9 @@ value: enabled_in_license: true minimum_license_required: enterprise supported_feature_ids: - - generativeAI + - generativeAIForSecurity + - generativeAIForObservability + - generativeAIForSearchPlayground is_system_action_type: false - id: .bedrock name: AWS Bedrock @@ -16,9 +18,10 @@ value: enabled_in_license: true minimum_license_required: enterprise supported_feature_ids: - - generativeAI + - generativeAIForSecurity + - generativeAIForObservability + - generativeAIForSearchPlayground is_system_action_type: false - - id: .gemini name: Google Gemini enabled: true @@ -26,5 +29,5 @@ value: enabled_in_license: true minimum_license_required: enterprise supported_feature_ids: - - generativeAI + - generativeAIForSecurity is_system_action_type: false diff --git a/x-pack/plugins/actions/docs/openapi/components/parameters/action_id.yaml b/x-pack/plugins/actions/docs/openapi/components/parameters/action_id.yaml index acbc6ab5acd63..3ee0b642c9dee 100644 --- a/x-pack/plugins/actions/docs/openapi/components/parameters/action_id.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/parameters/action_id.yaml @@ -4,5 +4,4 @@ description: An identifier for the action. required: true schema: type: string - examples: - - c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad + example: c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad diff --git a/x-pack/plugins/actions/docs/openapi/components/parameters/connector_id.yaml b/x-pack/plugins/actions/docs/openapi/components/parameters/connector_id.yaml index fdf1487e626a8..d98c2cec803ff 100644 --- a/x-pack/plugins/actions/docs/openapi/components/parameters/connector_id.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/parameters/connector_id.yaml @@ -4,5 +4,4 @@ description: An identifier for the connector. required: true schema: type: string - examples: - - df770e30-8b8b-11ed-a780-3b746c987a81 + example: df770e30-8b8b-11ed-a780-3b746c987a81 diff --git a/x-pack/plugins/actions/docs/openapi/components/parameters/space_id.yaml b/x-pack/plugins/actions/docs/openapi/components/parameters/space_id.yaml index 45787e844caec..0a9fba457e3e7 100644 --- a/x-pack/plugins/actions/docs/openapi/components/parameters/space_id.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/parameters/space_id.yaml @@ -4,5 +4,4 @@ description: An identifier for the space. If `/s/` and the identifier are omitte required: true schema: type: string - examples: - - default + example: default diff --git a/x-pack/plugins/actions/docs/openapi/components/responses/400.yaml b/x-pack/plugins/actions/docs/openapi/components/responses/400.yaml index 4f8890737ff40..263623dd1fb4c 100644 --- a/x-pack/plugins/actions/docs/openapi/components/responses/400.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/responses/400.yaml @@ -6,13 +6,10 @@ content: properties: error: type: string - examples: - - Bad Request + example: Bad Request message: type: string - examples: - - "error validating action type config: [index]: expected value of type [string] but got [undefined]" + example: "error validating action type config: [index]: expected value of type [string] but got [undefined]" statusCode: type: integer - examples: - - 400 \ No newline at end of file + example: 400 \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/responses/401.yaml b/x-pack/plugins/actions/docs/openapi/components/responses/401.yaml index 78b77b3ab5f43..ff5cbfd4c2768 100644 --- a/x-pack/plugins/actions/docs/openapi/components/responses/401.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/responses/401.yaml @@ -7,15 +7,13 @@ content: properties: error: type: string - examples: - - Unauthorized + example: Unauthorized enum: - Unauthorized message: type: string statusCode: type: integer - examples: - - 401 + example: 401 enum: - 401 \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/responses/404.yaml b/x-pack/plugins/actions/docs/openapi/components/responses/404.yaml index d4cf816f5903c..56964c6015c85 100644 --- a/x-pack/plugins/actions/docs/openapi/components/responses/404.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/responses/404.yaml @@ -7,17 +7,14 @@ content: properties: error: type: string - examples: - - Not Found + example: Not Found enum: - Not Found message: type: string - examples: - - "Saved object [action/baf33fc0-920c-11ed-b36a-874bd1548a00] not found" + example: "Saved object [action/baf33fc0-920c-11ed-b36a-874bd1548a00] not found" statusCode: type: integer - examples: - - 404 + example: 404 enum: - 404 \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_cases_webhook.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_cases_webhook.yaml index a452a1fdfd060..b2a1ea8848ba7 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_cases_webhook.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_cases_webhook.yaml @@ -22,8 +22,7 @@ properties: connector. The JSON is validated once the Mustache variables have been placed when the REST method runs. Manually ensure that the JSON is valid, disregarding the Mustache variables, so the later validation will pass. - examples: - - '{"body": {{{case.comment}}}}' + example: '{"body": {{{case.comment}}}}' createCommentMethod: type: string description: > @@ -41,8 +40,7 @@ properties: You can use a variable to add the external system ID to the URL. If you are using the `xpack.actions.allowedHosts setting`, add the hostname to the allowed hosts. - examples: - - https://example.com/issue/{{{external.system.id}}}/comment + example: https://example.com/issue/{{{external.system.id}}}/comment createIncidentJson: type: string description: > @@ -54,8 +52,7 @@ properties: connector. The JSON is validated after the Mustache variables have been placed when REST method runs. Manually ensure that the JSON is valid to avoid future validation errors; disregard Mustache variables during your review. - examples: - - '{"fields": {"summary": {{{case.title}}},"description": {{{case.description}}},"labels": {{{case.tags}}}}}' + example: '{"fields": {"summary": {{{case.title}}},"description": {{{case.description}}},"labels": {{{case.tags}}}}}' createIncidentMethod: type: string description: > @@ -89,8 +86,7 @@ properties: variables have been placed when REST method runs. Manually ensure that the JSON is valid, disregarding the Mustache variables, so the later validation will pass. - examples: - - https://example.com/issue/{{{external.system.id}}} + example: https://example.com/issue/{{{external.system.id}}} hasAuth: type: boolean description: If true, a username and password for login type authentication must be provided. @@ -111,8 +107,7 @@ properties: connector. The JSON is validated after the Mustache variables have been placed when REST method runs. Manually ensure that the JSON is valid to avoid future validation errors; disregard Mustache variables during your review. - examples: - - '{"fields": {"summary": {{{case.title}}},"description": {{{case.description}}},"labels": {{{case.tags}}}}}' + example: '{"fields": {"summary": {{{case.title}}},"description": {{{case.description}}},"labels": {{{case.tags}}}}}' updateIncidentMethod: type: string description: > @@ -129,14 +124,12 @@ properties: The REST API URL to update the case by ID in the third-party system. You can use a variable to add the external system ID to the URL. If you are using the `xpack.actions.allowedHosts` setting, add the hostname to the allowed hosts. - examples: - - https://example.com/issue/{{{external.system.ID}}} + example: https://example.com/issue/{{{external.system.ID}}} viewIncidentUrl: type: string description: > The URL to view the case in the external system. You can use variables to add the external system ID or external system title to the URL. - examples: - - https://testing-jira.atlassian.net/browse/{{{external.system.title}}} + example: https://testing-jira.atlassian.net/browse/{{{external.system.title}}} diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_email.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_email.yaml index 202f4022a6fbd..6d3618e2bba27 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_email.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_email.yaml @@ -8,9 +8,8 @@ properties: description: > The client identifier, which is a part of OAuth 2.0 client credentials authentication, in GUID format. If `service` is `exchange_server`, this property is required. - type: - - "string" - - "null" + type: string + nullable: true from: description: > The from address for all emails sent by the connector. It must be specified in `user@host-name` format. @@ -28,9 +27,8 @@ properties: type: string oauthTokenUrl: # description: TBD - type: - - "string" - - "null" + type: string + nullable: true port: description: > The port to connect to on the service provider. @@ -57,6 +55,5 @@ properties: description: > The tenant identifier, which is part of OAuth 2.0 client credentials authentication, in GUID format. If `service` is `exchange_server`, this property is required. - type: - - "string" - - "null" \ No newline at end of file + type: string + nullable: true \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_gemini.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_gemini.yaml new file mode 100644 index 0000000000000..f68c43390143d --- /dev/null +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_gemini.yaml @@ -0,0 +1,21 @@ +title: Connector request properties for an Google Gemini connector +description: Defines properties for connectors when type is `.gemini`. +type: object +required: + - apiUrl + - gcpRegion + - gcpProjectID +properties: + apiUrl: + type: string + description: The Google Gemini request URL. + defaultModel: + type: string + description: The generative artificial intelligence model for Google Gemini to use. + default: gemini-1.5-pro-preview-0409 + gcpRegion: + type: string + description: The GCP region where the Vertex AI endpoint enabled. + gcpProjectID: + type: string + description: The Google ProjectID that has Vertex AI endpoint enabled. \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_index.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_index.yaml index f6d3af59b4937..6c335b166d20a 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_index.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_index.yaml @@ -7,9 +7,8 @@ properties: executionTimeField: description: A field that indicates when the document was indexed. default: null - type: - - "string" - - "null" + type: string + nullable: true index: description: The Elasticsearch index to be written to. type: string diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_pagerduty.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_pagerduty.yaml index bfbec7b46190b..562557f548ece 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_pagerduty.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_pagerduty.yaml @@ -4,8 +4,6 @@ type: object properties: apiUrl: description: The PagerDuty event URL. - type: - - "string" - - "null" - examples: - - https://events.pagerduty.com/v2/enqueue \ No newline at end of file + type: string + nullable: true + example: https://events.pagerduty.com/v2/enqueue \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_webhook.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_webhook.yaml index 601d410666576..bf073419a4e09 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_webhook.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_webhook.yaml @@ -4,10 +4,10 @@ type: object properties: authType: type: string + nullable: true enum: - webhook-authentication-basic - webhook-authentication-ssl - - "null" description: > The type of authentication to use: basic, SSL, or none. ca: @@ -27,9 +27,8 @@ properties: description: > If `true`, a user name and password must be provided for login type authentication. headers: - type: - - "object" - - "null" + type: object + nullable: true description: A set of key-value pairs sent as headers with the request. method: type: string diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_xmatters.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_xmatters.yaml index 3393c11ecd90d..350e96f3aa63d 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_xmatters.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/config_properties_xmatters.yaml @@ -6,9 +6,8 @@ properties: description: > The request URL for the Elastic Alerts trigger in xMatters. It is applicable only when `usesBasic` is `true`. - type: - - "string" - - "null" + type: string + nullable: true usesBasic: description: Specifies whether the connector uses HTTP basic authentication (`true`) or URL authentication (`false`). type: boolean diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties_gemini.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties_gemini.yaml new file mode 100644 index 0000000000000..eb263e13e95ed --- /dev/null +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties_gemini.yaml @@ -0,0 +1,32 @@ +title: Connector response properties for a Google Gemini connector +type: object +required: + - connector_type_id + - id + - is_deprecated + - is_preconfigured + - name +properties: + config: + $ref: 'config_properties_gemini.yaml' + connector_type_id: + type: string + description: The type of connector. + enum: + - .gemini + id: + type: string + description: The identifier for the connector. + is_deprecated: + $ref: 'is_deprecated.yaml' + is_missing_secrets: + $ref: 'is_missing_secrets.yaml' + is_preconfigured: + $ref: 'is_preconfigured.yaml' + is_system_action: + $ref: 'is_system_action.yaml' + name: + type: string + description: The display name for the connector. + referenced_by_count: + $ref: 'referenced_by_count.yaml' diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties_serverlog.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties_serverlog.yaml index da741478864b4..a397e668102a6 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties_serverlog.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/connector_response_properties_serverlog.yaml @@ -8,9 +8,8 @@ required: - name properties: config: - type: - - "object" - - "null" + type: object + nullable: true connector_type_id: type: string description: The type of connector. diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/connector_types.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/connector_types.yaml index f202efc087b00..db6262f04c010 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/connector_types.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/connector_types.yaml @@ -26,5 +26,4 @@ enum: - .torq - .webhook - .xmatters -examples: - - .server-log \ No newline at end of file +example: .server-log \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_bedrock.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_bedrock.yaml index e8feecb0051cd..2acc21bfbfac7 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_bedrock.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_bedrock.yaml @@ -14,12 +14,10 @@ properties: description: The type of connector. enum: - .bedrock - examples: - - .bedrock + example: .bedrock name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_bedrock.yaml' diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_cases_webhook.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_cases_webhook.yaml index 0cd030d740809..bcbe840c03513 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_cases_webhook.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_cases_webhook.yaml @@ -15,12 +15,10 @@ properties: description: The type of connector. enum: - .cases-webhook - examples: - - .cases-webhook + example: .cases-webhook name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_cases_webhook.yaml' \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_d3security.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_d3security.yaml index 6b5389cc80f31..39cdda80b7dd2 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_d3security.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_d3security.yaml @@ -15,12 +15,10 @@ properties: description: The type of connector. enum: - .d3security - examples: - - .d3security + example: .d3security name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_d3security.yaml' diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_email.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_email.yaml index 1f1c6c079770a..89f0b79c4e74b 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_email.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_email.yaml @@ -18,12 +18,10 @@ properties: description: The type of connector. enum: - .email - examples: - - .email + example: .email name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_email.yaml' \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_gemini.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_gemini.yaml new file mode 100644 index 0000000000000..5b9cc31ae3787 --- /dev/null +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_gemini.yaml @@ -0,0 +1,23 @@ +title: Create Google Gemini connector request +description: The Google Gemini connector uses axios to send a POST request to Google Gemini. +type: object +required: + - config + - connector_type_id + - name + - secrets +properties: + config: + $ref: 'config_properties_gemini.yaml' + connector_type_id: + type: string + description: The type of connector. + enum: + - .gemini + example: .gemini + name: + type: string + description: The display name for the connector. + example: my-connector + secrets: + $ref: 'secrets_properties_gemini.yaml' diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_genai.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_genai.yaml index 725f842f91093..95d65bdb80919 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_genai.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_genai.yaml @@ -16,12 +16,10 @@ properties: description: The type of connector. enum: - .gen-ai - examples: - - .gen-ai + example: .gen-ai name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_genai.yaml' diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_index.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_index.yaml index ad8e9be9a41dc..26d6e118c1fe8 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_index.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_index.yaml @@ -13,10 +13,8 @@ properties: description: The type of connector. enum: - .index - examples: - - .index + example: .index name: type: string description: The display name for the connector. - examples: - - my-connector \ No newline at end of file + example: my-connector \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_jira.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_jira.yaml index 95ccaa5b2ec6f..5b6077e875b24 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_jira.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_jira.yaml @@ -14,12 +14,10 @@ properties: description: The type of connector. enum: - .jira - examples: - - .jira + example: .jira name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_jira.yaml' \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_opsgenie.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_opsgenie.yaml index 51c29f5cdd8fd..6de1296dac43c 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_opsgenie.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_opsgenie.yaml @@ -14,12 +14,10 @@ properties: description: The type of connector. enum: - .opsgenie - examples: - - .opsgenie + example: .opsgenie name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_opsgenie.yaml' \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_pagerduty.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_pagerduty.yaml index 66ffc61d30f30..498488299afd3 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_pagerduty.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_pagerduty.yaml @@ -16,12 +16,10 @@ properties: description: The type of connector. enum: - .pagerduty - examples: - - .pagerduty + example: .pagerduty name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_pagerduty.yaml' \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_resilient.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_resilient.yaml index 60467336c0d9a..c3f766625b7da 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_resilient.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_resilient.yaml @@ -12,14 +12,12 @@ properties: connector_type_id: description: The type of connector. type: string - examples: - - .resilient + example: .resilient enum: - .resilient name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_resilient.yaml' \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_sentinelone.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_sentinelone.yaml index d741f9b35af35..0d2809f24d78b 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_sentinelone.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_sentinelone.yaml @@ -18,12 +18,10 @@ properties: description: The type of connector. enum: - .sentinelone - examples: - - .sentinelone + example: .sentinelone name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_sentinelone.yaml' diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_serverlog.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_serverlog.yaml index 0cb85403663c6..eac0a0d65b69f 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_serverlog.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_serverlog.yaml @@ -10,10 +10,8 @@ properties: description: The type of connector. enum: - .server-log - examples: - - .server-log + example: .server-log name: type: string description: The display name for the connector. - examples: - - my-connector \ No newline at end of file + example: my-connector \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_servicenow.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_servicenow.yaml index b0f35483cc39f..e03303dcada4f 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_servicenow.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_servicenow.yaml @@ -16,12 +16,10 @@ properties: description: The type of connector. enum: - .servicenow - examples: - - .servicenow + example: .servicenow name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_servicenow.yaml' \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_servicenow_itom.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_servicenow_itom.yaml index bfbeb231ca7dc..70a4c05c96522 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_servicenow_itom.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_servicenow_itom.yaml @@ -16,12 +16,10 @@ properties: description: The type of connector. enum: - .servicenow-itom - examples: - - .servicenow-itom + example: .servicenow-itom name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_servicenow.yaml' \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_servicenow_sir.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_servicenow_sir.yaml index 37519eb63f27b..4d247c456f3e6 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_servicenow_sir.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_servicenow_sir.yaml @@ -16,12 +16,10 @@ properties: description: The type of connector. enum: - .servicenow-sir - examples: - - .servicenow-sir + example: .servicenow-sir name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_servicenow.yaml' \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_slack_api.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_slack_api.yaml index 2044087fba78c..3870f418606a2 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_slack_api.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_slack_api.yaml @@ -13,12 +13,10 @@ properties: description: The type of connector. enum: - .slack_api - examples: - - .slack_api + example: .slack_api name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_slack_api.yaml' diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_slack_webhook.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_slack_webhook.yaml index 3e884daa6e3b8..1c046cc3f000c 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_slack_webhook.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_slack_webhook.yaml @@ -11,12 +11,10 @@ properties: description: The type of connector. enum: - .slack - examples: - - .slack + example: .slack name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_slack_webhook.yaml' diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_swimlane.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_swimlane.yaml index 633438a721ee9..3de4f5ecbccef 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_swimlane.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_swimlane.yaml @@ -14,12 +14,10 @@ properties: description: The type of connector. enum: - .swimlane - examples: - - .swimlane + example: .swimlane name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_swimlane.yaml' \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_teams.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_teams.yaml index 787f057c09ce6..5e0d449bf5546 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_teams.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_teams.yaml @@ -11,12 +11,10 @@ properties: description: The type of connector. enum: - .teams - examples: - - .teams + example: .teams name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_teams.yaml' \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_tines.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_tines.yaml index c5333c8acc479..224c3e03c4363 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_tines.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_tines.yaml @@ -15,12 +15,10 @@ properties: description: The type of connector. enum: - .tines - examples: - - .tines + example: .tines name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_tines.yaml' \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_torq.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_torq.yaml index a4ab3cc92aa0d..934f9c9c1b395 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_torq.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_torq.yaml @@ -15,12 +15,10 @@ properties: description: The type of connector. enum: - .torq - examples: - - .torq + example: .torq name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_torq.yaml' \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_webhook.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_webhook.yaml index 30e9663da8d99..e0ead115d48dc 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_webhook.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_webhook.yaml @@ -15,12 +15,10 @@ properties: description: The type of connector. enum: - .webhook - examples: - - .webhook + example: .webhook name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_webhook.yaml' \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_xmatters.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_xmatters.yaml index 753888b16ae5e..13213d39561b2 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_xmatters.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/create_connector_request_xmatters.yaml @@ -16,12 +16,10 @@ properties: description: The type of connector. enum: - .xmatters - examples: - - .xmatters + example: .xmatters name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_xmatters.yaml' \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/features.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/features.yaml index e75b745957552..c17b4a68b594b 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/features.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/features.yaml @@ -4,6 +4,8 @@ description: > enum: - alerting - cases - - generativeAI + - generativeAIForSecurity + - generativeAIForObservability + - generativeAIForSearchPlayground - siem - uptime \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/is_deprecated.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/is_deprecated.yaml index ac0a26102eed1..75fb1efe2f589 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/is_deprecated.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/is_deprecated.yaml @@ -1,4 +1,3 @@ type: boolean description: Indicates whether the connector type is deprecated. -examples: - - false \ No newline at end of file +example: false \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/is_missing_secrets.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/is_missing_secrets.yaml index a7ad3f9542b3f..cad03a44f8629 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/is_missing_secrets.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/is_missing_secrets.yaml @@ -1,4 +1,3 @@ type: boolean description: Indicates whether secrets are missing for the connector. Secrets configuration properties vary depending on the connector type. -examples: - - false \ No newline at end of file +example: false \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/is_preconfigured.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/is_preconfigured.yaml index d3f711c229399..e38741c83718e 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/is_preconfigured.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/is_preconfigured.yaml @@ -2,5 +2,4 @@ type: boolean description: > Indicates whether it is a preconfigured connector. If true, the `config` and `is_missing_secrets` properties are omitted from the response. -examples: - - false \ No newline at end of file +example: false \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/is_system_action.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/is_system_action.yaml index 5a78f4676646f..fd0dd06ef5fff 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/is_system_action.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/is_system_action.yaml @@ -1,4 +1,3 @@ type: boolean description: Indicates whether the connector is used for system actions. -examples: - - false \ No newline at end of file +example: false \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/referenced_by_count.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/referenced_by_count.yaml index 0a65bf9a854ff..61579fa3dc6ce 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/referenced_by_count.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/referenced_by_count.yaml @@ -3,5 +3,4 @@ description: > Indicates the number of saved objects that reference the connector. If `is_preconfigured` is true, this value is not calculated. This property is returned only by the get all connectors API. -examples: - - 2 \ No newline at end of file +example: 2 \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_params_trigger_pagerduty.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_params_trigger_pagerduty.yaml index 75a59af156264..71410725cbeae 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_params_trigger_pagerduty.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_params_trigger_pagerduty.yaml @@ -7,13 +7,11 @@ properties: class: description: The class or type of the event. type: string - examples: - - cpu load + example: cpu load component: description: The component of the source machine that is responsible for the event. type: string - examples: - - eth0 + example: eth0 customDetails: description: Additional details to add to the event. type: object @@ -31,8 +29,7 @@ properties: group: description: The logical grouping of components of a service. type: string - examples: - - app-stack + example: app-stack links: description: A list of links to add to the event. type: array diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_closeincident.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_closeincident.yaml index a53c4f90b226e..82f9a97a60412 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_closeincident.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_closeincident.yaml @@ -22,13 +22,15 @@ properties: - required: [externalId] properties: correlation_id: - type: ['null', string] + type: string + nullable: true description: > An identifier that is assigned to the incident when it is created by the connector. NOTE: If you use the default value and the rule generates multiple alerts that use the same alert IDs, the latest open incident for this correlation ID is closed unless you specify the external ID. maxLength: 100 default: '{{rule.id}}:{{alert.id}}' externalId: - type: ['null', string] + type: string + nullable: true description: The unique identifier (`incidentId`) for the incident in ServiceNow. \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_createalert.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_createalert.yaml index a53560951361f..e739a9ed6c91d 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_createalert.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_createalert.yaml @@ -30,8 +30,7 @@ properties: type: object description: The custom properties of the alert. additionalProperties: true - examples: - - {"key1":"value1","key2":"value2"} + example: {"key1":"value1","key2":"value2"} entity: type: string description: The domain of the alert. For example, the application or server name. diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_fieldsbyissuetype.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_fieldsbyissuetype.yaml index 6c39957c29fc4..e8c8869e7d68b 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_fieldsbyissuetype.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_fieldsbyissuetype.yaml @@ -18,6 +18,5 @@ properties: id: type: string description: The Jira issue type identifier. - examples: - - 10024 + example: 10024 \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_getincident.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_getincident.yaml index 7a16f3d9f8295..666c0257f68b8 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_getincident.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_getincident.yaml @@ -18,5 +18,4 @@ properties: externalId: type: string description: The Jira, ServiceNow ITSM, or ServiceNow SecOps issue identifier. - examples: - - 71778 + example: 71778 diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_issue.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_issue.yaml index 3743e7fa90bd3..56ee923b40063 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_issue.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/run_connector_subaction_issue.yaml @@ -17,5 +17,4 @@ properties: id: type: string description: The Jira issue identifier. - examples: - - 71778 \ No newline at end of file + example: 71778 \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_gemini.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_gemini.yaml new file mode 100644 index 0000000000000..98e89e1ba373c --- /dev/null +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/secrets_properties_gemini.yaml @@ -0,0 +1,9 @@ +title: Connector secrets properties for a Google Gemini connector +description: Defines secrets for connectors when type is `.gemini`. +type: object +required: + - credentialsJSON +properties: + credentialsJSON: + type: string + description: The service account credentials JSON file. The service account should have Vertex AI user IAM role assigned to it. \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_cases_webhook.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_cases_webhook.yaml index 9201a1b1e1d70..66250b31a94eb 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_cases_webhook.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_cases_webhook.yaml @@ -9,7 +9,6 @@ properties: name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_cases_webhook.yaml' \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_gemini.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_gemini.yaml new file mode 100644 index 0000000000000..93aade3b39d11 --- /dev/null +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_gemini.yaml @@ -0,0 +1,13 @@ +title: Update Google Gemini connector request +type: object +required: + - config + - name +properties: + config: + $ref: 'config_properties_gemini.yaml' + name: + type: string + description: The display name for the connector. + secrets: + $ref: 'secrets_properties_gemini.yaml' diff --git a/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_swimlane.yaml b/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_swimlane.yaml index 771625841a042..81321351b74ec 100644 --- a/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_swimlane.yaml +++ b/x-pack/plugins/actions/docs/openapi/components/schemas/update_connector_request_swimlane.yaml @@ -10,7 +10,6 @@ properties: name: type: string description: The display name for the connector. - examples: - - my-connector + example: my-connector secrets: $ref: 'secrets_properties_swimlane.yaml' \ No newline at end of file diff --git a/x-pack/plugins/actions/docs/openapi/entrypoint.yaml b/x-pack/plugins/actions/docs/openapi/entrypoint.yaml index d082d91a2a4e5..ba61b00c3c737 100644 --- a/x-pack/plugins/actions/docs/openapi/entrypoint.yaml +++ b/x-pack/plugins/actions/docs/openapi/entrypoint.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.0 +openapi: 3.0.3 info: title: Connectors description: OpenAPI schema for Connectors endpoints @@ -14,16 +14,6 @@ tags: servers: - url: / paths: - '/api/actions/connector': - $ref: 'paths/api@actions@connector.yaml' - '/api/actions/connector/{connectorId}': - $ref: 'paths/api@actions@connector@{connectorid}.yaml' - '/api/actions/connector/{connectorId}/_execute': - $ref: paths/api@actions@connector@{connectorid}@_execute.yaml - '/api/actions/connectors': - $ref: paths/api@actions@connectors.yaml - '/api/actions/connector_types': - $ref: paths/api@actions@connector_types.yaml '/s/{spaceId}/api/actions/connector': $ref: 'paths/s@{spaceid}@api@actions@connector.yaml' '/s/{spaceId}/api/actions/connector/{connectorId}': @@ -34,6 +24,17 @@ paths: $ref: paths/s@{spaceid}@api@actions@connector_types.yaml '/s/{spaceId}/api/actions/connector/{connectorId}/_execute': $ref: paths/s@{spaceid}@api@actions@connector@{connectorid}@_execute.yaml +# Default space + '/api/actions/connector': + $ref: 'paths/api@actions@connector.yaml' + '/api/actions/connector/{connectorId}': + $ref: 'paths/api@actions@connector@{connectorid}.yaml' + '/api/actions/connector/{connectorId}/_execute': + $ref: paths/api@actions@connector@{connectorid}@_execute.yaml + '/api/actions/connectors': + $ref: paths/api@actions@connectors.yaml + '/api/actions/connector_types': + $ref: paths/api@actions@connector_types.yaml # Deprecated endpoints: '/s/{spaceId}/api/actions/action/{actionId}': $ref: 'paths/s@{spaceid}@api@actions@action@{actionid}.yaml' diff --git a/x-pack/plugins/actions/docs/openapi/entrypoint_serverless.yaml b/x-pack/plugins/actions/docs/openapi/entrypoint_serverless.yaml index 4780a65da6520..60cca6f18bc44 100644 --- a/x-pack/plugins/actions/docs/openapi/entrypoint_serverless.yaml +++ b/x-pack/plugins/actions/docs/openapi/entrypoint_serverless.yaml @@ -1,4 +1,4 @@ -openapi: 3.1.0 +openapi: 3.0.3 info: title: Connectors description: OpenAPI schema for connectors in Serverless projects @@ -31,6 +31,9 @@ components: type: apiKey in: header name: Authorization - description: 'e.g. Authorization: ApiKey base64AccessApiKey' + description: > + Serverless APIs support only key-based authentication. + You must create an API key and use the encoded value in the request header. + For example: 'Authorization: ApiKey base64AccessApiKey'. security: - apiKeyAuth: [] diff --git a/x-pack/plugins/actions/docs/openapi/paths/api@actions@connector.yaml b/x-pack/plugins/actions/docs/openapi/paths/api@actions@connector.yaml index c6634bb6ea532..5f82202bed534 100644 --- a/x-pack/plugins/actions/docs/openapi/paths/api@actions@connector.yaml +++ b/x-pack/plugins/actions/docs/openapi/paths/api@actions@connector.yaml @@ -1,5 +1,5 @@ post: - summary: Creates a connector. + summary: Create a connector operationId: createConnector tags: - connectors diff --git a/x-pack/plugins/actions/docs/openapi/paths/api@actions@connector@{connectorid}.yaml b/x-pack/plugins/actions/docs/openapi/paths/api@actions@connector@{connectorid}.yaml index 6464b9592436a..c9b4c269e6cc2 100644 --- a/x-pack/plugins/actions/docs/openapi/paths/api@actions@connector@{connectorid}.yaml +++ b/x-pack/plugins/actions/docs/openapi/paths/api@actions@connector@{connectorid}.yaml @@ -1,5 +1,5 @@ get: - summary: Retrieves a connector by ID. + summary: Get a connector information operationId: getConnector tags: - connectors @@ -21,7 +21,7 @@ get: $ref: '../components/responses/404.yaml' delete: - summary: Deletes a connector. + summary: Delete a connector operationId: deleteConnector tags: - connectors @@ -37,7 +37,7 @@ delete: $ref: '../components/responses/404.yaml' post: - summary: Creates a connector. + summary: Create a connector operationId: createConnectorId tags: - connectors @@ -51,8 +51,7 @@ post: required: true schema: type: string - examples: - - ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74 + example: ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74 requestBody: required: true content: @@ -76,7 +75,7 @@ post: $ref: '../components/responses/401.yaml' put: - summary: Updates the attributes for a connector. + summary: Update a connector operationId: updateConnector tags: - connectors diff --git a/x-pack/plugins/actions/docs/openapi/paths/api@actions@connector@{connectorid}@_execute.yaml b/x-pack/plugins/actions/docs/openapi/paths/api@actions@connector@{connectorid}@_execute.yaml index 6d4de54cda3bc..93037c6d21779 100644 --- a/x-pack/plugins/actions/docs/openapi/paths/api@actions@connector@{connectorid}@_execute.yaml +++ b/x-pack/plugins/actions/docs/openapi/paths/api@actions@connector@{connectorid}@_execute.yaml @@ -1,5 +1,5 @@ post: - summary: Runs a connector. + summary: Run a connector operationId: runConnector description: > You can use this API to test an action that involves interaction with Kibana services or integrations with third-party systems. diff --git a/x-pack/plugins/actions/docs/openapi/paths/api@actions@connector_types.yaml b/x-pack/plugins/actions/docs/openapi/paths/api@actions@connector_types.yaml index 94dbb727eea4a..3284d79cd9a49 100644 --- a/x-pack/plugins/actions/docs/openapi/paths/api@actions@connector_types.yaml +++ b/x-pack/plugins/actions/docs/openapi/paths/api@actions@connector_types.yaml @@ -1,5 +1,5 @@ get: - summary: Retrieves a list of all connector types. + summary: Get all connector types operationId: getConnectorTypes tags: - connectors @@ -24,41 +24,34 @@ get: enabled: type: boolean description: Indicates whether the connector type is enabled in Kibana. - examples: - - true + example: true enabled_in_config: type: boolean description: Indicates whether the connector type is enabled in the Kibana configuration file. - examples: - - true + example: true enabled_in_license: type: boolean description: Indicates whether the connector is enabled in the license. - examples: - - true + example: true id: $ref: '../components/schemas/connector_types.yaml' is_system_action_type: type: boolean - examples: - - false + example: false minimum_license_required: type: string description: The license that is required to use the connector type. - examples: - - basic + example: basic name: type: string description: The name of the connector type. - examples: - - Index + example: Index supported_feature_ids: type: array description: The features that are supported by the connector type. items: $ref: '../components/schemas/features.yaml' - examples: - - [alerting, cases, siem] + example: [alerting, cases, siem] examples: getConnectorTypesServerlessResponse: $ref: '../components/examples/get_connector_types_generativeai_response.yaml' diff --git a/x-pack/plugins/actions/docs/openapi/paths/api@actions@connectors.yaml b/x-pack/plugins/actions/docs/openapi/paths/api@actions@connectors.yaml index 82b83651644e7..b94954173e22e 100644 --- a/x-pack/plugins/actions/docs/openapi/paths/api@actions@connectors.yaml +++ b/x-pack/plugins/actions/docs/openapi/paths/api@actions@connectors.yaml @@ -1,5 +1,5 @@ get: - summary: Retrieves all connectors. + summary: Get all connectors operationId: getConnectors tags: - connectors diff --git a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions.yaml b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions.yaml index aad5ca61ac3be..94aa1f5c420a1 100644 --- a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions.yaml +++ b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions.yaml @@ -1,5 +1,5 @@ get: - summary: Retrieves all connectors. + summary: Get all connectors operationId: legacyGetConnectors deprecated: true description: Deprecated in 7.13.0. Use the get all connectors API instead. @@ -20,7 +20,7 @@ get: $ref: '../components/responses/401.yaml' post: - summary: Creates a connector. + summary: Create a connector operationId: legacyCreateConnector deprecated: true description: Deprecated in 7.13.0. Use the create connector API instead. diff --git a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@action@{actionid}.yaml b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@action@{actionid}.yaml index dcf7a2759a35a..f2a5f62516aba 100644 --- a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@action@{actionid}.yaml +++ b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@action@{actionid}.yaml @@ -1,5 +1,5 @@ delete: - summary: Deletes a connector. + summary: Delete a connector operationId: legacyDeleteConnector deprecated: true description: > @@ -18,7 +18,7 @@ delete: $ref: '../components/responses/401.yaml' get: - summary: Retrieves a connector by ID. + summary: Get connector information operationId: legacyGetConnector description: Deprecated in 7.13.0. Use the get connector API instead. deprecated: true @@ -34,7 +34,7 @@ get: $ref: '../components/responses/401.yaml' put: - summary: Updates the attributes for a connector. + summary: Update a connector operationId: legacyUpdateConnector deprecated: true description: Deprecated in 7.13.0. Use the update connector API instead. diff --git a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@action@{actionid}@_execute.yaml b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@action@{actionid}@_execute.yaml index 9fe5cedda84de..1d1db3f341e52 100644 --- a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@action@{actionid}@_execute.yaml +++ b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@action@{actionid}@_execute.yaml @@ -1,5 +1,5 @@ post: - summary: Runs a connector. + summary: Run a connector operationId: legacyRunConnector deprecated: true description: Deprecated in 7.13.0. Use the run connector API instead. diff --git a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector.yaml b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector.yaml index f116a699ed868..749647e8b9ce5 100644 --- a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector.yaml +++ b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector.yaml @@ -1,5 +1,5 @@ post: - summary: Creates a connector. + summary: Create a connector operationId: createConnectorWithSpaceId description: > You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. diff --git a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector@{connectorid}.yaml b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector@{connectorid}.yaml index 27351f0954eee..d4511a486c0e1 100644 --- a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector@{connectorid}.yaml +++ b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector@{connectorid}.yaml @@ -1,5 +1,5 @@ get: - summary: Retrieves a connector by ID. + summary: Get connector information operationId: getConnectorWithSpaceId description: > You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. @@ -24,7 +24,7 @@ get: $ref: '../components/responses/404.yaml' delete: - summary: Deletes a connector. + summary: Delete a connector operationId: deleteConnectorWithSpaceId description: > You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. @@ -44,7 +44,7 @@ delete: $ref: '../components/responses/404.yaml' post: - summary: Creates a connector. + summary: Create a connector operationId: createConnectorIdWithSpaceId description: > You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. @@ -59,8 +59,7 @@ post: required: true schema: type: string - examples: - - ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74 + example: ac4e6b90-6be7-11eb-ba0d-9b1c1f912d74 requestBody: required: true content: @@ -84,7 +83,7 @@ post: $ref: '../components/responses/401.yaml' put: - summary: Updates the attributes for a connector. + summary: Update a connector operationId: updateConnectorWithSpaceId description: > You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. diff --git a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector@{connectorid}@_execute.yaml b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector@{connectorid}@_execute.yaml index 507194f31c380..76a30f54bab80 100644 --- a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector@{connectorid}@_execute.yaml +++ b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector@{connectorid}@_execute.yaml @@ -1,5 +1,5 @@ post: - summary: Runs a connector. + summary: Run a connector operationId: runConnectorWithSpaceId description: > You can use this API to test an action that involves interaction with Kibana services or integrations with third-party systems. diff --git a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector_types.yaml b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector_types.yaml index 9a0fababdf166..88fbc1612e507 100644 --- a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector_types.yaml +++ b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connector_types.yaml @@ -1,5 +1,5 @@ get: - summary: Retrieves a list of all connector types. + summary: Get all connector types operationId: getConnectorTypesWithSpaceId description: > You do not need any Kibana feature privileges to run this API. @@ -27,37 +27,31 @@ get: enabled: type: boolean description: Indicates whether the connector type is enabled in Kibana. - examples: - - true + example: true enabled_in_config: type: boolean description: Indicates whether the connector type is enabled in the Kibana `.yml` file. - examples: - - true + example: true enabled_in_license: type: boolean description: Indicates whether the connector is enabled in the license. - examples: - - true + example: true id: $ref: '../components/schemas/connector_types.yaml' minimum_license_required: type: string description: The license that is required to use the connector type. - examples: - - basic + example: basic name: type: string description: The name of the connector type. - examples: - - Index + example: Index supported_feature_ids: type: array description: The Kibana features that are supported by the connector type. items: $ref: '../components/schemas/features.yaml' - examples: - - [alerting, uptime, siem] + example: [alerting, uptime, siem] examples: getConnectorTypesResponse: $ref: '../components/examples/get_connector_types_response.yaml' diff --git a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connectors.yaml b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connectors.yaml index 7670187845710..3913919998e98 100644 --- a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connectors.yaml +++ b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@connectors.yaml @@ -1,5 +1,5 @@ get: - summary: Retrieves all connectors. + summary: Get all connectors operationId: getConnectorsWithSpaceId description: > You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. diff --git a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@list_action_types.yaml b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@list_action_types.yaml index 6bc2b9e5e53a8..daea4ed5219eb 100644 --- a/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@list_action_types.yaml +++ b/x-pack/plugins/actions/docs/openapi/paths/s@{spaceid}@api@actions@list_action_types.yaml @@ -1,5 +1,5 @@ get: - summary: Retrieves a list of all connector types. + summary: Get connector types operationId: legacyGetConnectorTypes deprecated: true description: Deprecated in 7.13.0. Use the get all connector types API instead. @@ -28,8 +28,7 @@ get: enabledInLicense: type: boolean description: Indicates whether the connector is enabled in the license. - examples: - - true + example: true id: type: string description: The unique identifier for the connector type. diff --git a/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap b/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap index 10f8265b15cc3..c9c525272a391 100644 --- a/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap +++ b/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap @@ -212,6 +212,19 @@ Object { "presence": "optional", }, "keys": Object { + "maxTokens": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], + "type": "number", + }, "messages": Object { "flags": Object { "error": [Function], @@ -399,6 +412,19 @@ Object { "presence": "optional", }, "keys": Object { + "maxTokens": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], + "type": "number", + }, "messages": Object { "flags": Object { "error": [Function], @@ -1113,6 +1139,117 @@ Object { "presence": "optional", }, "keys": Object { + "authType": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "allow": Array [ + "webhook-authentication-basic", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "webhook-authentication-ssl", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], + "type": "alternatives", + }, + "ca": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "certType": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "allow": Array [ + "ssl-crt-key", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "ssl-pfx", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], + "type": "alternatives", + }, "createCommentJson": Object { "flags": Object { "default": null, @@ -1373,7 +1510,7 @@ Object { }, "headers": Object { "flags": Object { - "default": [Function], + "default": null, "error": [Function], "presence": "optional", }, @@ -1515,6 +1652,57 @@ Object { ], "type": "string", }, + "verificationMode": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "allow": Array [ + "none", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "certificate", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + Object { + "schema": Object { + "allow": Array [ + "full", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], + "type": "alternatives", + }, "viewIncidentUrl": Object { "flags": Object { "error": [Function], @@ -1549,7 +1737,7 @@ Object { "presence": "optional", }, "keys": Object { - "password": Object { + "crt": Object { "flags": Object { "default": null, "error": [Function], @@ -1587,7 +1775,7 @@ Object { ], "type": "alternatives", }, - "user": Object { + "key": Object { "flags": Object { "default": null, "error": [Function], @@ -1625,35 +1813,33 @@ Object { ], "type": "alternatives", }, - }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, - "type": "object", -} -`; - -exports[`Connector type config checks detect connector type changes for: .cases-webhook 3`] = ` -Object { - "flags": Object { - "error": [Function], - }, - "matches": Array [ - Object { - "schema": Object { - "flags": Object { - "default": Object { - "special": "deep", + "password": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", }, - "error": [Function], - "presence": "optional", }, - "keys": Object { - "subAction": Object { + Object { + "schema": Object { "allow": Array [ - "pushToService", + null, ], "flags": Object { "error": [Function], @@ -1661,7 +1847,131 @@ Object { }, "type": "any", }, - "subActionParams": Object { + }, + ], + "type": "alternatives", + }, + "pfx": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + "user": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "object", +} +`; + +exports[`Connector type config checks detect connector type changes for: .cases-webhook 3`] = ` +Object { + "flags": Object { + "error": [Function], + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "subAction": Object { + "allow": Array [ + "pushToService", + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + "subActionParams": Object { "flags": Object { "default": Object { "special": "deep", @@ -2995,21 +3305,349 @@ Object { ], "type": "string", }, - "signal": Object { + "signal": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "metas": Array [ + Object { + "x-oas-any-type": true, + }, + Object { + "x-oas-optional": true, + }, + ], + "type": "any", + }, + "stopSequences": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], + "type": "array", + }, + "temperature": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], + "type": "number", + }, + "timeout": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], + "type": "number", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", +} +`; + +exports[`Connector type config checks detect connector type changes for: .gemini 2`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "dashboardId": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", +} +`; + +exports[`Connector type config checks detect connector type changes for: .gemini 3`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "body": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "model": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "signal": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "metas": Array [ + Object { + "x-oas-any-type": true, + }, + Object { + "x-oas-optional": true, + }, + ], + "type": "any", + }, + "stopSequences": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], + "type": "array", + }, + "temperature": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], + "type": "number", + }, + "timeout": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], + "type": "number", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", +} +`; + +exports[`Connector type config checks detect connector type changes for: .gemini 4`] = ` +Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "messages": Object { + "flags": Object { + "error": [Function], + }, + "metas": Array [ + Object { + "x-oas-any-type": true, + }, + ], + "type": "any", + }, + "model": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "signal": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "metas": Array [ + Object { + "x-oas-any-type": true, + }, + Object { + "x-oas-optional": true, + }, + ], + "type": "any", + }, + "stopSequences": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], + "type": "array", + }, + "temperature": Object { "flags": Object { "default": [Function], "error": [Function], "presence": "optional", }, "metas": Array [ - Object { - "x-oas-any-type": true, - }, Object { "x-oas-optional": true, }, ], - "type": "any", + "type": "number", }, "timeout": Object { "flags": Object { @@ -3034,7 +3672,7 @@ Object { } `; -exports[`Connector type config checks detect connector type changes for: .gemini 2`] = ` +exports[`Connector type config checks detect connector type changes for: .gemini 5`] = ` Object { "flags": Object { "default": Object { @@ -3044,44 +3682,28 @@ Object { "presence": "optional", }, "keys": Object { - "dashboardId": Object { + "messages": Object { "flags": Object { "error": [Function], }, - "rules": Array [ + "metas": Array [ Object { - "args": Object { - "method": [Function], - }, - "name": "custom", + "x-oas-any-type": true, }, ], - "type": "string", - }, - }, - "preferences": Object { - "stripUnknown": Object { - "objects": false, - }, - }, - "type": "object", -} -`; - -exports[`Connector type config checks detect connector type changes for: .gemini 3`] = ` -Object { - "flags": Object { - "default": Object { - "special": "deep", + "type": "any", }, - "error": [Function], - "presence": "optional", - }, - "keys": Object { - "body": Object { + "model": Object { "flags": Object { + "default": [Function], "error": [Function], + "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3092,42 +3714,64 @@ Object { ], "type": "string", }, - "model": Object { + "signal": Object { "flags": Object { "default": [Function], "error": [Function], "presence": "optional", }, "metas": Array [ + Object { + "x-oas-any-type": true, + }, Object { "x-oas-optional": true, }, ], - "rules": Array [ + "type": "any", + }, + "stopSequences": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "items": Array [ Object { - "args": Object { - "method": [Function], + "flags": Object { + "error": [Function], + "presence": "optional", }, - "name": "custom", + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", }, ], - "type": "string", + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], + "type": "array", }, - "signal": Object { + "temperature": Object { "flags": Object { "default": [Function], "error": [Function], "presence": "optional", }, "metas": Array [ - Object { - "x-oas-any-type": true, - }, Object { "x-oas-optional": true, }, ], - "type": "any", + "type": "number", }, "timeout": Object { "flags": Object { @@ -3152,7 +3796,7 @@ Object { } `; -exports[`Connector type config checks detect connector type changes for: .gemini 4`] = ` +exports[`Connector type config checks detect connector type changes for: .gemini 6`] = ` Object { "flags": Object { "default": Object { @@ -3230,7 +3874,7 @@ Object { } `; -exports[`Connector type config checks detect connector type changes for: .gemini 5`] = ` +exports[`Connector type config checks detect connector type changes for: .gemini 7`] = ` Object { "flags": Object { "default": Object { @@ -3264,7 +3908,7 @@ Object { } `; -exports[`Connector type config checks detect connector type changes for: .gemini 6`] = ` +exports[`Connector type config checks detect connector type changes for: .gemini 8`] = ` Object { "flags": Object { "default": Object { @@ -26462,6 +27106,85 @@ Object { "presence": "optional", }, "keys": Object { + "additional_fields": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "metas": Array [ + Object { + "x-oas-get-additional-properties": [Function], + }, + ], + "rules": Array [ + Object { + "args": Object { + "key": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "value": Object { + "flags": Object { + "error": [Function], + }, + "metas": Array [ + Object { + "x-oas-any-type": true, + }, + ], + "type": "any", + }, + }, + "name": "entries", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "record", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, "category": Object { "flags": Object { "default": null, @@ -28617,6 +29340,85 @@ Object { "presence": "optional", }, "keys": Object { + "additional_fields": Object { + "flags": Object { + "default": null, + "error": [Function], + "presence": "optional", + }, + "matches": Array [ + Object { + "schema": Object { + "flags": Object { + "error": [Function], + }, + "metas": Array [ + Object { + "x-oas-get-additional-properties": [Function], + }, + ], + "rules": Array [ + Object { + "args": Object { + "key": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "value": Object { + "flags": Object { + "error": [Function], + }, + "metas": Array [ + Object { + "x-oas-any-type": true, + }, + ], + "type": "any", + }, + }, + "name": "entries", + }, + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "record", + }, + }, + Object { + "schema": Object { + "allow": Array [ + null, + ], + "flags": Object { + "error": [Function], + "only": true, + }, + "type": "any", + }, + }, + ], + "type": "alternatives", + }, "category": Object { "flags": Object { "default": null, @@ -31997,7 +32799,7 @@ Object { }, "headers": Object { "flags": Object { - "default": [Function], + "default": null, "error": [Function], "presence": "optional", }, diff --git a/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.test.ts b/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.test.ts index c3624178046fc..d6c89a35ba7ff 100644 --- a/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.test.ts +++ b/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.test.ts @@ -256,6 +256,7 @@ describe('getGenAiTokenTracking', () => { }) ); }); + it('should return 0s for the total, prompt, and completion token counts when given an invalid OpenAI async iterator response', async () => { const actionTypeId = '.gen-ai'; const result = { @@ -360,6 +361,37 @@ describe('getGenAiTokenTracking', () => { expect(logger.error).toHaveBeenCalled(); }); + it('should return the total, prompt, and completion token counts when given a valid Gemini streamed response', async () => { + const actionTypeId = '.gemini'; + const result = { + actionId: '123', + status: 'ok' as const, + data: { + usageMetadata: { + promptTokenCount: 50, + candidatesTokenCount: 50, + totalTokenCount: 100, + }, + }, + }; + const validatedParams = { + subAction: 'invokeStream', + }; + + const tokenTracking = await getGenAiTokenTracking({ + actionTypeId, + logger, + result, + validatedParams, + }); + + expect(tokenTracking).toEqual({ + total_tokens: 100, + prompt_tokens: 50, + completion_tokens: 50, + }); + }); + describe('shouldTrackGenAiToken', () => { it('should be true with OpenAI action', () => { expect(shouldTrackGenAiToken('.gen-ai')).toEqual(true); @@ -367,7 +399,7 @@ describe('getGenAiTokenTracking', () => { it('should be true with bedrock action', () => { expect(shouldTrackGenAiToken('.bedrock')).toEqual(true); }); - it('should be true with Gemini action', () => { + it('should be true with gemini action', () => { expect(shouldTrackGenAiToken('.gemini')).toEqual(true); }); it('should be false with any other action', () => { diff --git a/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.ts b/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.ts index 15c5b234d24bc..41bfa28605f40 100644 --- a/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.ts +++ b/x-pack/plugins/actions/server/lib/gen_ai_token_tracking.ts @@ -16,7 +16,11 @@ import { import { getTokenCountFromBedrockInvoke } from './get_token_count_from_bedrock_invoke'; import { ActionTypeExecutorRawResult } from '../../common'; import { getTokenCountFromOpenAIStream } from './get_token_count_from_openai_stream'; -import { getTokenCountFromInvokeStream, InvokeBody } from './get_token_count_from_invoke_stream'; +import { + getTokenCountFromInvokeStream, + InvokeBody, + parseGeminiStreamForUsageMetadata, +} from './get_token_count_from_invoke_stream'; interface OwnProps { actionTypeId: string; @@ -80,8 +84,37 @@ export const getGenAiTokenTracking = async ({ } } + // this is a streamed Gemini response, using the subAction invokeStream to stream the response as a simple string + if ( + validatedParams.subAction === 'invokeStream' && + result.data instanceof Readable && + actionTypeId === '.gemini' + ) { + try { + const { totalTokenCount, promptTokenCount, candidatesTokenCount } = + await parseGeminiStreamForUsageMetadata({ + responseStream: result.data.pipe(new PassThrough()), + logger, + }); + + return { + total_tokens: totalTokenCount, + prompt_tokens: promptTokenCount, + completion_tokens: candidatesTokenCount, + }; + } catch (e) { + logger.error('Failed to calculate tokens from Invoke Stream subaction streaming response'); + logger.error(e); + // silently fail and null is returned at bottom of fuction + } + } + // this is a streamed OpenAI or Bedrock response, using the subAction invokeStream to stream the response as a simple string - if (validatedParams.subAction === 'invokeStream' && result.data instanceof Readable) { + if ( + validatedParams.subAction === 'invokeStream' && + result.data instanceof Readable && + actionTypeId !== '.gemini' + ) { try { const { total, prompt, completion } = await getTokenCountFromInvokeStream({ responseStream: result.data.pipe(new PassThrough()), diff --git a/x-pack/plugins/actions/server/lib/get_token_count_from_invoke_stream.test.ts b/x-pack/plugins/actions/server/lib/get_token_count_from_invoke_stream.test.ts index dad1785538b2c..3a2cabbb1b0e4 100644 --- a/x-pack/plugins/actions/server/lib/get_token_count_from_invoke_stream.test.ts +++ b/x-pack/plugins/actions/server/lib/get_token_count_from_invoke_stream.test.ts @@ -5,7 +5,10 @@ * 2.0. */ import { Transform } from 'stream'; -import { getTokenCountFromInvokeStream } from './get_token_count_from_invoke_stream'; +import { + getTokenCountFromInvokeStream, + parseGeminiStreamForUsageMetadata, +} from './get_token_count_from_invoke_stream'; import { loggerMock } from '@kbn/logging-mocks'; import { EventStreamCodec } from '@smithy/eventstream-codec'; import { fromUtf8, toUtf8 } from '@smithy/util-utf8'; @@ -57,8 +60,29 @@ describe('getTokenCountFromInvokeStream', () => { ], }; + const geminiChunk = { + candidates: [ + { + content: { + role: 'model', + parts: [ + { + text: '. I be no real-life pirate, but I be mighty good at pretendin!', + }, + ], + }, + }, + ], + usageMetadata: { + promptTokenCount: 23, + candidatesTokenCount: 50, + totalTokenCount: 73, + }, + }; + const PROMPT_TOKEN_COUNT = 34; const COMPLETION_TOKEN_COUNT = 2; + describe('OpenAI stream', () => { beforeEach(() => { stream = createStreamMock(); @@ -200,6 +224,24 @@ describe('getTokenCountFromInvokeStream', () => { }); }); }); + describe('Gemini stream', () => { + beforeEach(() => { + stream = createStreamMock(); + stream.write(`data: ${JSON.stringify(geminiChunk)}`); + }); + + it('counts the prompt, completion & total tokens for Gemini response', async () => { + stream.complete(); + const tokens = await parseGeminiStreamForUsageMetadata({ + responseStream: stream.transform, + logger, + }); + + expect(tokens.promptTokenCount).toBe(23); + expect(tokens.candidatesTokenCount).toBe(50); + expect(tokens.totalTokenCount).toBe(73); + }); + }); }); function encodeBedrockResponse(completion: string | Record) { diff --git a/x-pack/plugins/actions/server/lib/get_token_count_from_invoke_stream.ts b/x-pack/plugins/actions/server/lib/get_token_count_from_invoke_stream.ts index 7fd05aa8c500d..604e623699136 100644 --- a/x-pack/plugins/actions/server/lib/get_token_count_from_invoke_stream.ts +++ b/x-pack/plugins/actions/server/lib/get_token_count_from_invoke_stream.ts @@ -20,6 +20,12 @@ export interface InvokeBody { signal?: AbortSignal; } +interface UsageMetadata { + promptTokenCount: number; + candidatesTokenCount: number; + totalTokenCount: number; +} + /** * Takes the OpenAI and Bedrock `invokeStream` sub action response stream and the request messages array as inputs. * Uses gpt-tokenizer encoding to calculate the number of tokens in the prompt and completion parts of the response stream @@ -130,6 +136,64 @@ const parseOpenAIStream: StreamParser = async (responseStream, logger, signal) = return parseOpenAIResponse(responseBody); }; +export const parseGeminiStreamForUsageMetadata = async ({ + responseStream, + logger, +}: { + responseStream: Readable; + logger: Logger; +}): Promise => { + let responseBody = ''; + + const onData = (chunk: Buffer) => { + responseBody += chunk.toString(); + }; + + responseStream.on('data', onData); + + return new Promise((resolve, reject) => { + responseStream.on('end', () => { + resolve(parseGeminiUsageMetadata(responseBody)); + }); + responseStream.on('error', (err) => { + logger.error('An error occurred while calculating streaming response tokens'); + reject(err); + }); + }); +}; + +/** Parse Gemini stream response body */ +const parseGeminiUsageMetadata = (responseBody: string): UsageMetadata => { + const parsedLines = responseBody + .split('\n') + .filter((line) => line.startsWith('data: ') && !line.endsWith('[DONE]')) + .map((line) => JSON.parse(line.replace('data: ', ''))); + + parsedLines + .filter( + ( + line + ): line is { + candidates: Array<{ + content: { role: string; parts: Array<{ text: string }> }; + finishReason: string; + safetyRatings: Array<{ category: string; probability: string }>; + }>; + } => 'candidates' in line + ) + .reduce((prev, line) => { + const parts = line.candidates[0].content?.parts; + const chunkText = parts?.map((part) => part.text).join(''); + return prev + chunkText; + }, ''); + + // Extract usage metadata from the last chunk + const lastChunk = parsedLines[parsedLines.length - 1]; + const usageMetadata = 'usageMetadata' in lastChunk ? lastChunk.usageMetadata : null; + + return usageMetadata; +}; + /** * Parses a Bedrock buffer from an array of chunks. * diff --git a/x-pack/plugins/actions/server/saved_objects/index.ts b/x-pack/plugins/actions/server/saved_objects/index.ts index e39c3a4a8a383..a4d7886091fe5 100644 --- a/x-pack/plugins/actions/server/saved_objects/index.ts +++ b/x-pack/plugins/actions/server/saved_objects/index.ts @@ -13,6 +13,7 @@ import type { import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server'; import { getOldestIdleActionTask } from '@kbn/task-manager-plugin/server'; import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; +import { actionTaskParamsModelVersions } from './model_versions'; import { actionMappings, actionTaskParamsMappings, connectorTokenMappings } from './mappings'; import { getActionsMigrations } from './actions_migrations'; import { getActionTaskParamsMigrations } from './action_task_params_migrations'; @@ -25,6 +26,7 @@ import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, CONNECTOR_TOKEN_SAVED_OBJECT_TYPE, } from '../constants/saved_objects'; +import { connectorModelVersions } from './model_versions'; export function setupSavedObjects( savedObjects: SavedObjectsServiceSetup, @@ -60,6 +62,7 @@ export function setupSavedObjects( }; }, }, + modelVersions: connectorModelVersions, }); // Encrypted attributes @@ -94,6 +97,7 @@ export function setupSavedObjects( }, }; }, + modelVersions: actionTaskParamsModelVersions, }); encryptedSavedObjects.registerType({ type: ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, diff --git a/x-pack/plugins/actions/server/saved_objects/model_versions/action_task_params_model_versions.ts b/x-pack/plugins/actions/server/saved_objects/model_versions/action_task_params_model_versions.ts new file mode 100644 index 0000000000000..570ebff743c17 --- /dev/null +++ b/x-pack/plugins/actions/server/saved_objects/model_versions/action_task_params_model_versions.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 { SavedObjectsModelVersionMap } from '@kbn/core-saved-objects-server'; +import { actionTaskParamsSchemaV1 } from '../schemas/action_task_params'; + +export const actionTaskParamsModelVersions: SavedObjectsModelVersionMap = { + '1': { + changes: [], + schemas: { + create: actionTaskParamsSchemaV1, + }, + }, +}; diff --git a/x-pack/plugins/actions/server/saved_objects/model_versions/connector_model_versions.ts b/x-pack/plugins/actions/server/saved_objects/model_versions/connector_model_versions.ts new file mode 100644 index 0000000000000..03d305d26a086 --- /dev/null +++ b/x-pack/plugins/actions/server/saved_objects/model_versions/connector_model_versions.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 { SavedObjectsModelVersionMap } from '@kbn/core-saved-objects-server'; +import { rawConnectorSchemaV1 } from '../schemas/raw_connector'; + +export const connectorModelVersions: SavedObjectsModelVersionMap = { + '1': { + changes: [], + schemas: { + create: rawConnectorSchemaV1, + }, + }, +}; diff --git a/x-pack/plugins/actions/server/saved_objects/model_versions/index.ts b/x-pack/plugins/actions/server/saved_objects/model_versions/index.ts new file mode 100644 index 0000000000000..fdfc6adecd8e0 --- /dev/null +++ b/x-pack/plugins/actions/server/saved_objects/model_versions/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { connectorModelVersions } from './connector_model_versions'; +export { actionTaskParamsModelVersions } from './action_task_params_model_versions'; diff --git a/x-pack/plugins/maps/public/embeddable/index.ts b/x-pack/plugins/actions/server/saved_objects/schemas/action_task_params/index.ts similarity index 71% rename from x-pack/plugins/maps/public/embeddable/index.ts rename to x-pack/plugins/actions/server/saved_objects/schemas/action_task_params/index.ts index b73f9fb9de42a..4ce0e679adf5e 100644 --- a/x-pack/plugins/maps/public/embeddable/index.ts +++ b/x-pack/plugins/actions/server/saved_objects/schemas/action_task_params/index.ts @@ -5,6 +5,4 @@ * 2.0. */ -export * from './map_embeddable'; -export * from './types'; -export * from './map_embeddable_factory'; +export { actionTaskParamsSchema as actionTaskParamsSchemaV1 } from './v1'; diff --git a/x-pack/plugins/actions/server/saved_objects/schemas/action_task_params/v1.ts b/x-pack/plugins/actions/server/saved_objects/schemas/action_task_params/v1.ts new file mode 100644 index 0000000000000..ceef18ae2dd75 --- /dev/null +++ b/x-pack/plugins/actions/server/saved_objects/schemas/action_task_params/v1.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 { schema } from '@kbn/config-schema'; + +export const actionTaskParamsSchema = schema.object({ + actionId: schema.string(), + executionId: schema.maybe(schema.string()), + apiKey: schema.nullable(schema.string()), + params: schema.recordOf(schema.string(), schema.maybe(schema.any()), { defaultValue: {} }), + consumer: schema.maybe(schema.string()), + source: schema.maybe(schema.string()), + relatedSavedObjects: schema.maybe( + schema.arrayOf( + schema.object({ + namespace: schema.maybe(schema.string({ minLength: 1 })), + id: schema.string({ minLength: 1 }), + type: schema.string({ minLength: 1 }), + // optional; for SO types like action/alert that have type id's + typeId: schema.maybe(schema.string({ minLength: 1 })), + }), + { defaultValue: [] } + ) + ), +}); diff --git a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/index.ts b/x-pack/plugins/actions/server/saved_objects/schemas/raw_connector/index.ts similarity index 78% rename from x-pack/plugins/security_solution/public/detections/components/endpoint_responder/index.ts rename to x-pack/plugins/actions/server/saved_objects/schemas/raw_connector/index.ts index 5581755690e92..f86d616c03393 100644 --- a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/index.ts +++ b/x-pack/plugins/actions/server/saved_objects/schemas/raw_connector/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { useResponderActionItem } from './use_responder_action_item'; +export { rawConnectorSchema as rawConnectorSchemaV1 } from './v1'; diff --git a/x-pack/plugins/actions/server/raw_connector_schema.test.ts b/x-pack/plugins/actions/server/saved_objects/schemas/raw_connector/v1.test.ts similarity index 93% rename from x-pack/plugins/actions/server/raw_connector_schema.test.ts rename to x-pack/plugins/actions/server/saved_objects/schemas/raw_connector/v1.test.ts index 80c8ba0cfef7c..ed2fe6b3a371c 100644 --- a/x-pack/plugins/actions/server/raw_connector_schema.test.ts +++ b/x-pack/plugins/actions/server/saved_objects/schemas/raw_connector/v1.test.ts @@ -5,18 +5,18 @@ * 2.0. */ -import { rawConnectorSchema } from './raw_connector_schema'; +import { rawConnectorSchema } from './v1'; const action = { actionTypeId: '12345', name: 'test-action-name', - isMissingSecrets: true, + isMissingSecrets: false, config: { foo: 'bar', }, - secrets: { + secrets: JSON.stringify({ pass: 'foo', - }, + }), isPreconfigured: false, isSystemAction: false, }; diff --git a/x-pack/plugins/actions/server/raw_connector_schema.ts b/x-pack/plugins/actions/server/saved_objects/schemas/raw_connector/v1.ts similarity index 73% rename from x-pack/plugins/actions/server/raw_connector_schema.ts rename to x-pack/plugins/actions/server/saved_objects/schemas/raw_connector/v1.ts index e98b31aef177f..df1927972af89 100644 --- a/x-pack/plugins/actions/server/raw_connector_schema.ts +++ b/x-pack/plugins/actions/server/saved_objects/schemas/raw_connector/v1.ts @@ -11,8 +11,10 @@ export const rawConnectorSchema = schema.object({ actionTypeId: schema.string(), name: schema.string(), isMissingSecrets: schema.boolean(), - config: schema.recordOf(schema.string(), schema.any()), - secrets: schema.recordOf(schema.string(), schema.any()), + config: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), + // Encrypted fields are saved as string. Decrypted version of the 'secrets' field is an object + secrets: schema.string({ defaultValue: '{}' }), + isPreconfigured: schema.maybe(schema.boolean()), isSystemAction: schema.maybe(schema.boolean()), id: schema.maybe(schema.string()), 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 6ab69bf7ccd1c..c2716f0ec8af7 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 @@ -6,7 +6,7 @@ */ import type { SignificantItem } from '@kbn/ml-agg-utils'; -import type { GroupTableItem } from '@kbn/aiops-components'; +import type { GroupTableItem } from '@kbn/aiops-log-rate-analysis/state'; import { buildExtendedBaseFilterCriteria } from './build_extended_base_filter_criteria'; 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 9d77f68cfa15b..1c67031967ac7 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 @@ -14,7 +14,7 @@ import type { Query } from '@kbn/es-query'; import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import { buildBaseFilterCriteria } from '@kbn/ml-query-utils'; import { getCategoryQuery } from '@kbn/aiops-log-pattern-analysis/get_category_query'; -import type { GroupTableItem } from '@kbn/aiops-components'; +import type { GroupTableItem } from '@kbn/aiops-log-rate-analysis/state'; /* * Contains utility functions for building and processing queries. diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/fields_config.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/fields_config.tsx index 1f401c815b2a8..38b5620465a0d 100644 --- a/x-pack/plugins/aiops/public/components/change_point_detection/fields_config.tsx +++ b/x-pack/plugins/aiops/public/components/change_point_detection/fields_config.tsx @@ -567,7 +567,7 @@ const FieldPanel: FC = ({ )} iconType="boxesHorizontal" color="text" - onClick={setIsActionMenuOpen.bind(null, true)} + onClick={setIsActionMenuOpen.bind(null, !isActionMenuOpen)} /> } isOpen={isActionMenuOpen} diff --git a/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx b/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx index 6a13d705cc918..23caf21c39ee3 100644 --- a/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx +++ b/x-pack/plugins/aiops/public/components/document_count_content/document_count_content/document_count_content.tsx @@ -13,30 +13,18 @@ import type { RectAnnotationSpec, } from '@elastic/charts/dist/chart_types/xy_chart/utils/specs'; -import type { LogRateHistogramItem, WindowParameters } from '@kbn/aiops-log-rate-analysis'; -import { - DocumentCountChartWithAutoAnalysisStart, - type BrushSelectionUpdateHandler, -} from '@kbn/aiops-components'; +import { useAppSelector } from '@kbn/aiops-log-rate-analysis/state'; +import { DocumentCountChartRedux } from '@kbn/aiops-components'; import { useAiopsAppContext } from '../../../hooks/use_aiops_app_context'; -import type { DocumentCountStats } from '../../../get_document_stats'; import { TotalCountHeader } from '../total_count_header'; export interface DocumentCountContentProps { - brushSelectionUpdateHandler: BrushSelectionUpdateHandler; - documentCountStats?: DocumentCountStats; - documentCountStatsSplit?: DocumentCountStats; - documentCountStatsSplitLabel?: string; - isBrushCleared: boolean; - totalCount: number; - sampleProbability: number; /** Optional color override for the default bar color for charts */ barColorOverride?: string; /** Optional color override for the highlighted bar color for charts */ barHighlightColorOverride?: string; - windowParameters?: WindowParameters; baselineLabel?: string; deviationLabel?: string; barStyleAccessor?: BarStyleAccessor; @@ -45,77 +33,35 @@ export interface DocumentCountContentProps { } export const DocumentCountContent: FC = ({ - brushSelectionUpdateHandler, - documentCountStats, - documentCountStatsSplit, - documentCountStatsSplitLabel = '', - isBrushCleared, - totalCount, - sampleProbability, barColorOverride, barHighlightColorOverride, - windowParameters, ...docCountChartProps }) => { const { data, uiSettings, fieldFormats, charts } = useAiopsAppContext(); - const bucketTimestamps = Object.keys(documentCountStats?.buckets ?? {}).map((time) => +time); - const splitBucketTimestamps = Object.keys(documentCountStatsSplit?.buckets ?? {}).map( - (time) => +time - ); - const timeRangeEarliest = Math.min(...[...bucketTimestamps, ...splitBucketTimestamps]); - const timeRangeLatest = Math.max(...[...bucketTimestamps, ...splitBucketTimestamps]); + const { documentStats } = useAppSelector((s) => s.logRateAnalysis); + const { sampleProbability, totalCount, documentCountStats } = documentStats; - if ( - documentCountStats === undefined || - documentCountStats.buckets === undefined || - timeRangeEarliest === undefined || - timeRangeLatest === undefined - ) { + if (documentCountStats === undefined) { return totalCount !== undefined ? ( ) : null; } - const chartPoints: LogRateHistogramItem[] = Object.entries(documentCountStats.buckets).map( - ([time, value]) => ({ - time: +time, - value, - }) - ); - - let chartPointsSplit: LogRateHistogramItem[] | undefined; - if (documentCountStatsSplit?.buckets !== undefined) { - chartPointsSplit = Object.entries(documentCountStatsSplit?.buckets).map(([time, value]) => ({ - time: +time, - value, - })); - } - return ( - {documentCountStats.interval !== undefined && ( - - - - )} + + + ); }; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/document_count_chart.tsx b/x-pack/plugins/aiops/public/components/log_categorization/document_count_chart.tsx index d6007b96f11d9..55a4ff9bf50ea 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/document_count_chart.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/document_count_chart.tsx @@ -11,9 +11,9 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { DocumentCountChart as DocumentCountChartRoot } from '@kbn/aiops-components'; import type { Category } from '@kbn/aiops-log-pattern-analysis/types'; +import type { DocumentCountStats } from '@kbn/aiops-log-rate-analysis/types'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; -import type { DocumentCountStats } from '../../get_document_stats'; import { TotalCountHeader } from '../document_count_content/total_count_header'; diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx index ca06d3c7c9b37..86b04382defd6 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_app_state.tsx @@ -16,8 +16,8 @@ import { UrlStateProvider } from '@kbn/ml-url-state'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; +import { LogRateAnalysisReduxProvider } from '@kbn/aiops-log-rate-analysis/state'; -import { LogRateAnalysisStateProvider } from '@kbn/aiops-components'; import type { AiopsAppDependencies } from '../../hooks/use_aiops_app_context'; import { AiopsAppContext } from '../../hooks/use_aiops_app_context'; import { DataSourceContext } from '../../hooks/use_data_source'; @@ -38,8 +38,6 @@ export interface LogRateAnalysisAppStateProps { savedSearch: SavedSearch | null; /** App dependencies */ appDependencies: AiopsAppDependencies; - /** Option to make main histogram sticky */ - stickyHistogram?: boolean; /** Optional flag to indicate whether kibana is running in serverless */ showFrozenDataTierChoice?: boolean; } @@ -48,7 +46,6 @@ export const LogRateAnalysisAppState: FC = ({ dataView, savedSearch, appDependencies, - stickyHistogram, showFrozenDataTierChoice = true, }) => { if (!dataView) return null; @@ -69,13 +66,13 @@ export const LogRateAnalysisAppState: FC = ({ - + - + - + 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 f017011596473..abeb4c603b4e6 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 @@ -6,14 +6,12 @@ */ import { isEqual } from 'lodash'; -import React, { useCallback, useEffect, useMemo, useRef, useState, type FC } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, type FC } from 'react'; import { EuiButton, EuiEmptyPrompt, EuiHorizontalRule, EuiPanel } from '@elastic/eui'; -import type { Moment } from 'moment'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { BarStyleAccessor } from '@elastic/charts/dist/chart_types/xy_chart/utils/specs'; -import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { getWindowParametersForTrigger, @@ -21,14 +19,16 @@ import { getSnappedWindowParameters, LOG_RATE_ANALYSIS_HIGHLIGHT_COLOR, LOG_RATE_ANALYSIS_TYPE, - type LogRateAnalysisType, type WindowParameters, } from '@kbn/aiops-log-rate-analysis'; -import type { SignificantItem } from '@kbn/ml-agg-utils'; -import { useLogRateAnalysisStateContext, type GroupTableItem } from '@kbn/aiops-components'; - -import { useData } from '../../../hooks/use_data'; -import { useDataSource } from '../../../hooks/use_data_source'; +import { + clearAllRowState, + clearSelection, + setAutoRunAnalysis, + setInitialAnalysisStart, + useAppDispatch, + useAppSelector, +} from '@kbn/aiops-log-rate-analysis/state'; import { DocumentCountContent } from '../../document_count_content/document_count_content'; import { @@ -36,7 +36,7 @@ import { type LogRateAnalysisResultsData, } from '../log_rate_analysis_results'; -const DEFAULT_SEARCH_QUERY: estypes.QueryDslQueryContainer = { match_all: {} }; +export const DEFAULT_SEARCH_QUERY: estypes.QueryDslQueryContainer = { match_all: {} }; const DEFAULT_SEARCH_BAR_QUERY: estypes.QueryDslQueryContainer = { bool: { filter: [], @@ -49,26 +49,9 @@ const DEFAULT_SEARCH_BAR_QUERY: estypes.QueryDslQueryContainer = { }, }; -export function getDocumentCountStatsSplitLabel( - significantItem?: SignificantItem, - group?: GroupTableItem -) { - if (significantItem) { - return `${significantItem?.fieldName}:${significantItem?.fieldValue}`; - } else if (group) { - return i18n.translate('xpack.aiops.logRateAnalysis.page.documentCountStatsSplitGroupLabel', { - defaultMessage: 'Selected group', - }); - } -} - export interface LogRateAnalysisContentProps { - /** Optional time range override */ - timeRange?: { min: Moment; max: Moment }; /** Elasticsearch query to pass to analysis endpoint */ esSearchQuery?: estypes.QueryDslQueryContainer; - /** Option to make the main histogram sticky */ - stickyHistogram?: boolean; /** Optional color override for the default bar color for charts */ barColorOverride?: string; /** Optional color override for the highlighted bar color for charts */ @@ -82,26 +65,23 @@ export interface LogRateAnalysisContentProps { } export const LogRateAnalysisContent: FC = ({ - timeRange, esSearchQuery = DEFAULT_SEARCH_QUERY, - stickyHistogram, barColorOverride, barHighlightColorOverride, onAnalysisCompleted, onWindowParametersChange, embeddingOrigin, }) => { - const { dataView } = useDataSource(); + const dispatch = useAppDispatch(); - const [windowParameters, setWindowParameters] = useState(); - const [isBrushCleared, setIsBrushCleared] = useState(true); - const [logRateAnalysisType, setLogRateAnalysisType] = useState( - LOG_RATE_ANALYSIS_TYPE.SPIKE + const isRunning = useAppSelector((s) => s.logRateAnalysisStream.isRunning); + const significantItems = useAppSelector((s) => s.logRateAnalysisResults.significantItems); + const significantItemsGroups = useAppSelector( + (s) => s.logRateAnalysisResults.significantItemsGroups ); - - useEffect(() => { - setIsBrushCleared(windowParameters === undefined); - }, [windowParameters]); + const loaded = useAppSelector((s) => s.logRateAnalysisResults.loaded); + const analysisType = useAppSelector((s) => s.logRateAnalysis.analysisType); + const windowParameters = useAppSelector((s) => s.logRateAnalysis.windowParameters); // Window parameters stored in the url state use this components // `initialAnalysisStart` prop to set the initial params restore from url state. @@ -132,55 +112,15 @@ export const LogRateAnalysisContent: FC = ({ [esSearchQuery] ); - const { - autoRunAnalysis, - currentSelectedSignificantItem, - currentSelectedGroup, - setAutoRunAnalysis, - setInitialAnalysisStart, - setPinnedSignificantItem, - setPinnedGroup, - setSelectedSignificantItem, - setSelectedGroup, - } = useLogRateAnalysisStateContext(); - - const { documentStats, earliest, latest } = useData( - dataView, - 'log_rate_analysis', - searchQuery, - undefined, - currentSelectedSignificantItem, - currentSelectedGroup, - undefined, - true, - timeRange + const { autoRunAnalysis, documentStats, earliest, latest, isBrushCleared } = useAppSelector( + (s) => s.logRateAnalysis ); - const { sampleProbability, totalCount, documentCountStats, documentCountStatsCompare } = - documentStats; + const { documentCountStats } = documentStats; - function brushSelectionUpdate( - windowParametersUpdate: WindowParameters, - force: boolean, - logRateAnalysisTypeUpdate: LogRateAnalysisType - ) { - if (!isBrushCleared || force) { - setWindowParameters(windowParametersUpdate); - } - if (force) { - setIsBrushCleared(false); - } - setLogRateAnalysisType(logRateAnalysisTypeUpdate); - } - - function clearSelection() { - setWindowParameters(undefined); - setPinnedSignificantItem(null); - setPinnedGroup(null); - setSelectedSignificantItem(null); - setSelectedGroup(null); - setIsBrushCleared(true); - setInitialAnalysisStart(undefined); + function clearSelectionHandler() { + dispatch(clearSelection()); + dispatch(clearAllRowState()); } const barStyle = { @@ -204,8 +144,8 @@ export const LogRateAnalysisContent: FC = ({ : undefined; const triggerAnalysisForManualSelection = useCallback(() => { - setAutoRunAnalysis(true); - }, [setAutoRunAnalysis]); + dispatch(setAutoRunAnalysis(true)); + }, [dispatch]); const triggerAnalysisForChangePoint = useCallback(() => { if (documentCountStats) { @@ -224,10 +164,21 @@ export const LogRateAnalysisContent: FC = ({ const wpSnap = getSnappedWindowParameters(wp, snapTimestamps); triggerAnalysisForManualSelection(); - setInitialAnalysisStart(wpSnap); + dispatch(setInitialAnalysisStart(wpSnap)); } } - }, [documentCountStats, setInitialAnalysisStart, triggerAnalysisForManualSelection]); + }, [documentCountStats, dispatch, triggerAnalysisForManualSelection]); + + useEffect(() => { + if (!isRunning && loaded === 1 && onAnalysisCompleted) { + onAnalysisCompleted({ + analysisType, + significantItems, + significantItemsGroups, + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isRunning, loaded]); const showDocumentCountContent = documentCountStats !== undefined; @@ -255,16 +206,6 @@ export const LogRateAnalysisContent: FC = ({ {showDocumentCountContent && ( = ({ {showLogRateAnalysisResults && ( )} @@ -315,7 +248,7 @@ export const LogRateAnalysisContent: FC = ({ {' '} clearSelection()} + onClick={() => clearSelectionHandler()} color="text" > = ({ data-test-subj="aiopsChangePointDetectedPrompt" /> )} - {showDefaultEmptyPrompt && ( + {showDocumentCountContent && showDefaultEmptyPrompt && ( - + - + - + diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_document_count_chart_data.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_document_count_chart_data.tsx new file mode 100644 index 0000000000000..7709c047fd6ae --- /dev/null +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_content/log_rate_analysis_document_count_chart_data.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { type FC, useEffect } from 'react'; +import type { Moment } from 'moment'; + +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +import { + useAppDispatch, + useCurrentSelectedSignificantItem, + useCurrentSelectedGroup, + setDocumentCountChartData, +} from '@kbn/aiops-log-rate-analysis/state'; + +import { DEFAULT_SEARCH_QUERY } from './log_rate_analysis_content'; + +import { useData } from '../../../hooks/use_data'; +import { useDataSource } from '../../../hooks/use_data_source'; + +export interface LogRateAnalysisDocumentcountChartDataProps { + /** Optional time range */ + timeRange?: { min: Moment; max: Moment }; + /** Optional Elasticsearch query to pass to analysis endpoint */ + esSearchQuery?: estypes.QueryDslQueryContainer; +} + +export const LogRateAnalysisDocumentCountChartData: FC< + LogRateAnalysisDocumentcountChartDataProps +> = ({ timeRange, esSearchQuery }) => { + const { dataView } = useDataSource(); + + const currentSelectedGroup = useCurrentSelectedGroup(); + const currentSelectedSignificantItem = useCurrentSelectedSignificantItem(); + const dispatch = useAppDispatch(); + + const { documentStats, earliest, latest, intervalMs } = useData( + dataView, + 'log_rate_analysis', + esSearchQuery ?? DEFAULT_SEARCH_QUERY, + undefined, + currentSelectedSignificantItem, + currentSelectedGroup, + undefined, + true, + timeRange + ); + + // TODO Since `useData` isn't just used within Log Rate Analysis, this is a bit of + // a workaround to pass the result on to the redux store. At least this ensures + // we now use `useData` only once across Log Rate Analysis! Originally `useData` + // was quite general, but over time it got quite some specific features used + // across Log Rate Analysis and Pattern Analysis. We discussed that we should + // split this up into more specific hooks. + useEffect(() => { + dispatch( + setDocumentCountChartData({ + earliest, + latest, + intervalMs, + documentStats, + }) + ); + }, [documentStats, dispatch, earliest, intervalMs, latest]); + + return null; +}; 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 f6a40f2540f65..fcb5fa7e3a87b 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 @@ -18,7 +18,13 @@ import { useUrlState, usePageUrlState } from '@kbn/ml-url-state'; import type { SearchQueryLanguage } from '@kbn/ml-query-utils'; import type { WindowParameters } from '@kbn/aiops-log-rate-analysis'; import { AIOPS_TELEMETRY_ID } from '@kbn/aiops-common/constants'; -import { useLogRateAnalysisStateContext } from '@kbn/aiops-components'; +import { + useAppDispatch, + useCurrentSelectedSignificantItem, + useCurrentSelectedGroup, + setInitialAnalysisStart, + setDocumentCountChartData, +} from '@kbn/aiops-log-rate-analysis/state'; import { useDataSource } from '../../hooks/use_data_source'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; @@ -35,16 +41,14 @@ import { SearchPanel } from '../search_panel'; import { PageHeader } from '../page_header'; import { LogRateAnalysisContent } from './log_rate_analysis_content/log_rate_analysis_content'; -interface Props { - stickyHistogram?: boolean; -} -export const LogRateAnalysisPage: FC = ({ stickyHistogram }) => { +export const LogRateAnalysisPage: FC = () => { const { data: dataService } = useAiopsAppContext(); const { dataView, savedSearch } = useDataSource(); - const { currentSelectedSignificantItem, currentSelectedGroup, setInitialAnalysisStart } = - useLogRateAnalysisStateContext(); + const currentSelectedGroup = useCurrentSelectedGroup(); + const currentSelectedSignificantItem = useCurrentSelectedSignificantItem(); + const dispatch = useAppDispatch(); const [stateFromUrl, setUrlState] = usePageUrlState( 'logRateAnalysis', @@ -89,7 +93,7 @@ export const LogRateAnalysisPage: FC = ({ stickyHistogram }) => { stateFromUrl ); - const { timefilter } = useData( + const { documentStats, timefilter, earliest, latest, intervalMs } = useData( dataView, 'log_rate_analysis', searchQuery, @@ -98,6 +102,23 @@ export const LogRateAnalysisPage: FC = ({ stickyHistogram }) => { currentSelectedGroup ); + // TODO Since `useData` isn't just used within Log Rate Analysis, this is a bit of + // a workaround to pass the result on to the redux store. At least this ensures + // we now use `useData` only once across Log Rate Analysis! Originally `useData` + // was quite general, but over time it got quite some specific features used + // across Log Rate Analysis and Pattern Analysis. We discussed that we should + // split this up into more specific hooks. + useEffect(() => { + dispatch( + setDocumentCountChartData({ + earliest, + latest, + intervalMs, + documentStats, + }) + ); + }, [documentStats, dispatch, earliest, intervalMs, latest]); + useEffect( // TODO: Consolidate this hook/function with the one in `x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx` function clearFiltersOnLeave() { @@ -144,7 +165,7 @@ export const LogRateAnalysisPage: FC = ({ stickyHistogram }) => { useEffect( () => { - setInitialAnalysisStart(appStateToWindowParameters(stateFromUrl.wp)); + dispatch(setInitialAnalysisStart(appStateToWindowParameters(stateFromUrl.wp))); }, // eslint-disable-next-line react-hooks/exhaustive-deps [] @@ -179,7 +200,6 @@ export const LogRateAnalysisPage: FC = ({ stickyHistogram }) => { embeddingOrigin={AIOPS_TELEMETRY_ID.AIOPS_DEFAULT_SOURCE} esSearchQuery={searchQuery} onWindowParametersChange={onWindowParametersHandler} - stickyHistogram={stickyHistogram} /> 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 8ff224b65f678..21f5db55fce6f 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 @@ -24,7 +24,12 @@ import { import { reportPerformanceMetricEvent } from '@kbn/ebt-tools'; import { ProgressControls } from '@kbn/aiops-components'; -import { useFetchStream } from '@kbn/ml-response-stream/client'; +import { cancelStream, startStream } from '@kbn/ml-response-stream/client'; +import { + clearAllRowState, + useAppDispatch, + useAppSelector, +} from '@kbn/aiops-log-rate-analysis/state'; import { LOG_RATE_ANALYSIS_TYPE, type LogRateAnalysisType, @@ -34,10 +39,8 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { SignificantItem, SignificantItemGroup } from '@kbn/ml-agg-utils'; import { AIOPS_TELEMETRY_ID } from '@kbn/aiops-common/constants'; -import { initialState, streamReducer } from '@kbn/aiops-log-rate-analysis/api/stream_reducer'; import type { AiopsLogRateAnalysisSchema } from '@kbn/aiops-log-rate-analysis/api/schema'; import type { AiopsLogRateAnalysisSchemaSignificantItem } from '@kbn/aiops-log-rate-analysis/api/schema_v2'; -import { useLogRateAnalysisStateContext } from '@kbn/aiops-components'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; import { useDataSource } from '../../hooks/use_data_source'; @@ -131,56 +134,45 @@ export interface LogRateAnalysisResultsData { * LogRateAnalysis props require a data view. */ interface LogRateAnalysisResultsProps { - /** The type of analysis, whether it's a spike or dip */ - analysisType?: LogRateAnalysisType; - /** Start timestamp filter */ - earliest: number; - /** End timestamp filter */ - latest: number; - isBrushCleared: boolean; - /** Option to make main histogram sticky */ - stickyHistogram?: boolean; /** Callback for resetting the analysis */ onReset: () => void; - /** Window parameters for the analysis */ - windowParameters: WindowParameters; /** The search query to be applied to the analysis as a filter */ searchQuery: estypes.QueryDslQueryContainer; - /** Sample probability to be applied to random sampler aggregations */ - sampleProbability: number; /** Optional color override for the default bar color for charts */ barColorOverride?: string; /** Optional color override for the highlighted bar color for charts */ barHighlightColorOverride?: string; - /** Optional callback that exposes data of the completed analysis */ - onAnalysisCompleted?: (d: LogRateAnalysisResultsData) => void; /** Identifier to indicate the plugin utilizing the component */ embeddingOrigin: string; } export const LogRateAnalysisResults: FC = ({ - analysisType = LOG_RATE_ANALYSIS_TYPE.SPIKE, - earliest, - isBrushCleared, - latest, - stickyHistogram, onReset, - windowParameters, searchQuery, - sampleProbability, barColorOverride, barHighlightColorOverride, - onAnalysisCompleted, embeddingOrigin, }) => { const { analytics, http } = useAiopsAppContext(); const { dataView } = useDataSource(); + const dispatch = useAppDispatch(); + const { + analysisType, + earliest, + latest, + windowParameters, + documentStats: { sampleProbability }, + stickyHistogram, + isBrushCleared, + } = useAppSelector((s) => s.logRateAnalysis); + const { isRunning, errors: streamErrors } = useAppSelector((s) => s.logRateAnalysisStream); + const data = useAppSelector((s) => s.logRateAnalysisResults); + // Store the performance metric's start time using a ref // to be able to track it across rerenders. const analysisStartTime = useRef(window.performance.now()); - - const { clearAllRowState } = useLogRateAnalysisStateContext(); + const abortCtrl = useRef(new AbortController()); const [currentAnalysisType, setCurrentAnalysisType] = useState(); const [currentAnalysisWindowParameters, setCurrentAnalysisWindowParameters] = useState< @@ -201,7 +193,7 @@ export const LogRateAnalysisResults: FC = ({ setGroupResults(optionId === resultsGroupedOnId); // When toggling the group switch, clear all row selections - clearAllRowState(); + dispatch(clearAllRowState()); }; const onFieldsFilterChange = (skippedFields: string[]) => { @@ -221,49 +213,18 @@ export const LogRateAnalysisResults: FC = ({ setSkippedColumns(columns); }; - const { - cancel, - start, - data, - isRunning, - errors: streamErrors, - } = useFetchStream, typeof streamReducer>( - http, - '/internal/aiops/log_rate_analysis', - '2', - { - start: earliest, - end: latest, - searchQuery: JSON.stringify(searchQuery), - // TODO Handle data view without time fields. - timeFieldName: dataView.timeFieldName ?? '', - index: dataView.getIndexPattern(), - grouping: true, - flushFix: true, - // If analysis type is `spike`, pass on window parameters as is, - // if it's `dip`, swap baseline and deviation. - ...(analysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE - ? windowParameters - : { - baselineMin: windowParameters.deviationMin, - baselineMax: windowParameters.deviationMax, - deviationMin: windowParameters.baselineMin, - deviationMax: windowParameters.baselineMax, - }), - overrides, - sampleProbability, - }, - { reducer: streamReducer, initialState }, - { [AIOPS_TELEMETRY_ID.AIOPS_ANALYSIS_RUN_ORIGIN]: embeddingOrigin } - ); - - const { significantItems, zeroDocsFallback } = data; + const { significantItems } = data; useEffect( () => setUniqueFieldNames(uniq(significantItems.map((d) => d.fieldName)).sort()), [significantItems] ); + function cancelHandler() { + abortCtrl.current.abort(); + dispatch(cancelStream()); + } + useEffect(() => { if (!isRunning) { const { loaded, remainingFieldCandidates, groupsMissing } = data; @@ -282,15 +243,6 @@ export const LogRateAnalysisResults: FC = ({ // Reset all overrides. setOverrides(undefined); - // If provided call the `onAnalysisCompleted` callback with the analysis results. - if (onAnalysisCompleted) { - onAnalysisCompleted({ - analysisType, - significantItems: data.significantItems, - significantItemsGroups: data.significantItemsGroups, - }); - } - // Track performance metric if (analysisStartTime.current !== undefined) { const analysisDuration = window.performance.now() - analysisStartTime.current; @@ -322,7 +274,7 @@ export const LogRateAnalysisResults: FC = ({ if (resetGroupButton) { setGroupResults(false); setToggleIdSelected(resultsGroupedOffId); - clearAllRowState(); + dispatch(clearAllRowState()); } setCurrentAnalysisType(analysisType); @@ -333,18 +285,67 @@ export const LogRateAnalysisResults: FC = ({ setShouldStart(true); } + const startParams = useMemo(() => { + if (!windowParameters) { + return undefined; + } + + return { + http, + endpoint: '/internal/aiops/log_rate_analysis', + apiVersion: '2', + abortCtrl, + body: { + start: earliest, + end: latest, + searchQuery: JSON.stringify(searchQuery), + // TODO Handle data view without time fields. + timeFieldName: dataView.timeFieldName ?? '', + index: dataView.getIndexPattern(), + grouping: true, + flushFix: true, + // If analysis type is `spike`, pass on window parameters as is, + // if it's `dip`, swap baseline and deviation. + ...(analysisType === LOG_RATE_ANALYSIS_TYPE.SPIKE + ? windowParameters + : { + baselineMin: windowParameters.deviationMin, + baselineMax: windowParameters.deviationMax, + deviationMin: windowParameters.baselineMin, + deviationMax: windowParameters.baselineMax, + }), + overrides, + sampleProbability, + }, + headers: { [AIOPS_TELEMETRY_ID.AIOPS_ANALYSIS_RUN_ORIGIN]: embeddingOrigin }, + }; + }, [ + analysisType, + earliest, + latest, + http, + searchQuery, + dataView, + windowParameters, + sampleProbability, + overrides, + embeddingOrigin, + ]); + useEffect(() => { - if (shouldStart) { - start(); + if (shouldStart && startParams) { + dispatch(startStream(startParams)); setShouldStart(false); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [shouldStart]); useEffect(() => { - setCurrentAnalysisType(analysisType); - setCurrentAnalysisWindowParameters(windowParameters); - start(); + if (startParams) { + setCurrentAnalysisType(analysisType); + setCurrentAnalysisWindowParameters(windowParameters); + dispatch(startStream(startParams)); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -365,7 +366,6 @@ export const LogRateAnalysisResults: FC = ({ return p + c.groupItemsSortedByUniqueness.length; }, 0); const foundGroups = groupTableItems.length > 0 && groupItemCount > 0; - const timeRangeMs = { from: earliest, to: latest }; // Disable the grouping switch toggle only if no groups were found, // the toggle wasn't enabled already and no fields were selected to be skipped. @@ -392,7 +392,7 @@ export const LogRateAnalysisResults: FC = ({ progressMessage={data.loadingState ?? ''} isRunning={isRunning} onRefresh={() => startHandler(false)} - onCancel={cancel} + onCancel={cancelHandler} onReset={onReset} shouldRerunAnalysis={shouldRerunAnalysis} > @@ -454,10 +454,7 @@ export const LogRateAnalysisResults: FC = ({ {showLogRateAnalysisResultsTable && currentAnalysisType !== undefined && ( <> - + )} @@ -550,24 +547,17 @@ export const LogRateAnalysisResults: FC = ({ skippedColumns={skippedColumns} significantItems={data.significantItems} groupTableItems={groupTableItems} - loading={isRunning} - timeRangeMs={timeRangeMs} searchQuery={searchQuery} barColorOverride={barColorOverride} barHighlightColorOverride={barHighlightColorOverride} - zeroDocsFallback={zeroDocsFallback} /> ) : null} {showLogRateAnalysisResultsTable && !groupResults ? ( ) : null}
diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_type_callout.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_type_callout.tsx index fa012b96573ec..9c08e5d31590c 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_type_callout.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis/log_rate_analysis_type_callout.tsx @@ -10,17 +10,18 @@ import React, { type FC } from 'react'; import { EuiCallOut, EuiText } from '@elastic/eui'; import { LOG_RATE_ANALYSIS_TYPE, type LogRateAnalysisType } from '@kbn/aiops-log-rate-analysis'; +import { useAppSelector } from '@kbn/aiops-log-rate-analysis/state'; import { i18n } from '@kbn/i18n'; interface LogRateAnalysisTypeCallOutProps { analysisType: LogRateAnalysisType; - zeroDocsFallback: boolean; } export const LogRateAnalysisTypeCallOut: FC = ({ analysisType, - zeroDocsFallback, }) => { + const zeroDocsFallback = useAppSelector((s) => s.logRateAnalysisResults.zeroDocsFallback); + let callOutTitle: string; let callOutText: string; 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 c33713e111f02..4ff76792fc21f 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 @@ -8,7 +8,7 @@ import { sortBy } from 'lodash'; import type { SignificantItemGroup } from '@kbn/ml-agg-utils'; -import type { GroupTableItem, GroupTableItemGroup } from '@kbn/aiops-components'; +import type { GroupTableItem, GroupTableItemGroup } from '@kbn/aiops-log-rate-analysis/state'; export function getGroupTableItems( significantItemsGroups: SignificantItemGroup[] 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 6ac66468b658e..b9cf5c62443b9 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 @@ -7,7 +7,7 @@ import { escapeKuery, escapeQuotes } from '@kbn/es-query'; import { isSignificantItem, type SignificantItem } from '@kbn/ml-agg-utils'; -import type { GroupTableItem } from '@kbn/aiops-components'; +import type { GroupTableItem } from '@kbn/aiops-log-rate-analysis/state'; export const getTableItemAsKQL = (tableItem: GroupTableItem | SignificantItem) => { if (isSignificantItem(tableItem)) { 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 81315a83929b1..38cf611b4bdd1 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 @@ -12,12 +12,18 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { EuiTableSortingType } from '@elastic/eui'; import { useEuiBackgroundColor, EuiBasicTable } from '@elastic/eui'; -import { type SignificantItem } from '@kbn/ml-agg-utils'; -import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker'; -import { useLogRateAnalysisStateContext } from '@kbn/aiops-components'; +import type { SignificantItem } from '@kbn/ml-agg-utils'; +import { + setPinnedSignificantItem, + setSelectedSignificantItem, + useAppDispatch, + useAppSelector, +} from '@kbn/aiops-log-rate-analysis/state'; + +import type { GroupTableItemGroup } from '@kbn/aiops-log-rate-analysis/state'; import { useEuiTheme } from '../../hooks/use_eui_theme'; -import { useColumns, SIG_ITEMS_TABLE } from './use_columns'; +import { useColumns, LOG_RATE_ANALYSIS_RESULTS_TABLE_TYPE } from './use_columns'; const PAGINATION_SIZE_OPTIONS = [5, 10, 20, 50]; const DEFAULT_SORT_FIELD = 'pValue'; @@ -26,41 +32,59 @@ const DEFAULT_SORT_DIRECTION = 'asc'; const DEFAULT_SORT_DIRECTION_ZERO_DOCS_FALLBACK = 'desc'; interface LogRateAnalysisResultsTableProps { - significantItems: SignificantItem[]; - loading: boolean; - isExpandedRow?: boolean; + groupFilter?: GroupTableItemGroup[]; searchQuery: estypes.QueryDslQueryContainer; - timeRangeMs: TimeRangeMs; /** Optional color override for the default bar color for charts */ barColorOverride?: string; /** Optional color override for the highlighted bar color for charts */ barHighlightColorOverride?: string; skippedColumns: string[]; - zeroDocsFallback?: boolean; } export const LogRateAnalysisResultsTable: FC = ({ - significantItems, - loading, - isExpandedRow, + groupFilter, searchQuery, - timeRangeMs, barColorOverride, barHighlightColorOverride, skippedColumns, - zeroDocsFallback = false, }) => { const euiTheme = useEuiTheme(); const primaryBackgroundColor = useEuiBackgroundColor('primary'); - const { - pinnedGroup, - pinnedSignificantItem, - selectedGroup, - selectedSignificantItem, - setPinnedSignificantItem, - setSelectedSignificantItem, - } = useLogRateAnalysisStateContext(); + const allSignificantItems = useAppSelector((s) => s.logRateAnalysisResults.significantItems); + + const significantItems = useMemo(() => { + if (!groupFilter) { + return allSignificantItems; + } + + return groupFilter.reduce((p, groupItem) => { + const st = allSignificantItems.find( + (d) => d.fieldName === groupItem.fieldName && d.fieldValue === groupItem.fieldValue + ); + + if (st !== undefined) { + p.push({ + ...st, + unique: (groupItem.duplicate ?? 0) <= 1, + }); + } + + return p; + }, []); + }, [allSignificantItems, groupFilter]); + + const zeroDocsFallback = useAppSelector((s) => s.logRateAnalysisResults.zeroDocsFallback); + const pinnedGroup = useAppSelector((s) => s.logRateAnalysisTableRow.pinnedGroup); + const selectedGroup = useAppSelector((s) => s.logRateAnalysisTableRow.selectedGroup); + const pinnedSignificantItem = useAppSelector( + (s) => s.logRateAnalysisTableRow.pinnedSignificantItem + ); + const selectedSignificantItem = useAppSelector( + (s) => s.logRateAnalysisTableRow.selectedSignificantItem + ); + + const dispatch = useAppDispatch(); const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); @@ -72,15 +96,12 @@ export const LogRateAnalysisResultsTable: FC = ); const columns = useColumns( - SIG_ITEMS_TABLE, + LOG_RATE_ANALYSIS_RESULTS_TABLE_TYPE.SIGNIFICANT_ITEMS, skippedColumns, searchQuery, - timeRangeMs, - loading, - zeroDocsFallback, barColorOverride, barHighlightColorOverride, - isExpandedRow + groupFilter !== undefined ); const onChange = useCallback((tableSettings) => { @@ -151,7 +172,7 @@ export const LogRateAnalysisResultsTable: FC = selectedGroup === null && pinnedGroup === null ) { - setSelectedSignificantItem(pageOfItems[0]); + dispatch(setSelectedSignificantItem(pageOfItems[0])); } // If a user switched pages and a pinned row is no longer visible @@ -162,13 +183,12 @@ export const LogRateAnalysisResultsTable: FC = selectedGroup === null && pinnedGroup === null ) { - setPinnedSignificantItem(null); + dispatch(setPinnedSignificantItem(null)); } }, [ + dispatch, selectedGroup, selectedSignificantItem, - setSelectedSignificantItem, - setPinnedSignificantItem, pageOfItems, pinnedGroup, pinnedSignificantItem, @@ -178,8 +198,8 @@ export const LogRateAnalysisResultsTable: FC = // make sure to reset any hovered or pinned rows. useEffect( () => () => { - setSelectedSignificantItem(null); - setPinnedSignificantItem(null); + dispatch(setSelectedSignificantItem(null)); + dispatch(setPinnedSignificantItem(null)); }, // eslint-disable-next-line react-hooks/exhaustive-deps [] @@ -236,18 +256,18 @@ export const LogRateAnalysisResultsTable: FC = significantItem.fieldName === pinnedSignificantItem?.fieldName && significantItem.fieldValue === pinnedSignificantItem?.fieldValue ) { - setPinnedSignificantItem(null); + dispatch(setPinnedSignificantItem(null)); } else { - setPinnedSignificantItem(significantItem); + dispatch(setPinnedSignificantItem(significantItem)); } }, onMouseEnter: () => { if (pinnedSignificantItem === null) { - setSelectedSignificantItem(significantItem); + dispatch(setSelectedSignificantItem(significantItem)); } }, onMouseLeave: () => { - setSelectedSignificantItem(null); + dispatch(setSelectedSignificantItem(null)); }, 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 d0935ebb96ff0..3124e18a29ebe 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 @@ -27,16 +27,21 @@ import { import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { SignificantItem } from '@kbn/ml-agg-utils'; -import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker'; +import { type SignificantItem } from '@kbn/ml-agg-utils'; +import { + setPinnedGroup, + setSelectedGroup, + useAppDispatch, + useAppSelector, + type GroupTableItem, +} from '@kbn/aiops-log-rate-analysis/state'; import { stringHash } from '@kbn/ml-string-hash'; -import { useLogRateAnalysisStateContext, type GroupTableItem } from '@kbn/aiops-components'; import usePrevious from 'react-use/lib/usePrevious'; import useMountedState from 'react-use/lib/useMountedState'; import { LogRateAnalysisResultsTable } from './log_rate_analysis_results_table'; -import { GROUPS_TABLE, useColumns } from './use_columns'; +import { LOG_RATE_ANALYSIS_RESULTS_TABLE_TYPE, useColumns } from './use_columns'; const EXPAND_COLUMN_WIDTH = '40px'; const MAX_GROUP_BADGES = 5; @@ -51,29 +56,25 @@ interface LogRateAnalysisResultsTableProps { skippedColumns: string[]; significantItems: SignificantItem[]; groupTableItems: GroupTableItem[]; - loading: boolean; searchQuery: estypes.QueryDslQueryContainer; - timeRangeMs: TimeRangeMs; /** Optional color override for the default bar color for charts */ barColorOverride?: string; /** Optional color override for the highlighted bar color for charts */ barHighlightColorOverride?: string; - zeroDocsFallback?: boolean; } export const LogRateAnalysisResultsGroupsTable: FC = ({ skippedColumns, significantItems, groupTableItems, - loading, - timeRangeMs, searchQuery, barColorOverride, barHighlightColorOverride, - zeroDocsFallback = false, }) => { const prevSkippedColumns = usePrevious(skippedColumns); + const zeroDocsFallback = useAppSelector((s) => s.logRateAnalysisResults.zeroDocsFallback); + const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); const [sortField, setSortField] = useState<'docCount' | 'pValue'>( @@ -90,8 +91,9 @@ export const LogRateAnalysisResultsGroupsTable: FC s.logRateAnalysisTableRow.pinnedGroup); + const selectedGroup = useAppSelector((s) => s.logRateAnalysisTableRow.selectedGroup); + const dispatch = useAppDispatch(); const isMounted = useMountedState(); const toggleDetails = (item: GroupTableItem) => { @@ -102,26 +104,7 @@ export const LogRateAnalysisResultsGroupsTable: FC( - (p, groupItem) => { - const st = significantItems.find( - (d) => d.fieldName === groupItem.fieldName && d.fieldValue === groupItem.fieldValue - ); - - if (st !== undefined) { - p.push({ - ...st, - unique: (groupItem.duplicate ?? 0) <= 1, - }); - } - - return p; - }, - [] - )} - loading={loading} - isExpandedRow - timeRangeMs={timeRangeMs} + groupFilter={item.groupItemsSortedByUniqueness} searchQuery={searchQuery} barColorOverride={barColorOverride} barHighlightColorOverride={barHighlightColorOverride} @@ -254,12 +237,9 @@ export const LogRateAnalysisResultsGroupsTable: FC>; @@ -331,22 +311,22 @@ export const LogRateAnalysisResultsGroupsTable: FC 0 ) { - setSelectedGroup(pageOfItems[0]); + dispatch(setSelectedGroup(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 (pinnedGroup !== null && !pageOfItems.some((item) => isEqual(item, pinnedGroup))) { - setPinnedGroup(null); + dispatch(setPinnedGroup(null)); } - }, [selectedGroup, setSelectedGroup, setPinnedGroup, pageOfItems, pinnedGroup]); + }, [dispatch, selectedGroup, pageOfItems, pinnedGroup]); // When the analysis results table unmounts, // make sure to reset any hovered or pinned rows. useEffect( () => () => { - setSelectedGroup(null); - setPinnedGroup(null); + dispatch(setSelectedGroup(null)); + dispatch(setPinnedGroup(null)); }, // eslint-disable-next-line react-hooks/exhaustive-deps [] @@ -413,18 +393,18 @@ export const LogRateAnalysisResultsGroupsTable: FC { if (group.id === pinnedGroup?.id) { - setPinnedGroup(null); + dispatch(setPinnedGroup(null)); } else { - setPinnedGroup(group); + dispatch(setPinnedGroup(group)); } }, onMouseEnter: () => { if (pinnedGroup === null) { - setSelectedGroup(group); + dispatch(setSelectedGroup(group)); } }, onMouseLeave: () => { - setSelectedGroup(null); + dispatch(setSelectedGroup(null)); }, style: getRowStyle(group), }; diff --git a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx index 7a0c7d1ed8ee4..fa52207865f3c 100644 --- a/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx +++ b/x-pack/plugins/aiops/public/components/log_rate_analysis_results_table/use_columns.tsx @@ -12,8 +12,8 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; import { getCategoryQuery } from '@kbn/aiops-log-pattern-analysis/get_category_query'; -import type { TimeRange as TimeRangeMs } from '@kbn/ml-date-picker'; import type { FieldStatsServices } from '@kbn/unified-field-list/src/components/field_stats'; +import { useAppSelector } from '@kbn/aiops-log-rate-analysis/state'; import { getFailedTransactionsCorrelationImpactLabel } from './get_failed_transactions_correlation_impact_label'; import { FieldStatsPopover } from '../field_stats_popover'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; @@ -61,9 +61,13 @@ export const significantItemColumns = { ...commonColumns, } as const; -export const GROUPS_TABLE = 'groups'; -export const SIG_ITEMS_TABLE = 'significantItems'; -type TableType = typeof GROUPS_TABLE | typeof SIG_ITEMS_TABLE; +export const LOG_RATE_ANALYSIS_RESULTS_TABLE_TYPE = { + GROUPS: 'groups', + SIGNIFICANT_ITEMS: 'significantItems', +} as const; +export type LogRateAnalysisResultsTableType = + typeof LOG_RATE_ANALYSIS_RESULTS_TABLE_TYPE[keyof typeof LOG_RATE_ANALYSIS_RESULTS_TABLE_TYPE]; + export type ColumnNames = keyof typeof significantItemColumns | 'unique'; const logRateHelpMessage = i18n.translate( @@ -94,12 +98,9 @@ const impactMessage = i18n.translate( ); export const useColumns = ( - tableType: TableType, + tableType: LogRateAnalysisResultsTableType, skippedColumns: string[], searchQuery: estypes.QueryDslQueryContainer, - timeRangeMs: TimeRangeMs, - loading: boolean, - zeroDocsFallback: boolean, barColorOverride?: string, barHighlightColorOverride?: string, isExpandedRow: boolean = false @@ -111,7 +112,13 @@ export const useColumns = ( const viewInLogPatternAnalysisAction = useViewInLogPatternAnalysisAction(dataView.id); const copyToClipBoardAction = useCopyToClipboardAction(); - const isGroupsTable = tableType === GROUPS_TABLE; + const { earliest, latest } = useAppSelector((s) => s.logRateAnalysis); + const timeRangeMs = { from: earliest ?? 0, to: latest ?? 0 }; + + const loading = useAppSelector((s) => s.logRateAnalysisStream.isRunning); + const zeroDocsFallback = useAppSelector((s) => s.logRateAnalysisResults.zeroDocsFallback); + + const isGroupsTable = tableType === LOG_RATE_ANALYSIS_RESULTS_TABLE_TYPE.GROUPS; const fieldStatsServices: FieldStatsServices = useMemo(() => { return { 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 c110e8b2d56c8..01d07f844e175 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 @@ -14,7 +14,7 @@ import type { SignificantItem } from '@kbn/ml-agg-utils'; import { finalSignificantItemGroups } from '@kbn/aiops-test-utils/artificial_logs/final_significant_item_groups'; import { significantTerms } from '@kbn/aiops-test-utils/artificial_logs/significant_terms'; -import type { GroupTableItem } from '@kbn/aiops-components'; +import type { GroupTableItem } from '@kbn/aiops-log-rate-analysis/state'; import { getGroupTableItems } from './get_group_table_items'; import { useCopyToClipboardAction } from './use_copy_to_clipboard_action'; 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 403adfdbf070a..54ed77ae4ed62 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 @@ -11,7 +11,7 @@ import { EuiCopy, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isSignificantItem, type SignificantItem } from '@kbn/ml-agg-utils'; -import type { GroupTableItem, TableItemAction } from '@kbn/aiops-components'; +import type { GroupTableItem, TableItemAction } from '@kbn/aiops-log-rate-analysis/state'; import { TableActionButton } from './table_action_button'; import { getTableItemAsKQL } from './get_table_item_as_kql'; 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 7f657617517af..ec4284d6452e5 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 @@ -10,7 +10,7 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import type { SignificantItem } from '@kbn/ml-agg-utils'; import { SEARCH_QUERY_LANGUAGE } from '@kbn/ml-query-utils'; -import type { GroupTableItem, TableItemAction } from '@kbn/aiops-components'; +import type { GroupTableItem, TableItemAction } from '@kbn/aiops-log-rate-analysis/state'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; 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 6c29ee4607d60..dbac6fbe8c9f3 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 @@ -11,7 +11,7 @@ import type { SerializableRecord } from '@kbn/utility-types'; import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { isSignificantItem, type SignificantItem, SIGNIFICANT_ITEM_TYPE } from '@kbn/ml-agg-utils'; -import type { GroupTableItem, TableItemAction } from '@kbn/aiops-components'; +import type { GroupTableItem, TableItemAction } from '@kbn/aiops-log-rate-analysis/state'; import { SEARCH_QUERY_LANGUAGE } from '@kbn/ml-query-utils'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; diff --git a/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx b/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx index 5b8345dd28d39..7e0e00b7ac02d 100644 --- a/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx +++ b/x-pack/plugins/aiops/public/embeddables/change_point_chart/embeddable_change_point_chart_factory.tsx @@ -68,8 +68,8 @@ export const getChangePointChartEmbeddableFactory = ( ) => { const factory: ReactEmbeddableFactory< ChangePointEmbeddableState, - ChangePointEmbeddableApi, - ChangePointEmbeddableRuntimeState + ChangePointEmbeddableRuntimeState, + ChangePointEmbeddableApi > = { type: EMBEDDABLE_CHANGE_POINT_CHART_TYPE, deserializeState: (state) => { diff --git a/x-pack/plugins/aiops/public/get_document_stats.ts b/x-pack/plugins/aiops/public/get_document_stats.ts index 0846565cc1890..430c914c3cf58 100644 --- a/x-pack/plugins/aiops/public/get_document_stats.ts +++ b/x-pack/plugins/aiops/public/get_document_stats.ts @@ -10,28 +10,15 @@ import { get } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import dateMath from '@kbn/datemath'; -import { - getExtendedChangePoint, - type DocumentCountStatsChangePoint, -} from '@kbn/aiops-log-rate-analysis'; +import { getExtendedChangePoint, type DocumentCountStats } from '@kbn/aiops-log-rate-analysis'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import type { SignificantItem } from '@kbn/ml-agg-utils'; import type { Query } from '@kbn/es-query'; import type { RandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; -import type { GroupTableItem } from '@kbn/aiops-components'; +import type { GroupTableItem } from '@kbn/aiops-log-rate-analysis/state'; import { buildExtendedBaseFilterCriteria } from './application/utils/build_extended_base_filter_criteria'; -export interface DocumentCountStats { - interval?: number; - buckets?: { [key: string]: number }; - changePoint?: DocumentCountStatsChangePoint; - timeRangeEarliest?: number; - timeRangeLatest?: number; - totalCount: number; - lastDocTimeStampMs?: number; -} - export interface DocumentStatsSearchStrategyParams { earliest?: number; latest?: number; diff --git a/x-pack/plugins/aiops/public/hooks/use_data.ts b/x-pack/plugins/aiops/public/hooks/use_data.ts index 5c7acaf2b2372..9d54dfaac1eaf 100644 --- a/x-pack/plugins/aiops/public/hooks/use_data.ts +++ b/x-pack/plugins/aiops/public/hooks/use_data.ts @@ -18,7 +18,7 @@ import type { Dictionary } from '@kbn/ml-url-state'; import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker'; import { useTimeBuckets } from '@kbn/ml-time-buckets'; import { AIOPS_PLUGIN_ID } from '@kbn/aiops-common/constants'; -import type { GroupTableItem } from '@kbn/aiops-components'; +import type { GroupTableItem } from '@kbn/aiops-log-rate-analysis/state'; import type { DocumentStatsSearchStrategyParams } from '../get_document_stats'; 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 83f10a4a445d3..97f7f5a855382 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 @@ -14,8 +14,9 @@ import { stringHash } from '@kbn/ml-string-hash'; import { createRandomSamplerWrapper } from '@kbn/ml-random-sampler-utils'; import { extractErrorProperties } from '@kbn/ml-error-utils'; import { RANDOM_SAMPLER_SEED } from '@kbn/aiops-log-rate-analysis/constants'; +import type { DocumentCountStats } from '@kbn/aiops-log-rate-analysis/types'; -import type { DocumentCountStats, DocumentStatsSearchStrategyParams } from '../get_document_stats'; +import type { DocumentStatsSearchStrategyParams } from '../get_document_stats'; import { getDocumentCountStatsRequest, processDocumentCountStats } from '../get_document_stats'; import { useAiopsAppContext } from './use_aiops_app_context'; diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/grouping_handler.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/grouping_handler.ts index 3c7a1f4ff5478..8fe478a805355 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/grouping_handler.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/grouping_handler.ts @@ -21,10 +21,10 @@ import { import { RANDOM_SAMPLER_SEED } from '@kbn/aiops-log-rate-analysis/constants'; import { - addSignificantItemsGroupAction, - addSignificantItemsGroupHistogramAction, - updateLoadingStateAction, -} from '@kbn/aiops-log-rate-analysis/api/actions'; + addSignificantItemsGroup, + addSignificantItemsGroupHistogram, + updateLoadingState, +} from '@kbn/aiops-log-rate-analysis/api/stream_reducer'; import type { AiopsLogRateAnalysisApiVersion as ApiVersion } from '@kbn/aiops-log-rate-analysis/api/schema'; import { isRequestAbortedError } from '@kbn/aiops-common/is_request_aborted_error'; @@ -56,7 +56,7 @@ export const groupingHandlerFactory = function pushHistogramDataLoadingState() { responseStream.push( - updateLoadingStateAction({ + updateLoadingState({ ccsWarning: false, loaded: stateHandler.loaded(), loadingState: i18n.translate( @@ -70,7 +70,7 @@ export const groupingHandlerFactory = } responseStream.push( - updateLoadingStateAction({ + updateLoadingState({ ccsWarning: false, loaded: stateHandler.loaded(), loadingState: i18n.translate('xpack.aiops.logRateAnalysis.loadingState.groupingResults', { @@ -133,7 +133,7 @@ export const groupingHandlerFactory = const maxItems = Math.max(...significantItemGroups.map((g) => g.group.length)); if (maxItems > 1) { - responseStream.push(addSignificantItemsGroupAction(significantItemGroups)); + responseStream.push(addSignificantItemsGroup(significantItemGroups)); } stateHandler.loaded(PROGRESS_STEP_GROUPING, false); @@ -211,7 +211,7 @@ export const groupingHandlerFactory = }) ?? []; responseStream.push( - addSignificantItemsGroupHistogramAction([ + addSignificantItemsGroupHistogram([ { id: cpg.id, histogram, diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/histogram_handler.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/histogram_handler.ts index 8fd134df69cac..e3501a53f5df8 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/histogram_handler.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/histogram_handler.ts @@ -18,9 +18,9 @@ import { fetchHistogramsForFields } from '@kbn/ml-agg-utils'; import { RANDOM_SAMPLER_SEED } from '@kbn/aiops-log-rate-analysis/constants'; import { - addSignificantItemsHistogramAction, - updateLoadingStateAction, -} from '@kbn/aiops-log-rate-analysis/api/actions'; + addSignificantItemsHistogram, + updateLoadingState, +} from '@kbn/aiops-log-rate-analysis/api/stream_reducer'; import type { AiopsLogRateAnalysisApiVersion as ApiVersion } from '@kbn/aiops-log-rate-analysis/api/schema'; import { getCategoryQuery } from '@kbn/aiops-log-pattern-analysis/get_category_query'; @@ -50,7 +50,7 @@ export const histogramHandlerFactory = ) => { function pushHistogramDataLoadingState() { responseStream.push( - updateLoadingStateAction({ + updateLoadingState({ ccsWarning: false, loaded: stateHandler.loaded(), loadingState: i18n.translate( @@ -145,7 +145,7 @@ export const histogramHandlerFactory = stateHandler.loaded((1 / fieldValuePairsCount) * PROGRESS_STEP_HISTOGRAMS, false); pushHistogramDataLoadingState(); responseStream.push( - addSignificantItemsHistogramAction([ + addSignificantItemsHistogram([ { fieldName, fieldValue, @@ -238,7 +238,7 @@ export const histogramHandlerFactory = stateHandler.loaded((1 / fieldValuePairsCount) * PROGRESS_STEP_HISTOGRAMS, false); pushHistogramDataLoadingState(); responseStream.push( - addSignificantItemsHistogramAction([ + addSignificantItemsHistogram([ { fieldName, fieldValue, diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/index_info_handler.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/index_info_handler.ts index 038fc0588f6a2..5c326e7821887 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/index_info_handler.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/index_info_handler.ts @@ -8,9 +8,9 @@ import { i18n } from '@kbn/i18n'; import { - updateLoadingStateAction, + updateLoadingState, setZeroDocsFallback, -} from '@kbn/aiops-log-rate-analysis/api/actions'; +} from '@kbn/aiops-log-rate-analysis/api/stream_reducer'; import type { AiopsLogRateAnalysisApiVersion as ApiVersion } from '@kbn/aiops-log-rate-analysis/api/schema'; import { isRequestAbortedError } from '@kbn/aiops-common/is_request_aborted_error'; @@ -43,7 +43,7 @@ export const indexInfoHandlerFactory = if (!requestBody.overrides?.remainingFieldCandidates) { logDebugMessage('Fetch index information.'); responseStream.push( - updateLoadingStateAction({ + updateLoadingState({ ccsWarning: false, loaded: stateHandler.loaded(), loadingState: i18n.translate( @@ -85,7 +85,7 @@ export const indexInfoHandlerFactory = responseStream.pushPingWithTimeout(); responseStream.push( - updateLoadingStateAction({ + updateLoadingState({ ccsWarning: false, loaded: stateHandler.loaded(), loadingState: i18n.translate( diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/overrides_handler.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/overrides_handler.ts index d575fd1fed852..42b8b401b17ac 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/overrides_handler.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/overrides_handler.ts @@ -6,10 +6,10 @@ */ import { - resetAllAction, - resetErrorsAction, - resetGroupsAction, -} from '@kbn/aiops-log-rate-analysis/api/actions'; + resetAll, + resetErrors, + resetGroups, +} from '@kbn/aiops-log-rate-analysis/api/stream_reducer'; import type { AiopsLogRateAnalysisApiVersion as ApiVersion } from '@kbn/aiops-log-rate-analysis/api/schema'; import type { ResponseStreamFetchOptions } from '../response_stream_factory'; @@ -24,15 +24,15 @@ export const overridesHandlerFactory = () => { if (!requestBody.overrides) { logDebugMessage('Full Reset.'); - responseStream.push(resetAllAction()); + responseStream.push(resetAll()); } else { logDebugMessage('Reset Errors.'); - responseStream.push(resetErrorsAction()); + responseStream.push(resetErrors()); } if (requestBody.overrides?.regroupOnly) { logDebugMessage('Reset Groups.'); - responseStream.push(resetGroupsAction()); + responseStream.push(resetGroups()); } if (requestBody.overrides?.loaded) { diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/significant_items_handler.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/significant_items_handler.ts index 6a3a83352ca76..92d400b466653 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/significant_items_handler.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/significant_items_handler.ts @@ -10,9 +10,9 @@ import { queue } from 'async'; import { SIGNIFICANT_ITEM_TYPE, type SignificantItem } from '@kbn/ml-agg-utils'; import { i18n } from '@kbn/i18n'; import { - addSignificantItemsAction, - updateLoadingStateAction, -} from '@kbn/aiops-log-rate-analysis/api/actions'; + addSignificantItems, + updateLoadingState, +} from '@kbn/aiops-log-rate-analysis/api/stream_reducer'; import type { AiopsLogRateAnalysisSchema, AiopsLogRateAnalysisApiVersion as ApiVersion, @@ -137,7 +137,7 @@ export const significantItemsHandlerFactory = }); significantTerms.push(...pValues); - responseStream.push(addSignificantItemsAction(pValues)); + responseStream.push(addSignificantItems(pValues)); fieldValuePairsCount += pValues.length; } @@ -156,7 +156,7 @@ export const significantItemsHandlerFactory = if (significantCategoriesForField.length > 0) { significantCategories.push(...significantCategoriesForField); - responseStream.push(addSignificantItemsAction(significantCategoriesForField)); + responseStream.push(addSignificantItems(significantCategoriesForField)); fieldValuePairsCount += significantCategoriesForField.length; } } @@ -164,7 +164,7 @@ export const significantItemsHandlerFactory = stateHandler.loaded(loadingStep, false); responseStream.push( - updateLoadingStateAction({ + updateLoadingState({ ccsWarning: false, loaded: stateHandler.loaded(), loadingState: i18n.translate( diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/top_items_handler.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/top_items_handler.ts index f29b372116f48..8844bd5082e33 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/top_items_handler.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/analysis_handlers/top_items_handler.ts @@ -11,9 +11,9 @@ import { SIGNIFICANT_ITEM_TYPE, type SignificantItem } from '@kbn/ml-agg-utils'; import { i18n } from '@kbn/i18n'; import { - addSignificantItemsAction, - updateLoadingStateAction, -} from '@kbn/aiops-log-rate-analysis/api/actions'; + addSignificantItems, + updateLoadingState, +} from '@kbn/aiops-log-rate-analysis/api/stream_reducer'; import type { AiopsLogRateAnalysisSchema, @@ -75,7 +75,7 @@ export const topItemsHandlerFactory = ); if (topCategories.length > 0) { - responseStream.push(addSignificantItemsAction(topCategories)); + responseStream.push(addSignificantItems(topCategories)); } } @@ -137,11 +137,11 @@ export const topItemsHandlerFactory = }); topTerms.push(...fetchedTopTerms); - responseStream.push(addSignificantItemsAction(fetchedTopTerms)); + responseStream.push(addSignificantItems(fetchedTopTerms)); } responseStream.push( - updateLoadingStateAction({ + updateLoadingState({ ccsWarning: false, loaded: stateHandler.loaded(), loadingState: i18n.translate( diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_factory.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_factory.ts index 57e97ade7fb12..f6ada20d3f4f8 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_factory.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_factory.ts @@ -10,7 +10,7 @@ import type { ElasticsearchClient } from '@kbn/core/server'; import type { Headers, KibanaRequestEvents } from '@kbn/core-http-server'; import type { Logger } from '@kbn/logging'; -import { type AiopsLogRateAnalysisApiAction } from '@kbn/aiops-log-rate-analysis/api/actions'; +import { type AiopsLogRateAnalysisApiAction } from '@kbn/aiops-log-rate-analysis/api/stream_reducer'; import type { AiopsLogRateAnalysisSchema, diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_utils/stream_end_with_updated_loading_state.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_utils/stream_end_with_updated_loading_state.ts index 7765f6679d38d..0eee2b753d479 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_utils/stream_end_with_updated_loading_state.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_utils/stream_end_with_updated_loading_state.ts @@ -10,9 +10,9 @@ import { i18n } from '@kbn/i18n'; import type { StreamFactoryReturnType } from '@kbn/ml-response-stream/server'; import { - updateLoadingStateAction, + updateLoadingState, type AiopsLogRateAnalysisApiAction, -} from '@kbn/aiops-log-rate-analysis/api/actions'; +} from '@kbn/aiops-log-rate-analysis/api/stream_reducer'; /** * Helper function that will push a message to the stream that it's done and @@ -26,7 +26,7 @@ export const streamEndWithUpdatedLoadingStateFactory = ( ) => { return function endWithUpdatedLoadingState() { push( - updateLoadingStateAction({ + updateLoadingState({ ccsWarning: false, loaded: 1, loadingState: i18n.translate('xpack.aiops.logRateAnalysis.loadingState.doneMessage', { diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_utils/stream_push_error.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_utils/stream_push_error.ts index 92c3fb3661ae8..9db4982964a79 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_utils/stream_push_error.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_utils/stream_push_error.ts @@ -8,9 +8,9 @@ import type { StreamFactoryReturnType } from '@kbn/ml-response-stream/server'; import { - addErrorAction, + addError, type AiopsLogRateAnalysisApiAction, -} from '@kbn/aiops-log-rate-analysis/api/actions'; +} from '@kbn/aiops-log-rate-analysis/api/stream_reducer'; import type { LogDebugMessage } from './log_debug_message'; @@ -25,6 +25,6 @@ export const streamPushErrorFactory = ( ) => { return function pushError(m: string) { logDebugMessage('Push error.'); - push(addErrorAction(m)); + push(addError(m)); }; }; diff --git a/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_utils/stream_push_ping_with_timeout.ts b/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_utils/stream_push_ping_with_timeout.ts index bcedc82d5f5c4..758d54c81bca8 100644 --- a/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_utils/stream_push_ping_with_timeout.ts +++ b/x-pack/plugins/aiops/server/routes/log_rate_analysis/response_stream_utils/stream_push_ping_with_timeout.ts @@ -8,9 +8,9 @@ import type { StreamFactoryReturnType } from '@kbn/ml-response-stream/server'; import { - pingAction, + ping, type AiopsLogRateAnalysisApiAction, -} from '@kbn/aiops-log-rate-analysis/api/actions'; +} from '@kbn/aiops-log-rate-analysis/api/stream_reducer'; import type { LogDebugMessage } from './log_debug_message'; import type { StateHandler } from './state_handler'; @@ -32,7 +32,7 @@ export const streamPushPingWithTimeoutFactory = ( setTimeout(() => { if (stateHandler.isRunning()) { logDebugMessage('Ping message.'); - push(pingAction()); + push(ping()); pushPingWithTimeout(); } }, PING_FREQUENCY); diff --git a/x-pack/plugins/alerting/common/index.ts b/x-pack/plugins/alerting/common/index.ts index 23d2d48ab5e5b..6c072654cf3bd 100644 --- a/x-pack/plugins/alerting/common/index.ts +++ b/x-pack/plugins/alerting/common/index.ts @@ -7,9 +7,6 @@ // TODO: https://github.com/elastic/kibana/issues/110895 /* eslint-disable @kbn/eslint/no_export_all */ - -import { AlertsHealth } from './rule'; - export * from './rule'; export * from './rules_settings'; export * from './rule_type'; @@ -72,12 +69,6 @@ export { contextToSchemaName, } from './alert_schema'; -export interface AlertingFrameworkHealth { - isSufficientlySecure: boolean; - hasPermanentEncryptionKey: boolean; - alertingFrameworkHealth: AlertsHealth; -} - export const LEGACY_BASE_ALERT_API_PATH = '/api/alerts'; export const BASE_ALERTING_API_PATH = '/api/alerting'; export const INTERNAL_BASE_ALERTING_API_PATH = '/internal/alerting' as const; diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index 7d28c5fce8b54..d7f2c6920f4f4 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -5,11 +5,7 @@ * 2.0. */ -import type { - SavedObjectAttribute, - SavedObjectAttributes, - SavedObjectsResolveResponse, -} from '@kbn/core/server'; +import type { SavedObjectAttributes } from '@kbn/core/server'; import type { SanitizedRule, @@ -17,7 +13,7 @@ import type { AlertsFilterTimeframe, RuleAction, RuleSystemAction, - RuleTypeParams, + RuleActionParam, } from '@kbn/alerting-types'; export type { @@ -26,6 +22,7 @@ export type { SanitizedRule, RuleTypeParams, RuleActionParams, + RuleActionParam, IntervalSchedule, RuleActionFrequency, AlertsFilterTimeframe, @@ -45,6 +42,9 @@ export type { AlertDelay, SanitizedAlertsFilter, SanitizedRuleAction, + AlertsHealth, + AlertingFrameworkHealth, + ResolvedSanitizedRule, } from '@kbn/alerting-types'; export { @@ -68,19 +68,11 @@ export const RuleLastRunOutcomeOrderMap: Record = { export type RuleAlertingOutcome = 'failure' | 'success' | 'unknown' | 'warning'; -export type RuleActionParam = SavedObjectAttribute; - export type RuleActionAlertsFilterProperty = AlertsFilterTimeframe | RuleActionParam; export type RuleActionKey = keyof RuleAction; export type RuleSystemActionKey = keyof RuleSystemAction; -export type ResolvedSanitizedRule = SanitizedRule & - Omit & { - outcome: string; - alias_target_id?: string; - }; - export type SanitizedRuleConfig = Pick< SanitizedRule, | 'id' @@ -106,27 +98,6 @@ export type SanitizedRuleConfig = Pick< ruleTypeName: string; }; -export enum HealthStatus { - OK = 'ok', - Warning = 'warn', - Error = 'error', -} - -export interface AlertsHealth { - decryptionHealth: { - status: HealthStatus; - timestamp: string; - }; - executionHealth: { - status: HealthStatus; - timestamp: string; - }; - readHealth: { - status: HealthStatus; - timestamp: string; - }; -} - export interface RuleMonitoringLastRunMetrics extends SavedObjectAttributes { duration?: number; total_search_duration_ms?: number | null; diff --git a/x-pack/plugins/alerting/server/health/get_health.test.ts b/x-pack/plugins/alerting/server/health/get_health.test.ts index 51e03dc890e98..c733179c2cf2b 100644 --- a/x-pack/plugins/alerting/server/health/get_health.test.ts +++ b/x-pack/plugins/alerting/server/health/get_health.test.ts @@ -6,8 +6,9 @@ */ import { savedObjectsRepositoryMock, savedObjectsServiceMock } from '@kbn/core/server/mocks'; +import { HealthStatus } from '@kbn/alerting-types'; import { RULE_SAVED_OBJECT_TYPE } from '../saved_objects'; -import { RuleExecutionStatusErrorReasons, HealthStatus } from '../types'; +import { RuleExecutionStatusErrorReasons } from '../types'; import { getAlertingHealthStatus, getHealth } from './get_health'; const savedObjectsRepository = savedObjectsRepositoryMock.create(); diff --git a/x-pack/plugins/alerting/server/health/get_health.ts b/x-pack/plugins/alerting/server/health/get_health.ts index f4b727170a4b9..f29a88dec6489 100644 --- a/x-pack/plugins/alerting/server/health/get_health.ts +++ b/x-pack/plugins/alerting/server/health/get_health.ts @@ -6,8 +6,9 @@ */ import { ISavedObjectsRepository, SavedObjectsServiceStart } from '@kbn/core/server'; +import { AlertsHealth, HealthStatus } from '@kbn/alerting-types'; import { RULE_SAVED_OBJECT_TYPE } from '../saved_objects'; -import { AlertsHealth, HealthStatus, RawRule, RuleExecutionStatusErrorReasons } from '../types'; +import { RawRule, RuleExecutionStatusErrorReasons } from '../types'; import type { LatestTaskStateSchema } from './task_state'; export const getHealth = async ( diff --git a/x-pack/plugins/alerting/server/health/task.ts b/x-pack/plugins/alerting/server/health/task.ts index 7fb315d72deac..fb22fcf63c3fe 100644 --- a/x-pack/plugins/alerting/server/health/task.ts +++ b/x-pack/plugins/alerting/server/health/task.ts @@ -11,9 +11,9 @@ import { TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; +import { HealthStatus } from '@kbn/alerting-types'; import { AlertingConfig } from '../config'; import { AlertingPluginsStart } from '../plugin'; -import { HealthStatus } from '../types'; import { getAlertingHealthStatus } from './get_health'; import { stateSchemaByVersion, emptyState, type LatestTaskStateSchema } from './task_state'; diff --git a/x-pack/plugins/alerting/server/health/task_state.ts b/x-pack/plugins/alerting/server/health/task_state.ts index 8646abc17f712..fcb950f59d2c7 100644 --- a/x-pack/plugins/alerting/server/health/task_state.ts +++ b/x-pack/plugins/alerting/server/health/task_state.ts @@ -6,7 +6,7 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; -import { HealthStatus } from '../types'; +import { HealthStatus } from '@kbn/alerting-types'; /** * WARNING: Do not modify the existing versioned schema(s) below, instead define a new version (ex: 2, 3, 4). diff --git a/x-pack/plugins/alerting/server/lib/create_get_alert_indices_alias.test.ts b/x-pack/plugins/alerting/server/lib/create_get_alert_indices_alias.test.ts index 32fac87b7485b..9a8109977962c 100644 --- a/x-pack/plugins/alerting/server/lib/create_get_alert_indices_alias.test.ts +++ b/x-pack/plugins/alerting/server/lib/create_get_alert_indices_alias.test.ts @@ -33,7 +33,6 @@ describe('createGetAlertIndicesAliasFn', () => { licensing: licensingMock.createSetup(), minimumScheduleInterval: { value: '1m', enforce: false }, inMemoryMetrics, - latestRuleVersion: 1, }; const registry = new RuleTypeRegistry(ruleTypeRegistryParams); registry.register({ diff --git a/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.ts b/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.ts index 44a0681e6a36e..ac5b407e4029c 100644 --- a/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.ts +++ b/x-pack/plugins/alerting/server/lib/wrap_scoped_cluster_client.ts @@ -81,18 +81,47 @@ export function createWrappedScopedClusterClientFactory( }; } +class WrappedScopedClusterClientImpl implements IScopedClusterClient { + #asInternalUser?: ElasticsearchClient; + #asCurrentUser?: ElasticsearchClient; + #asSecondaryAuthUser?: ElasticsearchClient; + + constructor(private readonly opts: WrapScopedClusterClientOpts) {} + + public get asInternalUser() { + if (this.#asInternalUser === undefined) { + const { scopedClusterClient, ...rest } = this.opts; + this.#asInternalUser = wrapEsClient({ + ...rest, + esClient: scopedClusterClient.asInternalUser, + }); + } + return this.#asInternalUser; + } + public get asCurrentUser() { + if (this.#asCurrentUser === undefined) { + const { scopedClusterClient, ...rest } = this.opts; + this.#asCurrentUser = wrapEsClient({ + ...rest, + esClient: scopedClusterClient.asCurrentUser, + }); + } + return this.#asCurrentUser; + } + public get asSecondaryAuthUser() { + if (this.#asSecondaryAuthUser === undefined) { + const { scopedClusterClient, ...rest } = this.opts; + this.#asSecondaryAuthUser = wrapEsClient({ + ...rest, + esClient: scopedClusterClient.asSecondaryAuthUser, + }); + } + return this.#asSecondaryAuthUser; + } +} + function wrapScopedClusterClient(opts: WrapScopedClusterClientOpts): IScopedClusterClient { - const { scopedClusterClient, ...rest } = opts; - return { - asInternalUser: wrapEsClient({ - ...rest, - esClient: scopedClusterClient.asInternalUser, - }), - asCurrentUser: wrapEsClient({ - ...rest, - esClient: scopedClusterClient.asCurrentUser, - }), - }; + return new WrappedScopedClusterClientImpl(opts); } function wrapEsClient(opts: WrapEsClientOpts): ElasticsearchClient { diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 884fa761526ea..1eb130f523c9c 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -79,7 +79,6 @@ import { registerAlertingUsageCollector } from './usage'; import { initializeAlertingTelemetry, scheduleAlertingTelemetry } from './usage/task'; import { setupSavedObjects, - getLatestRuleVersion, RULE_SAVED_OBJECT_TYPE, AD_HOC_RUN_SAVED_OBJECT_TYPE, } from './saved_objects'; @@ -333,7 +332,6 @@ export class AlertingPlugin { alertsService: this.alertsService, minimumScheduleInterval: this.config.rules.minimumScheduleInterval, inMemoryMetrics: this.inMemoryMetrics, - latestRuleVersion: getLatestRuleVersion(), }); this.ruleTypeRegistry = ruleTypeRegistry; diff --git a/x-pack/plugins/alerting/server/routes/health.test.ts b/x-pack/plugins/alerting/server/routes/health.test.ts index d72d4fe1cf2a2..28117eaeeb55b 100644 --- a/x-pack/plugins/alerting/server/routes/health.test.ts +++ b/x-pack/plugins/alerting/server/routes/health.test.ts @@ -7,12 +7,12 @@ import { healthRoute } from './health'; import { httpServiceMock } from '@kbn/core/server/mocks'; +import { HealthStatus } from '@kbn/alerting-types'; import { mockHandlerArguments } from './_mock_handler_arguments'; import { verifyApiAccess } from '../lib/license_api_access'; import { licenseStateMock } from '../lib/license_state.mock'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; import { rulesClientMock } from '../rules_client.mock'; -import { HealthStatus } from '../types'; import { alertsMock } from '../mocks'; import { RecoveredActionGroup } from '../../common'; import { RegistryAlertTypeWithAuth } from '../authorization'; diff --git a/x-pack/plugins/alerting/server/routes/legacy/health.test.ts b/x-pack/plugins/alerting/server/routes/legacy/health.test.ts index 8c3702ca3c833..d582a8e0ec480 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/health.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/health.test.ts @@ -5,13 +5,14 @@ * 2.0. */ import { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/usage_counters/usage_counters_service.mock'; +import { HealthStatus } from '@kbn/alerting-types'; import { healthRoute } from './health'; import { httpServiceMock } from '@kbn/core/server/mocks'; import { mockHandlerArguments } from '../_mock_handler_arguments'; import { licenseStateMock } from '../../lib/license_state.mock'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; import { rulesClientMock } from '../../rules_client.mock'; -import { HealthStatus, RecoveredActionGroup } from '../../types'; +import { RecoveredActionGroup } from '../../types'; import { alertsMock } from '../../mocks'; import { trackLegacyRouteUsage } from '../../lib/track_legacy_route_usage'; import { RegistryAlertTypeWithAuth } from '../../authorization'; diff --git a/x-pack/plugins/alerting/server/rule_type_registry.mock.ts b/x-pack/plugins/alerting/server/rule_type_registry.mock.ts index 0aa7e5b68b40c..706484fdd92f6 100644 --- a/x-pack/plugins/alerting/server/rule_type_registry.mock.ts +++ b/x-pack/plugins/alerting/server/rule_type_registry.mock.ts @@ -18,7 +18,6 @@ const createRuleTypeRegistryMock = () => { list: jest.fn(), getAllTypes: jest.fn(), ensureRuleTypeEnabled: jest.fn(), - getLatestRuleVersion: jest.fn(), }; return mocked; }; diff --git a/x-pack/plugins/alerting/server/rule_type_registry.test.ts b/x-pack/plugins/alerting/server/rule_type_registry.test.ts index 2059f1e7548e6..709533bb898f2 100644 --- a/x-pack/plugins/alerting/server/rule_type_registry.test.ts +++ b/x-pack/plugins/alerting/server/rule_type_registry.test.ts @@ -40,7 +40,6 @@ beforeEach(() => { licensing: licensingMock.createSetup(), minimumScheduleInterval: { value: '1m', enforce: false }, inMemoryMetrics, - latestRuleVersion: 1, }; }); @@ -913,16 +912,6 @@ describe('Create Lifecycle', () => { ).toThrowErrorMatchingInlineSnapshot(`"Fail"`); }); }); - - describe('getLatestRuleVersion', () => { - test('should return the latest rule version', async () => { - const ruleTypeRegistry = new RuleTypeRegistry({ - ...ruleTypeRegistryParams, - latestRuleVersion: 5, - }); - expect(ruleTypeRegistry.getLatestRuleVersion()).toBe(5); - }); - }); }); function ruleTypeWithVariables( diff --git a/x-pack/plugins/alerting/server/rule_type_registry.ts b/x-pack/plugins/alerting/server/rule_type_registry.ts index 79c959a62eedf..50ab1f113ab8b 100644 --- a/x-pack/plugins/alerting/server/rule_type_registry.ts +++ b/x-pack/plugins/alerting/server/rule_type_registry.ts @@ -50,7 +50,6 @@ export interface ConstructorOptions { minimumScheduleInterval: AlertingRulesConfig['minimumScheduleInterval']; inMemoryMetrics: InMemoryMetrics; alertsService: AlertsService | null; - latestRuleVersion: number; } export interface RegistryRuleType @@ -160,7 +159,6 @@ export class RuleTypeRegistry { private readonly licensing: LicensingPluginSetup; private readonly inMemoryMetrics: InMemoryMetrics; private readonly alertsService: AlertsService | null; - private readonly latestRuleVersion: number; constructor({ config, @@ -172,7 +170,6 @@ export class RuleTypeRegistry { minimumScheduleInterval, inMemoryMetrics, alertsService, - latestRuleVersion, }: ConstructorOptions) { this.config = config; this.logger = logger; @@ -183,7 +180,6 @@ export class RuleTypeRegistry { this.minimumScheduleInterval = minimumScheduleInterval; this.inMemoryMetrics = inMemoryMetrics; this.alertsService = alertsService; - this.latestRuleVersion = latestRuleVersion; } public has(id: string) { @@ -436,10 +432,6 @@ export class RuleTypeRegistry { public getAllTypes(): string[] { return [...this.ruleTypes.keys()]; } - - public getLatestRuleVersion() { - return this.latestRuleVersion; - } } function normalizedActionVariables(actionVariables: RuleType['actionVariables']) { diff --git a/x-pack/plugins/alerting/server/saved_objects/ad_hoc_run_params_model_versions.ts b/x-pack/plugins/alerting/server/saved_objects/ad_hoc_run_params_model_versions.ts deleted file mode 100644 index 10d8dc759e9be..0000000000000 --- a/x-pack/plugins/alerting/server/saved_objects/ad_hoc_run_params_model_versions.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - SavedObjectsModelVersion, - SavedObjectsModelVersionMap, -} from '@kbn/core-saved-objects-server'; -import { AdHocRunSO } from '../data/ad_hoc_run/types'; -import { rawAdHocRunParamsSchemaV1 } from './schemas/raw_ad_hoc_run_params'; - -interface CustomSavedObjectsModelVersion extends SavedObjectsModelVersion { - isCompatibleWithPreviousVersion: (param: AdHocRunSO) => boolean; -} - -export interface CustomSavedObjectsModelVersionMap extends SavedObjectsModelVersionMap { - [modelVersion: string]: CustomSavedObjectsModelVersion; -} - -export const adHocRunParamsModelVersions: CustomSavedObjectsModelVersionMap = { - '1': { - changes: [], - schemas: { - forwardCompatibility: rawAdHocRunParamsSchemaV1.extends({}, { unknowns: 'ignore' }), - create: rawAdHocRunParamsSchemaV1, - }, - isCompatibleWithPreviousVersion: () => true, - }, -}; - -export const getLatestAdHocRunParamsVersion = () => - Math.max(...Object.keys(adHocRunParamsModelVersions).map(Number)); - -export function getMinimumCompatibleVersion( - modelVersions: CustomSavedObjectsModelVersionMap, - version: number, - adHocRunParam: AdHocRunSO -): number { - if (version === 1) { - return 1; - } - - if (modelVersions[version].isCompatibleWithPreviousVersion(adHocRunParam)) { - return getMinimumCompatibleVersion(modelVersions, version - 1, adHocRunParam); - } - - return version; -} diff --git a/x-pack/plugins/alerting/server/saved_objects/index.ts b/x-pack/plugins/alerting/server/saved_objects/index.ts index 0dd261c4c39f1..eb07a84950d14 100644 --- a/x-pack/plugins/alerting/server/saved_objects/index.ts +++ b/x-pack/plugins/alerting/server/saved_objects/index.ts @@ -24,13 +24,11 @@ import { getImportWarnings } from './get_import_warnings'; import { isRuleExportable } from './is_rule_exportable'; import { RuleTypeRegistry } from '../rule_type_registry'; export { partiallyUpdateRule } from './partially_update_rule'; -export { getLatestRuleVersion, getMinimumCompatibleVersion } from './rule_model_versions'; import { RULES_SETTINGS_SAVED_OBJECT_TYPE, MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE, } from '../../common'; -import { ruleModelVersions } from './rule_model_versions'; -import { adHocRunParamsModelVersions } from './ad_hoc_run_params_model_versions'; +import { ruleModelVersions, adHocRunParamsModelVersions } from './model_versions'; export const RULE_SAVED_OBJECT_TYPE = 'alert'; export const AD_HOC_RUN_SAVED_OBJECT_TYPE = 'ad_hoc_run_params'; diff --git a/x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.test.ts b/x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.test.ts index 7ed188915c869..27145ecfe072f 100644 --- a/x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.test.ts +++ b/x-pack/plugins/alerting/server/saved_objects/is_rule_exportable.test.ts @@ -38,7 +38,6 @@ beforeEach(() => { licensing: licensingMock.createSetup(), minimumScheduleInterval: { value: '1m', enforce: false }, inMemoryMetrics, - latestRuleVersion: 1, }; }); diff --git a/x-pack/plugins/alerting/server/saved_objects/model_versions/ad_hoc_run_params_model_versions.ts b/x-pack/plugins/alerting/server/saved_objects/model_versions/ad_hoc_run_params_model_versions.ts new file mode 100644 index 0000000000000..95f544be5c8e2 --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/model_versions/ad_hoc_run_params_model_versions.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 { SavedObjectsModelVersionMap } from '@kbn/core-saved-objects-server'; +import { rawAdHocRunParamsSchemaV1 } from '../schemas/raw_ad_hoc_run_params'; + +export const adHocRunParamsModelVersions: SavedObjectsModelVersionMap = { + '1': { + changes: [], + schemas: { + forwardCompatibility: rawAdHocRunParamsSchemaV1.extends({}, { unknowns: 'ignore' }), + create: rawAdHocRunParamsSchemaV1, + }, + }, +}; diff --git a/x-pack/plugins/alerting/server/saved_objects/model_versions/index.ts b/x-pack/plugins/alerting/server/saved_objects/model_versions/index.ts new file mode 100644 index 0000000000000..89c4f3a3cd2bb --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/model_versions/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { adHocRunParamsModelVersions } from './ad_hoc_run_params_model_versions'; +export { ruleModelVersions } from './rule_model_versions'; diff --git a/x-pack/plugins/alerting/server/saved_objects/model_versions/rule_model_versions.ts b/x-pack/plugins/alerting/server/saved_objects/model_versions/rule_model_versions.ts new file mode 100644 index 0000000000000..2d778667d2b79 --- /dev/null +++ b/x-pack/plugins/alerting/server/saved_objects/model_versions/rule_model_versions.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 { SavedObjectsModelVersionMap } from '@kbn/core-saved-objects-server'; +import { rawRuleSchemaV1 } from '../schemas/raw_rule'; + +export const ruleModelVersions: SavedObjectsModelVersionMap = { + '1': { + changes: [], + schemas: { + create: rawRuleSchemaV1, + }, + }, +}; diff --git a/x-pack/plugins/alerting/server/saved_objects/rule_model_versions.test.ts b/x-pack/plugins/alerting/server/saved_objects/rule_model_versions.test.ts deleted file mode 100644 index 9afcdaad8e2f4..0000000000000 --- a/x-pack/plugins/alerting/server/saved_objects/rule_model_versions.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - CustomSavedObjectsModelVersionMap, - getLatestRuleVersion, - getMinimumCompatibleVersion, -} from './rule_model_versions'; -import { schema } from '@kbn/config-schema'; -import { RawRule } from '../types'; - -describe('rule model versions', () => { - const ruleModelVersions: CustomSavedObjectsModelVersionMap = { - '1': { - changes: [], - schemas: { - create: schema.object({ - name: schema.string(), - }), - }, - isCompatibleWithPreviousVersion: (rawRule) => true, - }, - '2': { - changes: [], - schemas: { - create: schema.object({ - name: schema.string(), - }), - }, - isCompatibleWithPreviousVersion: (rawRule) => false, - }, - '3': { - changes: [], - schemas: { - create: schema.object({ - name: schema.string(), - }), - }, - isCompatibleWithPreviousVersion: (rawRule) => rawRule.name === 'test', - }, - '4': { - changes: [], - schemas: { - create: schema.object({ - name: schema.string(), - }), - }, - isCompatibleWithPreviousVersion: (rawRule) => rawRule.name === 'test', - }, - }; - - const rawRule = { name: 'test' } as RawRule; - const mismatchingRawRule = { enabled: true } as RawRule; - - describe('getMinimumCompatibleVersion', () => { - it('should return the minimum compatible version for the matching rawRule', () => { - expect(getMinimumCompatibleVersion(ruleModelVersions, 1, rawRule)).toBe(1); - expect(getMinimumCompatibleVersion(ruleModelVersions, 2, rawRule)).toBe(2); - expect(getMinimumCompatibleVersion(ruleModelVersions, 3, rawRule)).toBe(2); - expect(getMinimumCompatibleVersion(ruleModelVersions, 4, rawRule)).toBe(2); - }); - it('should return the minimum compatible version for the mismatching rawRule', () => { - expect(getMinimumCompatibleVersion(ruleModelVersions, 3, mismatchingRawRule)).toBe(3); - expect(getMinimumCompatibleVersion(ruleModelVersions, 4, mismatchingRawRule)).toBe(4); - }); - }); - - describe('getLatestRuleVersion', () => { - it('should return the latest rule model version', () => { - expect(getLatestRuleVersion()).toBe(1); - }); - }); -}); diff --git a/x-pack/plugins/alerting/server/saved_objects/rule_model_versions.ts b/x-pack/plugins/alerting/server/saved_objects/rule_model_versions.ts deleted file mode 100644 index 38adc17389b23..0000000000000 --- a/x-pack/plugins/alerting/server/saved_objects/rule_model_versions.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - SavedObjectsModelVersion, - SavedObjectsModelVersionMap, -} from '@kbn/core-saved-objects-server'; -import { RawRule } from '../types'; -import { rawRuleSchemaV1 } from './schemas/raw_rule'; - -interface CustomSavedObjectsModelVersion extends SavedObjectsModelVersion { - isCompatibleWithPreviousVersion: (param: RawRule) => boolean; -} - -export interface CustomSavedObjectsModelVersionMap extends SavedObjectsModelVersionMap { - [modelVersion: string]: CustomSavedObjectsModelVersion; -} - -export const ruleModelVersions: CustomSavedObjectsModelVersionMap = { - '1': { - changes: [], - schemas: { - create: rawRuleSchemaV1, - }, - isCompatibleWithPreviousVersion: (rawRule) => true, - }, -}; - -export const getLatestRuleVersion = () => Math.max(...Object.keys(ruleModelVersions).map(Number)); - -export function getMinimumCompatibleVersion( - modelVersions: CustomSavedObjectsModelVersionMap, - version: number, - rawRule: RawRule -): number { - if (version === 1) { - return 1; - } - - if (modelVersions[version].isCompatibleWithPreviousVersion(rawRule)) { - return getMinimumCompatibleVersion(modelVersions, version - 1, rawRule); - } - - return version; -} diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index d09bda0bc0cb8..342c3070379c5 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -28,6 +28,7 @@ import type { DefaultAlert, FieldMap } from '@kbn/alerts-as-data-utils'; import { Alert } from '@kbn/alerts-as-data-utils'; import { Filter } from '@kbn/es-query'; import { ActionsApiRequestHandlerContext } from '@kbn/actions-plugin/server'; +import { AlertsHealth } from '@kbn/alerting-types'; import { RuleTypeRegistry as OrigruleTypeRegistry } from './rule_type_registry'; import { PluginSetupContract, PluginStartContract } from './plugin'; import { RulesClient } from './rules_client'; @@ -50,7 +51,6 @@ import { ActionGroup, AlertInstanceContext, AlertInstanceState, - AlertsHealth, WithoutReservedActionGroups, ActionVariable, SanitizedRuleConfig, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.test.ts index 88acf0deabb35..6a5beeeac3464 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.test.ts @@ -6,29 +6,6 @@ */ import { savedMap } from './saved_map'; -import { getQueryFilters } from '../../../common/lib/build_embeddable_filters'; -import { ExpressionValueFilter } from '../../../types'; - -const filterContext: ExpressionValueFilter = { - type: 'filter', - and: [ - { - type: 'filter', - and: [], - value: 'filter-value', - column: 'filter-column', - filterType: 'exactly', - }, - { - type: 'filter', - and: [], - column: 'time-column', - filterType: 'time', - from: '2019-06-04T04:00:00.000Z', - to: '2019-06-05T04:00:00.000Z', - }, - ], -}; describe('savedMap', () => { const fn = savedMap().fn; @@ -43,13 +20,18 @@ describe('savedMap', () => { it('accepts null context', () => { const expression = fn(null, args, {} as any); - expect(expression.input.filters).toEqual([]); - }); - - it('accepts filter context', () => { - const expression = fn(filterContext, args, {} as any); - const embeddableFilters = getQueryFilters(filterContext.and); - - expect(expression.input.filters).toEqual(embeddableFilters); + expect(expression.input).toEqual({ + hiddenLayers: [], + hideFilterActions: true, + id: 'some-id', + isLayerTOCOpen: false, + mapCenter: undefined, + savedObjectId: 'some-id', + timeRange: { + from: 'now-15m', + to: 'now', + }, + title: undefined, + }); }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts index 14c0e11c69acd..41ea76e4905cc 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/saved_map.ts @@ -6,9 +6,8 @@ */ import { ExpressionFunctionDefinition } from '@kbn/expressions-plugin/common'; -import type { MapEmbeddableInput } from '@kbn/maps-plugin/public'; +import type { MapSerializedState } from '@kbn/maps-plugin/public'; import { SavedObjectReference } from '@kbn/core/types'; -import { getQueryFilters } from '../../../common/lib/build_embeddable_filters'; import { ExpressionValueFilter, MapCenter, TimeRange as TimeRangeArg } from '../../../types'; import { EmbeddableTypes, @@ -30,7 +29,7 @@ const defaultTimeRange = { to: 'now', }; -type Output = EmbeddableExpression; +type Output = EmbeddableExpression; export function savedMap(): ExpressionFunctionDefinition< 'savedMap', @@ -72,30 +71,19 @@ export function savedMap(): ExpressionFunctionDefinition< }, type: EmbeddableExpressionType, fn: (input, args) => { - const filters = input ? input.and : []; - - const center = args.center - ? { - lat: args.center.lat, - lon: args.center.lon, - zoom: args.center.zoom, - } - : undefined; - return { type: EmbeddableExpressionType, input: { id: args.id, - attributes: { title: '' }, savedObjectId: args.id, - filters: getQueryFilters(filters), timeRange: args.timerange || defaultTimeRange, - refreshConfig: { - pause: false, - value: 0, - }, - - mapCenter: center, + mapCenter: args.center + ? { + lat: args.center.lat, + lon: args.center.lon, + zoom: args.center.zoom, + } + : undefined, hideFilterActions: true, title: args.title === null ? undefined : args.title, isLayerTOCOpen: false, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts index 2e1c0d8c6cf5d..8c61c7a8006e2 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/map.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { MapEmbeddableInput } from '@kbn/maps-plugin/public'; +import { MapSerializedState } from '@kbn/maps-plugin/public'; -export function toExpression(input: MapEmbeddableInput & { savedObjectId: string }): string { +export function toExpression(input: MapSerializedState & { id: string }): string { const expressionParts = [] as string[]; expressionParts.push('savedMap'); diff --git a/x-pack/plugins/cases/public/components/case_view/components/edit_tags.test.tsx b/x-pack/plugins/cases/public/components/case_view/components/edit_tags.test.tsx index 7ced679368434..583d7599d14cb 100644 --- a/x-pack/plugins/cases/public/components/case_view/components/edit_tags.test.tsx +++ b/x-pack/plugins/cases/public/components/case_view/components/edit_tags.test.tsx @@ -25,8 +25,7 @@ const defaultProps: EditTagsProps = { tags: [], }; -// FLAKY: https://github.com/elastic/kibana/issues/175655 -describe.skip('EditTags ', () => { +describe('EditTags ', () => { let appMockRender: AppMockRenderer; const sampleTags = ['coke', 'pepsi']; @@ -43,18 +42,11 @@ describe.skip('EditTags ', () => { appMockRender = createAppMockRenderer(); }); - it('renders no tags, and then edit', async () => { + it('renders no tags message', async () => { appMockRender.render(); expect(await screen.findByTestId('no-tags')).toBeInTheDocument(); - - userEvent.click(await screen.findByTestId('tag-list-edit-button')); - - await waitFor(() => { - expect(screen.queryByTestId('no-tags')).not.toBeInTheDocument(); - }); - - expect(await screen.findByTestId('edit-tags')).toBeInTheDocument(); + expect(await screen.findByTestId('tag-list-edit-button')).toBeInTheDocument(); }); it('edit tag from options on submit', async () => { diff --git a/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.test.ts b/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.test.ts index 1574c1ddd2580..5732085d99c8e 100644 --- a/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.test.ts +++ b/x-pack/plugins/cases/public/containers/user_profiles/use_bulk_get_user_profiles.test.ts @@ -19,7 +19,8 @@ jest.mock('./api'); const useKibanaMock = useKibana as jest.Mock; -describe('useBulkGetUserProfiles', () => { +// FLAKY: https://github.com/elastic/kibana/issues/176335 +describe.skip('useBulkGetUserProfiles', () => { const props = { uids: userProfilesIds, }; diff --git a/x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts b/x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts index e907a8bc024b7..204b940c45cd5 100644 --- a/x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts +++ b/x-pack/plugins/cloud/common/register_cloud_deployment_id_analytics_context.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { AnalyticsClient } from '@kbn/analytics-client'; +import type { AnalyticsClient } from '@kbn/ebt/client'; import { of } from 'rxjs'; import { parseDeploymentIdFromDeploymentUrl } from './parse_deployment_id_from_deployment_url'; diff --git a/x-pack/plugins/cloud/tsconfig.json b/x-pack/plugins/cloud/tsconfig.json index f5d0f3623fc78..ec6d5881a0531 100644 --- a/x-pack/plugins/cloud/tsconfig.json +++ b/x-pack/plugins/cloud/tsconfig.json @@ -13,10 +13,10 @@ "kbn_references": [ "@kbn/core", "@kbn/usage-collection-plugin", - "@kbn/analytics-client", "@kbn/config-schema", "@kbn/logging-mocks", "@kbn/logging", + "@kbn/ebt", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/public/plugin.ts b/x-pack/plugins/cloud_integrations/cloud_full_story/public/plugin.ts index a9af52c9a6b31..a3b7207eb3122 100755 --- a/x-pack/plugins/cloud_integrations/cloud_full_story/public/plugin.ts +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/public/plugin.ts @@ -69,7 +69,7 @@ export class CloudFullStoryPlugin implements Plugin { } // Keep this import async so that we do not load any FullStory code into the browser when it is disabled. - const { FullStoryShipper } = await import('@kbn/analytics-shippers-fullstory'); + const { FullStoryShipper } = await import('@kbn/ebt/shippers/fullstory'); analytics.registerShipper(FullStoryShipper, { eventTypesAllowlist, fullStoryOrgId, diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/tsconfig.json b/x-pack/plugins/cloud_integrations/cloud_full_story/tsconfig.json index 47b6a5837a829..852ce8bb8af86 100644 --- a/x-pack/plugins/cloud_integrations/cloud_full_story/tsconfig.json +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/tsconfig.json @@ -14,7 +14,7 @@ "@kbn/core", "@kbn/cloud-plugin", "@kbn/config-schema", - "@kbn/analytics-shippers-fullstory", + "@kbn/ebt", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/cloud_security_posture/jest.config.js b/x-pack/plugins/cloud_security_posture/jest.config.js index eb1e880646e56..82ecbd0c85592 100644 --- a/x-pack/plugins/cloud_security_posture/jest.config.js +++ b/x-pack/plugins/cloud_security_posture/jest.config.js @@ -15,4 +15,13 @@ module.exports = { collectCoverageFrom: [ '/x-pack/plugins/cloud_security_posture/{common,public,server}/**/*.{ts,tsx}', ], + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + transformIgnorePatterns: [ + // ignore all node_modules except the modules below (monaco-editor, monaco-yaml, react-monaco-editor, etc) which requires babel transforms to handle dynamic import() + // since ESM modules are not natively supported in Jest yet (https://github.com/facebook/jest/issues/4842) + '[/\\\\]node_modules(?![\\/\\\\](byte-size|monaco-editor|monaco-yaml|monaco-languageserver-types|monaco-marker-data-provider|monaco-worker-manager|vscode-languageserver-types|react-monaco-editor|d3-interpolate|d3-color|langchain|langsmith|@cfworker|gpt-tokenizer|flat|@langchain|msw|@bundled-es-modules))[/\\\\].+\\.js$', + 'packages/kbn-pm/dist/index.js', + '[/\\\\]node_modules(?![\\/\\\\](langchain|langsmith|@langchain))/dist/[/\\\\].+\\.js$', + '[/\\\\]node_modules(?![\\/\\\\](langchain|langsmith|@langchain))/dist/util/[/\\\\].+\\.js$', + ], }; diff --git a/x-pack/plugins/cloud_security_posture/public/common/api/use_license_management_locator_api.tsx b/x-pack/plugins/cloud_security_posture/public/common/api/use_license_management_locator_api.tsx index 963d9bfae9834..cbfd64b8e60a8 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/api/use_license_management_locator_api.tsx +++ b/x-pack/plugins/cloud_security_posture/public/common/api/use_license_management_locator_api.tsx @@ -5,23 +5,21 @@ * 2.0. */ -import { useQuery } from '@tanstack/react-query'; import { useKibana } from '../hooks/use_kibana'; const LICENSE_MANAGEMENT_LOCATOR = 'LICENSE_MANAGEMENT_LOCATOR'; -const getLicenseManagementLocatorKey = 'license_management_url_key'; - +/** + * Hook to get the license management locator + * @returns a callback to navigate to the license management page + */ export const useLicenseManagementLocatorApi = () => { const { share } = useKibana().services; - return useQuery([getLicenseManagementLocatorKey], () => { - const locator = share.url.locators.get(LICENSE_MANAGEMENT_LOCATOR); - // license management does not exist on serverless - if (!locator) return; + const locator = share.url.locators.get(LICENSE_MANAGEMENT_LOCATOR); + + // license management does not exist on serverless + if (!locator) return; - return locator.getUrl({ - page: 'dashboard', - }); - }); + return () => locator.navigate({ page: 'dashboard' }); }; diff --git a/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.test.tsx b/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.test.tsx index f2e538194a2fa..05fedd4d8adec 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.test.tsx @@ -43,12 +43,7 @@ describe('', () => { }) ); - (useLicenseManagementLocatorApi as jest.Mock).mockImplementation(() => - createReactQueryResponse({ - status: 'success', - data: true, - }) - ); + (useLicenseManagementLocatorApi as jest.Mock).mockImplementation(undefined); }); const renderCloudPosturePage = ( @@ -85,7 +80,10 @@ describe('', () => { }) ); + (useLicenseManagementLocatorApi as jest.Mock).mockImplementation(() => 'http://license-url'); + renderCloudPosturePage(); + expect(screen.getByTestId('has_locator')).toBeInTheDocument(); }); @@ -97,12 +95,7 @@ describe('', () => { }) ); - (useLicenseManagementLocatorApi as jest.Mock).mockImplementation(() => - createReactQueryResponse({ - status: 'success', - data: undefined, - }) - ); + (useLicenseManagementLocatorApi as jest.Mock).mockImplementation(undefined); renderCloudPosturePage(); expect(screen.getByTestId('no_locator')).toBeInTheDocument(); diff --git a/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.tsx b/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.tsx index bfcaf85cfc457..b2f1d892da907 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.tsx @@ -15,7 +15,6 @@ import { SubscriptionNotAllowed } from './subscription_not_allowed'; import { useSubscriptionStatus } from '../common/hooks/use_subscription_status'; import { FullSizeCenteredPage } from './full_size_centered_page'; import { CspLoadingState } from './csp_loading_state'; -import { useLicenseManagementLocatorApi } from '../common/api/use_license_management_locator_api'; export const LOADING_STATE_TEST_SUBJECT = 'cloud_posture_page_loading'; export const ERROR_STATE_TEST_SUBJECT = 'cloud_posture_page_error'; @@ -151,9 +150,9 @@ export const defaultNoDataRenderer = () => ( ); -const subscriptionNotAllowedRenderer = (licenseManagementLocator?: string) => ( +const subscriptionNotAllowedRenderer = () => ( - + ); @@ -173,19 +172,18 @@ export const CloudPosturePage = ({ noDataRenderer = defaultNoDataRenderer, }: CloudPosturePageProps) => { const subscriptionStatus = useSubscriptionStatus(); - const getLicenseManagementLocator = useLicenseManagementLocatorApi(); const render = () => { if (subscriptionStatus.isError) { return defaultErrorRenderer(subscriptionStatus.error); } - if (subscriptionStatus.isLoading || getLicenseManagementLocator.isLoading) { + if (subscriptionStatus.isLoading) { return defaultLoadingRenderer(); } if (!subscriptionStatus.data) { - return subscriptionNotAllowedRenderer(getLicenseManagementLocator.data); + return subscriptionNotAllowedRenderer(); } if (!query) { diff --git a/x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/cloud_security_data_table.tsx b/x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/cloud_security_data_table.tsx index 623de8401810c..5c0cbd048f9d1 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/cloud_security_data_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/cloud_security_data_table/cloud_security_data_table.tsx @@ -5,7 +5,12 @@ * 2.0. */ import React, { useState, useMemo } from 'react'; -import { UnifiedDataTableSettings, useColumns } from '@kbn/unified-data-table'; +import _ from 'lodash'; +import { + UnifiedDataTableSettings, + UnifiedDataTableSettingsColumn, + useColumns, +} from '@kbn/unified-data-table'; import { UnifiedDataTable, DataLoadingState } from '@kbn/unified-data-table'; import { CellActionsProvider } from '@kbn/cell-actions'; import { HttpSetup } from '@kbn/core-http-browser'; @@ -130,20 +135,34 @@ export const CloudSecurityDataTable = ({ columnsLocalStorageKey, defaultColumns.map((c) => c.id) ); - const [settings, setSettings] = useLocalStorage( + const [persistedSettings, setPersistedSettings] = useLocalStorage( `${columnsLocalStorageKey}:settings`, { - columns: defaultColumns.reduce((prev, curr) => { - const columnDefaultSettings = curr.width - ? { width: curr.width, display: columnHeaders?.[curr.id] } - : { display: columnHeaders?.[curr.id] }; - const newColumn = { [curr.id]: columnDefaultSettings }; - return { ...prev, ...newColumn }; + columns: defaultColumns.reduce((columnSettings, column) => { + const columnDefaultSettings = column.width ? { width: column.width } : {}; + const newColumn = { [column.id]: columnDefaultSettings }; + return { ...columnSettings, ...newColumn }; }, {} as UnifiedDataTableSettings['columns']), } ); - const { dataView, dataViewIsRefetching, dataViewRefetch } = useDataViewContext(); + const settings = useMemo(() => { + return { + columns: Object.keys(persistedSettings?.columns as UnifiedDataTableSettings).reduce( + (columnSettings, columnId) => { + const newColumn: UnifiedDataTableSettingsColumn = { + ..._.pick(persistedSettings?.columns?.[columnId], ['width']), + display: columnHeaders?.[columnId], + }; + + return { ...columnSettings, [columnId]: newColumn }; + }, + {} as UnifiedDataTableSettings['columns'] + ), + }; + }, [persistedSettings, columnHeaders]); + + const { dataView, dataViewIsRefetching } = useDataViewContext(); const [expandedDoc, setExpandedDoc] = useState(undefined); @@ -161,7 +180,6 @@ export const CloudSecurityDataTable = ({ fieldFormats, toastNotifications, storage, - dataViewFieldEditor, } = useKibana().services; const styles = useStyles(); @@ -176,7 +194,6 @@ export const CloudSecurityDataTable = ({ toastNotifications, storage, data, - dataViewFieldEditor, }; const { @@ -242,14 +259,13 @@ export const CloudSecurityDataTable = ({ ); const onResize = (colSettings: { columnId: string; width: number }) => { - const grid = settings || {}; + const grid = persistedSettings || {}; const newColumns = { ...(grid.columns || {}) }; newColumns[colSettings.columnId] = { width: Math.round(colSettings.width), - display: columnHeaders?.[colSettings.columnId], }; const newGrid = { ...grid, columns: newColumns }; - setSettings(newGrid); + setPersistedSettings(newGrid); }; const externalCustomRenderers = useMemo(() => { @@ -346,7 +362,6 @@ export const CloudSecurityDataTable = ({ gridStyleOverride={gridStyle} rowLineHeightOverride="24px" controlColumnIds={controlColumnIds} - onFieldEdited={dataViewRefetch} />
diff --git a/x-pack/plugins/cloud_security_posture/public/components/no_findings_states.test.tsx b/x-pack/plugins/cloud_security_posture/public/components/no_findings_states.test.tsx deleted file mode 100644 index 840aad9af2e94..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/components/no_findings_states.test.tsx +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { render } from '@testing-library/react'; -import { createReactQueryResponse } from '../test/fixtures/react_query'; -import { TestProvider } from '../test/test_provider'; -import { NoFindingsStates } from './no_findings_states'; -import { useCspSetupStatusApi } from '../common/api/use_setup_status_api'; -import { useCspIntegrationLink } from '../common/navigation/use_csp_integration_link'; -import { useLicenseManagementLocatorApi } from '../common/api/use_license_management_locator_api'; -import { useSubscriptionStatus } from '../common/hooks/use_subscription_status'; - -jest.mock('../common/api/use_setup_status_api', () => ({ - useCspSetupStatusApi: jest.fn(), -})); - -jest.mock('../common/navigation/use_csp_integration_link', () => ({ - useCspIntegrationLink: jest.fn(), -})); - -jest.mock('../common/api/use_license_management_locator_api', () => ({ - useLicenseManagementLocatorApi: jest.fn(), -})); - -jest.mock('../common/hooks/use_subscription_status', () => ({ - useSubscriptionStatus: jest.fn(), -})); - -const customRederer = (postureType: 'cspm' | 'kspm') => { - return render( - - - - ); -}; - -describe('NoFindingsStates', () => { - beforeEach(() => { - jest.clearAllMocks(); - - (useSubscriptionStatus as jest.Mock).mockImplementation(() => - createReactQueryResponse({ - status: 'success', - data: true, - }) - ); - - (useLicenseManagementLocatorApi as jest.Mock).mockImplementation(() => - createReactQueryResponse({ - status: 'success', - data: true, - }) - ); - - (useCspIntegrationLink as jest.Mock).mockReturnValue('http://cspm-or-kspm-integration-link'); - }); - - it('should show the indexing notification when CSPM is not installed and KSPM is indexing', async () => { - (useCspSetupStatusApi as jest.Mock).mockImplementation(() => - createReactQueryResponse({ - status: 'success', - data: { - cspm: { - status: 'not-deployed', - }, - kspm: { - status: 'indexing', - }, - indicesDetails: [ - { index: 'logs-cloud_security_posture.findings_latest-default', status: 'empty' }, - { index: 'logs-cloud_security_posture.findings-default*', status: 'empty' }, - ], - }, - }) - ); - - const { getByText } = customRederer('kspm'); - expect(getByText(/posture evaluation underway/i)).toBeInTheDocument(); - expect( - getByText( - /waiting for data to be collected and indexed. check back later to see your findings/i - ) - ).toBeInTheDocument(); - }); - - it('should show the indexing notification when KSPM is not installed and CSPM is indexing', async () => { - (useCspSetupStatusApi as jest.Mock).mockImplementation(() => - createReactQueryResponse({ - status: 'success', - data: { - kspm: { - status: 'not-deployed', - }, - cspm: { - status: 'indexing', - }, - indicesDetails: [ - { index: 'logs-cloud_security_posture.findings_latest-default', status: 'empty' }, - { index: 'logs-cloud_security_posture.findings-default*', status: 'empty' }, - ], - }, - }) - ); - - const { getByText } = customRederer('cspm'); - expect(getByText(/posture evaluation underway/i)).toBeInTheDocument(); - expect( - getByText( - /waiting for data to be collected and indexed. Check back later to see your findings/i - ) - ).toBeInTheDocument(); - }); - - it('should show the indexing timout notification when CSPM is status is index-timeout', async () => { - (useCspSetupStatusApi as jest.Mock).mockImplementation(() => - createReactQueryResponse({ - status: 'success', - data: { - kspm: { - status: 'installed', - }, - cspm: { - status: 'index-timeout', - }, - indicesDetails: [ - { index: 'logs-cloud_security_posture.findings_latest-default', status: 'empty' }, - { index: 'logs-cloud_security_posture.findings-default*', status: 'empty' }, - ], - }, - }) - ); - - const { getByText } = customRederer('cspm'); - expect(getByText(/waiting for findings data/i)).toBeInTheDocument(); - const indexTimeOutMessage = getByText(/collecting findings is taking longer than expected/i); - expect(indexTimeOutMessage).toBeInTheDocument(); - }); - - it('should show the indexing timout notification when KSPM is status is index-timeout', async () => { - (useCspSetupStatusApi as jest.Mock).mockImplementation(() => - createReactQueryResponse({ - status: 'success', - data: { - kspm: { - status: 'index-timeout', - }, - cspm: { - status: 'installed', - }, - indicesDetails: [ - { index: 'logs-cloud_security_posture.findings_latest-default', status: 'empty' }, - { index: 'logs-cloud_security_posture.findings-default*', status: 'empty' }, - ], - }, - }) - ); - - const { getByText } = customRederer('kspm'); - expect(getByText(/waiting for findings data/i)).toBeInTheDocument(); - expect(getByText(/collecting findings is taking longer than expected/i)).toBeInTheDocument(); - }); - - it('should show the unprivileged notification when CSPM is status is index-timeout', async () => { - (useCspSetupStatusApi as jest.Mock).mockImplementation(() => - createReactQueryResponse({ - status: 'success', - data: { - kspm: { - status: 'installed', - }, - cspm: { - status: 'unprivileged', - }, - indicesDetails: [ - { - index: 'logs-cloud_security_posture.findings_latest-default', - status: 'unprivileged', - }, - { index: 'logs-cloud_security_posture.findings-default*', status: 'unprivileged' }, - ], - }, - }) - ); - - const { getByText } = customRederer('cspm'); - expect(getByText(/privileges required/i)).toBeInTheDocument(); - }); - - it('should show the unprivileged notification when KSPM is status is index-timeout', async () => { - (useCspSetupStatusApi as jest.Mock).mockImplementation(() => - createReactQueryResponse({ - status: 'success', - data: { - kspm: { - status: 'unprivileged', - }, - cspm: { - status: 'installed', - }, - indicesDetails: [ - { - index: 'logs-cloud_security_posture.findings_latest-default', - status: 'unprivileged', - }, - { index: 'logs-cloud_security_posture.findings-default*', status: 'unprivileged' }, - ], - }, - }) - ); - - const { getByText } = customRederer('kspm'); - expect(getByText(/privileges required/i)).toBeInTheDocument(); - }); - - it('should show the not-installed notification when CSPM and KSPM status is not-installed', async () => { - (useCspSetupStatusApi as jest.Mock).mockImplementation(() => - createReactQueryResponse({ - status: 'success', - data: { - kspm: { - status: 'not-installed', - }, - cspm: { - status: 'not-installed', - }, - indicesDetails: [ - { - index: 'logs-cloud_security_posture.findings_latest-default', - status: 'success', - }, - { index: 'logs-cloud_security_posture.findings-default*', status: 'success' }, - ], - }, - }) - ); - - const { getByText } = customRederer('cspm'); - expect(getByText(/learn more about cloud security posture/i)).toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/lists/common/api/values/create_list_index/create_list_index_route.ts b/x-pack/plugins/cloud_security_posture/public/components/no_findings_states/index.ts similarity index 65% rename from x-pack/plugins/lists/common/api/values/create_list_index/create_list_index_route.ts rename to x-pack/plugins/cloud_security_posture/public/components/no_findings_states/index.ts index 85e094b273e13..e136062ed2ccb 100644 --- a/x-pack/plugins/lists/common/api/values/create_list_index/create_list_index_route.ts +++ b/x-pack/plugins/cloud_security_posture/public/components/no_findings_states/index.ts @@ -5,6 +5,4 @@ * 2.0. */ -import { acknowledgeSchema } from '@kbn/securitysolution-io-ts-list-types'; - -export { acknowledgeSchema as createListIndexResponse }; +export * from './no_findings_states'; diff --git a/x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.handlers.mock.ts b/x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.handlers.mock.ts new file mode 100644 index 0000000000000..904083ca86f5c --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.handlers.mock.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { http, HttpResponse } from 'msw'; + +export const fleetCspPackageHandler = http.get( + `/api/fleet/epm/packages/cloud_security_posture`, + () => { + return HttpResponse.json({ + item: { + name: 'cloud_security_posture', + version: '1.9.0', + }, + }); + } +); diff --git a/x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.test.tsx b/x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.test.tsx new file mode 100644 index 0000000000000..ad3e482f614f5 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.test.tsx @@ -0,0 +1,163 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { screen, waitFor } from '@testing-library/react'; +import { setupMockServer, startMockServer } from '../../test/mock_server/mock_server'; +import { renderWrapper } from '../../test/mock_server/mock_server_test_provider'; +import { NoFindingsStates } from './no_findings_states'; +import * as statusHandlers from '../../../server/routes/status/status.handlers.mock'; +import * as benchmarksHandlers from '../../../server/routes/benchmarks/benchmarks.handlers.mock'; +import { fleetCspPackageHandler } from './no_findings_states.handlers.mock'; + +const server = setupMockServer(); + +const renderNoFindingsStates = (postureType: 'cspm' | 'kspm' = 'cspm') => { + return renderWrapper(); +}; + +describe('NoFindingsStates', () => { + startMockServer(server); + + beforeEach(() => { + server.use(fleetCspPackageHandler); + }); + + it('shows integrations installation prompt with installation links when integration is not-installed', async () => { + server.use(statusHandlers.notInstalledHandler); + renderNoFindingsStates(); + expect(screen.getByText(/loading/i)).toBeInTheDocument(); + + await waitFor(() => { + expect( + screen.getByText(/detect security misconfigurations in your cloud infrastructure!/i) + ).toBeInTheDocument(); + }); + + await waitFor(() => { + expect(screen.getByRole('link', { name: /add cspm integration/i })).toHaveAttribute( + 'href', + '/app/fleet/integrations/cloud_security_posture-1.9.0/add-integration/cspm' + ); + }); + + await waitFor(() => { + expect(screen.getByRole('link', { name: /add kspm integration/i })).toHaveAttribute( + 'href', + '/app/fleet/integrations/cloud_security_posture-1.9.0/add-integration/kspm' + ); + }); + }); + it('shows install agent prompt with install agent link when status is not-deployed', async () => { + server.use(statusHandlers.notDeployedHandler); + server.use(benchmarksHandlers.cspmInstalledHandler); + renderNoFindingsStates(); + expect(screen.getByText(/loading/i)).toBeInTheDocument(); + + await waitFor(() => { + expect(screen.getByText(/no agents installed/i)).toBeInTheDocument(); + }); + + await waitFor(() => { + expect(screen.getByRole('link', { name: /install agent/i })).toHaveAttribute( + 'href', + '/app/integrations/detail/cloud_security_posture-1.9.0/policies?addAgentToPolicyId=30cba674-531c-4225-b392-3f7810957511&integration=630f3e42-659e-4499-9007-61e36adf1d97' + ); + }); + }); + it('shows install agent prompt with install agent link when status is not-deployed and postureType is KSPM', async () => { + server.use(statusHandlers.notDeployedHandler); + server.use(benchmarksHandlers.kspmInstalledHandler); + renderNoFindingsStates('kspm'); + expect(screen.getByText(/loading/i)).toBeInTheDocument(); + + await waitFor(() => { + expect(screen.getByText(/no agents installed/i)).toBeInTheDocument(); + }); + + await waitFor(() => { + const link = screen.getByRole('link', { + name: /install agent/i, + }); + expect(link).toHaveAttribute( + 'href', + '/app/integrations/detail/cloud_security_posture-1.9.0/policies?addAgentToPolicyId=e2f72eea-bf76-4576-bed8-e29d2df102a7&integration=6aedf856-bc21-49aa-859a-a0952789f898' + ); + }); + }); + it('shows indexing message when status is indexing', async () => { + server.use(statusHandlers.indexingHandler); + + renderNoFindingsStates(); + expect(screen.getByText(/loading/i)).toBeInTheDocument(); + + await waitFor(() => { + expect(screen.getByText(/posture evaluation underway/i)).toBeInTheDocument(); + }); + + expect( + screen.getByText( + /waiting for data to be collected and indexed. check back later to see your findings/i + ) + ).toBeInTheDocument(); + }); + it('shows timeout message when status is index-timeout', async () => { + server.use(statusHandlers.indexTimeoutHandler); + + renderNoFindingsStates(); + expect(screen.getByText(/loading/i)).toBeInTheDocument(); + + await waitFor(() => { + screen.getByRole('heading', { + name: /waiting for findings data/i, + }); + }); + + expect( + screen.getByText(/collecting findings is taking longer than expected/i) + ).toBeInTheDocument(); + }); + it('shows unprivileged message when status is unprivileged', async () => { + server.use(statusHandlers.unprivilegedHandler); + + renderNoFindingsStates(); + expect(screen.getByText(/loading/i)).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(/privileges required/i)).toBeInTheDocument(); + + expect( + screen.getByText(/required elasticsearch index privilege for the following indices:/i) + ).toBeInTheDocument(); + expect( + screen.getByText('logs-cloud_security_posture.findings_latest-default') + ).toBeInTheDocument(); + expect(screen.getByText('logs-cloud_security_posture.findings-default*')).toBeInTheDocument(); + expect(screen.getByText('logs-cloud_security_posture.scores-default')).toBeInTheDocument(); + expect( + screen.getByText('logs-cloud_security_posture.vulnerabilities_latest-default') + ).toBeInTheDocument(); + }); + }); + it('renders empty container when the status does not match a no finding status', async () => { + server.use(statusHandlers.indexedHandler); + + const { container } = renderNoFindingsStates(); + + expect(screen.getByText(/loading/i)).toBeInTheDocument(); + + await waitFor(() => { + expect(screen.queryByText(/loading/i)).not.toBeInTheDocument(); + }); + expect(container).toMatchInlineSnapshot(` +
+
+
+ `); + }); +}); diff --git a/x-pack/plugins/cloud_security_posture/public/components/no_findings_states.tsx b/x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.tsx similarity index 90% rename from x-pack/plugins/cloud_security_posture/public/components/no_findings_states.tsx rename to x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.tsx index 8e20207719940..97dfce7b84c1e 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/no_findings_states.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/no_findings_states/no_findings_states.tsx @@ -20,21 +20,21 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { css } from '@emotion/react'; -import { CSPM_POLICY_TEMPLATE, KSPM_POLICY_TEMPLATE } from '../../common/constants'; -import { FullSizeCenteredPage } from './full_size_centered_page'; -import { useCISIntegrationPoliciesLink } from '../common/navigation/use_navigate_to_cis_integration_policies'; +import { CSPM_POLICY_TEMPLATE, KSPM_POLICY_TEMPLATE } from '../../../common/constants'; +import { FullSizeCenteredPage } from '../full_size_centered_page'; +import { useCISIntegrationPoliciesLink } from '../../common/navigation/use_navigate_to_cis_integration_policies'; import { CSPM_NOT_INSTALLED_ACTION_SUBJ, KSPM_NOT_INSTALLED_ACTION_SUBJ, NO_FINDINGS_STATUS_TEST_SUBJ, -} from './test_subjects'; -import { CloudPosturePage, PACKAGE_NOT_INSTALLED_TEST_SUBJECT } from './cloud_posture_page'; -import { useCspSetupStatusApi } from '../common/api/use_setup_status_api'; -import type { IndexDetails, PostureTypes, CspStatusCode } from '../../common/types_old'; -import noDataIllustration from '../assets/illustrations/no_data_illustration.svg'; -import { useCspIntegrationLink } from '../common/navigation/use_csp_integration_link'; -import { NO_FINDINGS_STATUS_REFRESH_INTERVAL_MS } from '../common/constants'; -import { cspIntegrationDocsNavigation } from '../common/navigation/constants'; +} from '../test_subjects'; +import { CloudPosturePage, PACKAGE_NOT_INSTALLED_TEST_SUBJECT } from '../cloud_posture_page'; +import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api'; +import type { IndexDetails, PostureTypes, CspStatusCode } from '../../../common/types_old'; +import noDataIllustration from '../../assets/illustrations/no_data_illustration.svg'; +import { useCspIntegrationLink } from '../../common/navigation/use_csp_integration_link'; +import { NO_FINDINGS_STATUS_REFRESH_INTERVAL_MS } from '../../common/constants'; +import { cspIntegrationDocsNavigation } from '../../common/navigation/constants'; const NotDeployed = ({ postureType }: { postureType: PostureTypes }) => { const integrationPoliciesLink = useCISIntegrationPoliciesLink({ @@ -169,13 +169,10 @@ const Unprivileged = ({ unprivilegedIndices }: { unprivilegedIndices: string[] } /> ); -const EmptySecurityFindingsPrompt = ({ - kspmIntegrationLink, - cspmIntegrationLink, -}: { - kspmIntegrationLink?: string; - cspmIntegrationLink?: string; -}) => { +const EmptySecurityFindingsPrompt = () => { + const kspmIntegrationLink = useCspIntegrationLink(KSPM_POLICY_TEMPLATE); + const cspmIntegrationLink = useCspIntegrationLink(CSPM_POLICY_TEMPLATE); + return ( { - const kspmIntegrationLink = useCspIntegrationLink(KSPM_POLICY_TEMPLATE); - const cspmIntegrationLink = useCspIntegrationLink(CSPM_POLICY_TEMPLATE); - const unprivilegedIndices = indicesStatus && indicesStatus @@ -267,13 +263,7 @@ const NoFindingsStatesNotification = ({ return ; if (status === 'indexing' || status === 'waiting_for_results') return ; if (status === 'index-timeout') return ; - if (isNotInstalled) - return ( - - ); + if (isNotInstalled) return ; if (status === 'not-deployed') return ; return null; diff --git a/x-pack/plugins/cloud_security_posture/public/components/subscription_not_allowed.tsx b/x-pack/plugins/cloud_security_posture/public/components/subscription_not_allowed.tsx index a2d8f4fe32c0b..e527a2f9f0c0c 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/subscription_not_allowed.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/subscription_not_allowed.tsx @@ -8,12 +8,11 @@ import React from 'react'; import { EuiEmptyPrompt, EuiLink, EuiPageSection } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { useLicenseManagementLocatorApi } from '../common/api/use_license_management_locator_api'; + +export const SubscriptionNotAllowed = () => { + const handleNavigateToLicenseManagement = useLicenseManagementLocatorApi(); -export const SubscriptionNotAllowed = ({ - licenseManagementLocator, -}: { - licenseManagementLocator?: string; -}) => { return ( } body={ - licenseManagementLocator ? ( + handleNavigateToLicenseManagement ? (

+ ({ + data: dataPluginMock.createStartContract(), + unifiedSearch: unifiedSearchPluginMock.createStartContract(), + charts: chartPluginMock.createStartContract(), + discover: discoverPluginMock.createStartContract(), + fleet: fleetMock.createStartMock(), + licensing: licensingMock.createStart(), + uiActions: uiActionsPluginMock.createStartContract(), + storage: sessionStorageMock.create(), + share: sharePluginMock.createStartContract(), +}); diff --git a/x-pack/plugins/cloud_security_posture/public/test/mock_server/handlers/index.ts b/x-pack/plugins/cloud_security_posture/public/test/mock_server/handlers/index.ts new file mode 100644 index 0000000000000..91eb25630b222 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/test/mock_server/handlers/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 { defaultApiLicensingInfo } from './licensing.handlers.mock'; + +/** + * Default handlers for the mock server, these are the handlers that are always enabled + * when the mock server is started, but can be overridden by specific tests when needed. + * Recommended to use these handlers for common endpoints. + */ +export const defaultHandlers = [defaultApiLicensingInfo]; diff --git a/x-pack/plugins/cloud_security_posture/public/test/mock_server/handlers/licensing.handlers.mock.ts b/x-pack/plugins/cloud_security_posture/public/test/mock_server/handlers/licensing.handlers.mock.ts new file mode 100644 index 0000000000000..de426f5c72144 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/test/mock_server/handlers/licensing.handlers.mock.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 { http, HttpResponse } from 'msw'; + +export const MOCK_SERVER_LICENSING_INFO_URL = `/api/licensing/info`; + +export const defaultApiLicensingInfo = http.get(MOCK_SERVER_LICENSING_INFO_URL, () => { + const date = new Date(); + const expiryDateInMillis = date.setDate(date.getDate() + 30); + + return HttpResponse.json({ + license: { + uid: '000000-0000-0000-0000-000000000', + type: 'trial', + mode: 'trial', + expiryDateInMillis, + status: 'active', + }, + features: { + security: { + isAvailable: true, + isEnabled: true, + }, + }, + signature: '0000000000000000000000000000000000000000000000000000000', + }); +}); diff --git a/x-pack/plugins/cloud_security_posture/public/test/mock_server/mock_server.ts b/x-pack/plugins/cloud_security_posture/public/test/mock_server/mock_server.ts new file mode 100644 index 0000000000000..9fe4b156bc096 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/test/mock_server/mock_server.ts @@ -0,0 +1,174 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setupServer, SetupServerApi } from 'msw/node'; +import { coreMock } from '@kbn/core/public/mocks'; +import type { CoreStart } from '@kbn/core/public'; +import { licenseMock } from '@kbn/licensing-plugin/common/licensing.mock'; +import { createStubDataView } from '@kbn/data-views-plugin/common/stubs'; +import { indexPatternFieldEditorPluginMock as dataViewFieldEditorMock } from '@kbn/data-view-field-editor-plugin/public/mocks'; +import SearchBar from '@kbn/unified-search-plugin/public/search_bar/search_bar'; +import { http, HttpResponse, JsonBodyType } from 'msw'; +import { defaultHandlers } from './handlers'; +import { getMockDependencies } from '../fixtures/get_mock_dependencies'; +import { CspClientPluginStartDeps } from '../../types'; +import { MOCK_SERVER_LICENSING_INFO_URL } from './handlers/licensing.handlers.mock'; + +/** + * Mock the lastValueFrom function from rxjs to return the result of the promise instead of the Observable + * This is for simplifying the testing by avoiding the need to subscribe to the Observable while producing the same result + */ +jest.mock('rxjs', () => { + const actual = jest.requireActual('rxjs'); + return { + ...actual, + lastValueFrom: async (source: Promise) => { + const value = await source; + return value.result; + }, + }; +}); + +/** + * Setup a mock server with the default handlers + * @param debug - If true, log all requests to the console + * @returns The mock server + */ +export const setupMockServer = ({ debug = false }: { debug?: boolean } = {}) => { + const server = setupServer(...defaultHandlers); + + if (debug) { + // Debug: log all requests to the console + server.events.on('request:start', async ({ request }) => { + const payload = await request.clone().text(); + // eslint-disable-next-line no-console + console.log('MSW intercepted request:', request.method, request.url, payload); + }); + server.events.on('response:mocked', async ({ request, response }) => { + const body = await response.json(); + // eslint-disable-next-line no-console + console.log( + '%s %s received %s %s %s', + request.method, + request.url, + response.status, + response.statusText, + JSON.stringify(body, null, 2) + ); + }); + } + return server; +}; + +/** + * This function wraps beforeAll, afterAll and beforeEach for setup MSW server into a single call. + * That makes the describe code further down easier to read and makes + * sure we don't forget the handlers. Can easily be shared between tests. + * @param server - The MSW server instance, created with setupMockServer + */ +export const startMockServer = (server: SetupServerApi) => { + beforeAll(() => server.listen({ onUnhandledRequest: 'warn' })); + afterAll(() => server.close()); + beforeEach(() => { + server.resetHandlers(); + }); +}; + +const MOCK_SERVER_BASE_URL = 'http://localhost'; + +/** + * Get a set of dependencies for the mock server overriding default mock dependencies to perform + * HTTP calls that will be intercepted by the mock server + * @returns The core and deps dependencies used by the KibanaContextProvider + */ +export const getMockServerDependencies = () => { + return { + deps: { + ...getMockDependencies(), + data: { + ...getMockDependencies().data, + search: { + ...getMockDependencies().data.search, + search: async ({ params }: { params: any }) => { + const response = await fetch(`${MOCK_SERVER_BASE_URL}/internal/bsearch`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(params), + }); + return response.json(); + }, + }, + dataViews: { + ...getMockDependencies().data.dataViews, + find: async (pattern: string) => { + const response = await fetch( + `${MOCK_SERVER_BASE_URL}/internal/data_views/fields?pattern=${pattern}` + ); + + const responseJson = await response.json(); + + const fields = responseJson.fields.reduce((acc: any, field: any) => { + acc[field.name] = field; + return acc; + }, {}); + + const dataView = createStubDataView({ + spec: { + id: pattern, + title: pattern, + fields, + }, + }); + + return [dataView]; + }, + }, + }, + licensing: { + ...getMockDependencies().licensing, + refresh: async () => { + const response = await fetch(MOCK_SERVER_LICENSING_INFO_URL); + const responseJson = await response.json(); + return licenseMock.createLicense(responseJson); + }, + }, + dataViewFieldEditor: dataViewFieldEditorMock.createStartContract(), + unifiedSearch: { + ...getMockDependencies().unifiedSearch, + ui: { + ...getMockDependencies().unifiedSearch.ui, + SearchBar, + }, + }, + storage: { + ...getMockDependencies().storage, + get: (key: string) => { + return localStorage.getItem(key); + }, + set: (key: string, value: string) => { + localStorage.setItem(key, value); + }, + }, + } as unknown as Partial, + core: { + ...coreMock.createStart(), + http: { + ...coreMock.createStart().http, + get: async (path: string, options: any) => { + const response = await fetch(`${MOCK_SERVER_BASE_URL}${path}`, options); + return response.json(); + }, + }, + } as unknown as CoreStart, + }; +}; + +export const mockGetRequest = (path: string, response: JsonBodyType) => { + return http.get(path, () => HttpResponse.json(response)); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/test/mock_server/mock_server_test_provider.tsx b/x-pack/plugins/cloud_security_posture/public/test/mock_server/mock_server_test_provider.tsx new file mode 100644 index 0000000000000..19143d4641836 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/test/mock_server/mock_server_test_provider.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 { render } from '@testing-library/react'; +import type { CoreStart } from '@kbn/core/public'; +import { CspClientPluginStartDeps } from '../../types'; +import { TestProvider } from '../test_provider'; +import { getMockServerDependencies } from './mock_server'; +interface MockServerDependencies { + deps: Partial; + core: CoreStart; +} + +interface MockServerTestProviderProps { + children: React.ReactNode; + dependencies?: MockServerDependencies; +} + +/** + * Simple wrapper around the TestProvider that provides dependencies for the mock server. + */ +export const MockServerTestProvider = ({ + children, + dependencies = getMockServerDependencies(), +}: MockServerTestProviderProps) => { + return {children}; +}; + +/** + * Renders a component wrapped in the MockServerTestProvider. + */ +export const renderWrapper = (children: React.ReactNode, dependencies?: MockServerDependencies) => { + return render( + {children} + ); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/test/test_provider.tsx b/x-pack/plugins/cloud_security_posture/public/test/test_provider.tsx index d677df4258837..ab3173ec8c581 100755 --- a/x-pack/plugins/cloud_security_posture/public/test/test_provider.tsx +++ b/x-pack/plugins/cloud_security_posture/public/test/test_provider.tsx @@ -6,23 +6,16 @@ */ import type { AppMountParameters, CoreStart } from '@kbn/core/public'; -import React, { useMemo } from 'react'; +import React from 'react'; import { I18nProvider } from '@kbn/i18n-react'; // eslint-disable-next-line no-restricted-imports import { Router } from 'react-router-dom'; import { Route, Routes } from '@kbn/shared-ux-router'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { coreMock } from '@kbn/core/public/mocks'; -import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; -import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; -import { discoverPluginMock } from '@kbn/discover-plugin/public/mocks'; -import { fleetMock } from '@kbn/fleet-plugin/public/mocks'; -import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; -import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; -import { sessionStorageMock } from '@kbn/core-http-server-mocks'; import type { CspClientPluginStartDeps } from '../types'; +import { getMockDependencies } from './fixtures/get_mock_dependencies'; interface CspAppDeps { core: CoreStart; @@ -33,20 +26,17 @@ interface CspAppDeps { export const TestProvider: React.FC> = ({ core = coreMock.createStart(), - deps = { - data: dataPluginMock.createStartContract(), - unifiedSearch: unifiedSearchPluginMock.createStartContract(), - charts: chartPluginMock.createStartContract(), - discover: discoverPluginMock.createStartContract(), - fleet: fleetMock.createStartMock(), - licensing: licensingMock.createStart(), - uiActions: uiActionsPluginMock.createStartContract(), - storage: sessionStorageMock.create(), - }, + deps = getMockDependencies(), params = coreMock.createAppMountParameters(), children, } = {}) => { - const queryClient = useMemo(() => new QueryClient(), []); + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }); return ( diff --git a/x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.handlers.mock.ts b/x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.handlers.mock.ts new file mode 100644 index 0000000000000..ab9d84fbc5b09 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/server/routes/benchmarks/benchmarks.handlers.mock.ts @@ -0,0 +1,192 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { http, HttpResponse } from 'msw'; + +export const cspmInstalledHandler = http.get('/internal/cloud_security_posture/benchmarks', () => { + return HttpResponse.json({ + items: [ + { + package_policy: { + id: '630f3e42-659e-4499-9007-61e36adf1d97', + name: 'cspm-1', + namespace: 'default', + description: '', + package: { + name: 'cloud_security_posture', + title: 'Security Posture Management', + version: '1.9.0', + }, + enabled: true, + policy_id: '30cba674-531c-4225-b392-3f7810957511', + inputs: [ + { + type: 'cloudbeat/cis_aws', + policy_template: 'cspm', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { + type: 'logs', + dataset: 'cloud_security_posture.findings', + }, + vars: { + access_key_id: { + type: 'text', + }, + secret_access_key: { + type: 'text', + }, + session_token: { + type: 'text', + }, + shared_credential_file: { + type: 'text', + }, + credential_profile_name: { + type: 'text', + }, + role_arn: { + type: 'text', + }, + 'aws.credentials.type': { + type: 'text', + }, + 'aws.account_type': { + value: 'organization-account', + type: 'text', + }, + }, + id: 'cloudbeat/cis_aws-cloud_security_posture.findings-630f3e42-659e-4499-9007-61e36adf1d97', + compiled_stream: { + period: '24h', + config: { + v1: { + type: 'cspm', + deployment: 'aws', + benchmark: 'cis_aws', + aws: { + account_type: 'organization-account', + credentials: { + type: null, + }, + }, + }, + }, + }, + }, + ], + config: { + cloud_formation_template_url: { + value: + 'https://console.aws.amazon.com/cloudformation/home#/stacks/quickcreate?templateURL=https://elastic-cspm-cft.s3.eu-central-1.amazonaws.com/cloudformation-cspm-ACCOUNT_TYPE-8.14.0.yml&stackName=Elastic-Cloud-Security-Posture-Management¶m_EnrollmentToken=FLEET_ENROLLMENT_TOKEN¶m_FleetUrl=FLEET_URL¶m_ElasticAgentVersion=KIBANA_VERSION¶m_ElasticArtifactServer=https://artifacts.elastic.co/downloads/beats/elastic-agent', + }, + }, + }, + ], + vars: { + posture: { + value: 'cspm', + type: 'text', + }, + deployment: { + value: 'aws', + type: 'text', + }, + }, + revision: 1, + created_at: '2024-06-03T21:06:20.786Z', + created_by: 'system', + updated_at: '2024-06-03T21:06:20.786Z', + updated_by: 'system', + }, + agent_policy: { + id: '30cba674-531c-4225-b392-3f7810957511', + name: 'Agent policy 3', + agents: 0, + }, + rules_count: 55, + }, + ], + total: 1, + page: 1, + perPage: 100, + }); +}); + +export const kspmInstalledHandler = http.get('/internal/cloud_security_posture/benchmarks', () => { + return HttpResponse.json({ + items: [ + { + package_policy: { + id: '6aedf856-bc21-49aa-859a-a0952789f898', + version: 'WzE4ODcxLDE0XQ==', + name: 'kspm-1', + namespace: 'default', + description: '', + package: { + name: 'cloud_security_posture', + title: 'Security Posture Management', + version: '1.9.0', + }, + enabled: true, + policy_id: 'e2f72eea-bf76-4576-bed8-e29d2df102a7', + inputs: [ + { + type: 'cloudbeat/cis_k8s', + policy_template: 'kspm', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { + type: 'logs', + dataset: 'cloud_security_posture.findings', + }, + id: 'cloudbeat/cis_k8s-cloud_security_posture.findings-6aedf856-bc21-49aa-859a-a0952789f898', + compiled_stream: { + config: { + v1: { + type: 'kspm', + deployment: 'self_managed', + benchmark: 'cis_k8s', + }, + }, + }, + }, + ], + }, + ], + vars: { + posture: { + value: 'kspm', + type: 'text', + }, + deployment: { + value: 'self_managed', + type: 'text', + }, + }, + revision: 1, + created_at: '2024-06-03T21:23:23.139Z', + created_by: 'system', + updated_at: '2024-06-03T21:23:23.139Z', + updated_by: 'system', + }, + agent_policy: { + id: 'e2f72eea-bf76-4576-bed8-e29d2df102a7', + name: 'Agent policy 1', + agents: 0, + }, + rules_count: 92, + }, + ], + total: 1, + page: 1, + perPage: 100, + }); +}); diff --git a/x-pack/plugins/cloud_security_posture/server/routes/status/status.handlers.mock.ts b/x-pack/plugins/cloud_security_posture/server/routes/status/status.handlers.mock.ts new file mode 100644 index 0000000000000..0f2b3b9eab640 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/server/routes/status/status.handlers.mock.ts @@ -0,0 +1,251 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { http, HttpResponse } from 'msw'; + +const STATUS_URL = `/internal/cloud_security_posture/status`; + +export const notInstalledHandler = http.get(STATUS_URL, () => { + return HttpResponse.json({ + cspm: { + status: 'not-installed', + healthyAgents: 1, + installedPackagePolicies: 1, + }, + kspm: { + status: 'not-installed', + healthyAgents: 1, + installedPackagePolicies: 1, + }, + vuln_mgmt: { + status: 'not-installed', + healthyAgents: 1, + installedPackagePolicies: 1, + }, + indicesDetails: [ + { + index: 'logs-cloud_security_posture.findings_latest-default', + status: 'empty', + }, + { + index: 'logs-cloud_security_posture.findings-default*', + status: 'empty', + }, + { + index: 'logs-cloud_security_posture.scores-default', + status: 'empty', + }, + { + index: 'logs-cloud_security_posture.vulnerabilities_latest-default', + status: 'empty', + }, + ], + isPluginInitialized: true, + latestPackageVersion: '1.9.0', + }); +}); + +export const notDeployedHandler = http.get(STATUS_URL, () => { + return HttpResponse.json({ + cspm: { + status: 'not-deployed', + healthyAgents: 1, + installedPackagePolicies: 1, + }, + kspm: { + status: 'not-deployed', + healthyAgents: 1, + installedPackagePolicies: 1, + }, + vuln_mgmt: { + status: 'not-deployed', + healthyAgents: 1, + installedPackagePolicies: 1, + }, + indicesDetails: [ + { + index: 'logs-cloud_security_posture.findings_latest-default', + status: 'empty', + }, + { + index: 'logs-cloud_security_posture.findings-default*', + status: 'empty', + }, + { + index: 'logs-cloud_security_posture.scores-default', + status: 'not-empty', + }, + { + index: 'logs-cloud_security_posture.vulnerabilities_latest-default', + status: 'empty', + }, + ], + isPluginInitialized: true, + latestPackageVersion: '1.9.0', + installedPackageVersion: '1.9.0', + }); +}); + +export const indexingHandler = http.get(STATUS_URL, () => { + return HttpResponse.json({ + cspm: { + status: 'indexing', + healthyAgents: 1, + installedPackagePolicies: 1, + }, + kspm: { + status: 'indexing', + healthyAgents: 1, + installedPackagePolicies: 1, + }, + vuln_mgmt: { + status: 'indexing', + healthyAgents: 1, + installedPackagePolicies: 1, + }, + indicesDetails: [ + { + index: 'logs-cloud_security_posture.findings_latest-default', + status: 'empty', + }, + { + index: 'logs-cloud_security_posture.findings-default*', + status: 'not-empty', + }, + { + index: 'logs-cloud_security_posture.scores-default', + status: 'empty', + }, + { + index: 'logs-cloud_security_posture.vulnerabilities_latest-default', + status: 'empty', + }, + ], + isPluginInitialized: true, + latestPackageVersion: '1.9.0', + }); +}); + +export const indexTimeoutHandler = http.get(STATUS_URL, () => { + return HttpResponse.json({ + cspm: { + status: 'index-timeout', + healthyAgents: 1, + installedPackagePolicies: 1, + }, + kspm: { + status: 'index-timeout', + healthyAgents: 1, + installedPackagePolicies: 1, + }, + vuln_mgmt: { + status: 'index-timeout', + healthyAgents: 1, + installedPackagePolicies: 1, + }, + indicesDetails: [ + { + index: 'logs-cloud_security_posture.findings_latest-default', + status: 'empty', + }, + { + index: 'logs-cloud_security_posture.findings-default*', + status: 'empty', + }, + { + index: 'logs-cloud_security_posture.scores-default', + status: 'empty', + }, + { + index: 'logs-cloud_security_posture.vulnerabilities_latest-default', + status: 'empty', + }, + ], + isPluginInitialized: true, + latestPackageVersion: '1.9.0', + }); +}); + +export const unprivilegedHandler = http.get(STATUS_URL, () => { + return HttpResponse.json({ + cspm: { + status: 'unprivileged', + healthyAgents: 1, + installedPackagePolicies: 1, + }, + kspm: { + status: 'unprivileged', + healthyAgents: 1, + installedPackagePolicies: 1, + }, + vuln_mgmt: { + status: 'unprivileged', + healthyAgents: 1, + installedPackagePolicies: 1, + }, + indicesDetails: [ + { + index: 'logs-cloud_security_posture.findings_latest-default', + status: 'unprivileged', + }, + { + index: 'logs-cloud_security_posture.findings-default*', + status: 'unprivileged', + }, + { + index: 'logs-cloud_security_posture.scores-default', + status: 'unprivileged', + }, + { + index: 'logs-cloud_security_posture.vulnerabilities_latest-default', + status: 'unprivileged', + }, + ], + isPluginInitialized: true, + latestPackageVersion: '1.9.0', + }); +}); + +export const indexedHandler = http.get(STATUS_URL, () => { + return HttpResponse.json({ + cspm: { + status: 'indexed', + healthyAgents: 1, + installedPackagePolicies: 1, + }, + kspm: { + status: 'indexed', + healthyAgents: 1, + installedPackagePolicies: 1, + }, + vuln_mgmt: { + status: 'indexed', + healthyAgents: 1, + installedPackagePolicies: 1, + }, + indicesDetails: [ + { + index: 'logs-cloud_security_posture.findings_latest-default', + status: 'not-empty', + }, + { + index: 'logs-cloud_security_posture.findings-default*', + status: 'not-empty', + }, + { + index: 'logs-cloud_security_posture.scores-default', + status: 'not-empty', + }, + { + index: 'logs-cloud_security_posture.vulnerabilities_latest-default', + status: 'not-empty', + }, + ], + isPluginInitialized: true, + latestPackageVersion: '1.9.0', + installedPackageVersion: '1.9.0', + }); +}); diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_data_view_by_index_pattern.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_data_view_by_index_pattern.ts index 8d0385e8d9f9e..8ef09d59555ea 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_data_view_by_index_pattern.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_data_view_by_index_pattern.ts @@ -6,6 +6,7 @@ */ import type { DataView, DataViewsContract } from '@kbn/data-views-plugin/public'; +import { getESQLAdHocDataview } from '@kbn/esql-utils'; /** * Get a saved data view that matches the index pattern (as close as possible) @@ -34,14 +35,7 @@ export async function getOrCreateDataViewByIndexPattern( indexPatternFromQuery && (currentDataView?.isPersisted() || indexPatternFromQuery !== currentDataView?.getIndexPattern()) ) { - const dataViewObj = await dataViews.create({ - title: indexPatternFromQuery, - }); - - if (dataViewObj.fields.getByName('@timestamp')?.type === 'date') { - dataViewObj.timeFieldName = '@timestamp'; - } - return dataViewObj; + return await getESQLAdHocDataview(indexPatternFromQuery, dataViews); } return currentDataView; } diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/lib/data_stream/results_field_map.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/lib/data_stream/results_field_map.ts index 8fbb3c354df8d..c82c70ad6bcc1 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/lib/data_stream/results_field_map.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/lib/data_stream/results_field_map.ts @@ -18,12 +18,18 @@ export const resultsFieldMap: FieldMap = { totalFieldCount: { type: 'long', required: true }, ecsFieldCount: { type: 'long', required: true }, customFieldCount: { type: 'long', required: true }, - incompatibleFieldItems: { type: 'nested', required: true, array: true }, - 'incompatibleFieldItems.fieldName': { type: 'keyword', required: true }, - 'incompatibleFieldItems.expectedValue': { type: 'keyword', required: true }, - 'incompatibleFieldItems.actualValue': { type: 'keyword', required: true }, - 'incompatibleFieldItems.description': { type: 'keyword', required: true }, - 'incompatibleFieldItems.reason': { type: 'keyword', required: true }, + incompatibleFieldMappingItems: { type: 'nested', required: true, array: true }, + 'incompatibleFieldMappingItems.fieldName': { type: 'keyword', required: true }, + 'incompatibleFieldMappingItems.expectedValue': { type: 'keyword', required: true }, + 'incompatibleFieldMappingItems.actualValue': { type: 'keyword', required: true }, + 'incompatibleFieldMappingItems.description': { type: 'keyword', required: true }, + incompatibleFieldValueItems: { type: 'nested', required: true, array: true }, + 'incompatibleFieldValueItems.fieldName': { type: 'keyword', required: true }, + 'incompatibleFieldValueItems.expectedValues': { type: 'keyword', required: true }, + 'incompatibleFieldValueItems.actualValues': { type: 'nested', required: true, array: true }, + 'incompatibleFieldValueItems.actualValues.name': { type: 'keyword', required: true }, + 'incompatibleFieldValueItems.actualValues.count': { type: 'keyword', required: true }, + 'incompatibleFieldValueItems.description': { type: 'keyword', required: true }, incompatibleFieldCount: { type: 'long', required: true }, sameFamilyFieldCount: { type: 'long', required: true }, sameFamilyFieldItems: { type: 'nested', required: true, array: true }, diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/results.mock.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/results.mock.ts index 0b53c94b9b8e5..ad51233c5d203 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/results.mock.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/routes/results/results.mock.ts @@ -19,8 +19,38 @@ export const resultDocument: ResultDocument = { ecsFieldCount: 677, customFieldCount: 904, incompatibleFieldCount: 1, + incompatibleFieldMappingItems: [], + incompatibleFieldValueItems: [ + { + fieldName: 'event.category', + expectedValues: [ + `authentication`, + `configuration`, + `database`, + `driver`, + `email`, + `file`, + `host`, + `iam`, + `intrusion_detection`, + `malware`, + `network`, + `package`, + `process`, + `registry`, + `session`, + `threat`, + 'vulnerability', + 'web', + ], + actualValues: [{ name: 'behavior', count: 6 }], + description: + 'This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy.\n`event.category` represents the "big buckets" of ECS categories. For example, filtering on `event.category:process` yields all events relating to process activity. This field is closely related to `event.type`, which is used as a subcategory.\nThis field is an array. This will allow proper categorization of some events that fall in multiple categories.', + }, + ], sameFamilyFieldCount: 0, sameFamilyFields: [], + sameFamilyFieldItems: [], unallowedMappingFields: [], unallowedValueFields: ['event.category'], sizeInBytes: 173796, diff --git a/x-pack/plugins/ecs_data_quality_dashboard/server/schemas/result.ts b/x-pack/plugins/ecs_data_quality_dashboard/server/schemas/result.ts index c16a3d806b1bd..b242b6f80b1b2 100644 --- a/x-pack/plugins/ecs_data_quality_dashboard/server/schemas/result.ts +++ b/x-pack/plugins/ecs_data_quality_dashboard/server/schemas/result.ts @@ -19,8 +19,32 @@ const ResultDocumentInterface = t.interface({ ecsFieldCount: t.number, customFieldCount: t.number, incompatibleFieldCount: t.number, + incompatibleFieldMappingItems: t.array( + t.type({ + fieldName: t.string, + expectedValue: t.string, + actualValue: t.string, + description: t.string, + }) + ), + incompatibleFieldValueItems: t.array( + t.type({ + fieldName: t.string, + expectedValues: t.array(t.string), + actualValues: t.array(t.type({ name: t.string, count: t.number })), + description: t.string, + }) + ), sameFamilyFieldCount: t.number, sameFamilyFields: t.array(t.string), + sameFamilyFieldItems: t.array( + t.type({ + fieldName: t.string, + expectedValue: t.string, + actualValue: t.string, + description: t.string, + }) + ), unallowedMappingFields: t.array(t.string), unallowedValueFields: t.array(t.string), sizeInBytes: t.number, diff --git a/x-pack/plugins/elastic_assistant/common/constants.ts b/x-pack/plugins/elastic_assistant/common/constants.ts index 5c5233eaaa6c8..d8c2630c1aef8 100755 --- a/x-pack/plugins/elastic_assistant/common/constants.ts +++ b/x-pack/plugins/elastic_assistant/common/constants.ts @@ -27,6 +27,9 @@ export const ANONYMIZATION_FIELDS_TABLE_MAX_PAGE_SIZE = 100; export const MAX_PROMPTS_TO_UPDATE_IN_PARALLEL = 50; export const PROMPTS_TABLE_MAX_PAGE_SIZE = 100; +// Knowledge Base +export const KNOWLEDGE_BASE_ENTRIES_TABLE_MAX_PAGE_SIZE = 100; + // Capabilities export const CAPABILITIES = `${BASE_PATH}/capabilities`; diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/create_knowledge_base_entry.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/create_knowledge_base_entry.ts index 5430bf597ebe7..90c0850c503fe 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/create_knowledge_base_entry.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/create_knowledge_base_entry.ts @@ -18,8 +18,8 @@ import { CreateKnowledgeBaseEntrySchema } from './types'; export interface CreateKnowledgeBaseEntryParams { esClient: ElasticsearchClient; - logger: Logger; knowledgeBaseIndex: string; + logger: Logger; spaceId: string; user: AuthenticatedUser; knowledgeBaseEntry: KnowledgeBaseEntryCreateProps; diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/helpers.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/helpers.ts index dc7f64e1aeee1..839ac3e559ba1 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/helpers.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/helpers.ts @@ -6,6 +6,8 @@ */ import { errors } from '@elastic/elasticsearch'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { AuthenticatedUser } from '@kbn/core-security-common'; export const isModelAlreadyExistsError = (error: Error) => { return ( @@ -14,3 +16,87 @@ export const isModelAlreadyExistsError = (error: Error) => { error.body.error.type === 'status_exception') ); }; + +/** + * Returns an Elasticsearch query DSL that performs a vector search against the Knowledge Base for the given query/user/filter. + * + * @param filter - Optional filter to apply to the search + * @param kbResource - Specific resource tag to filter for, e.g. 'esql' or 'user' + * @param modelId - ID of the model to search with, e.g. `.elser_model_2` + * @param query - The search query provided by the user + * @param required - Whether to only include required entries + * @param user - The authenticated user + * @returns + */ +export const getKBVectorSearchQuery = ({ + filter, + kbResource, + modelId, + query, + required, + user, +}: { + filter?: QueryDslQueryContainer | undefined; + kbResource?: string | undefined; + modelId: string; + query: string; + required?: boolean | undefined; + user: AuthenticatedUser; +}): QueryDslQueryContainer => { + const resourceFilter = kbResource + ? [ + { + term: { + 'metadata.kbResource': kbResource, + }, + }, + ] + : []; + const requiredFilter = required + ? [ + { + term: { + 'metadata.required': required, + }, + }, + ] + : []; + + const userFilter = [ + { + nested: { + path: 'users', + query: { + bool: { + must: [ + { + match: user.profile_uid + ? { 'users.id': user.profile_uid } + : { 'users.name': user.username }, + }, + ], + }, + }, + }, + }, + ]; + + return { + bool: { + must: [ + { + text_expansion: { + 'vector.tokens': { + model_id: modelId, + model_text: query, + }, + }, + }, + ...requiredFilter, + ...resourceFilter, + ...userFilter, + ], + filter, + }, + }; +}; diff --git a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts index 771ec35c07c51..680ab17672f61 100644 --- a/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts +++ b/x-pack/plugins/elastic_assistant/server/ai_assistant_data_clients/knowledge_base/index.ts @@ -11,19 +11,24 @@ import { } from '@elastic/elasticsearch/lib/api/types'; import type { MlPluginSetup } from '@kbn/ml-plugin/server'; import type { KibanaRequest } from '@kbn/core-http-server'; -import type { Document } from 'langchain/document'; +import { Document } from 'langchain/document'; import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { KnowledgeBaseEntryResponse } from '@kbn/elastic-assistant-common'; +import { + KnowledgeBaseEntryCreateProps, + KnowledgeBaseEntryResponse, + Metadata, +} from '@kbn/elastic-assistant-common'; import pRetry from 'p-retry'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { AIAssistantDataClient, AIAssistantDataClientParams } from '..'; import { ElasticsearchStore } from '../../lib/langchain/elasticsearch_store/elasticsearch_store'; import { loadESQL } from '../../lib/langchain/content_loaders/esql_loader'; import { GetElser } from '../../types'; -import { transformToCreateSchema } from './create_knowledge_base_entry'; +import { createKnowledgeBaseEntry, transformToCreateSchema } from './create_knowledge_base_entry'; import { EsKnowledgeBaseEntrySchema } from './types'; import { transformESSearchToKnowledgeBaseEntry } from './transforms'; import { ESQL_DOCS_LOADED_QUERY } from '../../routes/knowledge_base/constants'; -import { isModelAlreadyExistsError } from './helpers'; +import { getKBVectorSearchQuery, isModelAlreadyExistsError } from './helpers'; interface KnowledgeBaseDataClientParams extends AIAssistantDataClientParams { ml: MlPluginSetup; @@ -217,13 +222,12 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient { /** * Adds LangChain Documents to the knowledge base * - * @param documents - * @param authenticatedUser + * @param {Array>} documents - LangChain Documents to add to the knowledge base */ public addKnowledgeBaseDocuments = async ({ documents, }: { - documents: Document[]; + documents: Array>; }): Promise => { const writer = await this.getWriter(); const changedAt = new Date().toISOString(); @@ -237,9 +241,8 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient { const { errors, docs_created: docsCreated } = await writer.bulk({ documentsToCreate: documents.map((doc) => transformToCreateSchema(changedAt, this.spaceId, authenticatedUser, { - // TODO: Update the LangChain Document Metadata type extension metadata: { - kbResource: doc.metadata.kbResourcer ?? 'unknown', + kbResource: doc.metadata.kbResource ?? 'unknown', required: doc.metadata.required ?? false, source: doc.metadata.source ?? 'unknown', }, @@ -261,4 +264,100 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient { return created?.data ? transformESSearchToKnowledgeBaseEntry(created?.data) : []; }; + + /** + * Performs similarity search to retrieve LangChain Documents from the knowledge base + */ + public getKnowledgeBaseDocuments = async ({ + filter, + kbResource, + query, + required, + }: { + filter?: QueryDslQueryContainer; + kbResource?: string; + query: string; + required?: boolean; + }): Promise => { + const user = this.options.currentUser; + if (user == null) { + throw new Error( + 'Authenticated user not found! Ensure kbDataClient was initialized from a request.' + ); + } + + const esClient = await this.options.elasticsearchClientPromise; + const modelId = await this.options.getElserId(); + + const vectorSearchQuery = getKBVectorSearchQuery({ + filter, + kbResource, + modelId, + query, + required, + user, + }); + + try { + const result = await esClient.search({ + index: this.indexTemplateAndPattern.alias, + size: 10, + query: vectorSearchQuery, + }); + + const results = result.hits.hits.map( + (hit) => + new Document({ + pageContent: hit?._source?.text ?? '', + metadata: hit?._source?.metadata ?? {}, + }) + ); + + this.options.logger.debug( + `getKnowledgeBaseDocuments() - Similarity Search Query:\n ${JSON.stringify( + vectorSearchQuery + )}` + ); + this.options.logger.debug( + `getKnowledgeBaseDocuments() - Similarity Search Results:\n ${JSON.stringify(results)}` + ); + + return results; + } catch (e) { + this.options.logger.error(`Error performing KB Similarity Search: ${e.message}`); + return []; + } + }; + + /** + * Creates a new Knowledge Base Entry. + * + * @param knowledgeBaseEntry + */ + public createKnowledgeBaseEntry = async ({ + knowledgeBaseEntry, + }: { + knowledgeBaseEntry: KnowledgeBaseEntryCreateProps; + }): Promise => { + const authenticatedUser = this.options.currentUser; + if (authenticatedUser == null) { + throw new Error( + 'Authenticated user not found! Ensure kbDataClient was initialized from a request.' + ); + } + + this.options.logger.debug( + `Creating Knowledge Base Entry:\n ${JSON.stringify(knowledgeBaseEntry, null, 2)}` + ); + this.options.logger.debug(`kbIndex: ${this.indexTemplateAndPattern.alias}`); + const esClient = await this.options.elasticsearchClientPromise; + return createKnowledgeBaseEntry({ + esClient, + knowledgeBaseIndex: this.indexTemplateAndPattern.alias, + logger: this.options.logger, + spaceId: this.spaceId, + user: authenticatedUser, + knowledgeBaseEntry, + }); + }; } diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/content_loaders/esql_loader.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/content_loaders/esql_loader.ts index b34beb5c5aa9c..3d4666cdf7540 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/content_loaders/esql_loader.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/content_loaders/esql_loader.ts @@ -9,7 +9,9 @@ import { Logger } from '@kbn/core/server'; import { DirectoryLoader } from 'langchain/document_loaders/fs/directory'; import { TextLoader } from 'langchain/document_loaders/fs/text'; import { resolve } from 'path'; +import { Document } from 'langchain/document'; +import { Metadata } from '@kbn/elastic-assistant-common'; import { ElasticsearchStore } from '../elasticsearch_store/elasticsearch_store'; import { addRequiredKbResourceMetadata } from './add_required_kb_resource_metadata'; import { ESQL_RESOURCE } from '../../../routes/knowledge_base/constants'; @@ -47,15 +49,15 @@ export const loadESQL = async (esStore: ElasticsearchStore, logger: Logger): Pro true ); - const docs = await docsLoader.load(); - const languageDocs = await languageLoader.load(); + const docs = (await docsLoader.load()) as Array>; + const languageDocs = (await languageLoader.load()) as Array>; const rawExampleQueries = await exampleQueriesLoader.load(); // Add additional metadata to the example queries that indicates they are required KB documents: const requiredExampleQueries = addRequiredKbResourceMetadata({ docs: rawExampleQueries, kbResource: ESQL_RESOURCE, - }); + }) as Array>; logger.info( `Loading ${docs.length} ES|QL docs, ${languageDocs.length} language docs, and ${requiredExampleQueries.length} example queries into the Knowledge Base` diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/elasticsearch_store/elasticsearch_store.test.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/elasticsearch_store/elasticsearch_store.test.ts index b38d4b82f48b4..e45ad55999af6 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/elasticsearch_store/elasticsearch_store.test.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/elasticsearch_store/elasticsearch_store.test.ts @@ -25,6 +25,7 @@ import { KNOWLEDGE_BASE_EXECUTION_ERROR_EVENT, KNOWLEDGE_BASE_EXECUTION_SUCCESS_EVENT, } from '../../telemetry/event_based_telemetry'; +import { Metadata } from '@kbn/elastic-assistant-common'; jest.mock('uuid', () => ({ v4: jest.fn(), @@ -244,9 +245,9 @@ describe('ElasticsearchStore', () => { ], }); - const document = new Document({ + const document = new Document({ pageContent: 'interesting stuff', - metadata: { source: '1' }, + metadata: { kbResource: 'esql', required: false, source: '1' }, }); const docsInstalled = await esStore.addDocuments([document]); @@ -262,6 +263,8 @@ describe('ElasticsearchStore', () => { }, { metadata: { + kbResource: 'esql', + required: false, source: '1', }, text: 'interesting stuff', diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/elasticsearch_store/elasticsearch_store.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/elasticsearch_store/elasticsearch_store.ts index 86791cec5f5ce..e076c90526a53 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/elasticsearch_store/elasticsearch_store.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/elasticsearch_store/elasticsearch_store.ts @@ -17,6 +17,7 @@ import { Document } from 'langchain/document'; import { VectorStore } from '@langchain/core/vectorstores'; import * as uuid from 'uuid'; +import { Metadata } from '@kbn/elastic-assistant-common'; import { transformError } from '@kbn/securitysolution-es-utils'; import { ElasticsearchEmbeddings } from '../embeddings/elasticsearch_embeddings'; import { FlattenedHit, getFlattenedHits } from './helpers/get_flattened_hits'; @@ -105,7 +106,7 @@ export class ElasticsearchStore extends VectorStore { * @returns Promise of document IDs added to the store */ addDocuments = async ( - documents: Document[], + documents: Array>, options?: Record ): Promise => { // Code path for when `assistantKnowledgeBaseByDefault` FF is enabled @@ -145,7 +146,7 @@ export class ElasticsearchStore extends VectorStore { }; addDocumentsViaDataClient = async ( - documents: Document[], + documents: Array>, options?: Record ): Promise => { if (!this.kbDataClient) { diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts index 8323712c50aa7..7ea1e5fb3c9b9 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/execute_custom_llm_chain/index.ts @@ -89,12 +89,13 @@ export const callAgentExecutor: AgentExecutor = async ({ // Fetch any applicable tools that the source plugin may have registered const assistantToolParams: AssistantToolParams = { - anonymizationFields, alertsIndexPattern, - isEnabledKnowledgeBase, + anonymizationFields, chain, - llm, esClient, + isEnabledKnowledgeBase, + llm, + logger, modelExists, onNewReplacements, replacements, diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts index 8acd7f4fcdde2..bd07099e312b3 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/executors/types.ts @@ -9,7 +9,7 @@ import { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/s import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { BaseMessage } from '@langchain/core/messages'; import { Logger } from '@kbn/logging'; -import { KibanaRequest, ResponseHeaders } from '@kbn/core-http-server'; +import { KibanaRequest, KibanaResponseFactory, ResponseHeaders } from '@kbn/core-http-server'; import type { LangChainTracer } from '@langchain/core/tracers/tracer_langchain'; import { ExecuteConnectorRequestBody, Message, Replacements } from '@kbn/elastic-assistant-common'; import { StreamResponseWithHeaders } from '@kbn/ml-response-stream/server'; @@ -17,6 +17,21 @@ import { AnonymizationFieldResponse } from '@kbn/elastic-assistant-common/impl/s import { ResponseBody } from '../types'; import type { AssistantTool } from '../../../types'; import { ElasticsearchStore } from '../elasticsearch_store/elasticsearch_store'; +import { AIAssistantKnowledgeBaseDataClient } from '../../../ai_assistant_data_clients/knowledge_base'; +import { AIAssistantConversationsDataClient } from '../../../ai_assistant_data_clients/conversations'; +import { AIAssistantDataClient } from '../../../ai_assistant_data_clients'; + +export type OnLlmResponse = ( + content: string, + traceData?: Message['traceData'], + isError?: boolean +) => Promise; + +export interface AssistantDataClients { + anonymizationFieldsDataClient?: AIAssistantDataClient; + conversationsDataClient?: AIAssistantConversationsDataClient; + kbDataClient?: AIAssistantKnowledgeBaseDataClient; +} export interface AgentExecutorParams { abortSignal?: AbortSignal; @@ -26,6 +41,8 @@ export interface AgentExecutorParams { isEnabledKnowledgeBase: boolean; assistantTools?: AssistantTool[]; connectorId: string; + conversationId?: string; + dataClients?: AssistantDataClients; esClient: ElasticsearchClient; esStore: ElasticsearchStore; langChainMessages: BaseMessage[]; @@ -34,12 +51,9 @@ export interface AgentExecutorParams { onNewReplacements?: (newReplacements: Replacements) => void; replacements: Replacements; isStream?: T; - onLlmResponse?: ( - content: string, - traceData?: Message['traceData'], - isError?: boolean - ) => Promise; + onLlmResponse?: OnLlmResponse; request: KibanaRequest; + response?: KibanaResponseFactory; size?: number; traceOptions?: TraceOptions; } diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts new file mode 100644 index 0000000000000..779bf20a61720 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/graph.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RunnableConfig } from '@langchain/core/runnables'; +import { END, START, StateGraph, StateGraphArgs } from '@langchain/langgraph'; +import { AgentAction, AgentFinish, AgentStep } from '@langchain/core/agents'; +import { AgentRunnableSequence } from 'langchain/dist/agents/agent'; +import { StructuredTool } from '@langchain/core/tools'; +import type { Logger } from '@kbn/logging'; + +import { BaseMessage } from '@langchain/core/messages'; +import { BaseChatModel } from '@langchain/core/language_models/chat_models'; +import { AgentState, NodeParamsBase } from './types'; +import { AssistantDataClients } from '../../executors/types'; +import { shouldContinue } from './nodes/should_continue'; +import { AGENT_NODE, runAgent } from './nodes/run_agent'; +import { executeTools, TOOLS_NODE } from './nodes/execute_tools'; + +export const DEFAULT_ASSISTANT_GRAPH_ID = 'Default Security Assistant Graph'; + +interface GetDefaultAssistantGraphParams { + agentRunnable: AgentRunnableSequence; + dataClients?: AssistantDataClients; + conversationId?: string; + llm: BaseChatModel; + logger: Logger; + messages: BaseMessage[]; + tools: StructuredTool[]; +} + +export type DefaultAssistantGraph = ReturnType; + +/** + * Returns a compiled default assistant graph + */ +export const getDefaultAssistantGraph = ({ + agentRunnable, + conversationId, + dataClients, + llm, + logger, + messages, + tools, +}: GetDefaultAssistantGraphParams) => { + try { + // Default graph state + const graphState: StateGraphArgs['channels'] = { + input: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + steps: { + value: (x: AgentStep[], y: AgentStep[]) => x.concat(y), + default: () => [], + }, + agentOutcome: { + value: ( + x: AgentAction | AgentFinish | undefined, + y?: AgentAction | AgentFinish | undefined + ) => y ?? x, + default: () => undefined, + }, + messages: { + value: (x: BaseMessage[], y: BaseMessage[]) => x.concat(y), + default: () => messages, + }, + }; + + // Default node parameters + const nodeParams: NodeParamsBase = { + model: llm, + logger, + }; + + // Create nodes + const runAgentNode = (state: AgentState, config?: RunnableConfig) => + runAgent({ + ...nodeParams, + agentRunnable, + config, + dataClients, + logger: logger.get(AGENT_NODE), + state, + }); + const executeToolsNode = (state: AgentState, config?: RunnableConfig) => + executeTools({ + ...nodeParams, + config, + logger: logger.get(TOOLS_NODE), + state, + tools, + }); + const shouldContinueEdge = (state: AgentState) => shouldContinue({ ...nodeParams, state }); + + // Put together a new graph using the nodes and default state from above + const graph = new StateGraph, '__start__' | 'agent' | 'tools'>({ + channels: graphState, + }); + // Define the nodes to cycle between + graph.addNode(AGENT_NODE, runAgentNode); + graph.addNode(TOOLS_NODE, executeToolsNode); + // Add conditional edge for basic routing + graph.addConditionalEdges(AGENT_NODE, shouldContinueEdge, { continue: TOOLS_NODE, end: END }); + // Add edges, alternating between agent and action until finished + graph.addEdge(START, AGENT_NODE); + graph.addEdge(TOOLS_NODE, AGENT_NODE); + // Compile the graph + return graph.compile(); + } catch (e) { + throw new Error(`Unable to compile DefaultAssistantGraph\n${e}`); + } +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts new file mode 100644 index 0000000000000..383b3e9f5cee8 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/helpers.ts @@ -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 agent, { Span } from 'elastic-apm-node'; +import type { Logger } from '@kbn/logging'; +import { streamFactory, StreamResponseWithHeaders } from '@kbn/ml-response-stream/server'; +import { transformError } from '@kbn/securitysolution-es-utils'; +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { ExecuteConnectorRequestBody, TraceData } from '@kbn/elastic-assistant-common'; +import { DEFAULT_ASSISTANT_GRAPH_ID, DefaultAssistantGraph } from './graph'; +import type { OnLlmResponse, TraceOptions } from '../../executors/types'; +import type { APMTracer } from '../../tracers/apm_tracer'; +import { withAssistantSpan } from '../../tracers/with_assistant_span'; + +interface StreamGraphParams { + apmTracer: APMTracer; + assistantGraph: DefaultAssistantGraph; + inputs: { input: string }; + logger: Logger; + onLlmResponse?: OnLlmResponse; + request: KibanaRequest; + traceOptions?: TraceOptions; +} + +/** + * Execute the graph in streaming mode + * + * @param apmTracer + * @param assistantGraph + * @param inputs + * @param logger + * @param onLlmResponse + * @param request + * @param traceOptions + */ +export const streamGraph = async ({ + apmTracer, + assistantGraph, + inputs, + logger, + onLlmResponse, + request, + traceOptions, +}: StreamGraphParams): Promise => { + let streamingSpan: Span | undefined; + if (agent.isStarted()) { + streamingSpan = agent.startSpan(`${DEFAULT_ASSISTANT_GRAPH_ID} (Streaming)`) ?? undefined; + } + const { + end: streamEnd, + push, + responseWithHeaders, + } = streamFactory<{ type: string; payload: string }>(request.headers, logger, false, false); + + let didEnd = false; + const handleStreamEnd = (finalResponse: string, isError = false) => { + if (onLlmResponse) { + onLlmResponse( + finalResponse, + { + transactionId: streamingSpan?.transaction?.ids?.['transaction.id'], + traceId: streamingSpan?.ids?.['trace.id'], + }, + isError + ).catch(() => {}); + } + streamEnd(); + didEnd = true; + if ((streamingSpan && !streamingSpan?.outcome) || streamingSpan?.outcome === 'unknown') { + streamingSpan.outcome = 'success'; + } + streamingSpan?.end(); + }; + + let finalMessage = ''; + const stream = assistantGraph.streamEvents(inputs, { + callbacks: [apmTracer, ...(traceOptions?.tracers ?? [])], + runName: DEFAULT_ASSISTANT_GRAPH_ID, + streamMode: 'values', + tags: traceOptions?.tags ?? [], + version: 'v1', + }); + + const processEvent = async () => { + try { + const { value, done } = await stream.next(); + if (done) return; + + const event = value; + if (event.event === 'on_llm_stream') { + const chunk = event.data?.chunk; + // TODO: For Bedrock streaming support, override `handleLLMNewToken` in callbacks, + // TODO: or maybe we can update ActionsClientSimpleChatModel to handle this `on_llm_stream` event + if (event.name === 'ActionsClientChatOpenAI') { + const msg = chunk.message; + + if (msg.tool_call_chunks && msg.tool_call_chunks.length > 0) { + /* empty */ + } else if (!didEnd) { + if (msg.response_metadata.finish_reason === 'stop') { + handleStreamEnd(finalMessage); + } else { + push({ payload: msg.content, type: 'content' }); + finalMessage += msg.content; + } + } + } + } + + await processEvent(); + } catch (err) { + // if I throw an error here, it crashes the server. Not sure how to get around that. + // If I put await on this function the error works properly, but when there is not an error + // it waits for the entire stream to complete before resolving + const error = transformError(err); + + if (error.message === 'AbortError') { + // user aborted the stream, we must end it manually here + return handleStreamEnd(finalMessage); + } + logger.error(`Error streaming from LangChain: ${error.message}`); + push({ payload: error.message, type: 'content' }); + handleStreamEnd(error.message, true); + } + }; + + // Start processing events, do not await! Return `responseWithHeaders` immediately + await processEvent(); + + return responseWithHeaders; +}; + +interface InvokeGraphParams { + apmTracer: APMTracer; + assistantGraph: DefaultAssistantGraph; + inputs: { input: string }; + onLlmResponse?: OnLlmResponse; + traceOptions?: TraceOptions; +} +interface InvokeGraphResponse { + output: string; + traceData: TraceData; +} + +/** + * Execute the graph in non-streaming mode + * + * @param apmTracer + * @param assistantGraph + * @param inputs + * @param onLlmResponse + * @param traceOptions + */ +export const invokeGraph = async ({ + apmTracer, + assistantGraph, + inputs, + onLlmResponse, + traceOptions, +}: InvokeGraphParams): Promise => { + return withAssistantSpan(DEFAULT_ASSISTANT_GRAPH_ID, async (span) => { + let traceData: TraceData = {}; + if (span?.transaction?.ids['transaction.id'] != null && span?.ids['trace.id'] != null) { + traceData = { + // Transactions ID since this span is the parent + transactionId: span.transaction.ids['transaction.id'], + traceId: span.ids['trace.id'], + }; + span.addLabels({ evaluationId: traceOptions?.evaluationId }); + } + + const r = await assistantGraph.invoke(inputs, { + callbacks: [apmTracer, ...(traceOptions?.tracers ?? [])], + runName: DEFAULT_ASSISTANT_GRAPH_ID, + tags: traceOptions?.tags ?? [], + }); + const output = r.agentOutcome.returnValues.output; + + if (onLlmResponse) { + await onLlmResponse(output, traceData); + } + + return { output, traceData }; + }); +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts new file mode 100644 index 0000000000000..1e40f6b2fe127 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/index.ts @@ -0,0 +1,153 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { StructuredTool } from '@langchain/core/tools'; +import { RetrievalQAChain } from 'langchain/chains'; +import { + getDefaultArguments, + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server'; +import { createOpenAIFunctionsAgent, createStructuredChatAgent } from 'langchain/agents'; +import { AssistantToolParams } from '../../../../types'; +import { AgentExecutor } from '../../executors/types'; +import { openAIFunctionAgentPrompt, structuredChatAgentPrompt } from './prompts'; +import { APMTracer } from '../../tracers/apm_tracer'; +import { getDefaultAssistantGraph } from './graph'; +import { invokeGraph, streamGraph } from './helpers'; + +/** + * Drop in replacement for the existing `callAgentExecutor` that uses LangGraph + */ +export const callAssistantGraph: AgentExecutor = async ({ + abortSignal, + actions, + alertsIndexPattern, + anonymizationFields, + isEnabledKnowledgeBase, + assistantTools = [], + connectorId, + conversationId, + dataClients, + esClient, + esStore, + langChainMessages, + llmType, + logger: parentLogger, + isStream = false, + onLlmResponse, + onNewReplacements, + replacements, + request, + size, + traceOptions, +}) => { + const logger = parentLogger.get('defaultAssistantGraph'); + const isOpenAI = llmType === 'openai'; + const llmClass = isOpenAI ? ActionsClientChatOpenAI : ActionsClientSimpleChatModel; + + const llm = new llmClass({ + actions, + connectorId, + request, + llmType, + logger, + // possible client model override, + // let this be undefined otherwise so the connector handles the model + model: request.body.model, + // ensure this is defined because we default to it in the language_models + // This is where the LangSmith logs (Metadata > Invocation Params) are set + temperature: getDefaultArguments(llmType).temperature, + signal: abortSignal, + streaming: isStream, + // prevents the agent from retrying on failure + // failure could be due to bad connector, we should deliver that result to the client asap + maxRetries: 0, + }); + const model = llm; + + const messages = langChainMessages.slice(0, -1); // all but the last message + const latestMessage = langChainMessages.slice(-1); // the last message + + const modelExists = await esStore.isModelInstalled(); + + // Create a chain that uses the ELSER backed ElasticsearchStore, override k=10 for esql query generation for now + const chain = RetrievalQAChain.fromLLM(model, esStore.asRetriever(10)); + + // Fetch any applicable tools that the source plugin may have registered + const assistantToolParams: AssistantToolParams = { + alertsIndexPattern, + anonymizationFields, + chain, + esClient, + isEnabledKnowledgeBase, + kbDataClient: dataClients?.kbDataClient, + llm: model, + logger, + modelExists, + onNewReplacements, + replacements, + request, + size, + }; + + const tools: StructuredTool[] = assistantTools.flatMap( + (tool) => tool.getTool(assistantToolParams) ?? [] + ); + + const agentRunnable = isOpenAI + ? await createOpenAIFunctionsAgent({ + llm, + tools, + prompt: openAIFunctionAgentPrompt, + streamRunnable: isStream, + }) + : await createStructuredChatAgent({ + llm, + tools, + prompt: structuredChatAgentPrompt, + streamRunnable: isStream, + }); + + const apmTracer = new APMTracer({ projectName: traceOptions?.projectName ?? 'default' }, logger); + + const assistantGraph = getDefaultAssistantGraph({ + agentRunnable, + conversationId, + dataClients, + llm, + logger, + messages, + tools, + }); + const inputs = { input: latestMessage[0].content as string }; + + if (isStream) { + return streamGraph({ apmTracer, assistantGraph, inputs, logger, onLlmResponse, request }); + } + + const graphResponse = await invokeGraph({ + apmTracer, + assistantGraph, + inputs, + onLlmResponse, + traceOptions, + }); + + return { + body: { + connector_id: connectorId, + data: graphResponse.output, + trace_data: graphResponse.traceData, + replacements, + status: 'ok', + }, + headers: { + 'content-type': 'application/json', + }, + }; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/execute_tools.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/execute_tools.ts new file mode 100644 index 0000000000000..b42455e14f6f1 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/execute_tools.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RunnableConfig } from '@langchain/core/runnables'; +import { StructuredTool } from '@langchain/core/tools'; +import { ToolExecutor } from '@langchain/langgraph/prebuilt'; +import { AgentState, NodeParamsBase } from '../types'; + +export interface ExecuteToolsParams extends NodeParamsBase { + state: AgentState; + config?: RunnableConfig; + tools: StructuredTool[]; +} + +export const TOOLS_NODE = 'tools'; + +/** + * Node to execute tools + * + * Note: Could maybe leverage `ToolNode` if tool selection state is pushed to `messages[]`. + * See: https://github.com/langchain-ai/langgraphjs/blob/0ef76d603b55c00a04f5793d1e6ab15af7c756cb/langgraph/src/prebuilt/tool_node.ts + * + * @param config - Any configuration that may've been supplied + * @param logger - The scoped logger + * @param state - The current state of the graph + * @param tools - The tools available to execute + */ +export const executeTools = async ({ config, logger, state, tools }: ExecuteToolsParams) => { + logger.debug(`Node state:\n${JSON.stringify(state, null, 2)}`); + + const toolExecutor = new ToolExecutor({ tools }); + const agentAction = state.agentOutcome; + + if (!agentAction || 'returnValues' in agentAction) { + throw new Error('Agent has not been run yet'); + } + const out = await toolExecutor.invoke(agentAction, config); + return { + steps: [{ action: agentAction, observation: JSON.stringify(out, null, 2) }], + }; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/generate_chat_title.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/generate_chat_title.ts new file mode 100644 index 0000000000000..bcba25eab0b0d --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/generate_chat_title.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 { StringOutputParser } from '@langchain/core/output_parsers'; + +import { ChatPromptTemplate } from '@langchain/core/prompts'; +import { AgentState, NodeParamsBase } from '../types'; +import { AIAssistantConversationsDataClient } from '../../../../../ai_assistant_data_clients/conversations'; + +export const GENERATE_CHAT_TITLE_PROMPT = ChatPromptTemplate.fromMessages([ + [ + 'system', + `You are a helpful assistant for Elastic Security. Assume the following user message is the start of a conversation between you and a user; give this conversation a title based on the content below. DO NOT UNDER ANY CIRCUMSTANCES wrap this title in single or double quotes. This title is shown in a list of conversations to the user, so title it for the user, not for you. As an example, for the given MESSAGE, this is the TITLE: + + MESSAGE: I am having trouble with the Elastic Security app. + TITLE: Troubleshooting Elastic Security app issues + `, + ], + ['human', '{input}'], +]); + +export interface GenerateChatTitleParams extends NodeParamsBase { + conversationsDataClient?: AIAssistantConversationsDataClient; + conversationId?: string; + state: AgentState; +} + +export const GENERATE_CHAT_TITLE_NODE = 'generateChatTitle'; + +export const generateChatTitle = async ({ + conversationsDataClient, + logger, + model, + state, +}: GenerateChatTitleParams) => { + logger.debug(`Node state:\n ${JSON.stringify(state, null, 2)}`); + if (state.messages.length !== 0) { + logger.debug('No need to generate chat title, messages already exist'); + return; + } + const outputParser = new StringOutputParser(); + const graph = GENERATE_CHAT_TITLE_PROMPT.pipe(model).pipe(outputParser); + + const chatTitle = await graph.invoke({ + input: JSON.stringify(state.input, null, 2), + }); + + logger.debug(`chatTitle: ${chatTitle}`); + + return { + chatTitle, + }; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/run_agent.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/run_agent.ts new file mode 100644 index 0000000000000..b0353bb5d8ec7 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/run_agent.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RunnableConfig } from '@langchain/core/runnables'; +import { AgentRunnableSequence } from 'langchain/dist/agents/agent'; +import { AgentState, NodeParamsBase } from '../types'; +import { AssistantDataClients } from '../../../executors/types'; + +export interface RunAgentParams extends NodeParamsBase { + agentRunnable: AgentRunnableSequence; + dataClients?: AssistantDataClients; + state: AgentState; + config?: RunnableConfig; +} + +export const AGENT_NODE = 'agent'; + +const NO_HISTORY = '[No existing knowledge history]'; +/** + * Node to run the agent + * + * @param agentRunnable - The agent to run + * @param config - Any configuration that may've been supplied + * @param logger - The scoped logger + * @param dataClients - Data clients available for use + * @param state - The current state of the graph + */ +export const runAgent = async ({ + agentRunnable, + config, + dataClients, + logger, + state, +}: RunAgentParams) => { + logger.debug(`Node state:\n${JSON.stringify(state, null, 2)}`); + + const knowledgeHistory = await dataClients?.kbDataClient?.getKnowledgeBaseDocuments({ + kbResource: 'user', + required: true, + query: '', + }); + + const agentOutcome = await agentRunnable.invoke( + { + ...state, + chat_history: state.messages, // TODO: Message de-dupe with ...state spread + knowledge_history: knowledgeHistory?.length ? knowledgeHistory : NO_HISTORY, + }, + config + ); + return { + agentOutcome, + }; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/should_continue.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/should_continue.ts new file mode 100644 index 0000000000000..281963df363a8 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/nodes/should_continue.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AgentState, NodeParamsBase } from '../types'; + +export interface ShouldContinueParams extends NodeParamsBase { + state: AgentState; +} + +/** + * Node to determine which conditional edge to choose. Essentially the 'router' node. + * + * @param logger - The scoped logger + * @param state - The current state of the graph + */ +export const shouldContinue = ({ logger, state }: ShouldContinueParams) => { + logger.debug(`Node state:\n${JSON.stringify(state, null, 2)}`); + + if (state.agentOutcome && 'returnValues' in state.agentOutcome) { + return 'end'; + } + + return 'continue'; +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/prompts.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/prompts.ts new file mode 100644 index 0000000000000..7d8a78f7387ec --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/prompts.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ChatPromptTemplate } from '@langchain/core/prompts'; + +export const openAIFunctionAgentPrompt = ChatPromptTemplate.fromMessages([ + [ + 'system', + 'You are a helpful assistant\n\nUse the below context as a sample of information about the user from their knowledge base:\n\n```{knowledge_history}```', + ], + ['placeholder', '{chat_history}'], + ['human', '{input}'], + ['placeholder', '{agent_scratchpad}'], +]); + +export const structuredChatAgentPrompt = ChatPromptTemplate.fromMessages([ + [ + 'system', + 'Respond to the human as helpfully and accurately as possible. You have access to the following tools:\n\n' + + '{tools}\n\n' + + 'Use a json blob to specify a tool by providing an action key (tool name) and an action_input key (tool input).\n\n' + + 'Valid "action" values: "Final Answer" or {tool_names}\n\n' + + 'Provide only ONE action per $JSON_BLOB, as shown:\n\n' + + '```\n\n' + + '{{\n\n' + + ' "action": $TOOL_NAME,\n\n' + + ' "action_input": $INPUT\n\n' + + '}}\n\n' + + '```\n\n' + + 'Follow this format:\n\n' + + 'Question: input question to answer\n\n' + + 'Thought: consider previous and subsequent steps\n\n' + + 'Action:\n\n' + + '```\n\n' + + '$JSON_BLOB\n\n' + + '```\n\n' + + 'Observation: action result\n\n' + + '... (repeat Thought/Action/Observation N times)\n\n' + + 'Thought: I know what to respond\n\n' + + 'Action:\n\n' + + '```\n\n' + + '{{\n\n' + + ' "action": "Final Answer",\n\n' + + ' "action_input": "Final response to human"\n\n' + + '}}\n\n' + + 'Begin! Reminder to ALWAYS respond with a valid json blob of a single action. Use tools if necessary. Respond directly if appropriate. Format is Action:```$JSON_BLOB```then Observation', + ], + ['placeholder', '{chat_history}'], + [ + 'human', + 'Use the below context as a sample of information about the user from their knowledge base:\n\n```\n{knowledge_history}\n```\n\n{input}\n\n{agent_scratchpad}\n(reminder to respond in a JSON blob no matter what)', + ], +]); diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts new file mode 100644 index 0000000000000..1d19646fb6eb3 --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/graphs/default_assistant_graph/types.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { BaseMessage } from '@langchain/core/messages'; +import { AgentAction, AgentFinish, AgentStep } from '@langchain/core/agents'; +import { BaseChatModel } from '@langchain/core/language_models/chat_models'; +import type { Logger } from '@kbn/logging'; + +export interface AgentStateBase { + agentOutcome?: AgentAction | AgentFinish; + steps: AgentStep[]; +} + +export interface AgentState extends AgentStateBase { + input: string; + messages: BaseMessage[]; +} + +export interface NodeParamsBase { + logger: Logger; + model: BaseChatModel; +} diff --git a/x-pack/plugins/elastic_assistant/server/lib/langchain/tracers/apm_tracer.ts b/x-pack/plugins/elastic_assistant/server/lib/langchain/tracers/apm_tracer.ts index e5f91a379ed3d..ba4baef0433ad 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/langchain/tracers/apm_tracer.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/langchain/tracers/apm_tracer.ts @@ -38,7 +38,7 @@ export class APMTracer extends BaseTracer implements LangChainTracerFields { this.projectName = projectName ?? 'default'; this.exampleId = exampleId; - this.logger = logger; + this.logger = logger.get('apmTracer'); } protected async persistRun(_run: Run): Promise {} diff --git a/x-pack/plugins/elastic_assistant/server/lib/parse_stream.test.ts b/x-pack/plugins/elastic_assistant/server/lib/parse_stream.test.ts index 76b29fc115b4b..959bb51c40949 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/parse_stream.test.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/parse_stream.test.ts @@ -99,6 +99,46 @@ describe('handleStreamStorage', () => { it('saves the error message on a failed streaming event', async () => { const tokenPromise = handleStreamStorage({ ...defaultProps, actionTypeId: '.bedrock' }); + stream.fail(); + await expect(tokenPromise).resolves.not.toThrow(); + expect(onMessageSent).toHaveBeenCalledWith( + `An error occurred while streaming the response:\n\nStream failed` + ); + }); + }); + describe('Gemini stream', () => { + beforeEach(() => { + stream = createStreamMock(); + const payload = { + candidates: [ + { + content: { + parts: [ + { + text: 'Single.', + }, + ], + }, + }, + ], + }; + stream.write(`data: ${JSON.stringify(payload)}`); + defaultProps = { + responseStream: stream.transform, + actionTypeId: '.gemini', + onMessageSent, + logger: mockLogger, + }; + }); + + it('saves the final string successful streaming event', async () => { + stream.complete(); + await handleStreamStorage(defaultProps); + expect(onMessageSent).toHaveBeenCalledWith('Single.'); + }); + it('saves the error message on a failed streaming event', async () => { + const tokenPromise = handleStreamStorage(defaultProps); + stream.fail(); await expect(tokenPromise).resolves.not.toThrow(); expect(onMessageSent).toHaveBeenCalledWith( diff --git a/x-pack/plugins/elastic_assistant/server/lib/parse_stream.ts b/x-pack/plugins/elastic_assistant/server/lib/parse_stream.ts index e42ce6e3651fb..55be80cbb522d 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/parse_stream.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/parse_stream.ts @@ -7,7 +7,7 @@ import { Readable } from 'stream'; import { Logger } from '@kbn/core/server'; -import { parseBedrockStream } from '@kbn/langchain/server'; +import { parseBedrockStream, parseGeminiResponse } from '@kbn/langchain/server'; type StreamParser = ( responseStream: Readable, @@ -30,7 +30,12 @@ export const handleStreamStorage = async ({ logger: Logger; }): Promise => { try { - const parser = actionTypeId === '.bedrock' ? parseBedrockStream : parseOpenAIStream; + const parser = + actionTypeId === '.bedrock' + ? parseBedrockStream + : actionTypeId === '.gemini' + ? parseGeminiStream + : parseOpenAIStream; const parsedResponse = await parser(responseStream, logger, abortSignal); if (onMessageSent) { onMessageSent(parsedResponse); @@ -87,3 +92,25 @@ const parseOpenAIResponse = (responseBody: string) => const msg = line.choices[0].delta; return prev + (msg.content || ''); }, ''); + +export const parseGeminiStream: StreamParser = async (stream, logger, abortSignal) => { + let responseBody = ''; + stream.on('data', (chunk) => { + responseBody += chunk.toString(); + }); + return new Promise((resolve, reject) => { + stream.on('end', () => { + resolve(parseGeminiResponse(responseBody)); + }); + stream.on('error', (err) => { + reject(err); + }); + if (abortSignal) { + abortSignal.addEventListener('abort', () => { + stream.destroy(); + logger.info('Gemini stream parsing was aborted.'); + resolve(parseGeminiResponse(responseBody)); + }); + } + }); +}; diff --git a/x-pack/plugins/elastic_assistant/server/lib/telemetry/event_based_telemetry.ts b/x-pack/plugins/elastic_assistant/server/lib/telemetry/event_based_telemetry.ts index ad25c6b2e976f..85859b2c232b7 100644 --- a/x-pack/plugins/elastic_assistant/server/lib/telemetry/event_based_telemetry.ts +++ b/x-pack/plugins/elastic_assistant/server/lib/telemetry/event_based_telemetry.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { EventTypeOpts } from '@kbn/analytics-client'; +import type { EventTypeOpts } from '@kbn/core/server'; export const KNOWLEDGE_BASE_EXECUTION_SUCCESS_EVENT: EventTypeOpts<{ model: string; diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts index 65a3c96b27a0c..4f916be8105d4 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/helpers.ts @@ -6,6 +6,7 @@ */ import { KibanaRequest } from '@kbn/core/server'; +import { Logger } from '@kbn/logging'; import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { AttackDiscoveryPostRequestBody, @@ -40,6 +41,7 @@ export const getAssistantToolParams = ({ langChainTimeout, latestReplacements, llm, + logger, onNewReplacements, request, size, @@ -50,6 +52,7 @@ export const getAssistantToolParams = ({ langChainTimeout: number; latestReplacements: Replacements; llm: ActionsClientLlm; + logger: Logger; onNewReplacements: (newReplacements: Replacements) => void; request: KibanaRequest< unknown, @@ -65,6 +68,7 @@ export const getAssistantToolParams = ({ esClient, langChainTimeout, llm, + logger, modelExists: false, // not required for attack discovery onNewReplacements, replacements: latestReplacements, diff --git a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.ts b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.ts index 5d9240dd1d97d..7859d635ccb30 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/attack_discovery/post_attack_discovery.ts @@ -127,6 +127,7 @@ export const postAttackDiscoveryRoute = ( latestReplacements, langChainTimeout: LANG_CHAIN_TIMEOUT, llm, + logger, onNewReplacements, request, size, diff --git a/x-pack/plugins/elastic_assistant/server/routes/helpers.ts b/x-pack/plugins/elastic_assistant/server/routes/helpers.ts index 932eafb4a549b..243de14d67ed3 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/helpers.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/helpers.ts @@ -5,11 +5,15 @@ * 2.0. */ -import { KibanaRequest } from '@kbn/core-http-server'; +import { IKibanaResponse, KibanaRequest, KibanaResponseFactory } from '@kbn/core-http-server'; import { Logger } from '@kbn/core/server'; import { Message, TraceData } from '@kbn/elastic-assistant-common'; import { ILicense } from '@kbn/licensing-plugin/server'; +import { AwaitedProperties } from '@kbn/utility-types'; +import { AssistantFeatureKey } from '@kbn/elastic-assistant-common/impl/capabilities'; import { MINIMUM_AI_ASSISTANT_LICENSE } from '../../common/constants'; +import { ElasticAssistantRequestHandlerContext } from '../types'; +import { buildResponse } from './utils'; interface GetPluginNameFromRequestParams { request: KibanaRequest; @@ -87,3 +91,69 @@ export const hasAIAssistantLicense = (license: ILicense): boolean => export const UPGRADE_LICENSE_MESSAGE = 'Your license does not support AI Assistant. Please upgrade your license.'; + +interface PerformChecksParams { + authenticatedUser?: boolean; + capability?: AssistantFeatureKey; + context: AwaitedProperties< + Pick + >; + license?: boolean; + request: KibanaRequest; + response: KibanaResponseFactory; +} + +/** + * Helper to perform checks for authenticated user, capability, and license. Perform all or one + * of the checks by providing relevant optional params. Check order is license, authenticated user, + * then capability. + * + * @param authenticatedUser - Whether to check for an authenticated user + * @param capability - Specific capability to check if enabled, e.g. `assistantModelEvaluation` + * @param context - Route context + * @param license - Whether to check for a valid license + * @param request - Route KibanaRequest + * @param response - Route KibanaResponseFactory + */ +export const performChecks = ({ + authenticatedUser, + capability, + context, + license, + request, + response, +}: PerformChecksParams): IKibanaResponse | undefined => { + const assistantResponse = buildResponse(response); + + if (license) { + if (!hasAIAssistantLicense(context.licensing.license)) { + return response.forbidden({ + body: { + message: UPGRADE_LICENSE_MESSAGE, + }, + }); + } + } + + if (authenticatedUser) { + if (context.elasticAssistant.getCurrentUser() == null) { + return assistantResponse.error({ + body: `Authenticated user not found`, + statusCode: 401, + }); + } + } + + if (capability) { + const pluginName = getPluginNameFromRequest({ + request, + defaultPluginName: DEFAULT_PLUGIN_NAME, + }); + const registeredFeatures = context.elasticAssistant.getRegisteredFeatures(pluginName); + if (!registeredFeatures[capability]) { + return response.notFound(); + } + } + + return undefined; +}; diff --git a/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/bulk_actions_route.ts b/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/bulk_actions_route.ts new file mode 100644 index 0000000000000..dbd9d83bae3ac --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/bulk_actions_route.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 moment from 'moment'; +import type { AuthenticatedUser, IKibanaResponse, KibanaResponseFactory } from '@kbn/core/server'; + +import { transformError } from '@kbn/securitysolution-es-utils'; +import { + ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_BULK_ACTION, + PerformKnowledgeBaseEntryBulkActionRequestBody, + API_VERSIONS, + KnowledgeBaseEntryBulkCrudActionResults, + KnowledgeBaseEntryBulkCrudActionResponse, + KnowledgeBaseEntryBulkCrudActionSummary, + PerformKnowledgeBaseEntryBulkActionResponse, +} from '@kbn/elastic-assistant-common'; +import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common'; + +import { performChecks } from '../../helpers'; +import { KNOWLEDGE_BASE_ENTRIES_TABLE_MAX_PAGE_SIZE } from '../../../../common/constants'; +import { EsKnowledgeBaseEntrySchema } from '../../../ai_assistant_data_clients/knowledge_base/types'; +import { ElasticAssistantPluginRouter } from '../../../types'; +import { buildResponse } from '../../utils'; +import { transformESSearchToKnowledgeBaseEntry } from '../../../ai_assistant_data_clients/knowledge_base/transforms'; +import { transformToCreateSchema } from '../../../ai_assistant_data_clients/knowledge_base/create_knowledge_base_entry'; + +export interface BulkOperationError { + message: string; + status?: number; + document: { + id: string; + name?: string; + }; +} + +export type BulkResponse = KnowledgeBaseEntryBulkCrudActionResults & { + errors?: BulkOperationError[]; +}; + +export type BulkActionError = BulkOperationError | unknown; + +const buildBulkResponse = ( + response: KibanaResponseFactory, + { + errors = [], + updated = [], + created = [], + deleted = [], + skipped = [], + }: KnowledgeBaseEntryBulkCrudActionResults & { errors: BulkOperationError[] } +): IKibanaResponse => { + const numSucceeded = updated.length + created.length + deleted.length; + const numSkipped = skipped.length; + const numFailed = errors.length; + + const summary: KnowledgeBaseEntryBulkCrudActionSummary = { + failed: numFailed, + succeeded: numSucceeded, + skipped: numSkipped, + total: numSucceeded + numFailed + numSkipped, + }; + + const results: KnowledgeBaseEntryBulkCrudActionResults = { + updated, + created, + deleted, + skipped, + }; + + if (numFailed > 0) { + return response.custom({ + headers: { 'content-type': 'application/json' }, + body: { + message: summary.succeeded > 0 ? 'Bulk edit partially failed' : 'Bulk edit failed', + attributes: { + errors: errors.map((e: BulkOperationError) => ({ + statusCode: e.status ?? 500, + knowledgeBaseEntries: [{ id: e.document.id, name: '' }], + message: e.message, + })), + results, + summary, + }, + }, + statusCode: 500, + }); + } + + const responseBody: KnowledgeBaseEntryBulkCrudActionResponse = { + success: true, + knowledgeBaseEntriesCount: summary.total, + attributes: { results, summary }, + }; + + return response.ok({ body: responseBody }); +}; + +export const bulkActionKnowledgeBaseEntriesRoute = (router: ElasticAssistantPluginRouter) => { + router.versioned + .post({ + access: 'internal', + path: ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_BULK_ACTION, + options: { + tags: ['access:elasticAssistant'], + timeout: { + idleSocket: moment.duration(15, 'minutes').asMilliseconds(), + }, + }, + }) + .addVersion( + { + version: API_VERSIONS.internal.v1, + validate: { + request: { + body: buildRouteValidationWithZod(PerformKnowledgeBaseEntryBulkActionRequestBody), + }, + }, + }, + async ( + context, + request, + response + ): Promise> => { + const assistantResponse = buildResponse(response); + try { + const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']); + const logger = ctx.elasticAssistant.logger; + + // Perform license, authenticated user and FF checks + const checkResponse = performChecks({ + authenticatedUser: true, + capability: 'assistantKnowledgeBaseByDefault', + context: ctx, + license: true, + request, + response, + }); + if (checkResponse) { + return checkResponse; + } + + logger.debug( + `Performing bulk action on Knowledge Base Entries:\n${JSON.stringify(request.body)}` + ); + + const { body } = request; + + const operationsCount = + (body?.update ? body.update?.length : 0) + + (body?.create ? body.create?.length : 0) + + (body?.delete ? body.delete?.ids?.length ?? 0 : 0); + if (operationsCount > KNOWLEDGE_BASE_ENTRIES_TABLE_MAX_PAGE_SIZE) { + return assistantResponse.error({ + body: `More than ${KNOWLEDGE_BASE_ENTRIES_TABLE_MAX_PAGE_SIZE} ids sent for bulk edit action.`, + statusCode: 400, + }); + } + + const abortController = new AbortController(); + + // subscribing to completed$, because it handles both cases when request was completed and aborted. + // when route is finished by timeout, aborted$ is not getting fired + request.events.completed$.subscribe(() => abortController.abort()); + const kbDataClient = await ctx.elasticAssistant.getAIAssistantKnowledgeBaseDataClient( + false + ); + const spaceId = ctx.elasticAssistant.getSpaceId(); + // Authenticated user null check completed in `performChecks()` above + const authenticatedUser = ctx.elasticAssistant.getCurrentUser() as AuthenticatedUser; + + if (body.create && body.create.length > 0) { + const result = await kbDataClient?.findDocuments({ + perPage: 100, + page: 1, + filter: `users:{ id: "${authenticatedUser?.profile_uid}" }`, + fields: [], + }); + if (result?.data != null && result.total > 0) { + return assistantResponse.error({ + statusCode: 409, + body: `Knowledge Base Entry id's: "${transformESSearchToKnowledgeBaseEntry( + result.data + ) + .map((c) => c.id) + .join(',')}" already exists`, + }); + } + } + + const writer = await kbDataClient?.getWriter(); + const changedAt = new Date().toISOString(); + const { + errors, + docs_created: docsCreated, + docs_updated: docsUpdated, + docs_deleted: docsDeleted, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + } = await writer!.bulk({ + documentsToCreate: body.create?.map((c) => + transformToCreateSchema(changedAt, spaceId, authenticatedUser, c) + ), + documentsToDelete: body.delete?.ids, + documentsToUpdate: [], // TODO: Support bulk update + authenticatedUser, + }); + const created = + docsCreated.length > 0 + ? await kbDataClient?.findDocuments({ + page: 1, + perPage: 100, + filter: docsCreated.map((c) => `_id:${c}`).join(' OR '), + }) + : undefined; + + return buildBulkResponse(response, { + // @ts-ignore-next-line TS2322 + updated: docsUpdated, + created: created?.data ? transformESSearchToKnowledgeBaseEntry(created?.data) : [], + deleted: docsDeleted ?? [], + errors, + }); + } catch (err) { + const error = transformError(err); + return assistantResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/create_route.ts b/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/create_route.ts index 0c31974a20785..b93ab4e894c8b 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/create_route.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/create_route.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { IKibanaResponse } from '@kbn/core/server'; +import { IKibanaResponse } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; import { API_VERSIONS, @@ -15,15 +15,17 @@ import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/ import { KnowledgeBaseEntryCreateProps, KnowledgeBaseEntryResponse, + Metadata, } from '@kbn/elastic-assistant-common/impl/schemas/knowledge_base/common_attributes.gen'; +import type { Document } from 'langchain/document'; import { ElasticAssistantPluginRouter } from '../../../types'; import { buildResponse } from '../../utils'; -import { UPGRADE_LICENSE_MESSAGE, hasAIAssistantLicense } from '../../helpers'; +import { performChecks } from '../../helpers'; export const createKnowledgeBaseEntryRoute = (router: ElasticAssistantPluginRouter): void => { router.versioned .post({ - access: 'public', + access: 'internal', path: ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL, options: { @@ -32,7 +34,7 @@ export const createKnowledgeBaseEntryRoute = (router: ElasticAssistantPluginRout }) .addVersion( { - version: API_VERSIONS.public.v1, + version: API_VERSIONS.internal.v1, validate: { request: { body: buildRouteValidationWithZod(KnowledgeBaseEntryCreateProps), @@ -43,27 +45,40 @@ export const createKnowledgeBaseEntryRoute = (router: ElasticAssistantPluginRout const assistantResponse = buildResponse(response); try { const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']); - const license = ctx.licensing.license; - if (!hasAIAssistantLicense(license)) { - return response.forbidden({ - body: { - message: UPGRADE_LICENSE_MESSAGE, - }, - }); + const logger = ctx.elasticAssistant.logger; + + // Perform license, authenticated user and FF checks + const checkResponse = performChecks({ + authenticatedUser: true, + capability: 'assistantKnowledgeBaseByDefault', + context: ctx, + license: true, + request, + response, + }); + if (checkResponse) { + return checkResponse; } - const authenticatedUser = ctx.elasticAssistant.getCurrentUser(); - if (authenticatedUser == null) { + logger.debug(`Creating KB Entry:\n${JSON.stringify(request.body)}`); + const documents: Array> = [ + { + metadata: request.body.metadata, + pageContent: request.body.text, + }, + ]; + const kbDataClient = await ctx.elasticAssistant.getAIAssistantKnowledgeBaseDataClient( + false + ); + const createResponse = await kbDataClient?.addKnowledgeBaseDocuments({ documents }); + + if (createResponse == null) { return assistantResponse.error({ - body: `Authenticated user not found`, - statusCode: 401, + body: `Knowledge Base Entry was not created`, + statusCode: 400, }); } - - return assistantResponse.error({ - body: `knowledge base entry was not created`, - statusCode: 400, - }); + return response.ok({ body: KnowledgeBaseEntryResponse.parse(createResponse[0]) }); } catch (err) { const error = transformError(err as Error); return assistantResponse.error({ diff --git a/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/find_route.ts b/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/find_route.ts new file mode 100644 index 0000000000000..9dc7bb3f39dbf --- /dev/null +++ b/x-pack/plugins/elastic_assistant/server/routes/knowledge_base/entries/find_route.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IKibanaResponse } from '@kbn/core/server'; +import { transformError } from '@kbn/securitysolution-es-utils'; + +import { + API_VERSIONS, + ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_FIND, + FindKnowledgeBaseEntriesRequestQuery, + FindKnowledgeBaseEntriesResponse, +} from '@kbn/elastic-assistant-common'; +import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common'; +import { ElasticAssistantPluginRouter } from '../../../types'; +import { buildResponse } from '../../utils'; + +import { performChecks } from '../../helpers'; +import { transformESSearchToKnowledgeBaseEntry } from '../../../ai_assistant_data_clients/knowledge_base/transforms'; +import { EsKnowledgeBaseEntrySchema } from '../../../ai_assistant_data_clients/knowledge_base/types'; + +export const findKnowledgeBaseEntriesRoute = (router: ElasticAssistantPluginRouter) => { + router.versioned + .get({ + access: 'internal', + path: ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_FIND, + options: { + tags: ['access:elasticAssistant'], + }, + }) + .addVersion( + { + version: API_VERSIONS.internal.v1, + validate: { + request: { + query: buildRouteValidationWithZod(FindKnowledgeBaseEntriesRequestQuery), + }, + }, + }, + async ( + context, + request, + response + ): Promise> => { + const assistantResponse = buildResponse(response); + try { + const { query } = request; + const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']); + + // Perform license, authenticated user and FF checks + const checkResponse = performChecks({ + authenticatedUser: true, + capability: 'assistantKnowledgeBaseByDefault', + context: ctx, + license: true, + request, + response, + }); + if (checkResponse) { + return checkResponse; + } + + const kbDataClient = await ctx.elasticAssistant.getAIAssistantKnowledgeBaseDataClient( + false + ); + const currentUser = ctx.elasticAssistant.getCurrentUser(); + + const additionalFilter = query.filter ? ` AND ${query.filter}` : ''; + const result = await kbDataClient?.findDocuments({ + perPage: query.per_page, + page: query.page, + sortField: query.sort_field, + sortOrder: query.sort_order, + filter: `users:{ id: "${currentUser?.profile_uid}" }${additionalFilter}`, // TODO: Update filter to include non-user system entries + fields: query.fields, + }); + + if (result) { + return response.ok({ + body: { + perPage: result.perPage, + page: result.page, + total: result.total, + data: transformESSearchToKnowledgeBaseEntry(result.data), + }, + }); + } + return response.ok({ + body: { perPage: query.per_page, page: query.page, data: [], total: 0 }, + }); + } catch (err) { + const error = transformError(err); + return assistantResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts b/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts index e170784cc7626..197479fc24dd5 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/post_actions_connector_execute.ts @@ -21,7 +21,11 @@ import { import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common'; import { i18n } from '@kbn/i18n'; import { getLlmType } from './utils'; -import { StaticReturnType } from '../lib/langchain/executors/types'; +import { + AgentExecutorParams, + AssistantDataClients, + StaticReturnType, +} from '../lib/langchain/executors/types'; import { INVOKE_ASSISTANT_ERROR_EVENT, INVOKE_ASSISTANT_SUCCESS_EVENT, @@ -42,6 +46,7 @@ import { getLangSmithTracer } from './evaluate/utils'; import { EsAnonymizationFieldsSchema } from '../ai_assistant_data_clients/anonymization_fields/types'; import { transformESSearchToAnonymizationFields } from '../ai_assistant_data_clients/anonymization_fields/helpers'; import { ElasticsearchStore } from '../lib/langchain/elasticsearch_store/elasticsearch_store'; +import { callAssistantGraph } from '../lib/langchain/graphs/default_assistant_graph'; export const postActionsConnectorExecuteRoute = ( router: IRouter, @@ -325,7 +330,7 @@ export const postActionsConnectorExecuteRoute = ( }); // Create an ElasticsearchStore for KB interactions - // Setup with kbDataClient if `enableKnowledgeBaseByDefault` FF is enabled + // Setup with kbDataClient if `assistantKnowledgeBaseByDefault` FF is enabled const enableKnowledgeBaseByDefault = assistantContext.getRegisteredFeatures(pluginName).assistantKnowledgeBaseByDefault; const kbDataClient = enableKnowledgeBaseByDefault @@ -345,7 +350,14 @@ export const postActionsConnectorExecuteRoute = ( kbDataClient ); - const result: StreamResponseWithHeaders | StaticReturnType = await callAgentExecutor({ + const dataClients: AssistantDataClients = { + anonymizationFieldsDataClient: anonymizationFieldsDataClient ?? undefined, + conversationsDataClient: conversationsDataClient ?? undefined, + kbDataClient, + }; + + // Shared executor params + const executorParams: AgentExecutorParams = { abortSignal, alertsIndexPattern: request.body.alertsIndexPattern, anonymizationFields: anonymizationFieldsRes @@ -355,6 +367,8 @@ export const postActionsConnectorExecuteRoute = ( isEnabledKnowledgeBase: request.body.isEnabledKnowledgeBase ?? false, assistantTools, connectorId, + conversationId, + dataClients, esClient, esStore, isStream: request.body.subAction !== 'invokeAI', @@ -364,6 +378,7 @@ export const postActionsConnectorExecuteRoute = ( onNewReplacements, onLlmResponse, request, + response, replacements: request.body.replacements, size: request.body.size, traceOptions: { @@ -374,7 +389,15 @@ export const postActionsConnectorExecuteRoute = ( logger, }), }, - }); + }; + + // New code path for LangGraph implementation, behind `assistantKnowledgeBaseByDefault` FF + let result: StreamResponseWithHeaders | StaticReturnType; + if (enableKnowledgeBaseByDefault) { + result = await callAssistantGraph(executorParams); + } else { + result = await callAgentExecutor(executorParams); + } telemetry.reportEvent(INVOKE_ASSISTANT_SUCCESS_EVENT.eventType, { actionTypeId, diff --git a/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts b/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts index fc0e30f4a925c..374b32d6cceb5 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/register_routes.ts @@ -27,6 +27,9 @@ import { bulkPromptsRoute } from './prompts/bulk_actions_route'; import { findPromptsRoute } from './prompts/find_route'; import { bulkActionAnonymizationFieldsRoute } from './anonymization_fields/bulk_actions_route'; import { findAnonymizationFieldsRoute } from './anonymization_fields/find_route'; +import { bulkActionKnowledgeBaseEntriesRoute } from './knowledge_base/entries/bulk_actions_route'; +import { createKnowledgeBaseEntryRoute } from './knowledge_base/entries/create_route'; +import { findKnowledgeBaseEntriesRoute } from './knowledge_base/entries/find_route'; export const registerRoutes = ( router: ElasticAssistantPluginRouter, @@ -49,11 +52,16 @@ export const registerRoutes = ( // User Conversations search findUserConversationsRoute(router); - // Knowledge Base + // Knowledge Base Setup deleteKnowledgeBaseRoute(router); getKnowledgeBaseStatusRoute(router, getElserId); postKnowledgeBaseRoute(router, getElserId); + // Knowledge Base Entries + findKnowledgeBaseEntriesRoute(router); + createKnowledgeBaseEntryRoute(router); + bulkActionKnowledgeBaseEntriesRoute(router); + // Actions Connector Execute (LLM Wrapper) postActionsConnectorExecuteRoute(router, getElserId); diff --git a/x-pack/plugins/elastic_assistant/server/routes/utils.ts b/x-pack/plugins/elastic_assistant/server/routes/utils.ts index 243befcb19271..324d2fefa46b9 100644 --- a/x-pack/plugins/elastic_assistant/server/routes/utils.ts +++ b/x-pack/plugins/elastic_assistant/server/routes/utils.ts @@ -170,6 +170,7 @@ export const getLlmType = (actionTypeId: string): string | undefined => { const llmTypeDictionary: Record = { [`.gen-ai`]: `openai`, [`.bedrock`]: `bedrock`, + [`.gemini`]: `gemini`, }; return llmTypeDictionary[actionTypeId]; }; diff --git a/x-pack/plugins/elastic_assistant/server/types.ts b/x-pack/plugins/elastic_assistant/server/types.ts index 3a392eaa8c256..f12bacde983df 100755 --- a/x-pack/plugins/elastic_assistant/server/types.ts +++ b/x-pack/plugins/elastic_assistant/server/types.ts @@ -212,8 +212,10 @@ export interface AssistantToolParams { isEnabledKnowledgeBase: boolean; chain?: RetrievalQAChain; esClient: ElasticsearchClient; + kbDataClient?: AIAssistantKnowledgeBaseDataClient; langChainTimeout?: number; llm?: ActionsClientLlm | ActionsClientChatOpenAI | ActionsClientSimpleChatModel; + logger: Logger; modelExists: boolean; onNewReplacements?: (newReplacements: Replacements) => void; replacements?: Replacements; diff --git a/x-pack/plugins/elastic_assistant/tsconfig.json b/x-pack/plugins/elastic_assistant/tsconfig.json index d4ce180d8496d..dde693653c04c 100644 --- a/x-pack/plugins/elastic_assistant/tsconfig.json +++ b/x-pack/plugins/elastic_assistant/tsconfig.json @@ -13,7 +13,6 @@ "../../../typings/**/*" ], "kbn_references": [ - "@kbn/analytics-client", "@kbn/core", "@kbn/core-http-server", "@kbn/licensing-plugin", diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts index 8f49e3cae3c02..9830f66a441ed 100644 --- a/x-pack/plugins/enterprise_search/common/constants.ts +++ b/x-pack/plugins/enterprise_search/common/constants.ts @@ -8,6 +8,7 @@ import { ENTERPRISE_SEARCH_APP_ID, ENTERPRISE_SEARCH_CONTENT_APP_ID, + ENTERPRISE_SEARCH_INFERENCE_ENDPOINTS_APP_ID, ENTERPRISE_SEARCH_APPLICATIONS_APP_ID, ENTERPRISE_SEARCH_ANALYTICS_APP_ID, ENTERPRISE_SEARCH_APPSEARCH_APP_ID, @@ -176,6 +177,22 @@ export const VECTOR_SEARCH_PLUGIN = { URL: '/app/enterprise_search/vector_search', }; +export const INFERENCE_ENDPOINTS_PLUGIN = { + ID: ENTERPRISE_SEARCH_INFERENCE_ENDPOINTS_APP_ID, + NAME: i18n.translate('xpack.enterpriseSearch.inferenceEndpoints.productName', { + defaultMessage: 'Inference Endpoints', + }), + NAV_TITLE: i18n.translate('xpack.enterpriseSearch.inferenceEndpoints.navTitle', { + defaultMessage: 'Relevance', + }), + DESCRIPTION: i18n.translate('xpack.enterpriseSearch.inferenceEndpoints.description', { + defaultMessage: 'View for managing inference endpoints.', + }), + URL: '/app/enterprise_search/relevance', + LOGO: 'logoEnterpriseSearch', + SUPPORT_URL: 'https://discuss.elastic.co/c/enterprise-search/', +}; + export const LICENSED_SUPPORT_URL = 'https://support.elastic.co'; export const JSON_HEADER = { diff --git a/x-pack/plugins/enterprise_search/kibana.jsonc b/x-pack/plugins/enterprise_search/kibana.jsonc index cefa247025aa5..0dc2562ff2fe3 100644 --- a/x-pack/plugins/enterprise_search/kibana.jsonc +++ b/x-pack/plugins/enterprise_search/kibana.jsonc @@ -30,7 +30,9 @@ "guidedOnboarding", "console", "searchConnectors", + "searchHomepage", "searchPlayground", + "searchInferenceEndpoints", "embeddable", "discover", "charts", diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts index 5aa7f5236e269..5f4774be15b96 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts @@ -45,6 +45,7 @@ export const mockKibanaValues = { history: mockHistory, indexMappingComponent: null, isCloud: false, + isSearchHomepageEnabled: false, isSidebarEnabled: true, lens: { EmbeddableComponent: jest.fn(), @@ -64,6 +65,8 @@ export const mockKibanaValues = { hasWebCrawler: true, }, renderHeaderActions: jest.fn(), + searchHomepage: null, + searchInferenceEndpoints: null, searchPlayground: searchPlaygroundMock.createStart(), security: securityMock.createStart(), setBreadcrumbs: jest.fn(), diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.tsx index 4ddd0920038e1..19727db552acf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.tsx @@ -84,7 +84,6 @@ export const MultiCheckboxFacetsView: React.FC = ({ options={checkboxGroupOptions} idToSelectedMap={idToSelectedMap} onChange={onChange} - compressed /> {showMore && ( <> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx index 09e6b494f94e4..7c13a3f524ccf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/search_index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { useParams } from 'react-router-dom'; @@ -15,12 +15,15 @@ import { EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { ClientConfigType } from '../../../../../common/types'; + import { generateEncodedPath } from '../../../shared/encode_path_params'; import { ErrorStatePrompt } from '../../../shared/error_state'; import { HttpLogic } from '../../../shared/http'; import { KibanaLogic } from '../../../shared/kibana'; import { SEARCH_INDEX_PATH, SEARCH_INDEX_TAB_PATH } from '../../routes'; +import { ElasticsearchViewIndex } from '../../types'; import { isConnectorIndex, isCrawlerIndex } from '../../utils/indices'; import { ConnectorConfiguration } from '../connector_detail/connector_configuration'; import { EnterpriseSearchContentPageTemplate } from '../layout/page_template'; @@ -223,20 +226,6 @@ export const SearchIndex: React.FC = () => { ...(hasDefaultIngestPipeline ? [PIPELINES_TAB] : []), ]; - const selectedTab = tabs.find((tab) => tab.id === tabId); - - const onTabClick = (tab: EuiTabbedContentTab) => { - KibanaLogic.values.navigateToUrl( - generateEncodedPath( - tab.id === SearchIndexTabId.OVERVIEW ? SEARCH_INDEX_PATH : SEARCH_INDEX_TAB_PATH, - { - indexName, - tabId: tab.id, - } - ) - ); - }; - return ( { rightSideItems: getHeaderActions(index), }} > - {isCrawlerIndex(index) && !index.connector ? ( - - ) : isCrawlerIndex(index) && (Boolean(errorConnectingMessage) || !config.host) ? ( - - ) : ( - <> - {indexName === index?.name && ( - - )} - {isCrawlerIndex(index) && } - - )} + ); }; + +interface ContentProps { + config?: ClientConfigType; + errorConnectingMessage: string; + index?: ElasticsearchViewIndex; + tabId?: string; + tabs: EuiTabbedContentTab[]; +} + +const Content: React.FC = ({ + config, + errorConnectingMessage, + index, + tabs, + tabId, +}) => { + const selectedTab = useMemo(() => tabs.find((tab) => tab.id === tabId), [tabId]); + + const onTabClick = (tab: EuiTabbedContentTab) => { + KibanaLogic.values.navigateToUrl( + generateEncodedPath( + tab.id === SearchIndexTabId.OVERVIEW ? SEARCH_INDEX_PATH : SEARCH_INDEX_TAB_PATH, + { + indexName: index?.name || '', + tabId: tab.id, + } + ) + ); + }; + + if (isCrawlerIndex(index) && !index.connector) { + return ; + } + if (isCrawlerIndex(index) && (Boolean(errorConnectingMessage) || !config?.host)) { + return ; + } + return ( + <> + + {isCrawlerIndex(index) && } + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/inference_endpoints.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/inference_endpoints.tsx new file mode 100644 index 0000000000000..440638b57d4b6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/inference_endpoints.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 { useValues } from 'kea'; + +import { i18n } from '@kbn/i18n'; + +import { KibanaLogic } from '../../shared/kibana'; + +import { EnterpriseSearchRelevancePageTemplate } from './layout/page_template'; + +export const InferenceEndpoints: React.FC = () => { + const { searchInferenceEndpoints } = useValues(KibanaLogic); + + if (!searchInferenceEndpoints) { + return null; + } + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/index.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/index.ts new file mode 100644 index 0000000000000..0a155ce20b555 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/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 { EnterpriseSearchRelevancePageTemplate } from './page_template'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.test.tsx new file mode 100644 index 0000000000000..6b9e6b891a59f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.test.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +jest.mock('../../../shared/layout/nav', () => ({ + useEnterpriseSearchNav: () => [], +})); + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { i18n } from '@kbn/i18n'; + +import { SetEnterpriseSearchRelevanceChrome } from '../../../shared/kibana_chrome'; +import { EnterpriseSearchPageTemplateWrapper } from '../../../shared/layout'; +import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; + +import { EnterpriseSearchRelevancePageTemplate } from './page_template'; + +describe('EnterpriseSearchRelevancePageTemplate', () => { + it('renders', () => { + const wrapper = shallow( + +

+ {i18n.translate('xpack.enterpriseSearch..div.worldLabel', { defaultMessage: 'world' })} +
+ + ); + + expect(wrapper.type()).toEqual(EnterpriseSearchPageTemplateWrapper); + expect(wrapper.prop('solutionNav')).toEqual({ items: [], name: 'Search' }); + expect(wrapper.find('.hello').text()).toEqual('world'); + }); + + describe('page chrome', () => { + it('takes a breadcrumb array & renders a product-specific page chrome', () => { + const wrapper = shallow(); + const setPageChrome = wrapper + .find(EnterpriseSearchPageTemplateWrapper) + .prop('setPageChrome') as any; + + expect(setPageChrome.type).toEqual(SetEnterpriseSearchRelevanceChrome); + expect(setPageChrome.props.trail).toEqual(['Some page']); + }); + }); + + describe('page telemetry', () => { + it('takes a metric & renders product-specific telemetry viewed event', () => { + const wrapper = shallow( + + ); + + expect(wrapper.find(SendEnterpriseSearchTelemetry).prop('action')).toEqual('viewed'); + expect(wrapper.find(SendEnterpriseSearchTelemetry).prop('metric')).toEqual('some_page'); + }); + }); + + describe('props', () => { + it('passes down any ...pageTemplateProps that EnterpriseSearchPageTemplateWrapper accepts', () => { + const wrapper = shallow( + } + /> + ); + + expect( + wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('pageHeader')!.pageTitle + ).toEqual('hello world'); + expect(wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('isLoading')).toEqual(false); + expect(wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('emptyState')).toEqual(
); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.tsx new file mode 100644 index 0000000000000..258b9c9a68ae1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { SEARCH_PRODUCT_NAME } from '../../../../../common/constants'; +import { SetEnterpriseSearchRelevanceChrome } from '../../../shared/kibana_chrome'; +import { EnterpriseSearchPageTemplateWrapper, PageTemplateProps } from '../../../shared/layout'; +import { useEnterpriseSearchNav } from '../../../shared/layout'; +import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; + +export const EnterpriseSearchRelevancePageTemplate: React.FC = ({ + children, + pageChrome, + pageViewTelemetry, + ...pageTemplateProps +}) => { + return ( + } + > + {pageViewTelemetry && ( + + )} + {children} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/index.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/index.ts new file mode 100644 index 0000000000000..482c1a58faa9c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/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 { NotFound } from './not_found'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.test.tsx new file mode 100644 index 0000000000000..7b5436958edb7 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.test.tsx @@ -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 React from 'react'; + +import { shallow } from 'enzyme'; + +import { NotFoundPrompt } from '../../../shared/not_found'; +import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; +import { EnterpriseSearchRelevancePageTemplate } from '../layout'; + +import { NotFound } from '.'; + +describe('NotFound', () => { + const wrapper = shallow(); + + it('renders the shared not found prompt', () => { + expect(wrapper.find(NotFoundPrompt)).toHaveLength(1); + }); + + it('renders a telemetry error event', () => { + expect(wrapper.find(SendEnterpriseSearchTelemetry).prop('action')).toEqual('error'); + }); + + it('passes optional preceding page chrome', () => { + wrapper.setProps({ pageChrome: ['Inference Endpoints', 'some-index'] }); + + expect(wrapper.find(EnterpriseSearchRelevancePageTemplate).prop('pageChrome')).toEqual([ + 'Inference Endpoints', + 'some-index', + '404', + ]); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.tsx new file mode 100644 index 0000000000000..1c599fdc884e3 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { INFERENCE_ENDPOINTS_PLUGIN } from '../../../../../common/constants'; +import { PageTemplateProps } from '../../../shared/layout'; +import { NotFoundPrompt } from '../../../shared/not_found'; +import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; +import { EnterpriseSearchRelevancePageTemplate } from '../layout'; + +export const NotFound: React.FC = ({ pageChrome = [] }) => { + return ( + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.test.tsx new file mode 100644 index 0000000000000..5ac28448c7c4d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.test.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 { setMockValues } from '../__mocks__/kea_logic'; +import '../__mocks__/shallow_useeffect.mock'; +import '../__mocks__/enterprise_search_url.mock'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { VersionMismatchPage } from '../shared/version_mismatch'; + +import { EnterpriseSearchRelevance, EnterpriseSearchRelevanceConfigured } from '.'; + +describe('EnterpriseSearchRelevance', () => { + it('renders VersionMismatchPage when there are mismatching versions', () => { + setMockValues({ config: { canDeployEntSearch: true, host: 'host' } }); + const wrapper = shallow( + + ); + + expect(wrapper.find(VersionMismatchPage)).toHaveLength(1); + }); + + it('renders EnterpriseSearchRelevanceConfigured when config.host is set & available', () => { + setMockValues({ + config: { canDeployEntSearch: true, host: 'some.url' }, + errorConnectingMessage: '', + }); + const wrapper = shallow(); + + expect(wrapper.find(EnterpriseSearchRelevanceConfigured)).toHaveLength(1); + }); + + it('renders EnterpriseSearchRelevanceConfigured when config.host is not set & Ent Search cannot be deployed', () => { + setMockValues({ config: { canDeployEntSearch: false, host: '' }, errorConnectingMessage: '' }); + const wrapper = shallow(); + + expect(wrapper.find(EnterpriseSearchRelevanceConfigured)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.tsx new file mode 100644 index 0000000000000..6e75d8055619b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { Redirect } from 'react-router-dom'; + +import { useValues } from 'kea'; + +import { Route, Routes } from '@kbn/shared-ux-router'; + +import { isVersionMismatch } from '../../../common/is_version_mismatch'; +import { InitialAppData } from '../../../common/types'; +import { ErrorStatePrompt } from '../shared/error_state'; +import { HttpLogic } from '../shared/http'; +import { VersionMismatchPage } from '../shared/version_mismatch'; + +import { InferenceEndpoints } from './components/inference_endpoints'; +import { NotFound } from './components/not_found'; +import { INFERENCE_ENDPOINTS_PATH, ERROR_STATE_PATH, ROOT_PATH } from './routes'; + +export const EnterpriseSearchRelevance: React.FC = (props) => { + const { errorConnectingMessage } = useValues(HttpLogic); + const { enterpriseSearchVersion, kibanaVersion } = props; + const incompatibleVersions = isVersionMismatch(enterpriseSearchVersion, kibanaVersion); + + const showView = () => { + if (incompatibleVersions) { + return ( + + ); + } + + return )} />; + }; + + return ( + + + {errorConnectingMessage ? : } + + {showView()} + + ); +}; + +export const EnterpriseSearchRelevanceConfigured: React.FC> = () => { + return ( + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/jest.config.js b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/jest.config.js new file mode 100644 index 0000000000000..d186396d14a07 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/jest.config.js @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../../../..', + roots: [ + '/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance', + ], + collectCoverage: true, + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/x-pack/plugins/enterprise_search/public/applications/**/*.{ts,tsx}', + '!/x-pack/plugins/enterprise_search/public/*.ts', + '!/x-pack/plugins/enterprise_search/server/*.ts', + '!/x-pack/plugins/enterprise_search/public/applications/test_helpers/**/*.{ts,tsx}', + ], + coverageDirectory: + '/target/kibana-coverage/jest/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance', + modulePathIgnorePatterns: [ + '/x-pack/plugins/enterprise_search/public/applications/app_search/cypress', + '/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress', + ], +}; diff --git a/x-pack/plugins/lists/common/api/values/read_list_index/read_list_index_route.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/routes.ts similarity index 62% rename from x-pack/plugins/lists/common/api/values/read_list_index/read_list_index_route.ts rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/routes.ts index 772d042b7e11c..5ea7ad1c781a8 100644 --- a/x-pack/plugins/lists/common/api/values/read_list_index/read_list_index_route.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/routes.ts @@ -5,6 +5,6 @@ * 2.0. */ -import { listItemIndexExistSchema } from '@kbn/securitysolution-io-ts-list-types'; - -export { listItemIndexExistSchema as readListIndexResponse }; +export const ROOT_PATH = '/'; +export const ERROR_STATE_PATH = '/error_state'; +export const INFERENCE_ENDPOINTS_PATH = `${ROOT_PATH}inference_endpoints`; diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx index 27241a0a628b8..98d6677c35fc1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx @@ -19,7 +19,6 @@ import { I18nProvider } from '@kbn/i18n-react'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; -import { AuthenticatedUser } from '@kbn/security-plugin/public'; import { Router } from '@kbn/shared-ux-router'; import { DEFAULT_PRODUCT_FEATURES } from '../../common/constants'; @@ -99,19 +98,6 @@ export const renderApp = ( resetContext({ createStore: true }); const store = getContext().store; - let user: AuthenticatedUser | null = null; - try { - security?.authc - .getCurrentUser() - .then((newUser) => { - user = newUser; - }) - .catch(() => { - user = null; - }); - } catch { - user = null; - } const indexMappingComponent = indexManagementPlugin?.getIndexMappingComponent({ history }); const connectorTypes = plugins.searchConnectors?.getConnectorTypes() || []; @@ -124,12 +110,14 @@ export const renderApp = ( config, connectorTypes, console: plugins.console, + coreSecurity: core.security, data: plugins.data, esConfig, getChromeStyle$: chrome.getChromeStyle$, guidedOnboarding, history, indexMappingComponent, + isSearchHomepageEnabled: plugins.searchHomepage?.isHomepageFeatureEnabled() ?? false, isSidebarEnabled, lens, ml, @@ -140,7 +128,9 @@ export const renderApp = ( params.setHeaderActionMenu( HeaderActions ? renderHeaderActions.bind(null, HeaderActions, store, params) : undefined ), + searchHomepage: plugins.searchHomepage, searchPlayground: plugins.searchPlayground, + searchInferenceEndpoints: plugins.searchInferenceEndpoints, security, setBreadcrumbs: chrome.setBreadcrumbs, setChromeIsVisible: chrome.setIsVisible, @@ -148,7 +138,6 @@ export const renderApp = ( share, uiSettings, updateSideNavDefinition, - user, }); const unmountLicensingLogic = mountLicensingLogic({ canManageLicense: core.application.capabilities.management?.stack?.license_management, @@ -160,7 +149,6 @@ export const renderApp = ( readOnlyMode, }); const unmountFlashMessagesLogic = mountFlashMessagesLogic({ notifications }); - ReactDOM.render( diff --git a/x-pack/plugins/enterprise_search/public/applications/search_homepage/components/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/search_homepage/components/layout/page_template.test.tsx new file mode 100644 index 0000000000000..c44cc39c5eb1d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/search_homepage/components/layout/page_template.test.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { TestHelper } from '../../../test_helpers/test_utils.test_helper'; + +import { SearchHomepagePageTemplate } from './page_template'; + +describe('SearchHomepagePageTemplate', () => { + beforeAll(() => { + TestHelper.prepare(); + }); + + it('renders as expected', async () => { + const { container } = TestHelper.render( + +
Test
+
+ ); + + expect(container.querySelector('.kbnSolutionNav__title')).toHaveTextContent('Search'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/search_homepage/components/layout/page_template.tsx b/x-pack/plugins/enterprise_search/public/applications/search_homepage/components/layout/page_template.tsx new file mode 100644 index 0000000000000..76f2e6e526239 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/search_homepage/components/layout/page_template.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { SEARCH_PRODUCT_NAME } from '../../../../../common/constants'; +import { SetSearchChrome } from '../../../shared/kibana_chrome'; +import { EnterpriseSearchPageTemplateWrapper, PageTemplateProps } from '../../../shared/layout'; +import { useEnterpriseSearchNav } from '../../../shared/layout'; +import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry'; + +export const SearchHomepagePageTemplate: React.FC = ({ + children, + pageChrome, + pageViewTelemetry, + ...pageTemplateProps +}) => { + return ( + } + > + {pageViewTelemetry && ( + + )} + {children} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/search_homepage/components/search_homepage.tsx b/x-pack/plugins/enterprise_search/public/applications/search_homepage/components/search_homepage.tsx new file mode 100644 index 0000000000000..a605010fcb00d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/search_homepage/components/search_homepage.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useValues } from 'kea'; + +import { KibanaLogic } from '../../shared/kibana'; +import { SetSearchChrome } from '../../shared/kibana_chrome'; + +import { SearchHomepagePageTemplate } from './layout/page_template'; + +export const SearchHomepagePage = () => { + const { isSearchHomepageEnabled, searchHomepage } = useValues(KibanaLogic); + + if (!isSearchHomepageEnabled || !searchHomepage) { + return null; + } + + return ( + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/search_homepage/index.tsx b/x-pack/plugins/enterprise_search/public/applications/search_homepage/index.tsx new file mode 100644 index 0000000000000..43963f21d3b5d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/search_homepage/index.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 { Routes, Route } from '@kbn/shared-ux-router'; + +import { isVersionMismatch } from '../../../common/is_version_mismatch'; +import type { InitialAppData } from '../../../common/types'; +import { VersionMismatchPage } from '../shared/version_mismatch'; + +import { SearchHomepagePage } from './components/search_homepage'; + +export const SearchHomepage: React.FC = (props) => { + const { enterpriseSearchVersion, kibanaVersion } = props; + const incompatibleVersions = isVersionMismatch(enterpriseSearchVersion, kibanaVersion); + + const showView = () => { + if (incompatibleVersions) { + return ( + + ); + } + + return ; + }; + + return ( + + + {showView()} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/search_homepage/jest.config.js b/x-pack/plugins/enterprise_search/public/applications/search_homepage/jest.config.js new file mode 100644 index 0000000000000..c18a3561afb65 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/search_homepage/jest.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../../../..', + roots: ['/x-pack/plugins/enterprise_search/public/applications/search_homepage'], + collectCoverage: true, + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/x-pack/plugins/enterprise_search/public/applications/**/*.{ts,tsx}', + '!/x-pack/plugins/enterprise_search/public/*.ts', + '!/x-pack/plugins/enterprise_search/server/*.ts', + '!/x-pack/plugins/enterprise_search/public/applications/test_helpers/**/*.{ts,tsx}', + ], + coverageDirectory: + '/target/kibana-coverage/jest/x-pack/plugins/enterprise_search/public/applications/search_homepage', + modulePathIgnorePatterns: [ + '/x-pack/plugins/enterprise_search/public/applications/app_search/cypress', + '/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress', + ], +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts index e4975452a29c5..4920b25cffd75 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts @@ -19,6 +19,7 @@ import { ScopedHistory, IUiSettingsClient, ChromeStart, + SecurityServiceStart, } from '@kbn/core/public'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; @@ -28,6 +29,8 @@ import { LensPublicStart } from '@kbn/lens-plugin/public'; import { MlPluginStart } from '@kbn/ml-plugin/public'; import { ELASTICSEARCH_URL_PLACEHOLDER } from '@kbn/search-api-panels/constants'; import { ConnectorDefinition } from '@kbn/search-connectors-plugin/public'; +import type { SearchHomepagePluginStart } from '@kbn/search-homepage/public'; +import { SearchInferenceEndpointsPluginStart } from '@kbn/search-inference-endpoints/public'; import { SearchPlaygroundPluginStart } from '@kbn/search-playground/public'; import { AuthenticatedUser, SecurityPluginStart } from '@kbn/security-plugin/public'; import { SharePluginStart } from '@kbn/share-plugin/public'; @@ -49,12 +52,14 @@ export interface KibanaLogicProps { config: ClientConfigType; connectorTypes?: ConnectorDefinition[]; console?: ConsolePluginStart; + coreSecurity?: SecurityServiceStart; data?: DataPublicPluginStart; esConfig: ESConfig; getChromeStyle$: ChromeStart['getChromeStyle$']; guidedOnboarding?: GuidedOnboardingPluginStart; history: ScopedHistory; indexMappingComponent?: React.FC; + isSearchHomepageEnabled: boolean; isSidebarEnabled: boolean; lens?: LensPublicStart; ml?: MlPluginStart; @@ -62,7 +67,9 @@ export interface KibanaLogicProps { productAccess: ProductAccess; productFeatures: ProductFeatures; renderHeaderActions(HeaderActions?: FC): void; + searchHomepage?: SearchHomepagePluginStart; searchPlayground?: SearchPlaygroundPluginStart; + searchInferenceEndpoints?: SearchInferenceEndpointsPluginStart; security?: SecurityPluginStart; setBreadcrumbs(crumbs: ChromeBreadcrumb[]): void; setChromeIsVisible(isVisible: boolean): void; @@ -70,7 +77,6 @@ export interface KibanaLogicProps { share?: SharePluginStart; uiSettings?: IUiSettingsClient; updateSideNavDefinition: UpdateSideNavDefinitionFn; - user: AuthenticatedUser | null; } export interface KibanaValues { @@ -88,6 +94,7 @@ export interface KibanaValues { history: ScopedHistory; indexMappingComponent: React.FC | null; isCloud: boolean; + isSearchHomepageEnabled: boolean; isSidebarEnabled: boolean; lens: LensPublicStart | null; ml: MlPluginStart | null; @@ -95,7 +102,9 @@ export interface KibanaValues { productAccess: ProductAccess; productFeatures: ProductFeatures; renderHeaderActions(HeaderActions?: FC): void; + searchHomepage: SearchHomepagePluginStart | null; searchPlayground: SearchPlaygroundPluginStart | null; + searchInferenceEndpoints: SearchInferenceEndpointsPluginStart | null; security: SecurityPluginStart | null; setBreadcrumbs(crumbs: ChromeBreadcrumb[]): void; setChromeIsVisible(isVisible: boolean): void; @@ -107,6 +116,9 @@ export interface KibanaValues { } export const KibanaLogic = kea>({ + actions: { + setUser: (user: AuthenticatedUser | null) => ({ user }), + }, path: ['enterprise_search', 'kibana_logic'], reducers: ({ props }) => ({ application: [props.application, {}], @@ -122,6 +134,7 @@ export const KibanaLogic = kea>({ guidedOnboarding: [props.guidedOnboarding || null, {}], history: [props.history, {}], indexMappingComponent: [props.indexMappingComponent || null, {}], + isSearchHomepageEnabled: [props.isSearchHomepageEnabled, {}], isSidebarEnabled: [props.isSidebarEnabled, {}], lens: [props.lens || null, {}], ml: [props.ml || null, {}], @@ -136,7 +149,9 @@ export const KibanaLogic = kea>({ productAccess: [props.productAccess, {}], productFeatures: [props.productFeatures, {}], renderHeaderActions: [props.renderHeaderActions, {}], + searchHomepage: [props.searchHomepage || null, {}], searchPlayground: [props.searchPlayground || null, {}], + searchInferenceEndpoints: [props.searchInferenceEndpoints || null, {}], security: [props.security || null, {}], setBreadcrumbs: [props.setBreadcrumbs, {}], setChromeIsVisible: [props.setChromeIsVisible, {}], @@ -144,7 +159,12 @@ export const KibanaLogic = kea>({ share: [props.share || null, {}], uiSettings: [props.uiSettings, {}], updateSideNavDefinition: [props.updateSideNavDefinition, {}], - user: [props.user || null, {}], + user: [ + props.user || null, + { + setUser: (_, { user }) => user || null, + }, + ], }), selectors: ({ selectors }) => ({ isCloud: [() => [selectors.cloud], (cloud?: CloudSetup) => Boolean(cloud?.isCloudEnabled)], @@ -154,5 +174,8 @@ export const KibanaLogic = kea>({ export const mountKibanaLogic = (props: KibanaLogicProps) => { KibanaLogic(props); const unmount = KibanaLogic.mount(); + props.coreSecurity?.authc.getCurrentUser()?.then((user) => { + KibanaLogic.actions.setUser(user); + }); return unmount; }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/breadcrumbs_home.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/breadcrumbs_home.ts new file mode 100644 index 0000000000000..34e9e12c88b76 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/breadcrumbs_home.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 { ENTERPRISE_SEARCH_OVERVIEW_PLUGIN } from '../../../../common/constants'; + +/** + * HACK for base homepage URL, this can be removed and updated to a static + * URL when Search Homepage is no longer feature flagged. + */ +const breadCrumbHome = { url: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL }; +export const getHomeURL = () => breadCrumbHome.url; +export const setBreadcrumbHomeUrl = (url: string) => { + breadCrumbHome.url = url; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts index d30ceee39b75b..5798a48680d1f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts @@ -14,7 +14,7 @@ import { ANALYTICS_PLUGIN, APP_SEARCH_PLUGIN, ENTERPRISE_SEARCH_CONTENT_PLUGIN, - ENTERPRISE_SEARCH_OVERVIEW_PLUGIN, + INFERENCE_ENDPOINTS_PLUGIN, ENTERPRISE_SEARCH_PRODUCT_NAME, AI_SEARCH_PLUGIN, SEARCH_EXPERIENCES_PLUGIN, @@ -28,6 +28,8 @@ import { HttpLogic } from '../http'; import { KibanaLogic } from '../kibana'; import { letBrowserHandleEvent, createHref } from '../react_router_helpers'; +import { getHomeURL } from './breadcrumbs_home'; + /** * Types */ @@ -106,7 +108,7 @@ export const useSearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => useEuiBreadcrumbs([ { text: SEARCH_PRODUCT_NAME, - path: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL, + path: getHomeURL(), shouldNotCreateHref: true, }, ...breadcrumbs, @@ -116,7 +118,7 @@ export const useEnterpriseSearchBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => useEuiBreadcrumbs([ { text: ENTERPRISE_SEARCH_PRODUCT_NAME, - path: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL, + path: getHomeURL(), shouldNotCreateHref: true, }, ...breadcrumbs, @@ -151,6 +153,9 @@ export const useEnterpriseSearchContentBreadcrumbs = (breadcrumbs: Breadcrumbs = ...breadcrumbs, ]); +export const useEnterpriseSearchRelevanceBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => + useSearchBreadcrumbs([{ text: INFERENCE_ENDPOINTS_PLUGIN.NAV_TITLE, path: '/' }, ...breadcrumbs]); + export const useSearchExperiencesBreadcrumbs = (breadcrumbs: Breadcrumbs = []) => useSearchBreadcrumbs([{ text: SEARCH_EXPERIENCES_PLUGIN.NAV_TITLE, path: '/' }, ...breadcrumbs]); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts index 75e72ab5e0208..ae2151287b1d1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts @@ -9,6 +9,7 @@ export { SetSearchChrome, SetAnalyticsChrome, SetEnterpriseSearchContentChrome, + SetEnterpriseSearchRelevanceChrome, SetElasticsearchChrome, SetAiSearchChrome, SetAppSearchChrome, diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx index 5c6758509c01d..ac85bd89b6ba9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx @@ -19,6 +19,7 @@ import { useEnterpriseSearchApplicationsBreadcrumbs, useAnalyticsBreadcrumbs, useEnterpriseSearchContentBreadcrumbs, + useEnterpriseSearchRelevanceBreadcrumbs, useAiSearchBreadcrumbs, useElasticsearchBreadcrumbs, useAppSearchBreadcrumbs, @@ -177,6 +178,19 @@ export const SetEnterpriseSearchContentChrome: React.FC = ({ tra return null; }; +export const SetEnterpriseSearchRelevanceChrome: React.FC = ({ trail = [] }) => { + const { setBreadcrumbs } = useValues(KibanaLogic); + + const crumbs = useGenerateBreadcrumbs(trail); + const breadcrumbs = useEnterpriseSearchRelevanceBreadcrumbs(crumbs); + + useEffect(() => { + setBreadcrumbs(breadcrumbs); + }, [trail]); + + return null; +}; + export const SetSearchExperiencesChrome: React.FC = ({ trail = [] }) => { const { setBreadcrumbs, setDocTitle } = useValues(KibanaLogic); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx index 3930e0f6c36f1..f818dfb9141f3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx @@ -84,6 +84,18 @@ const baseNavItems = [ ], name: 'Build', }, + { + id: 'relevance', + items: [ + { + href: '/app/enterprise_search/relevance/inference_endpoints', + id: 'inference_endpoints', + items: undefined, + name: 'Inference Endpoints', + }, + ], + name: 'Relevance', + }, { id: 'es_getting_started', items: [ @@ -244,6 +256,7 @@ describe('useEnterpriseSearchApplicationNav', () => { expect(navItems?.slice(1).map((ni) => ni.name)).toEqual([ 'Content', 'Build', + 'Relevance', 'Getting started', 'Enterprise Search', ]); @@ -303,6 +316,7 @@ describe('useEnterpriseSearchApplicationNav', () => { expect(navItems?.slice(1).map((ni) => ni.name)).toEqual([ 'Content', 'Build', + 'Relevance', 'Getting started', 'Enterprise Search', ]); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx index c2268d254197d..77454581c61e7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx @@ -23,6 +23,7 @@ import { AI_SEARCH_PLUGIN, VECTOR_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN, + INFERENCE_ENDPOINTS_PLUGIN, } from '../../../../common/constants'; import { SEARCH_APPLICATIONS_PATH, @@ -35,6 +36,8 @@ import { CRAWLERS_PATH, SEARCH_INDICES_PATH, } from '../../enterprise_search_content/routes'; + +import { INFERENCE_ENDPOINTS_PATH } from '../../enterprise_search_relevance/routes'; import { KibanaLogic } from '../kibana'; import { generateNavLink } from './nav_link_helpers'; @@ -46,7 +49,8 @@ import { generateNavLink } from './nav_link_helpers'; * @returns The Enterprise Search navigation items */ export const useEnterpriseSearchNav = (alwaysReturn = false) => { - const { isSidebarEnabled, productAccess } = useValues(KibanaLogic); + const { isSearchHomepageEnabled, searchHomepage, isSidebarEnabled, productAccess } = + useValues(KibanaLogic); const indicesNavItems = useIndicesNav(); if (!isSidebarEnabled && !alwaysReturn) return undefined; @@ -63,7 +67,10 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => { ...generateNavLink({ shouldNotCreateHref: true, shouldShowActiveForSubroutes: true, - to: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL, + to: + isSearchHomepageEnabled && searchHomepage + ? searchHomepage.app.appRoute + : ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL, }), }, { @@ -147,6 +154,25 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => { defaultMessage: 'Build', }), }, + { + id: 'relevance', + items: [ + { + id: 'inference_endpoints', + name: i18n.translate('xpack.enterpriseSearch.nav.inferenceEndpointsTitle', { + defaultMessage: 'Inference Endpoints', + }), + ...generateNavLink({ + shouldNotCreateHref: true, + shouldShowActiveForSubroutes: true, + to: INFERENCE_ENDPOINTS_PLUGIN.URL + INFERENCE_ENDPOINTS_PATH, + }), + }, + ], + name: i18n.translate('xpack.enterpriseSearch.nav.relevanceTitle', { + defaultMessage: 'Relevance', + }), + }, { id: 'es_getting_started', items: [ diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.test.tsx index 974c0d17c29f9..cde080aa70fbd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.test.tsx @@ -6,7 +6,8 @@ */ import '../../__mocks__/shallow_useeffect.mock'; -import { mockKibanaValues } from '../../__mocks__/kea_logic'; + +import { setMockValues } from '../../__mocks__/kea_logic'; import React from 'react'; @@ -17,18 +18,6 @@ import { EuiButton, EuiLink, EuiEmptyPrompt } from '@elastic/eui'; import { RolesEmptyPrompt } from './roles_empty_prompt'; describe('RolesEmptyPrompt', () => { - const { - security: { - authc: { getCurrentUser }, - }, - } = mockKibanaValues; - - const mockCurrentUser = (user?: unknown) => - (getCurrentUser as jest.Mock).mockReturnValue(Promise.resolve(user)); - - const mockCurrentUserError = () => - (getCurrentUser as jest.Mock).mockReturnValue(Promise.reject()); - const onEnable = jest.fn(); const props = { @@ -42,25 +31,20 @@ describe('RolesEmptyPrompt', () => { roles: ['superuser'], }; - beforeAll(() => { - mockCurrentUser(); - }); + beforeAll(() => {}); it('gets the current user on mount', () => { shallow(); - - expect(getCurrentUser).toHaveBeenCalled(); }); - it('does not render if the getCurrentUser promise returns error', async () => { - mockCurrentUserError(); + it('does not render if there is no user', async () => { const wrapper = await shallow(); expect(wrapper.isEmptyRender()).toBe(true); }); it('renders', async () => { - mockCurrentUser(mockUser); + setMockValues({ user: mockUser }); const wrapper = await shallow(); expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); @@ -68,9 +52,11 @@ describe('RolesEmptyPrompt', () => { }); it('disables button when non-superuser', async () => { - mockCurrentUser({ - username: 'user', - roles: ['foo'], + setMockValues({ + user: { + username: 'user', + roles: ['foo'], + }, }); const wrapper = await shallow(); @@ -81,7 +67,7 @@ describe('RolesEmptyPrompt', () => { }); it('calls onEnable on change', async () => { - mockCurrentUser(mockUser); + setMockValues({ user: mockUser }); const wrapper = await shallow(); const prompt = wrapper.find(EuiEmptyPrompt).dive(); prompt.find(EuiButton).simulate('click'); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.tsx index 1d9b8a6b01bcd..8b3b6ad4d6975 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/roles_empty_prompt.tsx @@ -5,14 +5,12 @@ * 2.0. */ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import { useValues } from 'kea'; import { EuiEmptyPrompt, EuiButton, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; -import type { AuthenticatedUser } from '@kbn/security-plugin/public'; - import { KibanaLogic } from '../kibana/kibana_logic'; import { ProductName } from '../types'; @@ -32,8 +30,7 @@ interface Props { } export const RolesEmptyPrompt: React.FC = ({ onEnable, docsLink, productName }) => { - const { security } = useValues(KibanaLogic); - const [currentUser, setCurrentUser] = useState(null); + const { user: currentUser } = useValues(KibanaLogic); const isSuperUser = currentUser?.roles.includes('superuser'); const rbacDisabledLabel = !isSuperUser && ( @@ -41,17 +38,6 @@ export const RolesEmptyPrompt: React.FC = ({ onEnable, docsLink, productN ); - useEffect(() => { - if (security) { - security.authc - .getCurrentUser() - .then(setCurrentUser) - .catch(() => { - setCurrentUser(null); - }); - } - }, [security?.authc]); - if (!currentUser) { return null; } @@ -67,12 +53,24 @@ export const RolesEmptyPrompt: React.FC = ({ onEnable, docsLink, productN } actions={[ - + {ENABLE_ROLES_BUTTON} , rbacDisabledLabel, , - + {ENABLE_ROLES_LINK} , ]} diff --git a/x-pack/plugins/enterprise_search/public/applications/test_helpers/test_utils.test_helper.tsx b/x-pack/plugins/enterprise_search/public/applications/test_helpers/test_utils.test_helper.tsx index 02102d8cc4dc4..0050165b8be50 100644 --- a/x-pack/plugins/enterprise_search/public/applications/test_helpers/test_utils.test_helper.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/test_helpers/test_utils.test_helper.tsx @@ -51,6 +51,7 @@ export const mockKibanaProps: KibanaLogicProps = { }, }, connectorTypes: [], + coreSecurity: undefined, data: dataPluginMock.createStartContract(), esConfig: { elasticsearch_host: 'https://your_deployment_url', @@ -61,6 +62,7 @@ export const mockKibanaProps: KibanaLogicProps = { indexMappingComponent: () => { return <>; }, + isSearchHomepageEnabled: false, isSidebarEnabled: true, lens: { EmbeddableComponent: jest.fn(), @@ -83,6 +85,7 @@ export const mockKibanaProps: KibanaLogicProps = { hasWebCrawler: true, }, renderHeaderActions: jest.fn(), + searchHomepage: undefined, searchPlayground: searchPlaygroundMock.createStart(), security: securityMock.createStart(), setBreadcrumbs: jest.fn(), @@ -91,7 +94,6 @@ export const mockKibanaProps: KibanaLogicProps = { share: sharePluginMock.createStartContract(), uiSettings: uiSettingsServiceMock.createStartContract(), updateSideNavDefinition: jest.fn(), - user: null, }; type LogicFile = LogicWrapper; @@ -114,7 +116,7 @@ interface TestHelper { defaultMockValues: typeof DEFAULT_VALUES; mountLogic: (logicFile: LogicFile, props?: object) => void; prepare: (options?: PrepareOptions) => void; - render: (children: JSX.Element) => void; + render: (children: JSX.Element) => ReturnType; } export const TestHelper: TestHelper = { @@ -147,7 +149,7 @@ export const TestHelper: TestHelper = { TestHelper.actionsToRun.forEach((action) => { action(); }); - testingLibraryRender( + return testingLibraryRender( {children} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.test.tsx index c89a1647af331..8867955e36478 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.test.tsx @@ -6,7 +6,7 @@ */ import '../../../__mocks__/shallow_useeffect.mock'; -import { mockKibanaValues } from '../../../__mocks__/kea_logic'; +import { mockKibanaValues, setMockValues } from '../../../__mocks__/kea_logic'; import React from 'react'; @@ -17,45 +17,20 @@ import { AccountSettings } from '.'; describe('AccountSettings', () => { const { security: { - authc: { getCurrentUser }, uiApi: { components: { getPersonalInfo, getChangePassword }, }, }, } = mockKibanaValues; - const mockCurrentUser = (user?: unknown) => - (getCurrentUser as jest.Mock).mockReturnValue(Promise.resolve(user)); - - const mockCurrentUserError = () => - (getCurrentUser as jest.Mock).mockReturnValue(Promise.reject()); - - beforeAll(() => { - mockCurrentUser(); - }); - - it('gets the current user on mount', () => { - shallow(); - - expect(getCurrentUser).toHaveBeenCalled(); - }); - it('does not render if the current user does not exist', async () => { - mockCurrentUser(null); - const wrapper = await shallow(); - - expect(wrapper.isEmptyRender()).toBe(true); - }); - - it('does not render if the getCurrentUser promise returns error', async () => { - mockCurrentUserError(); const wrapper = await shallow(); expect(wrapper.isEmptyRender()).toBe(true); }); it('renders the security UI components when the user exists', async () => { - mockCurrentUser({ username: 'mock user' }); + setMockValues({ user: { username: 'mock user' } }); (getPersonalInfo as jest.Mock).mockReturnValue(
); (getChangePassword as jest.Mock).mockReturnValue(
); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.tsx index deecc9a3199d2..3012a0a5514c5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/account_settings/account_settings.tsx @@ -5,31 +5,16 @@ * 2.0. */ -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useMemo } from 'react'; import { useValues } from 'kea'; -import type { AuthenticatedUser } from '@kbn/security-plugin/public'; - import { KibanaLogic } from '../../../shared/kibana/kibana_logic'; import { PersonalDashboardLayout } from '../../components/layout'; import { ACCOUNT_SETTINGS_TITLE } from '../../constants'; export const AccountSettings: React.FC = () => { - const { security } = useValues(KibanaLogic); - - const [currentUser, setCurrentUser] = useState(null); - - useEffect(() => { - if (security) { - security.authc - .getCurrentUser() - .then(setCurrentUser) - .catch(() => { - setCurrentUser(null); - }); - } - }, [security?.authc]); + const { user: currentUser, security } = useValues(KibanaLogic); const PersonalInfo = useMemo(() => security?.uiApi.components.getPersonalInfo, [security?.uiApi]); const ChangePassword = useMemo( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts index 336f892bad51b..3318f8b46a9a8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts @@ -17,9 +17,8 @@ import { nextTick } from '@kbn/test-jest-helpers'; import { itShowsServerErrorAsFlashMessage } from '../../../../../test_helpers'; -const contentSource = { id: 'source123' }; jest.mock('../../source_logic', () => ({ - SourceLogic: { values: { contentSource } }, + SourceLogic: { values: { contentSource: { id: 'source123' } } }, })); jest.mock('../../../../app_logic', () => ({ diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts index 7dba607026761..d9e9f10cea852 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts @@ -15,8 +15,9 @@ import { mostRecentIndexJob } from '../../../../__mocks__/content_sources.mock'; import { nextTick } from '@kbn/test-jest-helpers'; const contentSource = { id: 'source123' }; + jest.mock('../../source_logic', () => ({ - SourceLogic: { values: { contentSource } }, + SourceLogic: { values: { contentSource: { id: 'source123' } } }, })); jest.mock('../../../../app_logic', () => ({ diff --git a/x-pack/plugins/enterprise_search/public/navigation_tree.ts b/x-pack/plugins/enterprise_search/public/navigation_tree.ts index 9dc1d324fd118..051cfaa6779af 100644 --- a/x-pack/plugins/enterprise_search/public/navigation_tree.ts +++ b/x-pack/plugins/enterprise_search/public/navigation_tree.ts @@ -67,12 +67,14 @@ const euiItemTypeToNodeDefinition = ({ export const getNavigationTreeDefinition = ({ dynamicItems$, + isSearchHomepageEnabled, }: { dynamicItems$: Observable; + isSearchHomepageEnabled: boolean; }): AddSolutionNavigationArg => { return { dataTestSubj: 'searchSideNav', - homePage: 'enterpriseSearch', + homePage: isSearchHomepageEnabled ? 'searchHomepage' : 'enterpriseSearch', icon, id: 'es', navigationTree$: dynamicItems$.pipe( @@ -84,7 +86,7 @@ export const getNavigationTreeDefinition = ({ breadcrumbStatus: 'hidden', children: [ { - link: 'enterpriseSearch', + link: isSearchHomepageEnabled ? 'searchHomepage' : 'enterpriseSearch', }, { getIsActive: ({ pathNameSerialized, prepend }) => { @@ -206,6 +208,13 @@ export const getNavigationTreeDefinition = ({ defaultMessage: 'Build', }), }, + { + children: [{ link: 'searchInferenceEndpoints' }], + id: 'relevance', + title: i18n.translate('xpack.enterpriseSearch.searchNav.relevance', { + defaultMessage: 'Relevance', + }), + }, { children: [ { diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts index 96c4bab2b8059..280de2f04356b 100644 --- a/x-pack/plugins/enterprise_search/public/plugin.ts +++ b/x-pack/plugins/enterprise_search/public/plugin.ts @@ -32,6 +32,11 @@ import { MlPluginStart } from '@kbn/ml-plugin/public'; import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; import { ELASTICSEARCH_URL_PLACEHOLDER } from '@kbn/search-api-panels/constants'; import { SearchConnectorsPluginStart } from '@kbn/search-connectors-plugin/public'; +import type { + SearchHomepagePluginSetup, + SearchHomepagePluginStart, +} from '@kbn/search-homepage/public'; +import { SearchInferenceEndpointsPluginStart } from '@kbn/search-inference-endpoints/public'; import { SearchPlaygroundPluginStart } from '@kbn/search-playground/public'; import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public'; import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; @@ -48,6 +53,7 @@ import { SEARCH_PRODUCT_NAME, VECTOR_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN, + INFERENCE_ENDPOINTS_PLUGIN, } from '../common/constants'; import { CreatIndexLocatorDefinition, @@ -63,7 +69,9 @@ import { CRAWLERS_PATH, } from './applications/enterprise_search_content/routes'; +import { INFERENCE_ENDPOINTS_PATH } from './applications/enterprise_search_relevance/routes'; import { docLinks } from './applications/shared/doc_links'; +import { setBreadcrumbHomeUrl } from './applications/shared/kibana_chrome/breadcrumbs_home'; import type { DynamicSideNavItems } from './navigation_tree'; export interface ClientData extends InitialAppData { @@ -77,6 +85,7 @@ export type EnterpriseSearchPublicStart = ReturnType { - const kibanaDeps = await this.getKibanaDeps(core, params, cloud); - const { chrome, http } = kibanaDeps.core; - chrome.docTitle.change(ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.NAME); + if (useSearchHomepage) { + const { app } = plugins.searchHomepage!; + core.application.register({ + ...app, + category: DEFAULT_APP_CATEGORIES.enterpriseSearch, + euiIconType: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.LOGO, + visibleIn: ['home', 'kibanaOverview', 'globalSearch', 'sideNav'], + mount: async (params: AppMountParameters) => { + const kibanaDeps = await this.getKibanaDeps(core, params, cloud); + const { chrome, http } = kibanaDeps.core; + chrome.docTitle.change(app.title); - await this.getInitialData(http); - const pluginData = this.getPluginData(); + await this.getInitialData(http); + const pluginData = this.getPluginData(); - const { renderApp } = await import('./applications'); - const { EnterpriseSearchOverview } = await import( - './applications/enterprise_search_overview' - ); + const { renderApp } = await import('./applications'); + const { SearchHomepage } = await import('./applications/search_homepage'); - return renderApp(EnterpriseSearchOverview, kibanaDeps, pluginData); - }, - title: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.NAV_TITLE, - visibleIn: ['home', 'kibanaOverview', 'globalSearch', 'sideNav'], - }); + return renderApp(SearchHomepage, kibanaDeps, pluginData); + }, + }); + setBreadcrumbHomeUrl(app.appRoute); + } else { + core.application.register({ + appRoute: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL, + category: DEFAULT_APP_CATEGORIES.enterpriseSearch, + euiIconType: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.LOGO, + id: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.ID, + mount: async (params: AppMountParameters) => { + const kibanaDeps = await this.getKibanaDeps(core, params, cloud); + const { chrome, http } = kibanaDeps.core; + chrome.docTitle.change(ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.NAME); + + await this.getInitialData(http); + const pluginData = this.getPluginData(); + + const { renderApp } = await import('./applications'); + const { EnterpriseSearchOverview } = await import( + './applications/enterprise_search_overview' + ); + + return renderApp(EnterpriseSearchOverview, kibanaDeps, pluginData); + }, + title: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.NAV_TITLE, + visibleIn: ['home', 'kibanaOverview', 'globalSearch', 'sideNav'], + }); + } core.application.register({ appRoute: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL, @@ -395,6 +445,31 @@ export class EnterpriseSearchPlugin implements Plugin { title: ANALYTICS_PLUGIN.NAME, }); + core.application.register({ + appRoute: INFERENCE_ENDPOINTS_PLUGIN.URL, + category: DEFAULT_APP_CATEGORIES.enterpriseSearch, + deepLinks: relevanceLinks, + euiIconType: INFERENCE_ENDPOINTS_PLUGIN.LOGO, + id: INFERENCE_ENDPOINTS_PLUGIN.ID, + mount: async (params: AppMountParameters) => { + const kibanaDeps = await this.getKibanaDeps(core, params, cloud); + const { chrome, http } = kibanaDeps.core; + chrome.docTitle.change(INFERENCE_ENDPOINTS_PLUGIN.NAME); + + await this.getInitialData(http); + const pluginData = this.getPluginData(); + + const { renderApp } = await import('./applications'); + const { EnterpriseSearchRelevance } = await import( + './applications/enterprise_search_relevance' + ); + + return renderApp(EnterpriseSearchRelevance, kibanaDeps, pluginData); + }, + title: INFERENCE_ENDPOINTS_PLUGIN.NAME, + visibleIn: [], + }); + core.application.register({ appRoute: SEARCH_EXPERIENCES_PLUGIN.URL, category: DEFAULT_APP_CATEGORIES.enterpriseSearch, @@ -470,14 +545,27 @@ export class EnterpriseSearchPlugin implements Plugin { } if (plugins.home) { - plugins.home.featureCatalogue.registerSolution({ - description: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.DESCRIPTION, - icon: 'logoEnterpriseSearch', - id: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.ID, - order: 100, - path: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL, - title: SEARCH_PRODUCT_NAME, - }); + if (useSearchHomepage) { + const { searchHomepage } = plugins; + + plugins.home.featureCatalogue.registerSolution({ + description: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.DESCRIPTION, + icon: 'logoEnterpriseSearch', + id: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.ID, + order: 100, + path: searchHomepage!.app.appRoute, + title: SEARCH_PRODUCT_NAME, + }); + } else { + plugins.home.featureCatalogue.registerSolution({ + description: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.DESCRIPTION, + icon: 'logoEnterpriseSearch', + id: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.ID, + order: 100, + path: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.URL, + title: SEARCH_PRODUCT_NAME, + }); + } plugins.home.featureCatalogue.register({ category: 'data', @@ -545,7 +633,10 @@ export class EnterpriseSearchPlugin implements Plugin { import('./navigation_tree').then(({ getNavigationTreeDefinition }) => { return plugins.navigation.addSolutionNavigation( - getNavigationTreeDefinition({ dynamicItems$: this.sideNavDynamicItems$ }) + getNavigationTreeDefinition({ + dynamicItems$: this.sideNavDynamicItems$, + isSearchHomepageEnabled: plugins.searchHomepage?.isHomepageFeatureEnabled() ?? false, + }) ); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts index abdac9fb57e0f..7549f7ed002c9 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/api_keys.ts @@ -51,8 +51,9 @@ export function registerApiKeysRoutes( validate: {}, }, async (context, request, response) => { - const { client } = (await context.core).elasticsearch; - const user = security.authc.getCurrentUser(request); + const core = await context.core; + const { client } = core.elasticsearch; + const user = core.security.authc.getCurrentUser(); if (user) { try { const apiKeys = await client.asCurrentUser.security.getApiKey({ diff --git a/x-pack/plugins/enterprise_search/tsconfig.json b/x-pack/plugins/enterprise_search/tsconfig.json index 53f364e26cac2..a6bb797de5eb1 100644 --- a/x-pack/plugins/enterprise_search/tsconfig.json +++ b/x-pack/plugins/enterprise_search/tsconfig.json @@ -70,6 +70,7 @@ "@kbn/es-errors", "@kbn/search-connectors-plugin", "@kbn/search-playground", + "@kbn/search-inference-endpoints", "@kbn/utility-types", "@kbn/index-management", "@kbn/deeplinks-search", @@ -78,6 +79,7 @@ "@kbn/cloud", "@kbn/try-in-console", "@kbn/core-chrome-browser", - "@kbn/navigation-plugin" + "@kbn/navigation-plugin", + "@kbn/search-homepage" ] } diff --git a/x-pack/plugins/event_log/server/es/init.test.ts b/x-pack/plugins/event_log/server/es/init.test.ts index 8cf2b808b8939..bf9121b353d2c 100644 --- a/x-pack/plugins/event_log/server/es/init.test.ts +++ b/x-pack/plugins/event_log/server/es/init.test.ts @@ -47,6 +47,24 @@ describe('initializeEs', () => { ); }); + test(`should handle null response from getExistingLegacyIndexTemplates`, async () => { + // @ts-expect-error + esContext.esAdapter.getExistingLegacyIndexTemplates.mockResolvedValue(null); + + await initializeEs(esContext); + expect(esContext.esAdapter.getExistingLegacyIndexTemplates).toHaveBeenCalled(); + expect(esContext.esAdapter.setLegacyIndexTemplateToHidden).not.toHaveBeenCalled(); + }); + + test(`should handle undefined response from getExistingLegacyIndexTemplates`, async () => { + // @ts-expect-error + esContext.esAdapter.getExistingLegacyIndexTemplates.mockResolvedValue(undefined); + + await initializeEs(esContext); + expect(esContext.esAdapter.getExistingLegacyIndexTemplates).toHaveBeenCalled(); + expect(esContext.esAdapter.setLegacyIndexTemplateToHidden).not.toHaveBeenCalled(); + }); + test(`should not update existing index templates if any exist and are already hidden`, async () => { const testTemplate = { order: 0, @@ -204,6 +222,24 @@ describe('initializeEs', () => { expect(esContext.esAdapter.setIndexToHidden).not.toHaveBeenCalled(); }); + test(`should handle null response from getExistingIndices`, async () => { + // @ts-expect-error + esContext.esAdapter.getExistingIndices.mockResolvedValue(null); + + await initializeEs(esContext); + expect(esContext.esAdapter.getExistingIndices).toHaveBeenCalled(); + expect(esContext.esAdapter.setIndexToHidden).not.toHaveBeenCalled(); + }); + + test(`should handle undefined response from getExistingIndices`, async () => { + // @ts-expect-error + esContext.esAdapter.getExistingIndices.mockResolvedValue(undefined); + + await initializeEs(esContext); + expect(esContext.esAdapter.getExistingIndices).toHaveBeenCalled(); + expect(esContext.esAdapter.setIndexToHidden).not.toHaveBeenCalled(); + }); + test(`should not read or update existing index settings when specifying shouldSetExistingAssetsToHidden=false`, async () => { await initializeEs({ ...esContext, shouldSetExistingAssetsToHidden: false }); expect(esContext.esAdapter.getExistingIndices).not.toHaveBeenCalled(); @@ -449,6 +485,29 @@ describe('parseIndexAliases', () => { }, ]); }); + + test('should handle null or undefined input', () => { + // @ts-expect-error + expect(parseIndexAliases(null)).toEqual([]); + + // @ts-expect-error + expect(parseIndexAliases(undefined)).toEqual([]); + + expect( + parseIndexAliases({ + '.kibana-event-log-7.15.2-000003': { + // @ts-expect-error + aliases: null, + }, + '.kibana-event-log-7.15.2-000002': { + // @ts-expect-error + aliases: undefined, + }, + // @ts-expect-error + '.kibana-event-log-7.15.2-000001': {}, + }) + ).toEqual([]); + }); }); describe('retries', () => { diff --git a/x-pack/plugins/event_log/server/es/init.ts b/x-pack/plugins/event_log/server/es/init.ts index b0257f3270a55..37f20a5bf424f 100644 --- a/x-pack/plugins/event_log/server/es/init.ts +++ b/x-pack/plugins/event_log/server/es/init.ts @@ -69,13 +69,17 @@ export interface ParsedIndexAlias extends estypes.IndicesAliasDefinition { } export function parseIndexAliases(aliasInfo: estypes.IndicesGetAliasResponse): ParsedIndexAlias[] { - return Object.keys(aliasInfo).flatMap((indexName: string) => - Object.keys(aliasInfo[indexName].aliases).map((alias: string) => ({ + const aliasInfoKeys = aliasInfo ? Object.keys(aliasInfo) : []; + return aliasInfoKeys.flatMap((indexName: string) => { + const aliasInfoIndexNameKeys = aliasInfo[indexName].aliases + ? Object.keys(aliasInfo[indexName].aliases) + : []; + return aliasInfoIndexNameKeys.map((alias: string) => ({ ...aliasInfo[indexName].aliases[alias], indexName, alias, - })) - ); + })); + }); } class EsInitializationSteps { @@ -101,7 +105,8 @@ class EsInitializationSteps { this.esContext.logger.error(`error getting existing index templates - ${err.message}`); } - await asyncForEach(Object.keys(indexTemplates), async (indexTemplateName: string) => { + const indexTemplateKeys = indexTemplates ? Object.keys(indexTemplates) : []; + await asyncForEach(indexTemplateKeys, async (indexTemplateName: string) => { try { const hidden: string | boolean = indexTemplates[indexTemplateName]?.settings?.index?.hidden; // Check to see if this index template is hidden @@ -138,7 +143,9 @@ class EsInitializationSteps { // should not block the rest of initialization, log the error and move on this.esContext.logger.error(`error getting existing indices - ${err.message}`); } - await asyncForEach(Object.keys(indices), async (indexName: string) => { + + const indexKeys = indices ? Object.keys(indices) : []; + await asyncForEach(indexKeys, async (indexName: string) => { try { const hidden: string | boolean | undefined = indices[indexName]?.settings?.index?.hidden; @@ -177,7 +184,8 @@ class EsInitializationSteps { // Group by index alias name const indexAliasData = groupBy(parsedAliasData, 'alias'); - await asyncForEach(Object.keys(indexAliasData), async (aliasName: string) => { + const indexAliasDataKeys = indexAliasData ? Object.keys(indexAliasData) : []; + await asyncForEach(indexAliasDataKeys, async (aliasName: string) => { try { const aliasData = indexAliasData[aliasName]; const isNotHidden = aliasData.some((data) => data.is_hidden !== true); diff --git a/x-pack/plugins/fleet/common/constants/agent_policy.ts b/x-pack/plugins/fleet/common/constants/agent_policy.ts index 9f1bc458ec363..b6e32f86ac514 100644 --- a/x-pack/plugins/fleet/common/constants/agent_policy.ts +++ b/x-pack/plugins/fleet/common/constants/agent_policy.ts @@ -38,11 +38,5 @@ export const DEFAULT_MAX_AGENT_POLICIES_WITH_INACTIVITY_TIMEOUT = 750; export const AGENTLESS_POLICY_ID = 'agentless'; // the policy id defined here: https://github.com/elastic/project-controller/blob/main/internal/project/security/security_kibana_config.go#L86 -export const AGENT_LOG_LEVELS = { - info: 'info', - debug: 'debug', - warning: 'warning', - error: 'error', -}; - -export const DEFAULT_LOG_LEVEL = AGENT_LOG_LEVELS.info; +export const AGENT_LOG_LEVELS = ['error', 'warning', 'info', 'debug'] as const; +export const DEFAULT_LOG_LEVEL = 'info' as const; diff --git a/x-pack/plugins/fleet/common/experimental_features.ts b/x-pack/plugins/fleet/common/experimental_features.ts index 0350af4f24620..6233ef5f820cf 100644 --- a/x-pack/plugins/fleet/common/experimental_features.ts +++ b/x-pack/plugins/fleet/common/experimental_features.ts @@ -31,6 +31,7 @@ export const allowedExperimentalValues = Object.freeze>( enablePackagesStateMachine: true, advancedPolicySettings: true, useSpaceAwareness: false, + enableReusableIntegrationPolicies: false, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 4295b1527d000..cf3ee35fca6df 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -34,14 +34,16 @@ "schema": { "type": "object", "properties": { - "name": { - "type": "string" - }, "status": { "type": "string" }, + "id": { + "type": "string", + "description": "Fleet Server host id" + }, "host": { - "type": "string" + "type": "string", + "deprecated": true } } } @@ -65,10 +67,17 @@ "schema": { "type": "object", "properties": { - "host": { + "id": { "type": "string" + }, + "host": { + "type": "string", + "deprecated": true } - } + }, + "required": [ + "id" + ] } } } diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 4c50edd31a7b4..ad592b9ea1847 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -26,12 +26,14 @@ paths: schema: type: object properties: - name: - type: string status: type: string + id: + type: string + description: Fleet Server host id host: type: string + deprecated: true '400': $ref: '#/components/responses/error' operationId: fleet-server-health-check @@ -44,8 +46,13 @@ paths: schema: type: object properties: + id: + type: string host: type: string + deprecated: true + required: + - id /setup: post: summary: Initiate Fleet setup diff --git a/x-pack/plugins/fleet/common/openapi/paths/health_check.yaml b/x-pack/plugins/fleet/common/openapi/paths/health_check.yaml index ae87da3ff0e52..9428e53a46f28 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/health_check.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/health_check.yaml @@ -10,12 +10,14 @@ post: schema: type: object properties: - name: - type: string status: type: string + id: + type: string + description: Fleet Server host id host: type: string + deprecated: true '400': $ref: ../components/responses/error.yaml operationId: fleet-server-health-check @@ -28,5 +30,10 @@ post: schema: type: object properties: + id: + type: string host: type: string + deprecated: true + required: + - id diff --git a/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx b/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx index ed23ea121eae6..9d15f7a9e6e68 100644 --- a/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx +++ b/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx @@ -148,6 +148,6 @@ export const AGENT_POLICY_ADVANCED_SETTINGS: SettingsConfig[] = [ }, learnMoreLink: 'https://www.elastic.co/guide/en/fleet/current/agent-policy.html#agent-policy-log-level', - schema: z.nativeEnum(AGENT_LOG_LEVELS).default(DEFAULT_LOG_LEVEL), + schema: z.enum(AGENT_LOG_LEVELS).default(DEFAULT_LOG_LEVEL), }, ]; diff --git a/x-pack/plugins/fleet/common/types/models/agent.ts b/x-pack/plugins/fleet/common/types/models/agent.ts index 89b533c47341f..2add5750f1855 100644 --- a/x-pack/plugins/fleet/common/types/models/agent.ts +++ b/x-pack/plugins/fleet/common/types/models/agent.ts @@ -108,6 +108,7 @@ interface AgentBase { components?: FleetServerAgentComponent[]; agent?: FleetServerAgentMetadata; unhealthy_reason?: UnhealthyReason[]; + namespaces?: string[]; } export enum UnhealthyReason { @@ -348,6 +349,11 @@ export interface FleetServerAgent { * Unhealthy reason: input, output, other */ unhealthy_reason?: UnhealthyReason[]; + + /** + * Namespaces + */ + namespaces?: string[]; } /** diff --git a/x-pack/plugins/fleet/common/types/models/enrollment_api_key.ts b/x-pack/plugins/fleet/common/types/models/enrollment_api_key.ts index 4398675831b45..1dc220b86059d 100644 --- a/x-pack/plugins/fleet/common/types/models/enrollment_api_key.ts +++ b/x-pack/plugins/fleet/common/types/models/enrollment_api_key.ts @@ -42,4 +42,5 @@ export interface FleetServerEnrollmentAPIKey { expire_at?: string; created_at?: string; updated_at?: string; + namespaces?: string[]; } diff --git a/x-pack/plugins/fleet/common/types/models/package_policy.ts b/x-pack/plugins/fleet/common/types/models/package_policy.ts index c50c06b890ea1..1b8a407ff8dd9 100644 --- a/x-pack/plugins/fleet/common/types/models/package_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/package_policy.ts @@ -99,6 +99,7 @@ export interface UpdatePackagePolicy extends NewPackagePolicy { // SO definition for this type is declared in server/types/interfaces export interface PackagePolicy extends Omit { id: string; + spaceId?: string; inputs: PackagePolicyInput[]; version?: string; agents?: number; diff --git a/x-pack/plugins/fleet/common/types/rest_spec/health_check.ts b/x-pack/plugins/fleet/common/types/rest_spec/health_check.ts index c718081344236..4b25d958d21ad 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/health_check.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/health_check.ts @@ -7,12 +7,15 @@ export interface PostHealthCheckRequest { body: { - host: string; + id: string; + /** @deprecated use id field instead */ + host?: string; }; } export interface PostHealthCheckResponse { - name: string; - host: string; + host_id: string; + // deprecated + host?: string; status: string; } diff --git a/x-pack/plugins/fleet/common/types/rest_spec/settings.ts b/x-pack/plugins/fleet/common/types/rest_spec/settings.ts index 33abccbc88e74..73ad6a3a219fc 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/settings.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/settings.ts @@ -34,6 +34,7 @@ export type EnrollmentSettingsFleetServerPolicy = Pick< | 'has_fleet_server' | 'fleet_server_host_id' | 'download_source_id' + | 'space_id' >; export interface GetEnrollmentSettingsResponse { diff --git a/x-pack/plugins/fleet/cypress/e2e/fleet_settings_outputs.cy.ts b/x-pack/plugins/fleet/cypress/e2e/fleet_settings_outputs.cy.ts index 965dcf48eed6e..4fc4fa33ea5b0 100644 --- a/x-pack/plugins/fleet/cypress/e2e/fleet_settings_outputs.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/fleet_settings_outputs.cy.ts @@ -281,7 +281,7 @@ queue: }); // Verify SSL fields - cy.getBySel(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_SSL_OPTION).click(); + cy.getBySel(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_SSL_OPTION).find('label').click(); cy.get('[placeholder="Specify certificate authority"]'); cy.get('[placeholder="Specify ssl certificate"]'); cy.get('[placeholder="Specify certificate key"]'); @@ -292,7 +292,7 @@ queue: // Verify None fields - cy.getBySel(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_NONE_OPTION).click(); + cy.getBySel(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_NONE_OPTION).find('label').click(); cy.getBySel(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_SASL_SELECT).should('not.exist'); cy.getBySel(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_USERNAME_INPUT).should('not.exist'); @@ -308,14 +308,16 @@ queue: cy.getBySel(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_CONNECTION_TYPE_PLAIN_OPTION); cy.getBySel(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_CONNECTION_TYPE_ENCRYPTION_OPTION); - cy.getBySel( - SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_CONNECTION_TYPE_ENCRYPTION_OPTION - ).click(); + cy.getBySel(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_CONNECTION_TYPE_ENCRYPTION_OPTION) + .find('label') + .click(); cy.getBySel(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_VERIFICATION_MODE_INPUT); cy.get('[placeholder="Specify certificate authority"]'); - cy.getBySel(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_USERNAME_PASSWORD_OPTION).click(); + cy.getBySel(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_USERNAME_PASSWORD_OPTION) + .find('label') + .click(); // Verify Partitioning fields cy.getBySel(SETTINGS_OUTPUTS_KAFKA.PARTITIONING_PANEL).within(() => { @@ -327,13 +329,13 @@ queue: }); // Verify Round Robin fields - cy.getBySel(SETTINGS_OUTPUTS_KAFKA.PARTITIONING_RANDOM_OPTION).click(); + cy.getBySel(SETTINGS_OUTPUTS_KAFKA.PARTITIONING_RANDOM_OPTION).find('label').click(); cy.getBySel(SETTINGS_OUTPUTS_KAFKA.PARTITIONING_EVENTS_INPUT); // Verify Hash fields - cy.getBySel(SETTINGS_OUTPUTS_KAFKA.PARTITIONING_HASH_OPTION).click(); + cy.getBySel(SETTINGS_OUTPUTS_KAFKA.PARTITIONING_HASH_OPTION).find('label').click(); cy.getBySel(SETTINGS_OUTPUTS_KAFKA.PARTITIONING_HASH_INPUT); - cy.getBySel(SETTINGS_OUTPUTS_KAFKA.PARTITIONING_RANDOM_OPTION).click(); + cy.getBySel(SETTINGS_OUTPUTS_KAFKA.PARTITIONING_RANDOM_OPTION).find('label').click(); // Topics cy.getBySel(SETTINGS_OUTPUTS_KAFKA.TOPICS_PANEL).within(() => { diff --git a/x-pack/plugins/fleet/cypress/screens/fleet_outputs.ts b/x-pack/plugins/fleet/cypress/screens/fleet_outputs.ts index eb4d3310e113d..763e6fae1b1ef 100644 --- a/x-pack/plugins/fleet/cypress/screens/fleet_outputs.ts +++ b/x-pack/plugins/fleet/cypress/screens/fleet_outputs.ts @@ -31,7 +31,7 @@ export const selectKafkaOutput = () => { visit('/app/fleet/settings'); cy.getBySel(SETTINGS_OUTPUTS.ADD_BTN).click(); cy.getBySel(SETTINGS_OUTPUTS.TYPE_INPUT).select('kafka'); - cy.getBySel(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_USERNAME_PASSWORD_OPTION).click(); + cy.getBySel(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_USERNAME_PASSWORD_OPTION).find('label').click(); }; export const shouldDisplayError = (handler: string) => { @@ -190,8 +190,8 @@ export const fillInKafkaOutputForm = (create?: boolean) => { ); cy.get('[placeholder="Specify certificate authority"]').clear().type('testCA'); - cy.getBySel(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_SASL_SCRAM_256_OPTION).click(); - cy.getBySel(SETTINGS_OUTPUTS_KAFKA.PARTITIONING_HASH_OPTION).click(); + cy.getBySel(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_SASL_SCRAM_256_OPTION).find('label').click(); + cy.getBySel(SETTINGS_OUTPUTS_KAFKA.PARTITIONING_HASH_OPTION).find('label').click(); cy.getBySel(kafkaOutputFormValues.hash.selector).type(kafkaOutputFormValues.hash.value); cy.getBySel(kafkaOutputFormValues.defaultTopic.selector).type( @@ -265,7 +265,7 @@ export const validateOutputTypeChangeToKafka = (outputId: string) => { visit(`/app/fleet/settings/outputs/${outputId}`); cy.getBySel(kafkaOutputFormValues.name.selector).clear(); cy.getBySel(SETTINGS_OUTPUTS.TYPE_INPUT).select('kafka'); - cy.getBySel(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_USERNAME_PASSWORD_OPTION).click(); + cy.getBySel(SETTINGS_OUTPUTS_KAFKA.AUTHENTICATION_USERNAME_PASSWORD_OPTION).find('label').click(); fillInKafkaOutputForm(true); cy.intercept('PUT', '**/api/fleet/outputs/**').as('saveOutput'); diff --git a/x-pack/plugins/fleet/dev_docs/react_query.md b/x-pack/plugins/fleet/dev_docs/react_query.md index 55eca8669c1ec..1ae8fbadcf448 100644 --- a/x-pack/plugins/fleet/dev_docs/react_query.md +++ b/x-pack/plugins/fleet/dev_docs/react_query.md @@ -155,21 +155,18 @@ There's a bit of setup involved to actually get `react-query` up and running. Fi ```tsx //... - - - - - - - - - {children} - - - - - - + + + + + + + {children} + + + + + ``` We also set up `react-query`'s [dev tools](https://tanstack.com/query/v4/docs/react/devtools), which provide a useful developer console for debugging query and mutation state across the whole application. diff --git a/x-pack/plugins/fleet/public/applications/fleet/app.tsx b/x-pack/plugins/fleet/public/applications/fleet/app.tsx index faaf08adf4dbb..5d3dee34339cc 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/app.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/app.tsx @@ -18,9 +18,7 @@ import useObservable from 'react-use/lib/useObservable'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { css } from '@emotion/css'; - import type { TopNavMenuData } from '@kbn/navigation-plugin/public'; - import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; @@ -186,11 +184,20 @@ export const FleetAppContext: React.FC<{ routerHistory: _routerHistory, fleetStatus, }) => { + const XXL_BREAKPOINT = 1600; const darkModeObservable = useObservable(startServices.theme.theme$); const isDarkMode = darkModeObservable && darkModeObservable.darkMode; return ( - + + {/* This should be removed since theme is passed to `KibanaRenderContextProvider`, + however, removing this breaks usages of `props.theme.eui` in styled components */} diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/index.tsx index 511f5d05154fc..b46d4f2060669 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/index.tsx @@ -23,6 +23,7 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import styled from 'styled-components'; +import { MAX_FLYOUT_WIDTH } from '../../constants'; import { useStartServices, useFlyoutContext, useCheckPermissions } from '../../hooks'; import { FleetServerMissingESPrivileges } from '../../sections/agents/components'; @@ -133,7 +134,7 @@ export const FleetServerFlyout: React.FunctionComponent = ({ onClose }) = } return ( - +
{ - return ( - ( - ({ - text: key, - value: value as string, - }) - )} - /> - )} - /> - ); - } -); +settingComponentRegistry.set(ZodFirstPartyTypeKind.ZodEnum, ({ disabled, ...settingsConfig }) => { + return ( + ( + ({ + text: value, + value, + }))} + /> + )} + /> + ); +}); export function ConfiguredSettings({ configuredSettings, diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.test.tsx index c33a0427b5000..d572a00ec2241 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.test.tsx @@ -15,8 +15,11 @@ import { createFleetTestRendererMock } from '../../../mock'; import { AGENTS_PREFIX, FLEET_ENROLLMENT_API_PREFIX, - PACKAGE_POLICY_SAVED_OBJECT_TYPE, AGENT_POLICY_SAVED_OBJECT_TYPE, + AGENTS_INDEX, + ENROLLMENT_API_KEYS_INDEX, + INGEST_SAVED_OBJECT_INDEX, + PACKAGE_POLICY_SAVED_OBJECT_TYPE, } from '../constants'; import { SearchBar, getFieldSpecs } from './search_bar'; @@ -169,13 +172,12 @@ describe('SearchBar', () => { }); describe('getFieldSpecs', () => { - it('returns fieldSpecs for fleet-agents', () => { - expect(getFieldSpecs(`.${AGENTS_PREFIX}`)).toHaveLength(67); + it('returns fieldSpecs for Fleet agents', () => { + expect(getFieldSpecs(AGENTS_INDEX, AGENTS_PREFIX)).toHaveLength(67); }); - it('returns getFieldSpecs for fleet-enrollment-api-keys', () => { - const indexPattern = `.${FLEET_ENROLLMENT_API_PREFIX}`; - expect(getFieldSpecs(indexPattern)).toHaveLength(8); - expect(getFieldSpecs(indexPattern)).toEqual([ + + it('returns fieldSpecs for Fleet enrollment tokens', () => { + expect(getFieldSpecs(ENROLLMENT_API_KEYS_INDEX, FLEET_ENROLLMENT_API_PREFIX)).toEqual([ { aggregatable: true, esTypes: ['boolean'], @@ -235,176 +237,173 @@ describe('getFieldSpecs', () => { ]); }); - it('returns getFieldSpecs for fleet-agent-policy', () => { - const indexPattern = `.${AGENT_POLICY_SAVED_OBJECT_TYPE}`; - expect(getFieldSpecs(indexPattern)).toHaveLength(23); - expect(getFieldSpecs(indexPattern)).toEqual([ + it('returns fieldSpecs for Fleet agent policies', () => { + expect(getFieldSpecs(INGEST_SAVED_OBJECT_INDEX, AGENT_POLICY_SAVED_OBJECT_TYPE)).toEqual([ { aggregatable: true, esTypes: ['keyword'], - name: 'agent_features.name', + name: 'ingest-agent-policies.agent_features.name', searchable: true, type: 'string', }, { aggregatable: true, esTypes: ['boolean'], - name: 'agent_features.enabled', + name: 'ingest-agent-policies.agent_features.enabled', searchable: true, type: 'boolean', }, { aggregatable: true, esTypes: ['keyword'], - name: 'data_output_id', + name: 'ingest-agent-policies.data_output_id', searchable: true, type: 'string', }, { aggregatable: true, esTypes: ['text'], - name: 'description', + name: 'ingest-agent-policies.description', searchable: true, type: 'string', }, { aggregatable: true, esTypes: ['keyword'], - name: 'download_source_id', + name: 'ingest-agent-policies.download_source_id', searchable: true, type: 'string', }, { aggregatable: true, esTypes: ['keyword'], - name: 'fleet_server_host_id', + name: 'ingest-agent-policies.fleet_server_host_id', searchable: true, type: 'string', }, { aggregatable: true, esTypes: ['integer'], - name: 'inactivity_timeout', + name: 'ingest-agent-policies.inactivity_timeout', searchable: true, type: 'number', }, { aggregatable: true, esTypes: ['boolean'], - name: 'is_default', + name: 'ingest-agent-policies.is_default', searchable: true, type: 'boolean', }, { aggregatable: true, esTypes: ['boolean'], - name: 'is_default_fleet_server', + name: 'ingest-agent-policies.is_default_fleet_server', searchable: true, type: 'boolean', }, { aggregatable: true, esTypes: ['boolean'], - name: 'is_managed', + name: 'ingest-agent-policies.is_managed', searchable: true, type: 'boolean', }, { aggregatable: true, esTypes: ['keyword'], - name: 'is_preconfigured', + name: 'ingest-agent-policies.is_preconfigured', searchable: true, type: 'string', }, { aggregatable: true, esTypes: ['boolean'], - name: 'is_protected', + name: 'ingest-agent-policies.is_protected', searchable: true, type: 'boolean', }, { aggregatable: true, esTypes: ['keyword'], - name: 'monitoring_enabled', + name: 'ingest-agent-policies.monitoring_enabled', searchable: true, type: 'string', }, { aggregatable: true, esTypes: ['false'], - name: 'monitoring_enabled.index', + name: 'ingest-agent-policies.monitoring_enabled.index', searchable: true, type: 'false', }, { aggregatable: true, esTypes: ['keyword'], - name: 'monitoring_output_id', + name: 'ingest-agent-policies.monitoring_output_id', searchable: true, type: 'string', }, { aggregatable: true, esTypes: ['keyword'], - name: 'name', + name: 'ingest-agent-policies.name', searchable: true, type: 'string', }, { aggregatable: true, esTypes: ['keyword'], - name: 'namespace', + name: 'ingest-agent-policies.namespace', searchable: true, type: 'string', }, { aggregatable: true, esTypes: ['integer'], - name: 'revision', + name: 'ingest-agent-policies.revision', searchable: true, type: 'number', }, { aggregatable: true, esTypes: ['version'], - name: 'schema_version', + name: 'ingest-agent-policies.schema_version', searchable: true, type: 'string', }, { aggregatable: true, esTypes: ['keyword'], - name: 'status', + name: 'ingest-agent-policies.status', searchable: true, type: 'string', }, { aggregatable: true, esTypes: ['integer'], - name: 'unenroll_timeout', + name: 'ingest-agent-policies.unenroll_timeout', searchable: true, type: 'number', }, { aggregatable: true, esTypes: ['date'], - name: 'updated_at', + name: 'ingest-agent-policies.updated_at', searchable: true, type: 'date', }, { aggregatable: true, esTypes: ['keyword'], - name: 'updated_by', + name: 'ingest-agent-policies.updated_by', searchable: true, type: 'string', }, ]); }); - expect(getFieldSpecs(`.${PACKAGE_POLICY_SAVED_OBJECT_TYPE}`)).toHaveLength(19); it('returns empty array if indexPattern is not one of the previous', async () => { - expect(getFieldSpecs('.kibana_ingest')).toEqual([]); + expect(getFieldSpecs(INGEST_SAVED_OBJECT_INDEX, PACKAGE_POLICY_SAVED_OBJECT_TYPE)).toEqual([]); }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx index 6f23d25f38125..3e47a3a7955b3 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/search_bar.tsx @@ -16,19 +16,15 @@ import type { DataView } from '@kbn/data-views-plugin/public'; import { i18n } from '@kbn/i18n'; import { useStartServices } from '../hooks'; -import { - INDEX_NAME, - AGENTS_PREFIX, - FLEET_ENROLLMENT_API_PREFIX, - PACKAGE_POLICY_SAVED_OBJECT_TYPE, - AGENT_POLICY_SAVED_OBJECT_TYPE, -} from '../constants'; import { AGENT_POLICY_MAPPINGS, - PACKAGE_POLICIES_MAPPINGS, AGENT_MAPPINGS, ENROLLMENT_API_KEY_MAPPINGS, -} from '../../../../common/constants'; + AGENTS_INDEX, + ENROLLMENT_API_KEYS_INDEX, + AGENT_POLICY_SAVED_OBJECT_TYPE, + INGEST_SAVED_OBJECT_INDEX, +} from '../constants'; const NoWrapQueryStringInput = styled(QueryStringInput)` .kbnQueryBar__textarea { @@ -38,29 +34,42 @@ const NoWrapQueryStringInput = styled(QueryStringInput)` interface Props { value: string; - fieldPrefix?: string; + indexPattern: string; + fieldPrefix: string; onChange: (newValue: string, submit?: boolean) => void; placeholder?: string; - indexPattern?: string; dataTestSubj?: string; } -const getMappings = (indexPattern: string) => { +const getMappings = (indexPattern: string, fieldPrefix: string) => { switch (indexPattern) { - case `.${AGENTS_PREFIX}`: + case AGENTS_INDEX: return AGENT_MAPPINGS; - case `.${AGENT_POLICY_SAVED_OBJECT_TYPE}`: - return AGENT_POLICY_MAPPINGS; - case `.${PACKAGE_POLICY_SAVED_OBJECT_TYPE}`: - return PACKAGE_POLICIES_MAPPINGS; - case `.${FLEET_ENROLLMENT_API_PREFIX}`: + // Saved Objects are stored in .kibana_ingest. + // Currently, the search bar is only used to query agent policies. + case INGEST_SAVED_OBJECT_INDEX: + switch (fieldPrefix) { + case AGENT_POLICY_SAVED_OBJECT_TYPE: + return AGENT_POLICY_MAPPINGS; + default: + return {}; + } + case ENROLLMENT_API_KEYS_INDEX: return ENROLLMENT_API_KEY_MAPPINGS; default: return {}; } }; -const getType = (type: string) => { +const getFieldName = (indexPattern: string, fieldPrefix: string, name: string) => { + // Add Saved Object prefix if the field refers to a SO and is not already prefixed. + if (indexPattern !== INGEST_SAVED_OBJECT_INDEX || name.startsWith(fieldPrefix)) { + return name; + } + return `${fieldPrefix}.${name}`; +}; + +const getFieldType = (type: string) => { switch (type) { case 'keyword': return 'string'; @@ -88,9 +97,10 @@ const concatKeys = (obj: any, parentKey = '') => { } return result; }; + /** Exported for testing only **/ -export const getFieldSpecs = (indexPattern: string) => { - const mapping = getMappings(indexPattern); +export const getFieldSpecs = (indexPattern: string, fieldPrefix: string) => { + const mapping = getMappings(indexPattern, fieldPrefix); // @ts-ignore-next-line const rawFields = concatKeys(mapping?.properties) || []; const fields = rawFields @@ -100,8 +110,8 @@ export const getFieldSpecs = (indexPattern: string) => { const fieldSpecs: FieldSpec[] = fields.map((field) => { return { - name: field[0], - type: getType(field[1]), + name: getFieldName(indexPattern, fieldPrefix, field[0]), + type: getFieldType(field[1]), searchable: true, aggregatable: true, esTypes: [field[1]], @@ -115,7 +125,7 @@ export const SearchBar: React.FunctionComponent = ({ fieldPrefix, onChange, placeholder, - indexPattern = INDEX_NAME, + indexPattern, dataTestSubj, }) => { const { @@ -148,7 +158,7 @@ export const SearchBar: React.FunctionComponent = ({ useEffect(() => { const fetchFields = async () => { try { - const fieldSpecs = getFieldSpecs(indexPattern); + const fieldSpecs = getFieldSpecs(indexPattern, fieldPrefix); const fieldsMap = data.dataViews.fieldArrayToMap(fieldSpecs); const newDataView = await data.dataViews.create( { title: indexPattern, fields: fieldsMap }, diff --git a/x-pack/plugins/fleet/public/applications/fleet/layouts/error.tsx b/x-pack/plugins/fleet/public/applications/fleet/layouts/error.tsx index 89e47f3641c95..9f4dad848515d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/layouts/error.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/layouts/error.tsx @@ -71,7 +71,7 @@ export const PermissionsError: React.FunctionComponent<{ ) : ( "All", roleName2: "Read", diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/global_data_tags_table.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/global_data_tags_table.test.tsx new file mode 100644 index 0000000000000..a79c298a6e9e9 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/global_data_tags_table.test.tsx @@ -0,0 +1,290 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { RenderResult } from '@testing-library/react'; +import { fireEvent, act } from '@testing-library/react'; + +import { createFleetTestRendererMock, type TestRenderer } from '../../../../../../../mock'; +import type { GlobalDataTag } from '../../../../../../../../common/types'; + +import { GlobalDataTagsTable } from './global_data_tags_table'; + +jest.mock('../../../../../../../hooks/use_fleet_status', () => ({ + FleetStatusProvider: (props: any) => { + return props.children; + }, +})); + +const TEST_IDS = { + NAME_INPUT: 'globalDataTagsNameInput', + VALUE_INPUT: 'globalDataTagsValueInput', +}; + +describe('GlobalDataTagsTable', () => { + let renderResult: RenderResult; + let mockUpdateAgentPolicy: jest.Mock; + const globalDataTags: GlobalDataTag[] = [ + { name: 'tag1', value: 'value1' }, + { name: 'tag2', value: 'value2' }, + ]; + let renderer: TestRenderer; + + const renderComponent = (tags: GlobalDataTag[]) => { + mockUpdateAgentPolicy = jest.fn(); + renderer = createFleetTestRendererMock(); + + const TestComponent = () => { + const [agentPolicy, _updateAgentPolicy] = React.useState({ + global_data_tags: tags, + }); + + const updateAgentPolicy = React.useCallback((policy) => { + mockUpdateAgentPolicy(policy); + _updateAgentPolicy(policy); + }, []); + + return ( + + ); + }; + act(() => { + renderResult = renderer.render(); + }); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should render initial tags', async () => { + renderComponent(globalDataTags); + globalDataTags.forEach((tag) => { + expect(renderResult.getByText(tag.name)).toBeInTheDocument(); + expect(renderResult.getByText(tag.value)).toBeInTheDocument(); + }); + }); + + it('should add new tags', async () => { + renderComponent([]); + + await act(async () => { + fireEvent.click(renderResult.getByText('Add field')); + }); + + const nameInput = renderResult.getByTestId(TEST_IDS.NAME_INPUT); + const valueInput = renderResult.getByTestId(TEST_IDS.VALUE_INPUT); + + await act(async () => { + fireEvent.change(nameInput, { target: { value: 'newTag' } }); + }); + await act(async () => { + fireEvent.change(valueInput, { target: { value: '123' } }); + }); + + await act(async () => { + fireEvent.click(renderResult.getByLabelText('Confirm')); + }); + + expect(mockUpdateAgentPolicy).toHaveBeenCalledWith({ + global_data_tags: [{ name: 'newTag', value: 123 }], + }); + + await act(async () => { + fireEvent.click(renderResult.getByText('Add another field')); + }); + + const nameInput2 = renderResult.getByTestId(TEST_IDS.NAME_INPUT); + const valueInput2 = renderResult.getByTestId(TEST_IDS.VALUE_INPUT); + + await act(async () => { + fireEvent.change(nameInput2, { target: { value: 'newTag2' } }); + }); + await act(async () => { + fireEvent.change(valueInput2, { target: { value: '123 123' } }); + }); + + await act(async () => { + fireEvent.click(renderResult.getByLabelText('Confirm')); + }); + + expect(mockUpdateAgentPolicy).toHaveBeenCalledWith({ + global_data_tags: [ + { name: 'newTag', value: 123 }, + { name: 'newTag2', value: '123 123' }, + ], + }); + }); + + it('should edit an existing tag', async () => { + renderComponent(globalDataTags); + + await act(async () => { + fireEvent.click(renderResult.getAllByLabelText('Edit')[0]); + }); + + const nameInput = renderResult.getByTestId(TEST_IDS.NAME_INPUT); + const valueInput = renderResult.getByTestId(TEST_IDS.VALUE_INPUT); + + await act(async () => { + fireEvent.change(nameInput, { target: { value: 'updatedTag1' } }); + }); + await act(async () => { + fireEvent.change(valueInput, { target: { value: 'updatedValue1' } }); + }); + + await act(async () => { + fireEvent.click(renderResult.getByLabelText('Confirm')); + }); + + expect(mockUpdateAgentPolicy).toHaveBeenCalledWith({ + global_data_tags: [{ name: 'updatedTag1', value: 'updatedValue1' }, globalDataTags[1]], + }); + }); + + it('should show validation errors for empty name and value', async () => { + renderComponent(globalDataTags); + + await act(async () => { + fireEvent.click(renderResult.getByText('Add another field')); + }); + + await act(async () => { + fireEvent.click(renderResult.getByLabelText('Confirm')); + }); + + expect(renderResult.getByText('Name cannot be empty')).toBeInTheDocument(); + expect(renderResult.getByText('Value cannot be empty')).toBeInTheDocument(); + }); + + it('should delete a tag when confirming the modal', async () => { + renderComponent(globalDataTags); + renderer.startServices.overlays.openConfirm.mockResolvedValue(true); + + await act(async () => { + fireEvent.click(renderResult.getAllByLabelText('Delete')[0]); + }); + + expect(mockUpdateAgentPolicy).toHaveBeenCalledWith({ + global_data_tags: [globalDataTags[1]], + }); + }); + + it('should not delete a tag when confirming the modal', async () => { + renderComponent(globalDataTags); + renderer.startServices.overlays.openConfirm.mockResolvedValue(false); + + await act(async () => { + fireEvent.click(renderResult.getAllByLabelText('Delete')[0]); + }); + + expect(mockUpdateAgentPolicy).not.toHaveBeenCalledWith({ + global_data_tags: [globalDataTags[1]], + }); + }); + + it('should show validation errors for duplicate tag names', async () => { + renderComponent(globalDataTags); + + act(() => { + fireEvent.click(renderResult.getByText('Add another field')); + }); + + const nameInput = renderResult.getByTestId(TEST_IDS.NAME_INPUT); + act(() => { + fireEvent.change(nameInput, { target: { value: globalDataTags[0].name } }); + }); + + act(() => { + fireEvent.click(renderResult.getByLabelText('Confirm')); + }); + + expect(renderResult.getByText('Name must be unique')).toBeInTheDocument(); + }); + + it('should cancel adding a new tag', async () => { + renderComponent(globalDataTags); + + await act(async () => { + fireEvent.click(renderResult.getByText('Add another field')); + }); + + await act(async () => { + fireEvent.click(renderResult.getByLabelText('Cancel')); + }); + + expect(renderResult.queryByTestId(TEST_IDS.NAME_INPUT)).not.toBeInTheDocument(); + expect(renderResult.queryByTestId(TEST_IDS.VALUE_INPUT)).not.toBeInTheDocument(); + }); + + it('should cancel editing a tag', async () => { + renderComponent(globalDataTags); + + await act(async () => { + fireEvent.click(renderResult.getAllByLabelText('Edit')[0]); + }); + + await act(async () => { + fireEvent.click(renderResult.getByLabelText('Cancel')); + }); + + expect(renderResult.queryByTestId(TEST_IDS.NAME_INPUT)).not.toBeInTheDocument(); + expect(renderResult.queryByTestId(TEST_IDS.VALUE_INPUT)).not.toBeInTheDocument(); + }); + + it('should allow multiple tags to be in "edit" state concurrently', async () => { + renderComponent(globalDataTags); + + // Enter edit mode for the first tag + act(() => { + fireEvent.click(renderResult.getAllByLabelText('Edit')[0]); + }); + + act(() => { + const nameInput1 = renderResult.getByDisplayValue(globalDataTags[0].name); + fireEvent.change(nameInput1, { target: { value: 'updatedTag1' } }); + }); + act(() => { + const valueInput1 = renderResult.getByDisplayValue(globalDataTags[0].value); + fireEvent.change(valueInput1, { target: { value: 'updatedValue1' } }); + }); + + // Enter edit mode for the second tag without saving the first one + act(() => { + fireEvent.click(renderResult.getByLabelText('Edit')); + }); + + act(() => { + const nameInput2 = renderResult.getByDisplayValue(globalDataTags[1].name); + fireEvent.change(nameInput2, { target: { value: 'updatedTag2' } }); + }); + + act(() => { + const valueInput2 = renderResult.getByDisplayValue(globalDataTags[1].value); + fireEvent.change(valueInput2, { target: { value: 'updatedValue2' } }); + }); + + // Confirm changes for both tags + act(() => { + fireEvent.click(renderResult.getAllByLabelText('Confirm')[0]); + }); + act(() => { + fireEvent.click(renderResult.getByLabelText('Confirm')); + }); + + expect(mockUpdateAgentPolicy).toHaveBeenCalledWith({ + global_data_tags: [ + { name: 'updatedTag1', value: 'updatedValue1' }, + { name: 'updatedTag2', value: 'updatedValue2' }, + ], + }); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/global_data_tags_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/global_data_tags_table.tsx new file mode 100644 index 0000000000000..9f51b5c181459 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/global_data_tags_table.tsx @@ -0,0 +1,465 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, useMemo, useCallback } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; + +import { + EuiBasicTable, + EuiPanel, + EuiText, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiFormRow, + EuiFieldText, + EuiButtonIcon, + EuiCode, + type EuiBasicTableColumn, +} from '@elastic/eui'; + +import { useStartServices } from '../../../../../../../hooks'; + +import type { + NewAgentPolicy, + AgentPolicy, + GlobalDataTag, +} from '../../../../../../../../common/types'; + +interface Props { + updateAgentPolicy: (u: Partial) => void; + globalDataTags: GlobalDataTag[]; +} + +function parseValue(value: string | number): string | number { + if (typeof value === 'number') { + return value; + } + if (!value.match(/^[0-9]*$/)) { + return value.trim(); + } + const parsedValue = Number(value); + return isNaN(parsedValue) ? value.trim() : parsedValue; +} + +export const GlobalDataTagsTable: React.FunctionComponent = ({ + updateAgentPolicy, + globalDataTags, +}) => { + const { overlays } = useStartServices(); + const [editTags, setEditTags] = useState<{ [k: number]: GlobalDataTag }>({}); + // isAdding indicates whether a new row is currently being added to the table. + // When true, the table will display "Confirm" and "Cancel" actions for the new row + // to allow the user to either confirm the addition or cancel it. + const [isAdding, setIsAdding] = useState(false); + const [newTag, setNewTag] = useState({ name: '', value: '' }); + const [newTagErrors, setNewTagErrors] = useState<{ name: string; value: string }>({ + name: '', + value: '', + }); + const [errors, setErrors] = useState< + Record + >({}); + + const handleAddField = () => { + setIsAdding(true); + setNewTag({ name: '', value: '' }); + }; + + const handleEditChange = useCallback((e: React.ChangeEvent, index: number) => { + const newTargetValue = e.target.value; + const newTargetName = e.target.name; + + setEditTags((prevValue) => { + let tag = prevValue[index]; + if (!tag) { + tag = { name: '', value: '' }; + } + return { + ...prevValue, + [index]: { ...tag, [newTargetName]: newTargetValue }, + }; + }); + setErrors((prevErrors) => ({ + ...prevErrors, + [index]: { ...prevErrors[index], [newTargetName]: null }, + })); + }, []); + + const handleNewTagChange = useCallback( + (e: React.ChangeEvent) => { + setNewTag({ + ...newTag, + [e.target.name]: e.target.value, + }); + setNewTagErrors({ ...newTagErrors, [e.target.name]: null }); + }, + [newTag, newTagErrors] + ); + + const validateTag = useCallback( + (tag: GlobalDataTag, index?: number) => { + const trimmedName = tag.name.trim(); + const trimmedValue = tag.value.toString().trim(); + + let nameError = ''; + let valueError = ''; + + if (!trimmedName) { + nameError = 'Name cannot be empty'; + } else if (/\s/.test(trimmedName)) { + nameError = 'Name cannot contain spaces'; + } else if (globalDataTags.some((t, i) => i !== index && t.name === trimmedName)) { + nameError = 'Name must be unique'; + } + + if (!trimmedValue) { + valueError = 'Value cannot be empty'; + } + + return { nameError, valueError, isValid: !nameError && !valueError }; + }, + [globalDataTags] + ); + + const confirmNewTagChanges = useCallback(() => { + const { nameError, valueError, isValid } = validateTag(newTag); + if (!isValid) { + setNewTagErrors({ name: nameError, value: valueError }); + return; + } + + const updatedTags = [ + ...globalDataTags, + { ...newTag, name: newTag.name.trim(), value: parseValue(newTag.value) }, + ]; + + updateAgentPolicy({ global_data_tags: updatedTags }); + setNewTag({ name: '', value: '' }); + setIsAdding(false); + setNewTagErrors({ name: '', value: '' }); + }, [globalDataTags, newTag, updateAgentPolicy, validateTag]); + + const confirmEditChanges = useCallback( + (index: number) => { + const tag = editTags[index]; + const { nameError, valueError, isValid } = validateTag(tag, index); + + if (!isValid) { + setErrors((prevErrors) => ({ + ...prevErrors, + [index]: { name: nameError, value: valueError }, + })); + return; + } + + const updatedTags = globalDataTags.map((t, i) => + i === index ? { ...tag, name: tag.name.trim(), value: parseValue(tag.value) } : t + ); + updateAgentPolicy({ global_data_tags: updatedTags }); + setEditTags((prevValue) => { + const newValue = { ...prevValue }; + delete newValue[index]; + + return newValue; + }); + setErrors((prevErrors) => { + const newErrors = { ...prevErrors }; + delete newErrors[index]; + return newErrors; + }); + }, + [globalDataTags, updateAgentPolicy, validateTag, editTags] + ); + + const handleStartEdit = useCallback( + (index: number) => { + setEditTags((prevValue) => ({ + ...prevValue, + [index]: { ...globalDataTags[index] }, + })); + setErrors((prevErrors: Record) => ({ + ...prevErrors, + [index]: { name: null, value: null }, + })); + }, + [globalDataTags] + ); + + const cancelNewTagChanges = () => { + setNewTag({ name: '', value: '' }); + setIsAdding(false); + setNewTagErrors({ name: '', value: '' }); + }; + + const cancelEditChanges = (index: number) => { + setEditTags((prevValue) => { + const newValue = { ...prevValue }; + delete newValue[index]; + + return newValue; + }); + setErrors((prevErrors) => { + const newErrors = { ...prevErrors }; + delete newErrors[index]; + return newErrors; + }); + }; + + const deleteTag = useCallback( + async (index: number) => { + const res = await overlays.openConfirm( + i18n.translate('xpack.fleet.globalDataTagsTable.deleteModalText', { + defaultMessage: + 'Removing the field will affect the next sync. This action cannot be undone.', + }), + { + title: i18n.translate('xpack.fleet.globalDataTagsTable.deleteModalTitle', { + defaultMessage: 'Remove the {tag} field?', + values: { + tag: globalDataTags[index].name ?? '', + }, + }), + buttonColor: 'danger', + cancelButtonText: i18n.translate( + 'xpack.fleet.globalDataTagsTable.deleteModalCancelButtonText', + { + defaultMessage: 'Cancel', + } + ), + confirmButtonText: i18n.translate( + 'xpack.fleet.globalDataTagsTable.deleteModalConfirmButtonText', + { + defaultMessage: 'Remove', + } + ), + } + ); + if (!res) { + return; + } + const updatedTags = globalDataTags.filter((_, i) => i !== index); + setEditTags((prevValue) => { + const newValue = { ...prevValue }; + delete newValue[index]; + + return newValue; + }); + updateAgentPolicy({ global_data_tags: updatedTags }); + + setErrors((prevErrors) => { + const { [index]: removedError, ...remainingErrors } = prevErrors; + return remainingErrors; + }); + }, + [globalDataTags, overlays, updateAgentPolicy] + ); + + const columns = useMemo( + (): Array> => [ + { + valign: 'top', + field: 'name', + name: ( + + ), + render: (name: string, item: GlobalDataTag) => { + const index = globalDataTags.indexOf(item); + const isEditing = !!editTags[index]; + const isAddingRow = isAdding && item === newTag; + const error = isAddingRow ? newTagErrors : errors[index] || {}; + return isEditing || isAddingRow ? ( + + (isEditing ? handleEditChange(e, index) : handleNewTagChange(e))} + isInvalid={!!error.name} + compressed={true} + /> + + ) : ( + {name} + ); + }, + }, + { + valign: 'top', + field: 'value', + name: ( + + ), + render: (value: string, item: GlobalDataTag) => { + const index = globalDataTags.indexOf(item); + const isEditing = !!editTags[index]; + const isAddingRow = isAdding && item === newTag; + const error = isAddingRow ? newTagErrors : errors[index] || {}; + return isEditing || isAddingRow ? ( + + (isEditing ? handleEditChange(e, index) : handleNewTagChange(e))} + isInvalid={!!error.value} + compressed={true} + /> + + ) : ( + {value} + ); + }, + }, + { + actions: [ + { + name: ( + + ), + render: (item: GlobalDataTag) => { + const index = globalDataTags.indexOf(item); + const isEditing = !!editTags[index]; + const isAddingRow = isAdding && item === newTag; + return isEditing || isAddingRow ? ( + + + confirmEditChanges(index)} + aria-label="Confirm" + /> + + + ) : ( + handleStartEdit(index)} + /> + ); + }, + }, + { + name: 'Cancel/Delete', + render: (item: GlobalDataTag) => { + const index = globalDataTags.indexOf(item); + const isEditing = !!editTags[index]; + const isAddingRow = isAdding && item === newTag; + + return isEditing || isAddingRow ? ( + + + cancelEditChanges(index)} + aria-label="Cancel" + /> + + + ) : ( + deleteTag(index)} + /> + ); + }, + }, + ], + }, + ], + [ + confirmEditChanges, + confirmNewTagChanges, + editTags, + errors, + globalDataTags, + handleEditChange, + handleNewTagChange, + isAdding, + newTag, + newTagErrors, + deleteTag, + handleStartEdit, + ] + ); + + const items = isAdding ? [...globalDataTags, newTag] : globalDataTags; + + return ( + <> + {globalDataTags.length === 0 && !isAdding ? ( + + +

+ +

+
+ + + + + + + +
+ ) : ( + <> + + + + + + + + + + )} + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/index.test.tsx new file mode 100644 index 0000000000000..26c0742598ace --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/index.test.tsx @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import type { RenderResult } from '@testing-library/react'; + +import type { TestRenderer } from '../../../../../../../mock'; +import { createFleetTestRendererMock } from '../../../../../../../mock'; + +import type { NewAgentPolicy, AgentPolicy, PackagePolicyInput } from '../../../../../types'; + +import { GLOBAL_DATA_TAG_EXCLUDED_INPUTS } from '../../../../../../../../common/constants'; + +import { + createAgentPolicyMock, + createPackagePolicyMock, +} from '../../../../../../../../common/mocks'; + +import { CustomFields } from '.'; + +describe('CustomFields', () => { + let testRenderer: TestRenderer; + let renderResult: RenderResult; + const mockUpdateAgentPolicy = jest.fn(); + + const renderComponent = (agentPolicy: Partial) => { + renderResult = testRenderer.render( + + ); + }; + + beforeEach(() => { + testRenderer = createFleetTestRendererMock(); + jest.clearAllMocks(); + }); + + it('should render without crashing', () => { + const mockAgentPolicy = createAgentPolicyMock({ + global_data_tags: [], + package_policies: [createPackagePolicyMock()], + }); + renderComponent(mockAgentPolicy); + expect(renderResult.getByText('Custom fields')).toBeInTheDocument(); + expect(renderResult.getByText('This policy has no custom fields')).toBeInTheDocument(); + }); + + it('should render unsupported inputs warning if there are unsupported inputs', () => { + const unsupportedInputTypes = Array.from(GLOBAL_DATA_TAG_EXCLUDED_INPUTS); + + const mockAgentPolicy = createAgentPolicyMock({ + global_data_tags: [], + package_policies: [ + { + ...createPackagePolicyMock(), + inputs: [ + { type: 'supported' } as PackagePolicyInput, + ...unsupportedInputTypes.map((type) => ({ type } as PackagePolicyInput)), + ], + }, + ], + }); + + renderComponent(mockAgentPolicy); + + const unsupportedInputsWarning = renderResult.getByText('Unsupported Inputs'); + expect(unsupportedInputsWarning).toBeInTheDocument(); + + const strongElements = renderResult.container.querySelector('strong'); + const unsupportedInputs = + strongElements?.textContent?.split(', ').map((element) => element.trim()) ?? []; + + expect(unsupportedInputs.sort()).toEqual(unsupportedInputTypes.sort()); + }); + + it('should not render unsupported inputs warning if there are no unsupported inputs', () => { + const mockAgentPolicy = createAgentPolicyMock({ + global_data_tags: [], + package_policies: [ + { + ...createPackagePolicyMock(), + inputs: [{ type: 'supported1' } as PackagePolicyInput], + }, + ], + }); + renderComponent(mockAgentPolicy); + expect(renderResult.queryByText('Unsupported Inputs')).not.toBeInTheDocument(); + }); + + it('should render global data tags table with initial tags', () => { + const mockAgentPolicy = createAgentPolicyMock({ + global_data_tags: [{ name: 'tag1', value: 'value1' }], + }); + renderComponent(mockAgentPolicy); + expect(renderResult.getByText('tag1')).toBeInTheDocument(); + expect(renderResult.getByText('value1')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/index.tsx new file mode 100644 index 0000000000000..a10c6c3d2fb8e --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/custom_fields/index.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiDescribedFormGroup, EuiSpacer, EuiCallOut } from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; + +import type { + NewAgentPolicy, + AgentPolicy, + PackagePolicy, + PackagePolicyInput, +} from '../../../../../types'; + +import { GLOBAL_DATA_TAG_EXCLUDED_INPUTS } from '../../../../../../../../common/constants'; + +import { GlobalDataTagsTable } from './global_data_tags_table'; + +interface Props { + agentPolicy: Partial; + updateAgentPolicy: (u: Partial) => void; +} + +export const CustomFields: React.FunctionComponent = ({ + agentPolicy, + updateAgentPolicy, +}) => { + const isAgentPolicy = (policy: Partial): policy is AgentPolicy => { + return (policy as AgentPolicy).package_policies !== undefined; + }; + + const findUnsupportedInputs = ( + policy: Partial, + excludedInputs: Set + ): string[] => { + if (!isAgentPolicy(policy)) { + return []; + } + + const found = new Set([]); + policy.package_policies?.forEach((p: PackagePolicy) => { + p.inputs.forEach((input: PackagePolicyInput) => { + if (excludedInputs.has(input.type)) { + found.add(input.type); + } + }); + }); + return Array.from(found); + }; + + const unsupportedInputs = findUnsupportedInputs(agentPolicy, GLOBAL_DATA_TAG_EXCLUDED_INPUTS); + + return ( + + +

+ } + description={ + <> + + {unsupportedInputs.length > 0 ? ( + <> + + + } + color="warning" + iconType="alert" + size="s" + > +

+ {unsupportedInputs.join(', ')}, + }} + /> +

+
+ + ) : null} + + } + > + + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.test.tsx index 1fa4b2176f1ab..e404b30a37ced 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.test.tsx @@ -164,4 +164,13 @@ describe('Agent policy advanced options content', () => { }); }); }); + + describe('Custom Fields', () => { + it('should render the CustomFields component with correct props', () => { + usePlatinumLicense(); + render(); + expect(renderResult.queryByText('Custom fields')).toBeInTheDocument(); + expect(renderResult.queryByText('This policy has no custom fields')).toBeInTheDocument(); + }); + }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx index 53d0042f0b05e..470288f2ebac5 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_advanced_fields/index.tsx @@ -59,6 +59,8 @@ import { useFleetServerHostsOptions, } from './hooks'; +import { CustomFields } from './custom_fields'; + interface Props { agentPolicy: Partial; updateAgentPolicy: (u: Partial) => void; @@ -116,6 +118,7 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent = const AgentTamperProtectionSectionContent = useMemo( () => ( = return ( <> = = /> + = {AgentTamperProtectionSection} = = = = = = = ({ const generalSettingsWrapper = (children: JSX.Element[]) => ( +

-

+ } description={ = ({ return ( voi `?apiVersion=${API_VERSIONS.public.v1}`; return ( - +

diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx index 1ede87ba34c9b..e19ec358abd64 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/confirm_deploy_modal.tsx @@ -16,7 +16,7 @@ export const ConfirmDeployAgentPolicyModal: React.FunctionComponent<{ onConfirm: () => void; onCancel: () => void; agentCount: number; - agentPolicy: AgentPolicy; + agentPolicies: AgentPolicy[]; showUnprivilegedAgentsCallout?: boolean; unprivilegedAgentsCount?: number; dataStreams?: Array<{ name: string; title: string }>; @@ -24,7 +24,7 @@ export const ConfirmDeployAgentPolicyModal: React.FunctionComponent<{ onConfirm, onCancel, agentCount, - agentPolicy, + agentPolicies, showUnprivilegedAgentsCallout = false, unprivilegedAgentsCount = 0, dataStreams, @@ -66,11 +66,11 @@ export const ConfirmDeployAgentPolicyModal: React.FunctionComponent<{
{agentPolicy.name}, + policyNames: {agentPolicies.map((policy) => policy.name).join(', ')}, }} />
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/agent_policy_multi_select.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/agent_policy_multi_select.tsx new file mode 100644 index 0000000000000..4b10b2e2fc9ac --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/agent_policy_multi_select.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EuiComboBoxOptionOption } from '@elastic/eui'; +import { EuiComboBox } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import React, { useMemo } from 'react'; + +import type { PackageInfo } from '../../../../../../../../../common'; + +export interface Props { + isLoading: boolean; + agentPolicyMultiOptions: Array>; + selectedPolicyIds: string[]; + setSelectedPolicyIds: (policyIds: string[]) => void; + packageInfo?: PackageInfo; +} + +export const AgentPolicyMultiSelect: React.FunctionComponent = ({ + isLoading, + agentPolicyMultiOptions, + selectedPolicyIds, + setSelectedPolicyIds, +}) => { + const selectedOptions = useMemo(() => { + return agentPolicyMultiOptions.filter((option) => selectedPolicyIds.includes(option.key!)); + }, [agentPolicyMultiOptions, selectedPolicyIds]); + + return ( + { + setSelectedPolicyIds(newOptions.map((option: any) => option.key)); + }} + isClearable={true} + isLoading={isLoading} + /> + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.tsx index fd4a3a752eacd..247f908668eab 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_config.tsx @@ -14,6 +14,7 @@ import { EuiText, EuiSpacer, EuiButtonEmpty, + useIsWithinMinBreakpoint, } from '@elastic/eui'; import type { NewPackagePolicyInput, RegistryVarsEntry } from '../../../../../../types'; @@ -68,12 +69,15 @@ export const PackagePolicyInputConfig: React.FunctionComponent<{ [advancedVars, inputVarsValidationResults.vars] ); + const isBiggerScreen = useIsWithinMinBreakpoint('xxl'); + const flexWidth = isBiggerScreen ? 7 : 5; + return ( - +

( showPipelinesAndMappings, ]); + const isBiggerScreen = useIsWithinMinBreakpoint('xxl'); + const flexWidth = isBiggerScreen ? 7 : 5; + return ( <> @@ -170,7 +174,7 @@ export const PackagePolicyInputStreamConfig = memo( - + { - return { - ...jest.requireActual('../../../../../hooks'), - useGetPackagePolicies: jest.fn().mockReturnValue({ - data: { - items: [{ name: 'nginx-1' }, { name: 'other-policy' }], - }, - isLoading: false, - }), - useFleetStatus: jest.fn().mockReturnValue({ isReady: true } as any), - sendGetStatus: jest - .fn() - .mockResolvedValue({ data: { isReady: true, missing_requirements: [] } }), - }; -}); - describe('StepDefinePackagePolicy', () => { const packageInfo: PackageInfo = { name: 'apache', @@ -63,18 +45,32 @@ describe('StepDefinePackagePolicy', () => { }, ], }; - const agentPolicy: AgentPolicy = { - id: 'agent-policy-1', - namespace: 'ns', - name: 'Agent policy 1', - is_managed: false, - status: 'active', - updated_at: '', - updated_by: '', - revision: 1, - package_policies: [], - is_protected: false, - }; + const agentPolicies: AgentPolicy[] = [ + { + id: 'agent-policy-1', + namespace: 'ns', + name: 'Agent policy 1', + is_managed: false, + status: 'active', + updated_at: '', + updated_by: '', + revision: 1, + package_policies: [], + is_protected: false, + }, + { + id: 'agent-policy-2', + namespace: 'default', + name: 'Agent policy 2', + is_managed: false, + status: 'active', + updated_at: '', + updated_by: '', + revision: 1, + package_policies: [], + is_protected: false, + }, + ]; let packagePolicy: NewPackagePolicy; const mockUpdatePackagePolicy = jest.fn().mockImplementation((val: any) => { packagePolicy = { @@ -96,7 +92,7 @@ describe('StepDefinePackagePolicy', () => { const render = () => (renderResult = testRenderer.render( { waitFor(() => { expect(renderResult.getByRole('switch')).toHaveAttribute('aria-label', 'Advanced var'); + expect(renderResult.getByTestId('packagePolicyNamespaceInput')).toHaveAttribute( + 'placeholder', + 'ns' + ); }); }); }); - it('should set incremented name if other package policies exist', () => { - (useGetPackagePolicies as jest.MockedFunction).mockReturnValueOnce({ - data: { - items: [ - { name: 'apache-1' }, - { name: 'apache-2' }, - { name: 'apache-9' }, - { name: 'apache-10' }, - ], - }, - isLoading: false, - }); - - render(); - - waitFor(() => { - expect(renderResult.getByDisplayValue('apache-11')).toBeInTheDocument(); - }); - }); - describe('update', () => { describe('when package vars are introduced in a new package version', () => { it('should display new package vars', () => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.tsx index 4f50a0c1cc5f2..b963141210daa 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_define_package_policy.tsx @@ -48,7 +48,7 @@ const FormGroupResponsiveFields = styled(EuiDescribedFormGroup)` `; export const StepDefinePackagePolicy: React.FunctionComponent<{ - agentPolicy?: AgentPolicy; + agentPolicies?: AgentPolicy[]; packageInfo: PackageInfo; packagePolicy: NewPackagePolicy; updatePackagePolicy: (fields: Partial) => void; @@ -58,7 +58,7 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ noAdvancedToggle?: boolean; }> = memo( ({ - agentPolicy, + agentPolicies, packageInfo, packagePolicy, updatePackagePolicy, @@ -105,6 +105,7 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ )} @@ -149,6 +152,7 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ {/* Description */} @@ -283,8 +288,9 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{ } > { return { ...jest.requireActual('../../../../../hooks'), useGetAgentPolicies: jest.fn(), - useGetOutputs: jest.fn().mockResolvedValue({ - data: [], + useGetOutputs: jest.fn().mockReturnValue({ + data: { + items: [ + { + id: 'logstash-1', + type: 'logstash', + }, + ], + }, isLoading: false, }), - sendGetOneAgentPolicy: jest.fn().mockResolvedValue({ - data: { item: { id: 'policy-1' } }, - }), + sendBulkGetAgentPolicies: jest.fn().mockImplementation((ids) => + Promise.resolve({ + data: { items: ids.map((id: string) => ({ id, package_policies: [] })) }, + }) + ), useFleetStatus: jest.fn().mockReturnValue({ isReady: true } as any), sendGetFleetStatus: jest .fn() .mockResolvedValue({ data: { isReady: true, missing_requirements: [] } }), + useGetPackagePolicies: jest.fn().mockImplementation((query) => ({ + data: { + items: query.kuery.includes('osquery_manager') + ? [{ policy_ids: ['policy-1'] }] + : query.kuery.includes('apm') + ? [{ policy_ids: ['policy-2'] }] + : [], + }, + error: undefined, + isLoading: false, + resendRequest: jest.fn(), + })), }; }); @@ -41,21 +66,21 @@ describe('step select agent policy', () => { let testRenderer: TestRenderer; let renderResult: ReturnType; const mockSetHasAgentPolicyError = jest.fn(); - const updateAgentPolicyMock = jest.fn(); - const render = () => + const updateAgentPoliciesMock = jest.fn(); + const render = (packageInfo?: PackageInfo, selectedAgentPolicyId?: string) => (renderResult = testRenderer.render( )); beforeEach(() => { testRenderer = createFleetTestRendererMock(); - updateAgentPolicyMock.mockReset(); + updateAgentPoliciesMock.mockReset(); }); test('should not select agent policy by default if multiple exists', async () => { @@ -90,10 +115,151 @@ describe('step select agent policy', () => { } as any); render(); - await act(async () => {}); // Needed as updateAgentPolicy is called after multiple useEffect + await act(async () => {}); // Needed as updateAgentPolicies is called after multiple useEffect await act(async () => { - expect(updateAgentPolicyMock).toBeCalled(); - expect(updateAgentPolicyMock).toBeCalledWith({ id: 'policy-1' }); + expect(updateAgentPoliciesMock).toBeCalled(); + expect(updateAgentPoliciesMock).toBeCalledWith([{ id: 'policy-1', package_policies: [] }]); + }); + }); + + describe('multiple agent policies', () => { + beforeEach(() => { + jest + .spyOn(ExperimentalFeaturesService, 'get') + .mockReturnValue({ enableReusableIntegrationPolicies: true }); + + useGetAgentPoliciesMock.mockReturnValue({ + data: { + items: [ + { id: 'policy-1', name: 'Policy 1' }, + { id: 'policy-2', name: 'Policy 2' }, + ], + }, + error: undefined, + isLoading: false, + resendRequest: jest.fn(), + } as any); + }); + + test('should select agent policy by default if one exists', async () => { + useGetAgentPoliciesMock.mockReturnValueOnce({ + data: { items: [{ id: 'policy-1', name: 'Policy 1' }] }, + error: undefined, + isLoading: false, + resendRequest: jest.fn(), + } as any); + + render(); + await act(async () => {}); // Needed as updateAgentPolicies is called after multiple useEffect + await act(async () => { + expect(updateAgentPoliciesMock).toBeCalledWith([{ id: 'policy-1', package_policies: [] }]); + }); + }); + + test('should not select agent policy by default if multiple exists', async () => { + useGetAgentPoliciesMock.mockReturnValue({ + data: { + items: [ + { id: 'policy-1', name: 'Policy 1' }, + { id: 'policy-2', name: 'Policy 2' }, + ], + }, + error: undefined, + isLoading: false, + resendRequest: jest.fn(), + } as any); + + render(); + + await act(async () => { + const select = renderResult.container.querySelector( + '[data-test-subj="agentPolicyMultiSelect"]' + ); + expect((select as any)?.value).toEqual(undefined); + + expect(renderResult.getByText('An agent policy is required.')).toBeVisible(); + }); + }); + + test('should select agent policy if pre selected', async () => { + render(undefined, 'policy-1'); + await act(async () => {}); // Needed as updateAgentPolicies is called after multiple useEffect + await act(async () => { + expect(updateAgentPoliciesMock).toBeCalledWith([{ id: 'policy-1', package_policies: [] }]); + }); + }); + + test('should select multiple agent policies', async () => { + const result = render(); + expect(result.getByTestId('agentPolicyMultiSelect')).toBeInTheDocument(); + await act(async () => { + result.getByTestId('comboBoxToggleListButton').click(); + }); + expect(result.getAllByTestId('agentPolicyMultiItem').length).toBe(2); + await act(async () => { + result.getByText('Policy 1').click(); + }); + await act(async () => { + result.getByText('Policy 2').click(); + }); + expect(updateAgentPoliciesMock).toBeCalledWith([ + { id: 'policy-1', package_policies: [] }, + { id: 'policy-2', package_policies: [] }, + ]); + }); + + test('should disable option if agent policy has limited package', async () => { + useGetAgentPoliciesMock.mockReturnValue({ + data: { + items: [ + { id: 'policy-1', name: 'Policy 1' }, + { id: 'policy-2', name: 'Policy 2' }, + { id: 'policy-3', name: 'Policy 3' }, + ], + }, + error: undefined, + isLoading: false, + resendRequest: jest.fn(), + } as any); + const result = render({ + name: 'osquery_manager', + policy_templates: [{ multiple: false }], + } as any); + expect(result.getByTestId('agentPolicyMultiSelect')).toBeInTheDocument(); + await act(async () => { + result.getByTestId('comboBoxToggleListButton').click(); + }); + expect( + result.getByText('Policy 1').closest('[data-test-subj="agentPolicyMultiItem"]') + ).toBeDisabled(); + }); + + test('should disable option if agent policy has apm package and logstash output', async () => { + useGetAgentPoliciesMock.mockReturnValue({ + data: { + items: [ + { id: 'policy-1', name: 'Policy 1' }, + { id: 'policy-2', name: 'Policy 2', data_output_id: 'logstash-1' }, + { id: 'policy-3', name: 'Policy 3' }, + ], + }, + error: undefined, + isLoading: false, + resendRequest: jest.fn(), + } as any); + const result = render({ + name: 'apm', + } as any); + expect(result.getByTestId('agentPolicyMultiSelect')).toBeInTheDocument(); + await act(async () => { + result.getByTestId('comboBoxToggleListButton').click(); + }); + expect( + result.getByText('Policy 2').closest('[data-test-subj="agentPolicyMultiItem"]') + ).toBeDisabled(); + expect( + result.getByTitle('Policy 2').querySelector('[data-euiicon-type="warningFilled"]') + ).toBeInTheDocument(); }); }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.tsx index 1524c3590c82b..6156e81a133d7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_agent_policy.tsx @@ -9,7 +9,8 @@ import React, { useEffect, useState, useMemo, useCallback } from 'react'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { EuiSuperSelectOption } from '@elastic/eui'; +import type { EuiComboBoxOptionOption, EuiSuperSelectOption } from '@elastic/eui'; +import { EuiIcon, EuiToolTip } from '@elastic/eui'; import { EuiSuperSelect } from '@elastic/eui'; import { EuiFlexGroup, @@ -23,13 +24,17 @@ import { import { Error } from '../../../../../components'; import type { AgentPolicy, Output, PackageInfo } from '../../../../../types'; -import { isPackageLimited, doesAgentPolicyAlreadyIncludePackage } from '../../../../../services'; +import { + isPackageLimited, + doesAgentPolicyAlreadyIncludePackage, + ExperimentalFeaturesService, +} from '../../../../../services'; import { useGetAgentPolicies, useGetOutputs, - sendGetOneAgentPolicy, useFleetStatus, useGetPackagePolicies, + sendBulkGetAgentPolicies, } from '../../../../../hooks'; import { FLEET_APM_PACKAGE, @@ -38,6 +43,8 @@ import { PACKAGE_POLICY_SAVED_OBJECT_TYPE, } from '../../../../../../../../common/constants'; +import { AgentPolicyMultiSelect } from './components/agent_policy_multi_select'; + const AgentPolicyFormRow = styled(EuiFormRow)` .euiFormRow__label { width: 100%; @@ -126,7 +133,7 @@ function useAgentPoliciesOptions(packageInfo?: PackageInfo) { @@ -147,11 +154,55 @@ function useAgentPoliciesOptions(packageInfo?: PackageInfo) { ] ); + const agentPolicyMultiOptions: Array> = useMemo( + () => + packageInfo && !isOutputLoading && !isAgentPoliciesLoading && !isLoadingPackagePolicies + ? agentPolicies.map((policy) => { + const isLimitedPackageAlreadyInPolicy = + isPackageLimited(packageInfo) && + packagePoliciesForThisPackageByAgentPolicyId?.[policy.id]; + + const isAPMPackageAndDataOutputIsLogstash = + packageInfo?.name === FLEET_APM_PACKAGE && + getDataOutputForPolicy(policy)?.type === outputType.Logstash; + + return { + append: isAPMPackageAndDataOutputIsLogstash ? ( + + } + > + + + ) : null, + key: policy.id, + label: policy.name, + disabled: isLimitedPackageAlreadyInPolicy || isAPMPackageAndDataOutputIsLogstash, + 'data-test-subj': 'agentPolicyMultiItem', + }; + }) + : [], + [ + packageInfo, + agentPolicies, + packagePoliciesForThisPackageByAgentPolicyId, + getDataOutputForPolicy, + isOutputLoading, + isAgentPoliciesLoading, + isLoadingPackagePolicies, + ] + ); + return { agentPoliciesError, isLoading: isOutputLoading || isAgentPoliciesLoading || isLoadingPackagePolicies, agentPolicyOptions, agentPolicies, + agentPolicyMultiOptions, }; } @@ -163,14 +214,14 @@ function doesAgentPolicyHaveLimitedPackage(policy: AgentPolicy, pkgInfo: Package export const StepSelectAgentPolicy: React.FunctionComponent<{ packageInfo?: PackageInfo; - agentPolicy: AgentPolicy | undefined; - updateAgentPolicy: (agentPolicy: AgentPolicy | undefined) => void; + agentPolicies: AgentPolicy[]; + updateAgentPolicies: (agentPolicies: AgentPolicy[]) => void; setHasAgentPolicyError: (hasError: boolean) => void; selectedAgentPolicyId?: string; }> = ({ packageInfo, - agentPolicy, - updateAgentPolicy: updateSelectedAgentPolicy, + agentPolicies, + updateAgentPolicies: updateSelectedAgentPolicies, setHasAgentPolicyError, selectedAgentPolicyId, }) => { @@ -178,69 +229,101 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{ const [selectedAgentPolicyError, setSelectedAgentPolicyError] = useState(); - const { isLoading, agentPoliciesError, agentPolicyOptions, agentPolicies } = - useAgentPoliciesOptions(packageInfo); - // Selected agent policy state - const [selectedPolicyId, setSelectedPolicyId] = useState( - agentPolicy?.id ?? - (selectedAgentPolicyId || (agentPolicies.length === 1 ? agentPolicies[0].id : undefined)) - ); + const { enableReusableIntegrationPolicies } = ExperimentalFeaturesService.get(); + + const { + isLoading, + agentPoliciesError, + agentPolicyOptions, + agentPolicyMultiOptions, + agentPolicies: existingAgentPolicies, + } = useAgentPoliciesOptions(packageInfo); + + const [selectedPolicyIds, setSelectedPolicyIds] = useState([]); const [isLoadingSelectedAgentPolicy, setIsLoadingSelectedAgentPolicy] = useState(false); - const [selectedAgentPolicy, setSelectedAgentPolicy] = useState( - agentPolicy - ); + const [selectedAgentPolicies, setSelectedAgentPolicies] = useState(agentPolicies); - const updateAgentPolicy = useCallback( - (selectedPolicy: AgentPolicy | undefined) => { - setSelectedAgentPolicy(selectedPolicy); - updateSelectedAgentPolicy(selectedPolicy); + const updateAgentPolicies = useCallback( + (selectedPolicies: AgentPolicy[]) => { + setSelectedAgentPolicies(selectedPolicies); + updateSelectedAgentPolicies(selectedPolicies); }, - [updateSelectedAgentPolicy] + [updateSelectedAgentPolicies] ); // Update parent selected agent policy state useEffect(() => { const fetchAgentPolicyInfo = async () => { - if (selectedPolicyId) { + if (selectedPolicyIds.length > 0) { setIsLoadingSelectedAgentPolicy(true); - const { data, error } = await sendGetOneAgentPolicy(selectedPolicyId); + const { data, error } = await sendBulkGetAgentPolicies(selectedPolicyIds, { full: true }); if (error) { setSelectedAgentPolicyError(error); - updateAgentPolicy(undefined); - } else if (data && data.item) { + updateAgentPolicies([]); + } else if (data && data.items) { setSelectedAgentPolicyError(undefined); - updateAgentPolicy(data.item); + updateAgentPolicies(data.items); } setIsLoadingSelectedAgentPolicy(false); } else { setSelectedAgentPolicyError(undefined); - updateAgentPolicy(undefined); + updateAgentPolicies([]); } }; - if (!agentPolicy || selectedPolicyId !== agentPolicy.id) { + const agentPoliciesHaveAllSelectedIds = selectedPolicyIds.every((id) => + agentPolicies.map((policy) => policy.id).includes(id) + ); + if (agentPolicies.length === 0 || !agentPoliciesHaveAllSelectedIds) { fetchAgentPolicyInfo(); + } else if (agentPoliciesHaveAllSelectedIds && selectedPolicyIds.length < agentPolicies.length) { + setSelectedAgentPolicyError(undefined); + updateAgentPolicies(agentPolicies.filter((policy) => selectedPolicyIds.includes(policy.id))); } - }, [selectedPolicyId, agentPolicy, updateAgentPolicy]); + }, [selectedPolicyIds, agentPolicies, updateAgentPolicies]); // Try to select default agent policy useEffect(() => { - if (!selectedPolicyId && agentPolicies.length && agentPolicyOptions.length) { - const enabledOptions = agentPolicyOptions.filter((option) => !option.disabled); - if (enabledOptions.length === 1) { - setSelectedPolicyId(enabledOptions[0].value as string | undefined); + if ( + selectedPolicyIds.length === 0 && + existingAgentPolicies.length && + (enableReusableIntegrationPolicies + ? agentPolicyMultiOptions.length + : agentPolicyOptions.length) + ) { + if (enableReusableIntegrationPolicies) { + const enabledOptions = agentPolicyMultiOptions.filter((option) => !option.disabled); + if (enabledOptions.length === 1) { + setSelectedPolicyIds([enabledOptions[0].key!]); + } else if (selectedAgentPolicyId) { + setSelectedPolicyIds([selectedAgentPolicyId]); + } + } else { + const enabledOptions = agentPolicyOptions.filter((option) => !option.disabled); + if (enabledOptions.length === 1) { + setSelectedPolicyIds([enabledOptions[0].value]); + } else if (selectedAgentPolicyId) { + setSelectedPolicyIds([selectedAgentPolicyId]); + } } } - }, [agentPolicies, agentPolicyOptions, selectedPolicyId]); + }, [ + agentPolicyOptions, + agentPolicyMultiOptions, + enableReusableIntegrationPolicies, + selectedAgentPolicyId, + selectedPolicyIds, + existingAgentPolicies, + ]); // Bubble up any issues with agent policy selection useEffect(() => { - if (selectedPolicyId && !selectedAgentPolicyError) { + if (selectedPolicyIds.length > 0 && !selectedAgentPolicyError) { setHasAgentPolicyError(false); } else setHasAgentPolicyError(true); - }, [selectedAgentPolicyError, selectedPolicyId, setHasAgentPolicyError]); + }, [selectedAgentPolicyError, selectedPolicyIds, setHasAgentPolicyError]); const onChange = useCallback( - (newValue: string) => setSelectedPolicyId(newValue === '' ? undefined : newValue), + (newValue: string) => setSelectedPolicyIds(newValue === '' ? [] : [newValue]), [] ); @@ -286,7 +369,7 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{ } > @@ -298,24 +381,28 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{ } helpText={ - isFleetReady && selectedPolicyId && !isLoadingSelectedAgentPolicy ? ( + isFleetReady && selectedPolicyIds.length > 0 && !isLoadingSelectedAgentPolicy ? ( acc + (curr.agents ?? 0), + 0 + ), }} /> ) : null } isInvalid={Boolean( - !selectedPolicyId || + selectedPolicyIds.length === 0 || !packageInfo || - (selectedAgentPolicy && - doesAgentPolicyHaveLimitedPackage(selectedAgentPolicy, packageInfo)) + selectedAgentPolicies.every((selectedAgentPolicy) => + doesAgentPolicyHaveLimitedPackage(selectedAgentPolicy, packageInfo) + ) )} error={ - !selectedPolicyId ? ( + selectedPolicyIds.length === 0 ? ( - + {enableReusableIntegrationPolicies ? ( + + ) : ( + + )} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.test.tsx index 89de7ba44b3e6..70fb22fcebdf1 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.test.tsx @@ -24,9 +24,11 @@ jest.mock('../../../../../hooks', () => { data: [], isLoading: false, }), - sendGetOneAgentPolicy: jest.fn().mockResolvedValue({ - data: { item: { id: 'policy-1', name: 'Agent policy 1' } }, - }), + sendGetOneAgentPolicy: jest.fn().mockImplementation((id) => + Promise.resolve({ + data: { item: { id, name: `Agent policy ${id}` } }, + }) + ), }; }); @@ -44,18 +46,32 @@ describe('StepSelectHosts', () => { status: 'not_installed', vars: [], }; - const agentPolicy: AgentPolicy = { - id: 'agent-policy-1', - namespace: 'default', - name: 'Agent policy 1', - is_managed: false, - status: 'active', - updated_at: '', - updated_by: '', - revision: 1, - package_policies: [], - is_protected: false, - }; + const agentPolicies: AgentPolicy[] = [ + { + id: '1', + namespace: 'default', + name: 'Agent policy 1', + is_managed: false, + status: 'active', + updated_at: '', + updated_by: '', + revision: 1, + package_policies: [], + is_protected: false, + }, + { + id: '2', + namespace: 'default', + name: 'Agent policy 2', + is_managed: false, + status: 'active', + updated_at: '', + updated_by: '', + revision: 1, + package_policies: [], + is_protected: false, + }, + ]; const newAgentPolicy = { name: '', namespace: 'default', @@ -67,8 +83,8 @@ describe('StepSelectHosts', () => { const render = () => (renderResult = testRenderer.render( { it('should display tabs with New hosts selected when agent policies exist', () => { (useGetAgentPolicies as jest.MockedFunction).mockReturnValue({ data: { - items: [{ id: 'agent-policy-1', name: 'Agent policy 1', namespace: 'default' }], + items: [{ id: '1', name: 'Agent policy 1', namespace: 'default' }], }, }); @@ -110,7 +126,7 @@ describe('StepSelectHosts', () => { waitFor(() => { expect(renderResult.getByRole('tablist')).toBeInTheDocument(); - expect(renderResult.getByText('Agent policy 2')).toBeInTheDocument(); + expect(renderResult.getByText('Agent policy 3')).toBeInTheDocument(); }); expect(renderResult.getByText('New hosts').closest('button')).toHaveAttribute( 'aria-selected', @@ -121,7 +137,7 @@ describe('StepSelectHosts', () => { it('should display dropdown with agent policy selected when Existing hosts selected', async () => { (useGetAgentPolicies as jest.MockedFunction).mockReturnValue({ data: { - items: [{ id: 'agent-policy-1', name: 'Agent policy 1', namespace: 'default' }], + items: [{ id: '1', name: 'Agent policy 1', namespace: 'default' }], }, }); @@ -143,8 +159,8 @@ describe('StepSelectHosts', () => { (useGetAgentPolicies as jest.MockedFunction).mockReturnValue({ data: { items: [ - { id: 'agent-policy-1', name: 'Agent policy 1', namespace: 'default' }, - { id: 'agent-policy-2', name: 'Agent policy 2', namespace: 'default' }, + { id: '1', name: 'Agent policy 1', namespace: 'default' }, + { id: '2', name: 'Agent policy 2', namespace: 'default' }, ], }, }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.tsx index 87ef9ac864bfd..a4b4f5a3e0970 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/step_select_hosts.tsx @@ -32,8 +32,8 @@ const StyledEuiTabbedContent = styled(EuiTabbedContent)` `; interface Props { - agentPolicy: AgentPolicy | undefined; - updateAgentPolicy: (u: AgentPolicy | undefined) => void; + agentPolicies: AgentPolicy[]; + updateAgentPolicies: (u: AgentPolicy[]) => void; newAgentPolicy: Partial; updateNewAgentPolicy: (u: Partial) => void; withSysMonitoring: boolean; @@ -46,8 +46,8 @@ interface Props { } export const StepSelectHosts: React.FunctionComponent = ({ - agentPolicy, - updateAgentPolicy, + agentPolicies, + updateAgentPolicies, newAgentPolicy, updateNewAgentPolicy, withSysMonitoring, @@ -58,7 +58,7 @@ export const StepSelectHosts: React.FunctionComponent = ({ updateSelectedTab, selectedAgentPolicyId, }) => { - let agentPolicies: AgentPolicy[] = []; + let existingAgentPolicies: AgentPolicy[] = []; const { data: agentPoliciesData, error: err } = useGetAgentPolicies({ page: 1, perPage: SO_SEARCH_LIMIT, @@ -71,19 +71,19 @@ export const StepSelectHosts: React.FunctionComponent = ({ // eslint-disable-next-line no-console console.debug('Could not retrieve agent policies'); } - agentPolicies = useMemo( + existingAgentPolicies = useMemo( () => agentPoliciesData?.items.filter((policy) => !policy.is_managed) || [], [agentPoliciesData?.items] ); useEffect(() => { - if (agentPolicies.length > 0) { + if (existingAgentPolicies.length > 0) { updateNewAgentPolicy({ ...newAgentPolicy, - name: incrementPolicyName(agentPolicies), + name: incrementPolicyName(existingAgentPolicies), }); } - }, [agentPolicies.length]); // eslint-disable-line react-hooks/exhaustive-deps + }, [existingAgentPolicies.length]); // eslint-disable-line react-hooks/exhaustive-deps const tabs = [ { @@ -107,8 +107,8 @@ export const StepSelectHosts: React.FunctionComponent = ({ content: ( @@ -119,7 +119,7 @@ export const StepSelectHosts: React.FunctionComponent = ({ const handleOnTabClick = (tab: EuiTabbedContentTab) => updateSelectedTab(tab.id as SelectedPolicyTab); - return agentPolicies.length > 0 ? ( + return existingAgentPolicies.length > 0 ? ( { const Bar = noAnimation ? NoAnimationCenteredRoundedBottomBar : CenteredRoundedBottomBar; return ( - + diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/layout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/layout.tsx index 21d08d71eb5f7..445a0bcf94287 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/layout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/layout.tsx @@ -17,6 +17,7 @@ import { EuiDescriptionListDescription, EuiButtonEmpty, EuiSpacer, + useIsWithinMinBreakpoint, } from '@elastic/eui'; import { useAgentless } from '../hooks/setup_technology'; @@ -251,7 +252,8 @@ export const CreatePackagePolicySinglePageLayout: React.FunctionComponent<{ ) : undefined; - const maxWidth = 770; + const isBiggerScreen = useIsWithinMinBreakpoint('xxl'); + const maxWidth = isBiggerScreen ? 1200 : 800; return ( { expect(renderResult.result.current.packagePolicy).toEqual({ id: 'new-id', - policy_id: '', - policy_ids: [''], + policy_ids: [], namespace: 'newspace', description: '', enabled: true, @@ -147,8 +146,7 @@ describe('useOnSubmit', () => { inputs: [], name: 'apache-1', namespace: '', - policy_id: '', - policy_ids: [''], + policy_ids: [], package: { name: 'apache', title: 'Apache', @@ -193,8 +191,7 @@ describe('useOnSubmit', () => { inputs: [], name: 'apache-11', namespace: '', - policy_id: '', - policy_ids: [''], + policy_ids: [], package: { name: 'apache', title: 'Apache', diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx index 2f37ffea1b214..be80a96697269 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx @@ -9,6 +9,8 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { safeLoad } from 'js-yaml'; +import { isEqual } from 'lodash'; + import type { AgentPolicy, NewPackagePolicy, @@ -121,7 +123,7 @@ export function useOnSubmit({ // Used to initialize the package policy once const isInitializedRef = useRef(false); - const [agentPolicy, setAgentPolicy] = useState(); + const [agentPolicies, setAgentPolicies] = useState([]); // New package policy state const [packagePolicy, setPackagePolicy] = useState({ ...DEFAULT_PACKAGE_POLICY, @@ -136,22 +138,25 @@ export function useOnSubmit({ useAgentless(); // Update agent policy method - const updateAgentPolicy = useCallback( - (updatedAgentPolicy: AgentPolicy | undefined) => { - if (updatedAgentPolicy) { - setAgentPolicy(updatedAgentPolicy); + const updateAgentPolicies = useCallback( + (updatedAgentPolicies: AgentPolicy[]) => { + if (isEqual(updatedAgentPolicies, agentPolicies)) { + return; + } + if (updatedAgentPolicies.length > 0) { + setAgentPolicies(updatedAgentPolicies); if (packageInfo) { setHasAgentPolicyError(false); } } else { setHasAgentPolicyError(true); - setAgentPolicy(undefined); + setAgentPolicies([]); } // eslint-disable-next-line no-console - console.debug('Agent policy updated', updatedAgentPolicy); + console.debug('Agent policy updated', updatedAgentPolicies); }, - [packageInfo, setAgentPolicy] + [packageInfo, agentPolicies] ); // Update package policy validation const updatePackagePolicyValidation = useCallback( @@ -221,7 +226,7 @@ export function useOnSubmit({ updatePackagePolicy( packageToPackagePolicy( packageInfo, - agentPolicy?.id || '', + agentPolicies.map((policy) => policy.id), '', DEFAULT_PACKAGE_POLICY.name || incrementedName, DEFAULT_PACKAGE_POLICY.description, @@ -231,15 +236,21 @@ export function useOnSubmit({ setIsInitialized(true); } init(); - }, [packageInfo, agentPolicy, updatePackagePolicy, integrationToEnable, isInitialized]); + }, [packageInfo, agentPolicies, updatePackagePolicy, integrationToEnable, isInitialized]); useEffect(() => { - if (agentPolicy && !packagePolicy.policy_ids.includes(agentPolicy.id)) { + if ( + agentPolicies.length > 0 && + !isEqual( + agentPolicies.map((policy) => policy.id), + packagePolicy.policy_ids + ) + ) { updatePackagePolicy({ - policy_ids: [agentPolicy.id], + policy_ids: agentPolicies.map((policy) => policy.id), }); } - }, [packagePolicy, agentPolicy, updatePackagePolicy]); + }, [packagePolicy, agentPolicies, updatePackagePolicy]); const onSaveNavigate = useOnSaveNavigate({ packagePolicy, @@ -295,7 +306,7 @@ export function useOnSubmit({ packagePolicy, withSysMonitoring, }); - setAgentPolicy(createdPolicy); + setAgentPolicies([createdPolicy]); updatePackagePolicy({ policy_ids: [createdPolicy.id] }); } catch (e) { setFormState('VALID'); @@ -361,7 +372,7 @@ export function useOnSubmit({ setSavedPackagePolicy(data!.item); const promptForAgentEnrollment = - !(agentCount && agentPolicy) && hasFleetAddAgentsPrivileges; + !(agentCount && agentPolicies.length > 0) && hasFleetAddAgentsPrivileges; if (promptForAgentEnrollment && hasAzureArmTemplate) { setFormState('SUBMITTED_AZURE_ARM_TEMPLATE'); return; @@ -389,9 +400,9 @@ export function useOnSubmit({ }), text: promptForAgentEnrollment ? i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationMessage', { - defaultMessage: `Fleet will deploy updates to all agents that use the ''{agentPolicyName}'' policy.`, + defaultMessage: `Fleet will deploy updates to all agents that use the ''{agentPolicyNames}'' policies.`, values: { - agentPolicyName: agentPolicy!.name, + agentPolicyNames: agentPolicies.map((policy) => policy.name).join(', '), }, }) : undefined, @@ -431,15 +442,15 @@ export function useOnSubmit({ newAgentPolicy, updatePackagePolicy, notifications.toasts, - agentPolicy, + agentPolicies, onSaveNavigate, confirmForceInstall, ] ); return { - agentPolicy, - updateAgentPolicy, + agentPolicies, + updateAgentPolicies, packagePolicy, updatePackagePolicy, savedPackagePolicy, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.test.ts index dd2a1dcc81c79..5099cdde2277c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.test.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.test.ts @@ -25,7 +25,7 @@ type MockFn = jest.MockedFunction; describe('useSetupTechnology', () => { const updateNewAgentPolicyMock = jest.fn(); - const updateAgentPolicyMock = jest.fn(); + const updateAgentPoliciesMock = jest.fn(); const setSelectedPolicyTabMock = jest.fn(); const newAgentPolicyMock = { name: 'mock_new_agent_policy', @@ -59,7 +59,7 @@ describe('useSetupTechnology', () => { useSetupTechnology({ updateNewAgentPolicy: updateNewAgentPolicyMock, newAgentPolicy: newAgentPolicyMock, - updateAgentPolicy: updateAgentPolicyMock, + updateAgentPolicies: updateAgentPoliciesMock, setSelectedPolicyTab: setSelectedPolicyTabMock, }) ); @@ -74,7 +74,7 @@ describe('useSetupTechnology', () => { useSetupTechnology({ updateNewAgentPolicy: updateNewAgentPolicyMock, newAgentPolicy: newAgentPolicyMock, - updateAgentPolicy: updateAgentPolicyMock, + updateAgentPolicies: updateAgentPoliciesMock, setSelectedPolicyTab: setSelectedPolicyTabMock, }) ); @@ -95,7 +95,7 @@ describe('useSetupTechnology', () => { useSetupTechnology({ updateNewAgentPolicy: updateNewAgentPolicyMock, newAgentPolicy: newAgentPolicyMock, - updateAgentPolicy: updateAgentPolicyMock, + updateAgentPolicies: updateAgentPoliciesMock, setSelectedPolicyTab: setSelectedPolicyTabMock, }) ); @@ -110,7 +110,7 @@ describe('useSetupTechnology', () => { useSetupTechnology({ updateNewAgentPolicy: updateNewAgentPolicyMock, newAgentPolicy: newAgentPolicyMock, - updateAgentPolicy: updateAgentPolicyMock, + updateAgentPolicies: updateAgentPoliciesMock, setSelectedPolicyTab: setSelectedPolicyTabMock, }) ); @@ -121,7 +121,7 @@ describe('useSetupTechnology', () => { result.current.handleSetupTechnologyChange(SetupTechnology.AGENTLESS); }); - expect(updateAgentPolicyMock).toHaveBeenCalledWith({ id: 'agentless-policy-id' }); + expect(updateAgentPoliciesMock).toHaveBeenCalledWith([{ id: 'agentless-policy-id' }]); expect(setSelectedPolicyTabMock).toHaveBeenCalledWith(SelectedPolicyTab.EXISTING); }); @@ -130,7 +130,7 @@ describe('useSetupTechnology', () => { useSetupTechnology({ updateNewAgentPolicy: updateNewAgentPolicyMock, newAgentPolicy: newAgentPolicyMock, - updateAgentPolicy: updateAgentPolicyMock, + updateAgentPolicies: updateAgentPoliciesMock, setSelectedPolicyTab: setSelectedPolicyTabMock, }) ); @@ -164,7 +164,7 @@ describe('useSetupTechnology', () => { useSetupTechnology({ updateNewAgentPolicy: updateNewAgentPolicyMock, newAgentPolicy: newAgentPolicyMock, - updateAgentPolicy: updateAgentPolicyMock, + updateAgentPolicies: updateAgentPoliciesMock, setSelectedPolicyTab: setSelectedPolicyTabMock, }) ); @@ -183,7 +183,7 @@ describe('useSetupTechnology', () => { useSetupTechnology({ updateNewAgentPolicy: updateNewAgentPolicyMock, newAgentPolicy: newAgentPolicyMock, - updateAgentPolicy: updateAgentPolicyMock, + updateAgentPolicies: updateAgentPoliciesMock, setSelectedPolicyTab: setSelectedPolicyTabMock, }) ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts index ee2ef29f71d12..cc67adddeca19 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.ts @@ -64,13 +64,13 @@ export const useAgentless = () => { export function useSetupTechnology({ updateNewAgentPolicy, newAgentPolicy, - updateAgentPolicy, + updateAgentPolicies, setSelectedPolicyTab, packageInfo, }: { updateNewAgentPolicy: (policy: NewAgentPolicy) => void; newAgentPolicy: NewAgentPolicy; - updateAgentPolicy: (policy: AgentPolicy | undefined) => void; + updateAgentPolicies: (policies: AgentPolicy[]) => void; setSelectedPolicyTab: (tab: SelectedPolicyTab) => void; packageInfo?: PackageInfo; }) { @@ -109,13 +109,13 @@ export function useSetupTechnology({ if (setupTechnology === SetupTechnology.AGENTLESS) { if (agentlessPolicy) { - updateAgentPolicy(agentlessPolicy); + updateAgentPolicies([agentlessPolicy]); setSelectedPolicyTab(SelectedPolicyTab.EXISTING); } } else if (setupTechnology === SetupTechnology.AGENT_BASED) { updateNewAgentPolicy(newAgentPolicy); setSelectedPolicyTab(SelectedPolicyTab.NEW); - updateAgentPolicy(undefined); + updateAgentPolicies([]); } setSelectedSetupTechnology(setupTechnology); }, @@ -123,7 +123,7 @@ export function useSetupTechnology({ isAgentlessEnabled, selectedSetupTechnology, agentlessPolicy, - updateAgentPolicy, + updateAgentPolicies, setSelectedPolicyTab, updateNewAgentPolicy, newAgentPolicy, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx index 43c2c4b4e2841..f79a5ad00c67a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.test.tsx @@ -60,6 +60,11 @@ jest.mock('../../../../hooks', () => { }, }, }), + sendBulkGetAgentPolicies: jest.fn().mockImplementation((ids) => + Promise.resolve({ + data: { items: ids.map((id: string) => ({ id, package_policies: [] })) }, + }) + ), useGetPackageInfoByKeyQuery: jest.fn(), sendGetSettings: jest.fn().mockResolvedValue({ data: { item: {} }, @@ -308,7 +313,6 @@ describe('When on the package policy create page', () => { title: 'Nginx', version: '1.3.0', }, - policy_id: '', policy_ids: ['agent-policy-1'], vars: undefined, }; @@ -371,6 +375,7 @@ describe('When on the package policy create page', () => { expect(sendCreatePackagePolicy as jest.MockedFunction).toHaveBeenCalledWith({ ...newPackagePolicy, policy_id: 'agent-policy-1', + policy_ids: ['agent-policy-1'], force: false, }); expect(sendCreateAgentPolicy as jest.MockedFunction).not.toHaveBeenCalled(); @@ -498,6 +503,9 @@ describe('When on the package policy create page', () => { (sendCreateAgentPolicy as jest.MockedFunction).mockClear(); (sendCreatePackagePolicy as jest.MockedFunction).mockClear(); + (sendGetAgentStatus as jest.MockedFunction).mockResolvedValue({ + data: { results: { total: 0 } }, + }); }); test('should create agent policy before creating package policy on submit when new hosts is selected', async () => { @@ -545,7 +553,7 @@ describe('When on the package policy create page', () => { }); test('should show modal if agent policy has agents', async () => { - (sendGetAgentStatus as jest.MockedFunction).mockResolvedValueOnce({ + (sendGetAgentStatus as jest.MockedFunction).mockResolvedValue({ data: { results: { total: 1 } }, }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx index 4bc608e4d9059..761153401ed06 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState, Suspense } from 'react'; import { useRouteMatch } from 'react-router-dom'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; @@ -155,8 +155,8 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ onSubmit, updatePackagePolicy, packagePolicy, - agentPolicy, - updateAgentPolicy, + agentPolicies, + updateAgentPolicies, savedPackagePolicy, formState, setFormState, @@ -216,19 +216,23 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ ); // Retrieve agent count - const agentPolicyId = agentPolicy?.id; + const agentPolicyIds = agentPolicies.map((policy) => policy.id); const { cancelClickHandler, cancelUrl } = useCancelAddPackagePolicy({ from, pkgkey: params.pkgkey, - agentPolicyId, + agentPolicyId: agentPolicyIds[0], }); useEffect(() => { const getAgentCount = async () => { - const { data } = await sendGetAgentStatus({ policyId: agentPolicyId }); - if (data?.results.total !== undefined) { - setAgentCount(data.results.total); + let count = 0; + for (const policyId of agentPolicyIds) { + const { data } = await sendGetAgentStatus({ policyId }); + if (data?.results.total) { + count += data.results.total; + } } + setAgentCount(count); }; if (selectedPolicyTab === SelectedPolicyTab.NEW) { @@ -236,10 +240,10 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ return; } - if (isFleetEnabled && agentPolicyId) { + if (isFleetEnabled && agentPolicyIds.length > 0) { getAgentCount(); } - }, [agentPolicyId, selectedPolicyTab, isFleetEnabled]); + }, [agentPolicyIds, selectedPolicyTab, isFleetEnabled]); const handleExtensionViewOnChange = useCallback< PackagePolicyEditExtensionComponentProps['onChange'] @@ -269,18 +273,18 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ from, cancelUrl, onCancel: cancelClickHandler, - agentPolicy, + agentPolicies, packageInfo, integrationInfo, }), - [agentPolicy, cancelClickHandler, cancelUrl, from, integrationInfo, packageInfo] + [agentPolicies, cancelClickHandler, cancelUrl, from, integrationInfo, packageInfo] ); const stepSelectAgentPolicy = useMemo( () => ( + pliAuthBlockView?.Component && !isPackageInfoLoading + ? pliAuthBlockView.Component + : ({ children }) => <>{children}, // when no UI Extension is registered, render children + [isPackageInfoLoading, pliAuthBlockView?.Component] + ); + if (replaceDefineStepView && extensionView) { throw new Error( "'package-policy-create' and 'package-policy-replace-define-step' cannot both be registered as UI extensions" @@ -327,7 +345,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ useSetupTechnology({ newAgentPolicy, updateNewAgentPolicy, - updateAgentPolicy, + updateAgentPolicies, setSelectedPolicyTab, packageInfo, }); @@ -339,7 +357,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ ) : ( acc + (curr.unprivileged_agents ?? 0), + 0 + ); return ( - - {formState === 'CONFIRM' && agentPolicy && ( - setFormState('VALID')} - showUnprivilegedAgentsCallout={Boolean( - packageInfo && - isRootPrivilegesRequired(packageInfo) && - (agentPolicy?.unprivileged_agents ?? 0) > 0 + }> + + + {formState === 'CONFIRM' && agentPolicies.length > 0 && ( + setFormState('VALID')} + showUnprivilegedAgentsCallout={Boolean( + packageInfo && + isRootPrivilegesRequired(packageInfo) && + unprivilegedAgentsCount > 0 + )} + unprivilegedAgentsCount={unprivilegedAgentsCount} + dataStreams={rootPrivilegedDataStreams} + /> )} - unprivilegedAgentsCount={agentPolicy?.unprivileged_agents ?? 0} - dataStreams={rootPrivilegedDataStreams} - /> - )} - {formState === 'SUBMITTED_NO_AGENTS' && - agentPolicy && - packageInfo && - savedPackagePolicy && ( - navigateAddAgent(savedPackagePolicy)} - onCancel={() => navigateAddAgentHelp(savedPackagePolicy)} - /> - )} - {formState === 'SUBMITTED_AZURE_ARM_TEMPLATE' && agentPolicy && savedPackagePolicy && ( - navigateAddAgent(savedPackagePolicy)} - onCancel={() => navigateAddAgentHelp(savedPackagePolicy)} - /> - )} - {formState === 'SUBMITTED_CLOUD_FORMATION' && agentPolicy && savedPackagePolicy && ( - navigateAddAgent(savedPackagePolicy)} - onCancel={() => navigateAddAgentHelp(savedPackagePolicy)} - /> - )} - {formState === 'SUBMITTED_GOOGLE_CLOUD_SHELL' && agentPolicy && savedPackagePolicy && ( - navigateAddAgent(savedPackagePolicy)} - onCancel={() => navigateAddAgentHelp(savedPackagePolicy)} - /> - )} - {packageInfo && ( - - )} - {packageInfo && isRootPrivilegesRequired(packageInfo) ? ( - <> - - - - ) : null} - {numTransformAssets > 0 ? ( - <> - - - - ) : null} - {showSecretsDisabledCallout && ( - <> - 0 && + packageInfo && + savedPackagePolicy && ( + navigateAddAgent(savedPackagePolicy)} + onCancel={() => navigateAddAgentHelp(savedPackagePolicy)} /> - } - > - - - - ), - minimumSecretsVersion: {SECRETS_MINIMUM_FLEET_SERVER_VERSION}, - }} - /> - - - - - )} - - - - - - - {packageInfo && (formState === 'INVALID' || hasAgentPolicyError) ? ( - 0 && + savedPackagePolicy && ( + navigateAddAgent(savedPackagePolicy)} + onCancel={() => navigateAddAgentHelp(savedPackagePolicy)} /> - ) : null} - - - - - {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} - + )} + {formState === 'SUBMITTED_CLOUD_FORMATION' && + agentPolicies.length > 0 && + savedPackagePolicy && ( + navigateAddAgent(savedPackagePolicy)} + onCancel={() => navigateAddAgentHelp(savedPackagePolicy)} + /> + )} + {formState === 'SUBMITTED_GOOGLE_CLOUD_SHELL' && + agentPolicies.length > 0 && + savedPackagePolicy && ( + navigateAddAgent(savedPackagePolicy)} + onCancel={() => navigateAddAgentHelp(savedPackagePolicy)} + /> + )} + {packageInfo && ( + + )} + {packageInfo && isRootPrivilegesRequired(packageInfo) ? ( + <> + + + + ) : null} + {numTransformAssets > 0 ? ( + <> + + + + ) : null} + {showSecretsDisabledCallout && ( + <> + - - - {showDevtoolsRequest ? ( - - - - ) : null} + } + > + + + + ), + minimumSecretsVersion: ( + {SECRETS_MINIMUM_FLEET_SERVER_VERSION} + ), + }} + /> + + + + + )} + + + + + - onSubmit()} - isLoading={formState === 'LOADING'} - disabled={formState !== 'VALID' || hasAgentPolicyError || !validationResults} - iconType="save" - color="primary" - fill - data-test-subj="createPackagePolicySaveButton" - > + {packageInfo && (formState === 'INVALID' || hasAgentPolicyError) ? ( - + ) : null} + + + + + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + + + + + {showDevtoolsRequest ? ( + + + + ) : null} + + onSubmit()} + isLoading={formState === 'LOADING'} + disabled={ + formState !== 'VALID' || hasAgentPolicyError || !validationResults + } + iconType="save" + color="primary" + fill + data-test-subj="createPackagePolicySaveButton" + > + + + + - - - - + + + + ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx index fd9a27ed66f01..a121c797757d7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx @@ -249,7 +249,7 @@ export const PackagePoliciesTable: React.FunctionComponent = ({ render: (packagePolicy: InMemoryPackagePolicy) => { return canWriteIntegrationPolicies ? ( 'agent_features', 'is_protected', 'advanced_settings', + 'global_data_tags', ]); const FormWrapper = styled.div` - max-width: 800px; + max-width: 1200px; margin-right: auto; margin-left: auto; `; @@ -153,7 +154,7 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>( {agentCount ? ( { setAgentCount(0); submitUpdateAgentPolicy(); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/upgrade.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/upgrade.tsx index f81fb532f6bf7..15b2b3c6af8e9 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/upgrade.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/components/upgrade.tsx @@ -36,6 +36,7 @@ import type { import type { UpgradePackagePolicyDryRunResponse } from '../../../../../../../common/types/rest_spec'; import { useStartServices } from '../../../../hooks'; +import { MAX_FLYOUT_WIDTH } from '../../../../constants'; const FlyoutBody = styled(EuiFlyoutBody)` .euiFlyoutBody__overflowContent { @@ -167,7 +168,10 @@ export const UpgradeStatusCallout: React.FunctionComponent<{ <> {isPreviousVersionFlyoutOpen && currentPackagePolicy && ( - setIsPreviousVersionFlyoutOpen(false)} size="l" maxWidth={640}> + setIsPreviousVersionFlyoutOpen(false)} + maxWidth={MAX_FLYOUT_WIDTH} + >

diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy.test.tsx index 731a6b9791d7a..67b8f35a22eb4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy.test.tsx @@ -14,6 +14,7 @@ import { omit } from 'lodash'; +import { sendGetPackageInfoByKey, sendUpgradePackagePolicyDryRun } from '../../../../../../hooks'; import { createFleetTestRendererMock } from '../../../../../../mock'; import { usePackagePolicyWithRelatedData } from './use_package_policy'; @@ -83,6 +84,41 @@ jest.mock('../../../../../../hooks/use_request', () => ({ }, }; } + if (packagePolicyId === 'package-policy-2') { + return { + data: { + item: { + id: 'nginx-1', + name: 'nginx-1', + namespace: 'default', + description: 'Nginx description', + package: { name: 'nginx', title: 'Nginx', version: '1.3.0' }, + enabled: true, + policy_id: 'agent-policy-1', + policy_ids: ['agent-policy-1'], + inputs: [ + { + type: 'logfile', + policy_template: 'nginx', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'logs', dataset: 'nginx.access' }, + vars: { + paths: { value: ['/var/log/nginx/access.log*'], type: 'text' }, + }, + }, + ], + vars: { + existing_input_level_var: { value: 'existing-value', type: 'text' }, + }, + }, + ], + }, + }, + }; + } }, sendGetPackageInfoByKey: jest.fn().mockImplementation((name, version) => Promise.resolve({ @@ -308,4 +344,238 @@ describe('usePackagePolicy', () => { } `); }); + + it('should load the package policy if this is an upgrade with new input vars', async () => { + jest.mocked(sendUpgradePackagePolicyDryRun).mockResolvedValue({ + data: [ + { + diff: [ + { + id: 'nginx-1', + name: 'nginx-1', + namespace: 'default', + description: 'Nginx description', + package: { name: 'nginx', title: 'Nginx', version: '1.3.0' }, + enabled: true, + policy_id: 'agent-policy-1', + policy_ids: ['agent-policy-1'], + vars: {}, + inputs: [ + { + type: 'logfile', + policy_template: 'nginx', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'logs', dataset: 'nginx.access' }, + vars: { + paths: { value: ['/var/log/nginx/access.log*'], type: 'text' }, + existing_package_level_var: { + value: '/var/log/nginx/access.log*', + type: 'text', + }, + }, + }, + ], + vars: { + existing_package_level_var: { + value: '/var/log/nginx/access.log*', + type: 'text', + }, + }, + }, + ], + }, + { + id: 'nginx-1', + name: 'nginx-1', + namespace: 'default', + description: 'Nginx description', + package: { name: 'nginx', title: 'Nginx', version: '1.4.0' }, + enabled: true, + policy_id: 'agent-policy-1', + policy_ids: ['agent-policy-1'], + vars: { + new_package_level_var: { value: 'test', type: 'text' }, + }, + inputs: [ + { + type: 'logfile', + policy_template: 'nginx', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'logs', dataset: 'nginx.access' }, + vars: { + paths: { value: ['/var/log/nginx/access.log*'], type: 'text' }, + }, + }, + ], + vars: { + new_input_level_var: { value: 'test', type: 'text' }, + existing_input_level_var: { + value: 'default_value', + type: 'text', + }, + }, + }, + ], + }, + ], + }, + ], + } as any); + jest.mocked(sendGetPackageInfoByKey).mockResolvedValue({ + data: { + item: { + name: 'nginx', + title: 'Nginx', + version: '1.4.0', + release: 'ga', + description: 'Collect logs and metrics from Nginx HTTP servers with Elastic Agent.', + policy_templates: [ + { + name: 'nginx', + title: 'Nginx logs and metrics', + description: 'Collect logs and metrics from Nginx instances', + inputs: [ + { + type: 'logfile', + title: 'Collect logs from Nginx instances', + description: 'Collecting Nginx access and error logs', + vars: [ + { + name: 'new_input_level_var', + type: 'text', + title: 'Paths', + required: false, + show_user: true, + }, + { + name: 'existing_input_level_var', + type: 'text', + title: 'Paths', + required: false, + show_user: true, + }, + ], + }, + ], + multiple: true, + }, + ], + data_streams: [ + { + type: 'logs', + dataset: 'nginx.access', + title: 'Nginx access logs', + release: 'experimental', + ingest_pipeline: 'default', + streams: [ + { + input: 'logfile', + vars: [ + { + name: 'paths', + type: 'text', + title: 'Paths', + multi: true, + required: true, + show_user: true, + default: ['/var/log/nginx/access.log*'], + }, + ], + template_path: 'stream.yml.hbs', + title: 'Nginx access logs', + description: 'Collect Nginx access logs', + enabled: true, + }, + ], + package: 'nginx', + path: 'access', + }, + ], + latestVersion: '1.4.0', + keepPoliciesUpToDate: false, + status: 'not_installed', + vars: [ + { + name: 'new_package_level_var', + type: 'text', + title: 'Paths', + required: false, + show_user: true, + }, + ], + }, + }, + isLoading: false, + } as any); + const renderer = createFleetTestRendererMock(); + const { result, waitForNextUpdate } = renderer.renderHook(() => + usePackagePolicyWithRelatedData('package-policy-2', { + forceUpgrade: true, + }) + ); + await waitForNextUpdate(); + expect(result.current.packagePolicy).toMatchInlineSnapshot(` + Object { + "description": "Nginx description", + "enabled": true, + "inputs": Array [ + Object { + "enabled": true, + "policy_template": "nginx", + "streams": Array [ + Object { + "data_stream": Object { + "dataset": "nginx.access", + "type": "logs", + }, + "enabled": true, + "vars": Object { + "paths": Object { + "type": "text", + "value": Array [ + "/var/log/nginx/access.log*", + ], + }, + }, + }, + ], + "type": "logfile", + "vars": Object { + "existing_input_level_var": Object { + "type": "text", + "value": "existing-value", + }, + "new_input_level_var": Object { + "type": "text", + "value": "test", + }, + }, + }, + ], + "name": "nginx-1", + "namespace": "default", + "package": Object { + "name": "nginx", + "title": "Nginx", + "version": "1.4.0", + }, + "policy_id": "agent-policy-1", + "policy_ids": Array [ + "agent-policy-1", + ], + "vars": Object { + "new_package_level_var": Object { + "type": "text", + "value": "test", + }, + }, + } + `); + }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy.tsx index a44d218130d4f..470c6b95e61c0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_package_policy.tsx @@ -81,7 +81,7 @@ export function usePackagePolicyWithRelatedData( }); const [originalPackagePolicy, setOriginalPackagePolicy] = useState(); - const [agentPolicy, setAgentPolicy] = useState(); + const [agentPolicies, setAgentPolicies] = useState([]); const [isLoadingData, setIsLoadingData] = useState(true); const [dryRunData, setDryRunData] = useState(); const [loadingError, setLoadingError] = useState(); @@ -171,17 +171,21 @@ export function usePackagePolicyWithRelatedData( throw packagePolicyError; } - const { data: agentPolicyData, error: agentPolicyError } = await sendGetOneAgentPolicy( - packagePolicyData!.item.policy_ids[0] // TODO multiple - ); + const newAgentPolicies = []; + for (const policyId of packagePolicyData!.item.policy_ids) { + const { data: agentPolicyData, error: agentPolicyError } = await sendGetOneAgentPolicy( + policyId + ); - if (agentPolicyError) { - throw agentPolicyError; - } + if (agentPolicyError) { + throw agentPolicyError; + } - if (agentPolicyData?.item) { - setAgentPolicy(agentPolicyData.item); + if (agentPolicyData?.item) { + newAgentPolicies.push(agentPolicyData.item); + } } + setAgentPolicies(newAgentPolicies); const { data: upgradePackagePolicyDryRunData, error: upgradePackagePolicyDryRunError } = await sendUpgradePackagePolicyDryRun([packagePolicyId]); @@ -260,7 +264,7 @@ export function usePackagePolicyWithRelatedData( ...restOfInput } = input; - let basePolicyInputVars: any = + const basePolicyInputVars: any = isUpgradeScenario && basePolicy.inputs.find( (i) => i.type === input.type && i.policy_template === input.policy_template @@ -268,14 +272,7 @@ export function usePackagePolicyWithRelatedData( let newInputVars = inputVars; if (basePolicyInputVars && inputVars) { // merging vars from dry run with updated ones - basePolicyInputVars = Object.keys(inputVars).reduce( - (acc, curr) => ({ ...acc, [curr]: basePolicyInputVars[curr] }), - {} - ); - newInputVars = { - ...inputVars, - ...basePolicyInputVars, - }; + newInputVars = mergeVars(inputVars, basePolicyInputVars); } // Fix duration vars, if it's a migrated setting, and it's a plain old number with no suffix if (basePackage.name === 'apm') { @@ -353,7 +350,7 @@ export function usePackagePolicyWithRelatedData( isUpgrade, savePackagePolicy, isLoadingData, - agentPolicy, + agentPolicies, loadingError, packagePolicy, originalPackagePolicy, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index 475822e7789ff..0a0ed0b69ca79 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -6,7 +6,7 @@ */ import React, { useState, useEffect, useCallback, useMemo, memo } from 'react'; -import { omit } from 'lodash'; +import { isEmpty, omit } from 'lodash'; import { useRouteMatch } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; @@ -19,6 +19,7 @@ import { EuiErrorBoundary, } from '@elastic/eui'; +import { useSetIsReadOnly } from '../../../../integrations/hooks/use_read_only_context'; import { useLink, useBreadcrumbs, @@ -103,7 +104,7 @@ export const EditPackagePolicyForm = memo<{ const { // data - agentPolicy, + agentPolicies, isLoadingData, loadingError, packagePolicy, @@ -125,7 +126,7 @@ export const EditPackagePolicyForm = memo<{ }); const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies; - + useSetIsReadOnly(canWriteIntegrationPolicies); const newSecrets = useMemo(() => { if (!packageInfo) { return []; @@ -134,22 +135,26 @@ export const EditPackagePolicyForm = memo<{ return getNewSecrets({ packageInfo, packagePolicy }); }, [packageInfo, packagePolicy]); - const policyId = agentPolicy?.id ?? ''; + const policyIds = agentPolicies.map((policy) => policy.id); // Retrieve agent count const [agentCount, setAgentCount] = useState(0); useEffect(() => { const getAgentCount = async () => { - const { data } = await sendGetAgentStatus({ policyId }); - if (data?.results.total) { - setAgentCount(data.results.total); + let count = 0; + for (const policyId of policyIds) { + const { data } = await sendGetAgentStatus({ policyId }); + if (data?.results.total) { + count += data.results.total; + } } + setAgentCount(count); }; - if (isFleetEnabled && policyId) { + if (isFleetEnabled && policyIds.length > 0) { getAgentCount(); } - }, [policyId, isFleetEnabled]); + }, [policyIds, isFleetEnabled]); const handleExtensionViewOnChange = useCallback< PackagePolicyEditExtensionComponentProps['onChange'] @@ -170,25 +175,25 @@ export const EditPackagePolicyForm = memo<{ // if `from === 'edit'` then it links back to Policy Details // if `from === 'package-edit'`, or `upgrade-from-integrations-policy-list` then it links back to the Integration Policy List const cancelUrl = useMemo((): string => { - if (packageInfo && policyId) { + if (packageInfo && policyIds.length > 0) { return from === 'package-edit' ? getHref('integration_details_policies', { pkgkey: pkgKeyFromPackageInfo(packageInfo!), }) - : getHref('policy_details', { policyId }); + : getHref('policy_details', { policyId: policyIds[0] }); } return '/'; - }, [from, getHref, packageInfo, policyId]); + }, [from, getHref, packageInfo, policyIds]); const successRedirectPath = useMemo(() => { - if (packageInfo && policyId) { + if (packageInfo && policyIds.length > 0) { return from === 'package-edit' || from === 'upgrade-from-integrations-policy-list' ? getHref('integration_details_policies', { pkgkey: pkgKeyFromPackageInfo(packageInfo!), }) - : getHref('policy_details', { policyId }); + : getHref('policy_details', { policyId: policyIds[0] }); } return '/'; - }, [from, getHref, packageInfo, policyId]); + }, [from, getHref, packageInfo, policyIds]); useHistoryBlock(isEdited); @@ -197,7 +202,7 @@ export const EditPackagePolicyForm = memo<{ setFormState('INVALID'); return; } - if (agentCount !== 0 && policyId !== AGENTLESS_POLICY_ID && formState !== 'CONFIRM') { + if (agentCount !== 0 && !policyIds.includes(AGENTLESS_POLICY_ID) && formState !== 'CONFIRM') { setFormState('CONFIRM'); return; } @@ -215,11 +220,11 @@ export const EditPackagePolicyForm = memo<{ }), 'data-test-subj': 'policyUpdateSuccessToast', text: - agentCount && agentPolicy + agentCount && agentPolicies.length > 0 ? i18n.translate('xpack.fleet.editPackagePolicy.updatedNotificationMessage', { - defaultMessage: `Fleet will deploy updates to all agents that use the ''{agentPolicyName}'' policy`, + defaultMessage: `Fleet will deploy updates to all agents that use the ''{agentPolicyNames}'' policy`, values: { - agentPolicyName: agentPolicy.name, + agentPolicyNames: agentPolicies.map((policy) => policy.name).join(', '), }, }) : undefined, @@ -276,7 +281,7 @@ export const EditPackagePolicyForm = memo<{ const layoutProps = { from: extensionView?.useLatestPackageVersion && isUpgrade ? 'upgrade-from-extension' : from, cancelUrl, - agentPolicy, + agentPolicies, packageInfo, tabs: tabsViews?.length ? [ @@ -302,11 +307,11 @@ export const EditPackagePolicyForm = memo<{ const configurePackage = useMemo( () => - agentPolicy && packageInfo ? ( + agentPolicies && packageInfo ? ( <> {selectedTab === 0 && ( ) : null, [ - agentPolicy, + agentPolicies, packageInfo, packagePolicy, updatePackagePolicy, @@ -368,7 +373,7 @@ export const EditPackagePolicyForm = memo<{ const replaceConfigurePackage = replaceDefineStepView && originalPackagePolicy && packageInfo && ( {isLoadingData ? ( - ) : loadingError || !agentPolicy || !packageInfo ? ( + ) : loadingError || isEmpty(agentPolicies) || !packageInfo ? ( {formState === 'CONFIRM' && ( setFormState('VALID')} /> @@ -453,7 +458,7 @@ export const EditPackagePolicyForm = memo<{ - {agentPolicy && packageInfo && formState === 'INVALID' ? ( + {agentPolicies && packageInfo && formState === 'INVALID' ? ( = ({ ); return ( - onClose()} size="l" maxWidth={400} {...restOfProps}> + onClose()} {...restOfProps} maxWidth={MAX_FLYOUT_WIDTH}> {header} {body} {footer} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx index a2e0e6663e5cf..40f057b56551c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/index.tsx @@ -25,7 +25,7 @@ import { useHistory } from 'react-router-dom'; import type { AgentPolicy } from '../../../types'; import { getRootIntegrations } from '../../../../../../common/services'; -import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../constants'; +import { AGENT_POLICY_SAVED_OBJECT_TYPE, INGEST_SAVED_OBJECT_INDEX } from '../../../constants'; import { useAuthz, usePagination, @@ -320,6 +320,8 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => { { setPagination({ ...pagination, @@ -327,8 +329,6 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => { }); setSearch(newSearch); }} - indexPattern={`.${AGENT_POLICY_SAVED_OBJECT_TYPE}`} - fieldPrefix={AGENT_POLICY_SAVED_OBJECT_TYPE} dataTestSubj="agentPolicyList.queryInput" /> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details_json_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details_json_flyout.tsx index f1b5e628b5315..72dec15f970be 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details_json_flyout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details_json_flyout.tsx @@ -25,6 +25,7 @@ import { import type { Agent } from '../../../../types'; import { useStartServices } from '../../../../hooks'; +import { MAX_FLYOUT_WIDTH } from '../../../../constants'; export const AgentDetailsJsonFlyout = memo<{ agent: Agent; onClose: () => void }>( ({ agent, onClose }) => { @@ -44,7 +45,7 @@ export const AgentDetailsJsonFlyout = memo<{ agent: Agent; onClose: () => void } const { docLinks } = useStartServices(); return ( - +

diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_log_level.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_log_level.tsx index e565ddccc4b70..13c50b3770bcc 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_log_level.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/filter_log_level.tsx @@ -12,8 +12,6 @@ import { i18n } from '@kbn/i18n'; import { AGENT_LOG_LEVELS } from '../../../../../../../../common/constants'; -const LEVEL_VALUES = Object.values(AGENT_LOG_LEVELS); - export const LogLevelFilter: React.FunctionComponent<{ selectedLevels: string[]; onToggleLevel: (level: string) => void; @@ -24,7 +22,7 @@ export const LogLevelFilter: React.FunctionComponent<{ const closePopover = useCallback(() => setIsOpen(false), []); const [options, setOptions] = useState( - LEVEL_VALUES.map((level) => ({ + AGENT_LOG_LEVELS.map((level) => ({ label: level, checked: selectedLevels.includes(level) ? 'on' : undefined, key: level, @@ -39,7 +37,7 @@ export const LogLevelFilter: React.FunctionComponent<{ iconType="arrowDown" onClick={togglePopover} isSelected={isOpen} - numFilters={LEVEL_VALUES.length} + numFilters={options.length} hasActiveFilters={selectedLevels.length > 0} numActiveFilters={selectedLevels.length} > diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/select_log_level.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/select_log_level.tsx index c1f7afe710435..ad69e7ce3b22a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/select_log_level.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/select_log_level.tsx @@ -129,9 +129,9 @@ export const SelectLogLevel: React.FC<{ agent: Agent; agentPolicyLogLevel?: stri onChange={(event) => { setSelectedLogLevel(event.target.value); }} - options={Object.entries(AGENT_LOG_LEVELS).map(([key, value]) => ({ - value, - text: key, + options={AGENT_LOG_LEVELS.map((level) => ({ + value: level, + text: level, }))} /> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout/index.tsx index ed9a211db012e..e9bfbbef49d91 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout/index.tsx @@ -28,7 +28,7 @@ import { useStartServices, sendPostRetrieveAgentsByActions, } from '../../../../../hooks'; -import { SO_SEARCH_LIMIT } from '../../../../../constants'; +import { SO_SEARCH_LIMIT, MAX_FLYOUT_WIDTH } from '../../../../../constants'; import { getKuery } from '../../utils/get_kuery'; @@ -129,8 +129,8 @@ export const AgentActivityFlyout: React.FunctionComponent<{ refreshAgentActivity = false; onClose(); }} - size="m" paddingSize="none" + maxWidth={MAX_FLYOUT_WIDTH} > diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx index b0f6223193170..718684606f9d7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx @@ -163,14 +163,14 @@ export const SearchAndFilterBar: React.FunctionComponent { onDraftKueryChange(newSearch); if (submit) { onSubmitSearch(newSearch); } }} - fieldPrefix={AGENTS_PREFIX} - indexPattern={AGENTS_INDEX} dataTestSubj="agentList.queryInput" /> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx index b72794868a1c2..3a21103503ae5 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/index.tsx @@ -245,6 +245,8 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => { { setPagination({ ...pagination, @@ -252,8 +254,6 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => { }); setSearch(newSearch); }} - indexPattern={ENROLLMENT_API_KEYS_INDEX} - fieldPrefix={FLEET_ENROLLMENT_API_PREFIX} dataTestSubj="enrollmentKeysList.queryInput" /> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/debug/components/health_check_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/debug/components/health_check_panel.tsx index 5edd18716371c..b60734769542a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/debug/components/health_check_panel.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/debug/components/health_check_panel.tsx @@ -20,7 +20,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { useQuery } from '@tanstack/react-query'; import { sendPostHealthCheck, useGetFleetServerHosts } from '../../../hooks'; -import type { FleetServerHost } from '../../../types'; +import type { FleetServerHost, PostHealthCheckResponse } from '../../../types'; const POLLING_INTERVAL_S = 10; // 10 sec const POLLING_INTERVAL_MS = POLLING_INTERVAL_S * 1000; @@ -40,20 +40,21 @@ export const HealthCheckPanel: React.FunctionComponent = () => { } }, [fleetServerHosts]); - const hostName = useMemo( - () => selectedFleetServerHost?.host_urls[0] || '', - [selectedFleetServerHost?.host_urls] - ); + const id = useMemo(() => selectedFleetServerHost?.id || '', [selectedFleetServerHost?.id]); - const [healthData, setHealthData] = useState(); + const [healthData, setHealthData] = useState(); + const [error, setError] = useState(); const { data: healthCheckResponse } = useQuery( - ['fleetServerHealth', hostName], - () => sendPostHealthCheck({ host: hostName }), - { refetchInterval: POLLING_INTERVAL_MS } + ['fleetServerHealth', id], + () => sendPostHealthCheck({ id }), + { refetchInterval: POLLING_INTERVAL_MS, enabled: !!id } ); useEffect(() => { - setHealthData(healthCheckResponse); + setHealthData(healthCheckResponse?.data); + if (healthCheckResponse?.error) { + setError(healthCheckResponse.error); + } }, [healthCheckResponse]); const fleetServerHostsOptions = useMemo( @@ -133,7 +134,7 @@ export const HealthCheckPanel: React.FunctionComponent = () => { /> - {healthData?.data?.status && hostName === healthData?.data?.host ? ( + {healthData?.status && id === healthData?.host_id ? ( { defaultMessage="Status:" /> - {healthStatus(healthData?.data?.status)} + {healthStatus(healthData?.status)} ) : null} - {healthData?.error && ( + {error && ( <> - {healthData?.error?.message ?? ( + {error?.message ?? ( )} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/download_source_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/download_source_flyout/index.tsx index b6c2ef272d813..8249fd6fc82f3 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/download_source_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/download_source_flyout/index.tsx @@ -28,7 +28,7 @@ import { import { i18n } from '@kbn/i18n'; import type { DownloadSource, FleetProxy } from '../../../../types'; -import { FLYOUT_MAX_WIDTH } from '../../constants'; +import { MAX_FLYOUT_WIDTH } from '../../../../constants'; import { useBreadcrumbs, useStartServices } from '../../../../hooks'; import { ProxyWarning } from '../fleet_proxies_table/proxy_warning'; @@ -54,7 +54,7 @@ export const EditDownloadSourceFlyout: React.FunctionComponent +

diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_fleet_proxy_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_fleet_proxy_flyout/index.tsx index 864112d2c71ec..664519c441bd6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_fleet_proxy_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_fleet_proxy_flyout/index.tsx @@ -22,8 +22,8 @@ import { EuiSpacer, } from '@elastic/eui'; -import { FLYOUT_MAX_WIDTH } from '../../constants'; import type { FleetProxy } from '../../../../types'; +import { MAX_FLYOUT_WIDTH } from '../../../../constants'; import { TextInput, TextAreaInput } from '../form'; import { ProxyWarning } from '../fleet_proxies_table/proxy_warning'; @@ -43,7 +43,7 @@ export const FleetProxyFlyout: React.FunctionComponent = const { inputs } = form; return ( - +

diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx index 74e14248f553a..4133752733269 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx @@ -45,8 +45,9 @@ import { ExperimentalFeaturesService } from '../../../../../../services'; import { outputType, RESERVED_CONFIG_YML_KEYS } from '../../../../../../../common/constants'; +import { MAX_FLYOUT_WIDTH } from '../../../../constants'; + import type { Output, FleetProxy } from '../../../../types'; -import { FLYOUT_MAX_WIDTH } from '../../constants'; import { useBreadcrumbs, useFleetStatus, useStartServices } from '../../../../hooks'; @@ -230,7 +231,7 @@ export const EditOutputFlyout: React.FunctionComponent = }; return ( - +

@@ -437,6 +438,7 @@ export const EditOutputFlyout: React.FunctionComponent = } > inputs.presetInput.setValue(e.target.value)} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_elasticsearch.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_elasticsearch.tsx index 21e43fcba001a..bf79932416f5a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_elasticsearch.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_elasticsearch.tsx @@ -12,6 +12,8 @@ import { i18n } from '@kbn/i18n'; import { MultiRowInput } from '../multi_row_input'; +import { useStartServices } from '../../../../hooks'; + import type { OutputFormInputsType } from './use_output_form'; interface Props { @@ -20,6 +22,7 @@ interface Props { export const OutputFormElasticsearchSection: React.FunctionComponent = (props) => { const { inputs } = props; + const { cloud } = useStartServices(); return ( <> @@ -37,7 +40,7 @@ export const OutputFormElasticsearchSection: React.FunctionComponent = (p {...inputs.elasticsearchUrlInput.props} isUrl helpText={ - inputs.elasticsearchUrlInput.props.disabled && ( + cloud?.isServerlessEnabled && ( + <> @@ -173,7 +173,7 @@ export const FleetServerHostsFlyout: React.FunctionComponent { - const theme = useObservable(startServices.theme.theme$); - const isDarkMode = theme && theme.darkMode; + const XXL_BREAKPOINT = 1600; + const darkModeObservable = useObservable(startServices.theme.theme$); + const isDarkMode = darkModeObservable && darkModeObservable.darkMode; const CloudContext = startServices.cloud?.CloudContextProvider || EmptyContext; return ( - + + {/* This should be removed since theme is passed to `KibanaRenderContextProvider`, + however, removing this breaks usages of `props.theme.eui` in styled components */} @@ -96,16 +107,18 @@ export const IntegrationsAppContext: React.FC<{ - - - - - {children} - - - + + + + + + {children} + + + + diff --git a/x-pack/plugins/fleet/public/applications/integrations/components/header/header.tsx b/x-pack/plugins/fleet/public/applications/integrations/components/header/header.tsx index 6ca3dc473e10c..30fc0f0d4965f 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/components/header/header.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/components/header/header.tsx @@ -6,11 +6,20 @@ */ import React from 'react'; -import { EuiHeaderSectionItem, EuiHeaderSection, EuiHeaderLinks } from '@elastic/eui'; - +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiHeaderSectionItem, + EuiHeaderSection, + EuiHeaderLinks, + useEuiTheme, + EuiToolTip, + EuiButtonEmpty, +} from '@elastic/eui'; +import { css } from '@emotion/css'; import type { AppMountParameters } from '@kbn/core/public'; import type { FleetStartServices } from '../../../../plugin'; +import { useIsReadOnly } from '../../hooks/use_read_only_context'; import { HeaderPortal } from './header_portal'; import { DeploymentDetails } from './deployment_details'; @@ -22,6 +31,14 @@ export const IntegrationsHeader = ({ setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; startServices: Pick; }) => { + const { euiTheme } = useEuiTheme(); + const readOnlyBtnClass = React.useMemo(() => { + return css` + color: ${euiTheme.colors.text}; + `; + }, [euiTheme]); + const isReadOnly = useIsReadOnly(); + return ( @@ -30,6 +47,27 @@ export const IntegrationsHeader = ({ + {isReadOnly ? ( + + + + } + > + + + + + + + ) : null} ); diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_read_only_context.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_read_only_context.tsx new file mode 100644 index 0000000000000..a1cc117cea673 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_read_only_context.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { createContext, useContext, useEffect, useState } from 'react'; + +const ReadOnlyContext = createContext<{ isReadOnly: boolean; setIsReadOnly: (v: boolean) => void }>( + { isReadOnly: false, setIsReadOnly: () => {} } +); + +export const ReadOnlyContextProvider: React.FC = ({ children }) => { + const [isReadOnly, setIsReadOnly] = useState(false); + return ( + + {children} + + ); +}; + +export function useIsReadOnly() { + const context = useContext(ReadOnlyContext); + return context.isReadOnly; +} + +export function useSetIsReadOnly(isReadOnly: boolean) { + const context = useContext(ReadOnlyContext); + useEffect(() => { + context.setIsReadOnly(true); + return () => context.setIsReadOnly(false); + }, [context]); +} diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/search_box.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/search_box.tsx index 9206de2d48f17..15cff2645e9e3 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/search_box.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/search_box.tsx @@ -99,7 +99,7 @@ export const SearchBox: FunctionComponent = ({ onChange={(e) => onQueryChange(e)} isClearable={true} incremental={true} - fullWidth={true} + fullWidth prepend={ selectedCategoryTitle ? ( - + {fetchError && ( <> = ({ packageInfo }) => { return ( - + {isLoading && !configs ? ( ) : ( @@ -122,7 +117,7 @@ export const Configs: React.FC = ({ packageInfo }) => { )} - + ); }; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/custom/custom.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/custom/custom.tsx index b59804cea1733..824ceb0d569f7 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/custom/custom.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/custom/custom.tsx @@ -25,7 +25,7 @@ export const CustomViewPage: React.FC = memo(({ packageInfo }) => { return customViewExtension ? ( - + diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/documentation/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/documentation/index.tsx index c44982a261147..40a87bde535b4 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/documentation/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/documentation/index.tsx @@ -68,52 +68,44 @@ export const DocumentationPage: React.FunctionComponent = ({ packageInfo, const { docLinks } = useStartServices(); const showDocumentation = hasDocumentation({ packageInfo, integration }); - const content = ( - <> - - - - - - - ), - }} - /> - - - - - {showDocumentation ? ( - <> - - - - ) : ( - - } - /> - )} - - - ); - return ( - {content} + + + + + + ), + }} + /> + + + {showDocumentation ? ( + <> + + + + ) : ( + + } + /> + )} + + ); }; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx index be59e2d64f15c..6e1ce1cf8b2d4 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx @@ -21,6 +21,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedRelative, FormattedMessage } from '@kbn/i18n-react'; import { policyHasFleetServer } from '../../../../../../../../common/services'; +import { ExperimentalFeaturesService } from '../../../../../services'; import { InstallStatus } from '../../../../../types'; import type { GetAgentPoliciesResponseItem, InMemoryPackagePolicy } from '../../../../../types'; @@ -35,6 +36,7 @@ import { import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants'; import { AgentEnrollmentFlyout, + MultipleAgentPoliciesSummaryLine, AgentPolicySummaryLine, PackagePolicyActionsMenu, } from '../../../../../components'; @@ -50,7 +52,7 @@ interface PackagePoliciesPanelProps { interface InMemoryPackagePolicyAndAgentPolicy { packagePolicy: InMemoryPackagePolicy; - agentPolicy?: GetAgentPoliciesResponseItem; + agentPolicies: GetAgentPoliciesResponseItem[]; } const IntegrationDetailsLink = memo<{ @@ -101,6 +103,7 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps const getPackageInstallStatus = useGetPackageInstallStatus(); const packageInstallStatus = getPackageInstallStatus(name); const { pagination, pageSizeOptions, setPagination } = useUrlPagination(); + const { enableReusableIntegrationPolicies } = ExperimentalFeaturesService.get(); const { data, @@ -114,22 +117,24 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps const { isPackagePolicyUpgradable } = useIsPackagePolicyUpgradable(); const canWriteIntegrationPolicies = useAuthz().integrations.writeIntegrationPolicies; + const canReadIntegrationPolicies = useAuthz().integrations.readIntegrationPolicies; const canAddAgents = useAuthz().fleet.addAgents; const canAddFleetServers = useAuthz().fleet.addFleetServers; + const canReadAgentPolicies = useAuthz().fleet.readAgentPolicies; const packageAndAgentPolicies = useMemo((): Array<{ - agentPolicy?: GetAgentPoliciesResponseItem; + agentPolicies: GetAgentPoliciesResponseItem[]; packagePolicy: InMemoryPackagePolicy; }> => { if (!data?.items) { return []; } - const newPolicies = data.items.map(({ agentPolicy, packagePolicy }) => { + const newPolicies = data.items.map(({ agentPolicies, packagePolicy }) => { const hasUpgrade = isPackagePolicyUpgradable(packagePolicy); return { - agentPolicy, + agentPolicies, packagePolicy: { ...packagePolicy, hasUpgrade, @@ -140,8 +145,8 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps return newPolicies; }, [data?.items, isPackagePolicyUpgradable]); - const showAddAgentHelpForPackagePolicyId = packageAndAgentPolicies.find( - ({ agentPolicy }) => agentPolicy?.id === showAddAgentHelpForPolicyId + const showAddAgentHelpForPackagePolicyId = packageAndAgentPolicies.find(({ agentPolicies }) => + agentPolicies.find((agentPolicy) => agentPolicy.id === showAddAgentHelpForPolicyId) )?.packagePolicy?.id; // Handle the "add agent" link displayed in post-installation toast notifications in the case // where a user is clicking the link while on the package policies listing page @@ -167,7 +172,8 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps }, [setPagination] ); - + const canShowMultiplePoliciesCell = + enableReusableIntegrationPolicies && canReadIntegrationPolicies && canReadAgentPolicies; const columns: Array> = useMemo( () => [ { @@ -184,7 +190,7 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.version', { defaultMessage: 'Version', }), - render(_version, { agentPolicy, packagePolicy }) { + render(_version, { agentPolicies, packagePolicy }) { return ( @@ -197,13 +203,13 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps - {agentPolicy && packagePolicy.hasUpgrade && ( + {agentPolicies.length > 0 && packagePolicy.hasUpgrade && ( + render(id, { agentPolicies }) { + return agentPolicies.length > 0 ? ( + canShowMultiplePoliciesCell && agentPolicies.length > 1 ? ( + + ) : ( + + ) ) : ( ); @@ -263,10 +273,11 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps name: i18n.translate('xpack.fleet.epm.packageDetails.integrationList.agentCount', { defaultMessage: 'Agents', }), - render({ agentPolicy, packagePolicy }: InMemoryPackagePolicyAndAgentPolicy) { - if (!agentPolicy) { + render({ agentPolicies, packagePolicy }: InMemoryPackagePolicyAndAgentPolicy) { + if (agentPolicies.length === 0) { return null; } + const agentPolicy = agentPolicies[0]; // TODO: handle multiple agent policies const canAddAgentsForPolicy = policyHasFleetServer(agentPolicy) ? canAddFleetServers : canAddAgents; @@ -288,10 +299,11 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps }), width: '8ch', align: 'right', - render({ agentPolicy, packagePolicy }) { + render({ agentPolicies, packagePolicy }) { + const agentPolicy = agentPolicies[0]; // TODO: handle multiple agent policies return ( ); } - const selectedPolicies = packageAndAgentPolicies.find( - ({ agentPolicy: policy }) => policy?.id === flyoutOpenForPolicyId + const selectedPolicies = packageAndAgentPolicies.find(({ agentPolicies: policies }) => + policies.find((policy) => policy.id === flyoutOpenForPolicyId) ); - const agentPolicy = selectedPolicies?.agentPolicy; + const agentPolicies = selectedPolicies?.agentPolicies; const packagePolicy = selectedPolicies?.packagePolicy; return ( - + - {flyoutOpenForPolicyId && agentPolicy && !isLoading && ( + {flyoutOpenForPolicyId && agentPolicies && !isLoading && ( { setFlyoutOpenForPolicyId(null); const { addAgentToPolicyId, ...rest } = parse(search); history.replace({ search: stringify(rest) }); }} - agentPolicy={agentPolicy} + agentPolicy={agentPolicies[0]} isIntegrationFlow={true} installedPackagePolicy={{ name: packagePolicy?.package?.name || '', diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts index 560a2900ae406..f975933d53d99 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/use_package_policies_with_agent_policy.ts @@ -19,13 +19,9 @@ import { agentPolicyRouteService } from '../../../../../services'; import { useGetPackagePolicies, useConditionalRequest } from '../../../../../hooks'; import type { SendConditionalRequestConfig } from '../../../../../hooks'; -export interface PackagePolicyEnriched extends PackagePolicy { - _agentPolicy: GetAgentPoliciesResponseItem | undefined; -} - export interface PackagePolicyAndAgentPolicy { packagePolicy: PackagePolicy; - agentPolicy: GetAgentPoliciesResponseItem; + agentPolicies: GetAgentPoliciesResponseItem[]; } type GetPackagePoliciesWithAgentPolicy = Omit & { @@ -34,7 +30,7 @@ type GetPackagePoliciesWithAgentPolicy = Omit { return { packagePolicy, - agentPolicy: agentPoliciesById[packagePolicy.policy_ids[0]], // TODO multiple agent policies + agentPolicies: packagePolicy.policy_ids + .map((policyId: string) => agentPoliciesById[policyId]) + .filter((policy) => !!policy), }; } ); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx index c9513d759ed36..9255048674aef 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx @@ -256,7 +256,7 @@ export const SettingsPage: React.FC = memo(({ packageInfo, startServices <> - +

diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/update_button.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/update_button.tsx index d1f82c7a8b852..a6831cc0d2cb5 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/update_button.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/update_button.tsx @@ -335,7 +335,6 @@ export const UpdateButton: React.FunctionComponent = ({ {packagePolicyCount > 0 && ( = (props) => { diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx index 9902bf4f53497..57b3a8e0e0ff5 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/index.tsx @@ -29,7 +29,7 @@ import { useAgentEnrollmentFlyoutData, useFleetServerHostsForPolicy, } from '../../hooks'; -import { FLEET_SERVER_PACKAGE } from '../../constants'; +import { FLEET_SERVER_PACKAGE, MAX_FLYOUT_WIDTH } from '../../constants'; import type { PackagePolicy, AgentPolicy } from '../../types'; import { Loading } from '..'; @@ -106,7 +106,7 @@ export const AgentEnrollmentFlyout: React.FunctionComponent = ({ const { cloudSecurityIntegration } = useCloudSecurityIntegration(selectedPolicy ?? undefined); return ( - +

diff --git a/x-pack/plugins/fleet/public/components/link_and_revision.test.tsx b/x-pack/plugins/fleet/public/components/agent_policy_summary_line.test.tsx similarity index 95% rename from x-pack/plugins/fleet/public/components/link_and_revision.test.tsx rename to x-pack/plugins/fleet/public/components/agent_policy_summary_line.test.tsx index 46fdfe69c63bd..e398851e8892e 100644 --- a/x-pack/plugins/fleet/public/components/link_and_revision.test.tsx +++ b/x-pack/plugins/fleet/public/components/agent_policy_summary_line.test.tsx @@ -12,7 +12,7 @@ import { createFleetTestRendererMock } from '../mock'; import type { AgentPolicy, Agent } from '../types'; -import { AgentPolicySummaryLine } from './link_and_revision'; +import { AgentPolicySummaryLine } from './agent_policy_summary_line'; describe('AgentPolicySummaryLine', () => { let testRenderer: TestRenderer; diff --git a/x-pack/plugins/fleet/public/components/link_and_revision.tsx b/x-pack/plugins/fleet/public/components/agent_policy_summary_line.tsx similarity index 99% rename from x-pack/plugins/fleet/public/components/link_and_revision.tsx rename to x-pack/plugins/fleet/public/components/agent_policy_summary_line.tsx index 5d0f0070f6668..b055b51728a43 100644 --- a/x-pack/plugins/fleet/public/components/link_and_revision.tsx +++ b/x-pack/plugins/fleet/public/components/agent_policy_summary_line.tsx @@ -26,7 +26,6 @@ export const AgentPolicySummaryLine = memo<{ const { name, id, is_managed: isManaged, description } = policy; const revision = agent ? agent.policy_revision : policy.revision; - return ( diff --git a/x-pack/plugins/fleet/public/components/header.tsx b/x-pack/plugins/fleet/public/components/header.tsx index 5443477c976b7..d32a96ee26cdd 100644 --- a/x-pack/plugins/fleet/public/components/header.tsx +++ b/x-pack/plugins/fleet/public/components/header.tsx @@ -22,8 +22,12 @@ const Container = styled.div` } `; -const Wrapper = styled.div<{ maxWidth?: number }>` - max-width: ${(props) => props.maxWidth || 1200}px; +const Wrapper = styled.div<{ maxWidth?: number | string }>` + max-width: ${(props) => + typeof props.maxWidth === 'number' + ? `${props.maxWidth || 1200}px` || props.maxWidth + : props.maxWidth}; + margin-left: auto; margin-right: auto; padding-top: ${(props) => props.theme.eui.euiSizeXL}; @@ -39,7 +43,7 @@ const Tabs = styled(EuiTabs)` `; export interface HeaderProps { - maxWidth?: number; + maxWidth?: number | string; leftColumn?: JSX.Element; rightColumn?: JSX.Element; rightColumnGrow?: EuiFlexItemProps['grow']; diff --git a/x-pack/plugins/fleet/public/components/index.ts b/x-pack/plugins/fleet/public/components/index.ts index 06805679892f2..5a995ab1ecbce 100644 --- a/x-pack/plugins/fleet/public/components/index.ts +++ b/x-pack/plugins/fleet/public/components/index.ts @@ -16,12 +16,11 @@ export { Header } from './header'; export { NewEnrollmentTokenModal } from './new_enrollment_key_modal'; export { AgentPolicyPackageBadge } from './agent_policy_package_badge'; export { AgentPolicyPackageBadges } from './agent_policy_package_badges'; -export { DangerEuiContextMenuItem } from './danger_eui_context_menu_item'; export { PackagePolicyDeleteProvider } from './package_policy_delete_provider'; export { PackagePolicyActionsMenu } from './package_policy_actions_menu'; export { AddAgentHelpPopover } from './add_agent_help_popover'; export { EuiButtonWithTooltip } from './eui_button_with_tooltip'; -export * from './link_and_revision'; +export * from './agent_policy_summary_line'; export * from './agent_enrollment_flyout'; export * from './platform_selector'; export { ConfirmForceInstallModal } from './confirm_force_install_modal'; @@ -29,3 +28,4 @@ export { DevtoolsRequestFlyoutButton } from './devtools_request_flyout'; export { HeaderReleaseBadge, InlineReleaseBadge } from './release_badge'; export { WithGuidedOnboardingTour } from './with_guided_onboarding_tour'; export { UninstallCommandFlyout } from './uninstall_command_flyout'; +export { MultipleAgentPoliciesSummaryLine } from './multiple_agent_policy_summary_line'; diff --git a/x-pack/plugins/fleet/public/components/multiple_agent_policy_summary_line.test.tsx b/x-pack/plugins/fleet/public/components/multiple_agent_policy_summary_line.test.tsx new file mode 100644 index 0000000000000..0d88dcc4b44b7 --- /dev/null +++ b/x-pack/plugins/fleet/public/components/multiple_agent_policy_summary_line.test.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { act, fireEvent } from '@testing-library/react'; +import React from 'react'; + +import type { TestRenderer } from '../mock'; +import { createFleetTestRendererMock } from '../mock'; + +import type { AgentPolicy } from '../types'; + +import { MultipleAgentPoliciesSummaryLine } from './multiple_agent_policy_summary_line'; + +describe('MultipleAgentPolicySummaryLine', () => { + let testRenderer: TestRenderer; + + const render = (agentPolicies: AgentPolicy[]) => + testRenderer.render(); + + beforeEach(() => { + testRenderer = createFleetTestRendererMock(); + }); + + test('it should render only the policy name when there is only one policy', async () => { + const results = render([{ name: 'Test policy', revision: 2 }] as AgentPolicy[]); + expect(results.container.textContent).toBe('Test policy'); + expect(results.queryByTestId('agentPolicyNameBadge')).toBeInTheDocument(); + expect(results.queryByTestId('agentPoliciesNumberBadge')).not.toBeInTheDocument(); + }); + + test('it should render the first policy name and the badge when there are multiple policies', async () => { + const results = render([ + { name: 'Test policy 1', id: '0001' }, + { name: 'Test policy 2', id: '0002' }, + { name: 'Test policy 3', id: '0003' }, + ] as AgentPolicy[]); + expect(results.queryByTestId('agentPolicyNameBadge')).toBeInTheDocument(); + expect(results.queryByTestId('agentPoliciesNumberBadge')).toBeInTheDocument(); + expect(results.container.textContent).toBe('Test policy 1+2'); + + await act(async () => { + fireEvent.click(results.getByTestId('agentPoliciesNumberBadge')); + }); + expect(results.queryByTestId('agentPoliciesPopover')).toBeInTheDocument(); + expect(results.queryByTestId('agentPoliciesPopoverButton')).toBeInTheDocument(); + expect(results.queryByTestId('policy-0001')).toBeInTheDocument(); + expect(results.queryByTestId('policy-0002')).toBeInTheDocument(); + expect(results.queryByTestId('policy-0003')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/fleet/public/components/multiple_agent_policy_summary_line.tsx b/x-pack/plugins/fleet/public/components/multiple_agent_policy_summary_line.tsx new file mode 100644 index 0000000000000..2a869f12bd817 --- /dev/null +++ b/x-pack/plugins/fleet/public/components/multiple_agent_policy_summary_line.tsx @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiFlexGroup, + EuiFlexItem, + EuiBadge, + EuiPopover, + EuiPopoverTitle, + EuiPopoverFooter, + EuiButton, + EuiListGroup, + type EuiListGroupItemProps, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import type { CSSProperties } from 'react'; +import { useMemo } from 'react'; +import React, { memo, useState } from 'react'; + +import type { AgentPolicy } from '../../common/types'; +import { useLink } from '../hooks'; +const MIN_WIDTH: CSSProperties = { minWidth: 0 }; + +export const MultipleAgentPoliciesSummaryLine = memo<{ + policies: AgentPolicy[]; + direction?: 'column' | 'row'; +}>(({ policies, direction = 'row' }) => { + const { getHref } = useLink(); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const closePopover = () => setIsPopoverOpen(false); + + // as default, show only the first policy + const policy = policies[0]; + const { name, id } = policy; + + const listItems: EuiListGroupItemProps[] = useMemo(() => { + return policies.map((p) => { + return { + 'data-test-subj': `policy-${p.id}`, + label: p.name || p.id, + href: getHref('policy_details', { policyId: p.id }), + iconType: 'dot', + extraAction: { + color: 'text', + iconType: p.is_managed ? 'lock' : '', + alwaysShow: !!p.is_managed, + iconSize: 's', + 'aria-label': 'Hosted agent policy', + }, + showToolTip: !!p.is_managed, + toolTipText: i18n.translate('xpack.fleet.agentPolicySummaryLine.hostedPolicyTooltip', { + defaultMessage: + 'This policy is managed outside of Fleet. Most actions related to this policy are unavailable.', + }), + }; + }); + }, [getHref, policies]); + + return ( + + + + + + + + {name || id} + + + {policies.length > 1 && ( + + setIsPopoverOpen(!isPopoverOpen)} + onClickAriaLabel="Open agent policies popover" + > + {`+${policies.length - 1}`} + + + + {i18n.translate('xpack.fleet.agentPolicySummaryLine.popover.title', { + defaultMessage: 'This integration is shared by', + })} + +
+ +
+ + {/* TODO: implement missing onClick function */} + + {i18n.translate('xpack.fleet.agentPolicySummaryLine.popover.button', { + defaultMessage: 'Manage agent policies', + })} + + +
+
+ )} +
+
+
+
+
+ ); +}); diff --git a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.test.tsx b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.test.tsx index ac97e0f588478..96849c077f881 100644 --- a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.test.tsx +++ b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.test.tsx @@ -12,15 +12,17 @@ import { act } from '@testing-library/react'; import type { AgentPolicy, InMemoryPackagePolicy } from '../types'; import { createIntegrationsTestRendererMock } from '../mock'; +import { ExperimentalFeaturesService } from '../services'; + import { PackagePolicyActionsMenu } from './package_policy_actions_menu'; function renderMenu({ - agentPolicy, + agentPolicies, packagePolicy, showAddAgent = false, defaultIsOpen = true, }: { - agentPolicy: AgentPolicy; + agentPolicies: AgentPolicy[]; packagePolicy: InMemoryPackagePolicy; showAddAgent?: boolean; defaultIsOpen?: boolean; @@ -29,7 +31,7 @@ function renderMenu({ const utils = renderer.render( = {}): AgentPolicy { - return { - id: 'some-uuid1', - namespace: 'default', - monitoring_enabled: [], - name: 'Test Policy', - description: '', - is_preconfigured: false, - status: 'active', - is_managed: false, - revision: 1, - updated_at: '', - updated_by: 'elastic', - package_policies: [], - is_protected: false, - ...props, - }; +function createMockAgentPolicies(props: Partial = {}): AgentPolicy[] { + return [ + { + id: 'some-uuid1', + namespace: 'default', + monitoring_enabled: [], + name: 'Test Policy', + description: '', + is_preconfigured: false, + status: 'active', + is_managed: false, + revision: 1, + updated_at: '', + updated_by: 'elastic', + package_policies: [], + is_protected: false, + ...props, + }, + ]; } function createMockPackagePolicy( @@ -81,60 +85,67 @@ function createMockPackagePolicy( ...props, }; } +describe('PackagePolicyActionsMenu', () => { + beforeAll(() => { + jest.spyOn(ExperimentalFeaturesService, 'get').mockReturnValue({ + enableReusableIntegrationPolicies: false, + } as any); + }); -test('Should disable upgrade button if package does not have upgrade', async () => { - const agentPolicy = createMockAgentPolicy(); - const packagePolicy = createMockPackagePolicy({ hasUpgrade: false }); - const { utils } = renderMenu({ agentPolicy, packagePolicy }); - await act(async () => { - const upgradeButton = utils.getByText('Upgrade integration policy').closest('button'); - expect(upgradeButton).toBeDisabled(); + it('Should disable upgrade button if package does not have upgrade', async () => { + const agentPolicies = createMockAgentPolicies(); + const packagePolicy = createMockPackagePolicy({ hasUpgrade: false }); + const { utils } = renderMenu({ agentPolicies, packagePolicy }); + await act(async () => { + const upgradeButton = utils.getByText('Upgrade integration policy').closest('button'); + expect(upgradeButton).toBeDisabled(); + }); }); -}); -test('Should enable upgrade button if package has upgrade', async () => { - const agentPolicy = createMockAgentPolicy(); - const packagePolicy = createMockPackagePolicy({ hasUpgrade: true }); - const { utils } = renderMenu({ agentPolicy, packagePolicy }); + it('Should enable upgrade button if package has upgrade', async () => { + const agentPolicies = createMockAgentPolicies(); + const packagePolicy = createMockPackagePolicy({ hasUpgrade: true }); + const { utils } = renderMenu({ agentPolicies, packagePolicy }); - await act(async () => { - const upgradeButton = utils.getByTestId('PackagePolicyActionsUpgradeItem'); - expect(upgradeButton).not.toBeDisabled(); + await act(async () => { + const upgradeButton = utils.getByTestId('PackagePolicyActionsUpgradeItem'); + expect(upgradeButton).not.toBeDisabled(); + }); }); -}); -test('Should not be able to delete integration from a managed policy', async () => { - const agentPolicy = createMockAgentPolicy({ is_managed: true }); - const packagePolicy = createMockPackagePolicy(); - const { utils } = renderMenu({ agentPolicy, packagePolicy }); - await act(async () => { - expect(utils.queryByText('Delete integration')).toBeNull(); + it('Should not be able to delete integration from a managed policy', async () => { + const agentPolicies = createMockAgentPolicies({ is_managed: true }); + const packagePolicy = createMockPackagePolicy(); + const { utils } = renderMenu({ agentPolicies, packagePolicy }); + await act(async () => { + expect(utils.queryByText('Delete integration')).toBeNull(); + }); }); -}); -test('Should be able to delete integration from a non-managed policy', async () => { - const agentPolicy = createMockAgentPolicy({ is_managed: false }); - const packagePolicy = createMockPackagePolicy(); - const { utils } = renderMenu({ agentPolicy, packagePolicy }); - await act(async () => { - expect(utils.queryByText('Delete integration')).not.toBeNull(); + it('Should be able to delete integration from a non-managed policy', async () => { + const agentPolicies = createMockAgentPolicies({ is_managed: false }); + const packagePolicy = createMockPackagePolicy(); + const { utils } = renderMenu({ agentPolicies, packagePolicy }); + await act(async () => { + expect(utils.queryByText('Delete integration')).not.toBeNull(); + }); }); -}); -test('Should show add button if the policy is not managed and showAddAgent=true', async () => { - const agentPolicy = createMockAgentPolicy(); - const packagePolicy = createMockPackagePolicy({ hasUpgrade: true }); - const { utils } = renderMenu({ agentPolicy, packagePolicy, showAddAgent: true }); - await act(async () => { - expect(utils.queryByText('Add agent')).not.toBeNull(); + it('Should show add button if the policy is not managed and showAddAgent=true', async () => { + const agentPolicies = createMockAgentPolicies(); + const packagePolicy = createMockPackagePolicy({ hasUpgrade: true }); + const { utils } = renderMenu({ agentPolicies, packagePolicy, showAddAgent: true }); + await act(async () => { + expect(utils.queryByText('Add agent')).not.toBeNull(); + }); }); -}); -test('Should not show add button if the policy is managed and showAddAgent=true', async () => { - const agentPolicy = createMockAgentPolicy({ is_managed: true }); - const packagePolicy = createMockPackagePolicy({ hasUpgrade: true }); - const { utils } = renderMenu({ agentPolicy, packagePolicy, showAddAgent: true }); - await act(async () => { - expect(utils.queryByText('Add agent')).toBeNull(); + it('Should not show add button if the policy is managed and showAddAgent=true', async () => { + const agentPolicies = createMockAgentPolicies({ is_managed: true }); + const packagePolicy = createMockPackagePolicy({ hasUpgrade: true }); + const { utils } = renderMenu({ agentPolicies, packagePolicy, showAddAgent: true }); + await act(async () => { + expect(utils.queryByText('Add agent')).toBeNull(); + }); }); }); diff --git a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx index 23ae673ad892a..a8839e23a99f9 100644 --- a/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx +++ b/x-pack/plugins/fleet/public/components/package_policy_actions_menu.tsx @@ -19,14 +19,14 @@ import { DangerEuiContextMenuItem } from './danger_eui_context_menu_item'; import { PackagePolicyDeleteProvider } from './package_policy_delete_provider'; export const PackagePolicyActionsMenu: React.FunctionComponent<{ - agentPolicy?: AgentPolicy; + agentPolicies: AgentPolicy[]; packagePolicy: InMemoryPackagePolicy; showAddAgent?: boolean; defaultIsOpen?: boolean; upgradePackagePolicyHref?: string; from?: 'fleet-policy-list' | undefined; }> = ({ - agentPolicy, + agentPolicies, packagePolicy, showAddAgent, upgradePackagePolicyHref, @@ -37,6 +37,7 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{ const { getHref } = useLink(); const authz = useAuthz(); + const agentPolicy = agentPolicies.length > 0 ? agentPolicies[0] : undefined; // TODO: handle multiple agent policies const canWriteIntegrationPolicies = authz.integrations.writeIntegrationPolicies; const isFleetServerPolicy = agentPolicy && policyHasFleetServer(agentPolicy); @@ -126,7 +127,7 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{ ? DangerEuiContextMenuItem : EuiContextMenuItem; menuItems.push( - + {(deletePackagePoliciesPrompt) => { return ( React.ReactElement; } @@ -27,7 +29,7 @@ export type DeletePackagePoliciesPrompt = ( type OnSuccessCallback = (packagePoliciesDeleted: string[]) => void; export const PackagePolicyDeleteProvider: React.FunctionComponent = ({ - agentPolicy, + agentPolicies, children, }) => { const { notifications } = useStartServices(); @@ -40,27 +42,30 @@ export const PackagePolicyDeleteProvider: React.FunctionComponent = ({ const [agentsCount, setAgentsCount] = useState(0); const [isLoading, setIsLoading] = useState(false); const onSuccessCallback = useRef(null); + const { enableReusableIntegrationPolicies } = ExperimentalFeaturesService.get(); + + const hasMultipleAgentPolicies = + enableReusableIntegrationPolicies && agentPolicies && agentPolicies.length > 1; const fetchAgentsCount = useMemo( () => async () => { - if (isLoadingAgentsCount || !isFleetEnabled || !agentPolicy) { + if (isLoadingAgentsCount || !isFleetEnabled || !agentPolicies) { return; } setIsLoadingAgentsCount(true); - const { data } = await sendRequest<{ total: number }>({ - path: AGENT_API_ROUTES.LIST_PATTERN, - method: 'get', - query: { - page: 1, - perPage: 1, - kuery: `${AGENTS_PREFIX}.policy_id : ${agentPolicy.id}`, - }, - version: API_VERSIONS.public.v1, + + const kuery = `(${agentPolicies + .map((policy) => `${AGENTS_PREFIX}.policy_id:"${policy.id}"`) + .join(' or ')})`; + + const request = await sendGetAgents({ + kuery, + showInactive: false, }); - setAgentsCount(data?.total || 0); + setAgentsCount(request.data?.total || 0); setIsLoadingAgentsCount(false); }, - [agentPolicy, isFleetEnabled, isLoadingAgentsCount] + [agentPolicies, isFleetEnabled, isLoadingAgentsCount] ); const deletePackagePoliciesPrompt = useMemo( @@ -87,6 +92,11 @@ export const PackagePolicyDeleteProvider: React.FunctionComponent = ({ [] ); + const agentPoliciesNamesList = useMemo( + () => agentPolicies?.map((p) => p.name).join(', '), + [agentPolicies] + ); + const deletePackagePolicies = useMemo( () => async () => { setIsLoading(true); @@ -185,8 +195,23 @@ export const PackagePolicyDeleteProvider: React.FunctionComponent = ({ id="xpack.fleet.deletePackagePolicy.confirmModal.loadingAgentsCountMessage" defaultMessage="Checking affected agents…" /> - ) : agentsCount ? ( + ) : agentsCount && agentPolicies ? ( <> + {hasMultipleAgentPolicies && ( + <> + + } + /> + + + )} = ({ /> } > - {agentPolicy?.name}, - }} - /> + {hasMultipleAgentPolicies ? ( + + } + position="top" + /> + ), + }} + /> + ) : ( + {agentPolicies[0]?.name}, + }} + /> + )} diff --git a/x-pack/plugins/fleet/public/components/uninstall_command_flyout/uninstall_command_flyout.tsx b/x-pack/plugins/fleet/public/components/uninstall_command_flyout/uninstall_command_flyout.tsx index 447a6c2dc7877..7b21cabffc907 100644 --- a/x-pack/plugins/fleet/public/components/uninstall_command_flyout/uninstall_command_flyout.tsx +++ b/x-pack/plugins/fleet/public/components/uninstall_command_flyout/uninstall_command_flyout.tsx @@ -31,6 +31,7 @@ import { useGetUninstallToken, useGetUninstallTokens, } from '../../hooks/use_request/uninstall_tokens'; +import { MAX_FLYOUT_WIDTH } from '../../constants'; import { UninstallCommandsPerPlatform } from './uninstall_commands_per_platform'; import type { UninstallCommandTarget } from './types'; @@ -185,7 +186,11 @@ export const UninstallCommandFlyout: React.FunctionComponent { return ( - +

diff --git a/x-pack/plugins/fleet/public/constants/index.ts b/x-pack/plugins/fleet/public/constants/index.ts index 0af765a9912a6..1c0a04b9cb8a7 100644 --- a/x-pack/plugins/fleet/public/constants/index.ts +++ b/x-pack/plugins/fleet/public/constants/index.ts @@ -28,6 +28,10 @@ export { AUTO_UPGRADE_POLICIES_PACKAGES, LOCATORS_IDS, FLEET_ENROLLMENT_API_PREFIX, + INGEST_SAVED_OBJECT_INDEX, + AGENT_POLICY_MAPPINGS, + AGENT_MAPPINGS, + ENROLLMENT_API_KEY_MAPPINGS, } from '../../common/constants'; export * from './page_paths'; @@ -60,3 +64,5 @@ export type TourKey = keyof typeof TOUR_STORAGE_KEYS; export type TOUR_STORAGE_CONFIG = { [k in TourKey]: TourConfig; }; + +export const MAX_FLYOUT_WIDTH = 800; diff --git a/x-pack/plugins/fleet/public/hooks/use_request/agent_policy.ts b/x-pack/plugins/fleet/public/hooks/use_request/agent_policy.ts index cbccc54273e29..837222dae103f 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/agent_policy.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/agent_policy.ts @@ -65,6 +65,15 @@ export const useBulkGetAgentPoliciesQuery = (ids: string[], options?: { full?: b ); }; +export const sendBulkGetAgentPolicies = (ids: string[], options?: { full?: boolean }) => { + return sendRequest({ + path: agentPolicyRouteService.getBulkGetPath(), + method: 'post', + body: JSON.stringify({ ids, full: options?.full }), + version: API_VERSIONS.public.v1, + }); +}; + export const sendGetAgentPolicies = (query?: GetAgentPoliciesRequest['query']) => { return sendRequest({ path: agentPolicyRouteService.getListPath(), diff --git a/x-pack/plugins/fleet/public/layouts/with_header.tsx b/x-pack/plugins/fleet/public/layouts/with_header.tsx index 71f9b598c6428..db500a4633e1e 100644 --- a/x-pack/plugins/fleet/public/layouts/with_header.tsx +++ b/x-pack/plugins/fleet/public/layouts/with_header.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { EuiPageBody, EuiSpacer } from '@elastic/eui'; +import { EuiPageBody, EuiSpacer, useIsWithinMinBreakpoint } from '@elastic/eui'; import type { HeaderProps } from '../components'; import { Header } from '../components'; @@ -18,6 +18,7 @@ export interface WithHeaderLayoutProps extends HeaderProps { restrictHeaderWidth?: number; 'data-test-subj'?: string; children?: React.ReactNode; + isReadOnly?: boolean; } export const WithHeaderLayout: React.FC = ({ @@ -25,24 +26,30 @@ export const WithHeaderLayout: React.FC = ({ restrictHeaderWidth, children, 'data-test-subj': dataTestSubj, + isReadOnly, ...rest -}) => ( - -
- - - - - {children} - - - - -); +}) => { + const isBiggerScreen = useIsWithinMinBreakpoint('xxl'); + const fullWidthSize = isBiggerScreen ? '80%' : '100%'; + + return ( + +
+ + + + + {children} + + + + + ); +}; diff --git a/x-pack/plugins/fleet/public/types/ui_extensions.ts b/x-pack/plugins/fleet/public/types/ui_extensions.ts index a041762438b8d..b0445bf4e1697 100644 --- a/x-pack/plugins/fleet/public/types/ui_extensions.ts +++ b/x-pack/plugins/fleet/public/types/ui_extensions.ts @@ -119,6 +119,12 @@ export interface EndpointAgentTamperProtectionExtension { Component: LazyExoticComponent; } +export interface PliAuthBlockExtension { + package: string; + view: 'pli-auth-block'; + Component: LazyExoticComponent; +} + export interface PackageGenericErrorsListExtension { package: string; view: 'package-generic-errors-list'; @@ -226,4 +232,5 @@ export type UIExtensionPoint = | PackageGenericErrorsListExtension | AgentEnrollmentFlyoutFinalStepExtension | PackagePolicyCreateMultiStepExtension - | EndpointAgentTamperProtectionExtension; + | EndpointAgentTamperProtectionExtension + | PliAuthBlockExtension; diff --git a/x-pack/plugins/fleet/server/collectors/agent_policies.ts b/x-pack/plugins/fleet/server/collectors/agent_policies.ts index d70524e5f1e32..5210e7f3b229d 100644 --- a/x-pack/plugins/fleet/server/collectors/agent_policies.ts +++ b/x-pack/plugins/fleet/server/collectors/agent_policies.ts @@ -18,6 +18,8 @@ import type { OutputSOAttributes, AgentPolicy } from '../types'; export interface AgentPoliciesUsage { count: number; output_types: string[]; + count_with_global_data_tags: number; + avg_number_global_data_tags_per_policy?: number; } export const getAgentPoliciesUsage = async ( @@ -52,8 +54,26 @@ export const getAgentPoliciesUsage = async ( }) ); + const [policiesWithGlobalDataTag, totalNumberOfGlobalDataTagFields] = agentPolicies.reduce( + ([policiesNumber, fieldsNumber], agentPolicy) => { + if (agentPolicy.attributes.global_data_tags?.length ?? 0 > 0) { + return [ + policiesNumber + 1, + fieldsNumber + (agentPolicy.attributes.global_data_tags?.length ?? 0), + ]; + } + return [policiesNumber, fieldsNumber]; + }, + [0, 0] + ); + return { count: totalAgentPolicies, output_types: Array.from(uniqueOutputTypes), + count_with_global_data_tags: policiesWithGlobalDataTag, + avg_number_global_data_tags_per_policy: + policiesWithGlobalDataTag > 0 + ? Math.round(totalNumberOfGlobalDataTagFields / policiesWithGlobalDataTag) + : undefined, }; }; diff --git a/x-pack/plugins/fleet/server/collectors/agents_per_output.test.ts b/x-pack/plugins/fleet/server/collectors/agents_per_output.test.ts index f68a700865708..181467a69cf1f 100644 --- a/x-pack/plugins/fleet/server/collectors/agents_per_output.test.ts +++ b/x-pack/plugins/fleet/server/collectors/agents_per_output.test.ts @@ -9,23 +9,57 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/ import { getAgentsPerOutput } from './agents_per_output'; +jest.mock('../services/agents', () => { + return { + getAgentsByKuery: jest + .fn() + .mockImplementation(async (esClient, soClient, { kuery }: { kuery: string }) => { + if (kuery.includes('policy_id:policy1')) { + return { + total: 0, + }; + } else { + return { + total: 1, + }; + } + }), + }; +}); + jest.mock('../services', () => { return { agentPolicyService: { list: jest.fn().mockResolvedValue({ items: [ - { agents: 0, data_output_id: 'logstash1', monitoring_output_id: 'kafka1' }, - { agents: 1 }, - { agents: 1, data_output_id: 'logstash1' }, - { agents: 1, monitoring_output_id: 'kafka1' }, - { agents: 1, data_output_id: 'elasticsearch2', monitoring_output_id: 'elasticsearch2' }, - { agents: 1, data_output_id: 'elasticsearch3', monitoring_output_id: 'elasticsearch3' }, + { id: 'policy1', agents: 0, data_output_id: 'logstash1', monitoring_output_id: 'kafka1' }, + { id: 'policy2', agents: 1 }, + { id: 'policy3', agents: 1, data_output_id: 'logstash1' }, + { id: 'policy4', agents: 1, monitoring_output_id: 'kafka1' }, + { + id: 'policy5', + agents: 1, + data_output_id: 'elasticsearch2', + monitoring_output_id: 'elasticsearch2', + }, + { + id: 'policy5', + agents: 1, + data_output_id: 'elasticsearch3', + monitoring_output_id: 'elasticsearch3', + }, { + id: 'policy6', agents: 1, data_output_id: 'es-containerhost', monitoring_output_id: 'es-containerhost', }, - { agents: 1, data_output_id: 'remote-es', monitoring_output_id: 'remote-es' }, + { + id: 'policy7', + agents: 1, + data_output_id: 'remote-es', + monitoring_output_id: 'remote-es', + }, ], }), }, diff --git a/x-pack/plugins/fleet/server/collectors/agents_per_output.ts b/x-pack/plugins/fleet/server/collectors/agents_per_output.ts index 5090b1530bc02..a6d6254bc5276 100644 --- a/x-pack/plugins/fleet/server/collectors/agents_per_output.ts +++ b/x-pack/plugins/fleet/server/collectors/agents_per_output.ts @@ -46,9 +46,9 @@ export async function getAgentsPerOutput( const { items: agentPolicies } = await agentPolicyService.list(soClient, { esClient, - withAgentCount: true, page: 1, perPage: SO_SEARCH_LIMIT, + withAgentCount: true, }); const outputTypes: { [key: string]: AgentsPerOutputType } = {}; diff --git a/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts b/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts index 3ee659793f53f..77b3278bad39a 100644 --- a/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts +++ b/x-pack/plugins/fleet/server/integration_tests/fleet_usage_telemetry.test.ts @@ -428,6 +428,10 @@ describe('fleet usage telemetry', () => { schema_version: '1.0.0', data_output_id: 'output2', monitoring_output_id: 'output3', + global_data_tags: [ + { name: 'test', value: 'test1' }, + { name: 'test2', value: 'test2' }, + ], }, { id: 'policy2' } ); @@ -446,6 +450,10 @@ describe('fleet usage telemetry', () => { schema_version: '1.0.0', data_output_id: 'output4', monitoring_output_id: 'output4', + global_data_tags: [ + { name: 'test', value: 'test1' }, + { name: 'test2', value: 'test2' }, + ], }, { id: 'policy3' } ); @@ -566,6 +574,8 @@ describe('fleet usage telemetry', () => { agent_policies: { count: 3, output_types: expect.arrayContaining(['elasticsearch', 'logstash', 'third_type']), + count_with_global_data_tags: 2, + avg_number_global_data_tags_per_policy: 2, }, agent_logs_panics_last_hour: [ { diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts index 9e84626455b1b..781cc18e33425 100644 --- a/x-pack/plugins/fleet/server/mocks/index.ts +++ b/x-pack/plugins/fleet/server/mocks/index.ts @@ -121,6 +121,9 @@ export const createFleetRequestHandlerContextMock = (): jest.Mocked< asCurrentUser: createPackagePolicyServiceMock(), asInternalUser: createPackagePolicyServiceMock(), }, + uninstallTokenService: { + asCurrentUser: createUninstallTokenServiceMock(), + }, internalSoClient: savedObjectsClientMock.create(), spaceId: 'default', limitedToPackages: undefined, @@ -182,7 +185,6 @@ export const createPackagePolicyServiceMock = (): jest.Mocked => { return { get: jest.fn(), @@ -238,5 +240,6 @@ export function createUninstallTokenServiceMock(): UninstallTokenServiceInterfac encryptTokens: jest.fn(), checkTokenValidityForAllPolicies: jest.fn(), checkTokenValidityForPolicy: jest.fn(), + scoped: jest.fn().mockImplementation(() => createUninstallTokenServiceMock()), }; } diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index 1c2ff2353a1a2..67240be2cc542 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -521,6 +521,18 @@ export class FleetPlugin asInternalUser: agentService.asInternalUser, }; }, + get uninstallTokenService() { + const uninstallTokenService = new UninstallTokenService( + appContextService.getEncryptedSavedObjectsStart()!.getClient({ + includedHiddenTypes: [UNINSTALL_TOKENS_SAVED_OBJECT_TYPE], + }), + appContextService.getInternalUserSOClientForSpaceId(soClient.getCurrentNamespace()) + ); + + return { + asCurrentUser: uninstallTokenService, + }; + }, get packagePolicyService() { const service = plugin.setupPackagePolicyService(); diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index b292cc2f88c33..6f1ac12edb163 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -8,6 +8,7 @@ import { uniq } from 'lodash'; import { type RequestHandler, SavedObjectsErrorHelpers } from '@kbn/core/server'; import type { TypeOf } from '@kbn/config-schema'; +import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; import type { GetAgentsResponse, @@ -20,6 +21,7 @@ import type { GetAgentUploadsResponse, PostAgentReassignResponse, PostRetrieveAgentsByActionsResponse, + Agent, } from '../../../common/types'; import type { GetAgentsRequestSchema, @@ -37,22 +39,36 @@ import type { GetAgentUploadFileRequestSchema, DeleteAgentUploadFileRequestSchema, PostRetrieveAgentsByActionsRequestSchema, + FleetRequestHandler, } from '../../types'; -import { defaultFleetErrorHandler } from '../../errors'; +import { defaultFleetErrorHandler, FleetNotFoundError } from '../../errors'; import * as AgentService from '../../services/agents'; import { fetchAndAssignAgentMetrics } from '../../services/agents/agent_metrics'; +import { getAgentStatusForAgentPolicy } from '../../services/agents'; + +export function verifyNamespace(agent: Agent, currentNamespace?: string) { + const isInNamespace = + (currentNamespace && agent.namespaces?.includes(currentNamespace)) || + (!currentNamespace && + (!agent.namespaces || + agent.namespaces.length === 0 || + agent.namespaces?.includes(DEFAULT_NAMESPACE_STRING))); + if (!isInNamespace) { + throw new FleetNotFoundError(`${agent.id} not found in namespace`); + } +} -export const getAgentHandler: RequestHandler< +export const getAgentHandler: FleetRequestHandler< TypeOf, TypeOf > = async (context, request, response) => { - const coreContext = await context.core; - const esClient = coreContext.elasticsearch.client.asInternalUser; - const esClientCurrentUser = coreContext.elasticsearch.client.asCurrentUser; - const soClient = coreContext.savedObjects.client; - try { - let agent = await AgentService.getAgentById(esClient, soClient, request.params.agentId); + const [coreContext, fleetContext] = await Promise.all([context.core, context.fleet]); + const esClientCurrentUser = coreContext.elasticsearch.client.asCurrentUser; + + let agent = await fleetContext.agentClient.asCurrentUser.getAgent(request.params.agentId); + + verifyNamespace(agent, coreContext.savedObjects.client.getCurrentNamespace()); if (request.query.withMetrics) { agent = (await fetchAndAssignAgentMetrics(esClientCurrentUser, [agent]))[0]; @@ -77,10 +93,10 @@ export const getAgentHandler: RequestHandler< export const deleteAgentHandler: RequestHandler< TypeOf > = async (context, request, response) => { - const coreContext = await context.core; - const esClient = coreContext.elasticsearch.client.asInternalUser; - try { + const coreContext = await context.core; + const esClient = coreContext.elasticsearch.client.asInternalUser; + await AgentService.deleteAgent(esClient, request.params.agentId); const body = { @@ -162,17 +178,16 @@ export const bulkUpdateAgentTagsHandler: RequestHandler< } }; -export const getAgentsHandler: RequestHandler< +export const getAgentsHandler: FleetRequestHandler< undefined, TypeOf > = async (context, request, response) => { - const coreContext = await context.core; - const esClient = coreContext.elasticsearch.client.asInternalUser; + const [coreContext, fleetContext] = await Promise.all([context.core, context.fleet]); + const { agentClient } = fleetContext; const esClientCurrentUser = coreContext.elasticsearch.client.asCurrentUser; - const soClient = coreContext.savedObjects.client; try { - const agentRes = await AgentService.getAgentsByKuery(esClient, soClient, { + const agentRes = await agentClient.asCurrentUser.listAgents({ page: request.query.page, perPage: request.query.perPage, showInactive: request.query.showInactive, @@ -300,19 +315,20 @@ export const postBulkAgentReassignHandler: RequestHandler< } }; -export const getAgentStatusForAgentPolicyHandler: RequestHandler< +export const getAgentStatusForAgentPolicyHandler: FleetRequestHandler< undefined, TypeOf > = async (context, request, response) => { - const coreContext = await context.core; - const esClient = coreContext.elasticsearch.client.asInternalUser; - const soClient = coreContext.savedObjects.client; try { - const results = await AgentService.getAgentStatusForAgentPolicy( + const [coreContext, fleetContext] = await Promise.all([context.core, context.fleet]); + const esClient = coreContext.elasticsearch.client.asInternalUser; + const soClient = fleetContext.internalSoClient; + const results = await getAgentStatusForAgentPolicy( esClient, soClient, request.query.policyId, - request.query.kuery + request.query.kuery, + coreContext.savedObjects.client.getCurrentNamespace() ); const body: GetAgentStatusResponse = { results }; diff --git a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts index a5d6f7b83c854..d652dacf0dd9a 100644 --- a/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts @@ -6,12 +6,7 @@ */ import type { TypeOf } from '@kbn/config-schema'; -import type { - RequestHandler, - ResponseHeaders, - ElasticsearchClient, - SavedObjectsClientContract, -} from '@kbn/core/server'; +import type { RequestHandler, ResponseHeaders } from '@kbn/core/server'; import pMap from 'p-map'; import { safeDump } from 'js-yaml'; @@ -19,7 +14,7 @@ import { HTTPAuthorizationHeader } from '../../../common/http_authorization_head import { fullAgentPolicyToYaml } from '../../../common/services'; import { appContextService, agentPolicyService } from '../../services'; -import { getAgentsByKuery, getLatestAvailableAgentVersion } from '../../services/agents'; +import { type AgentClient, getLatestAvailableAgentVersion } from '../../services/agents'; import { AGENTS_PREFIX, UNPRIVILEGED_AGENT_KUERY } from '../../constants'; import type { GetAgentPoliciesRequestSchema, @@ -56,25 +51,28 @@ import { import { createAgentPolicyWithPackages } from '../../services/agent_policy_create'; export async function populateAssignedAgentsCount( - esClient: ElasticsearchClient, - soClient: SavedObjectsClientContract, + agentClient: AgentClient, agentPolicies: AgentPolicy[] ) { await pMap( agentPolicies, (agentPolicy: GetAgentPoliciesResponseItem) => { - const totalAgents = getAgentsByKuery(esClient, soClient, { - showInactive: true, - perPage: 0, - page: 1, - kuery: `${AGENTS_PREFIX}.policy_id:${agentPolicy.id}`, - }).then(({ total }) => (agentPolicy.agents = total)); - const unprivilegedAgents = getAgentsByKuery(esClient, soClient, { - showInactive: true, - perPage: 0, - page: 1, - kuery: `${AGENTS_PREFIX}.policy_id:${agentPolicy.id} and ${UNPRIVILEGED_AGENT_KUERY}`, - }).then(({ total }) => (agentPolicy.unprivileged_agents = total)); + const totalAgents = agentClient + .listAgents({ + showInactive: true, + perPage: 0, + page: 1, + kuery: `${AGENTS_PREFIX}.policy_id:${agentPolicy.id}`, + }) + .then(({ total }) => (agentPolicy.agents = total)); + const unprivilegedAgents = agentClient + .listAgents({ + showInactive: true, + perPage: 0, + page: 1, + kuery: `${AGENTS_PREFIX}.policy_id:${agentPolicy.id} and ${UNPRIVILEGED_AGENT_KUERY}`, + }) + .then(({ total }) => (agentPolicy.unprivileged_agents = total)); return Promise.all([totalAgents, unprivilegedAgents]); }, { concurrency: 10 } @@ -121,9 +119,12 @@ export const getAgentPoliciesHandler: FleetRequestHandler< withPackagePolicies, esClient, ...restOfQuery, - withAgentCount: !noAgentCount, }); + if (fleetContext.authz.fleet.readAgents && !noAgentCount) { + await populateAssignedAgentsCount(fleetContext.agentClient.asCurrentUser, items); + } + const body: GetAgentPoliciesResponse = { items: !fleetContext.authz.fleet.readAgentPolicies ? items.map(sanitizeItemForReadAgentOnly) @@ -144,9 +145,8 @@ export const bulkGetAgentPoliciesHandler: FleetRequestHandler< TypeOf > = async (context, request, response) => { try { - const [coreContext, fleetContext] = await Promise.all([context.core, context.fleet]); + const fleetContext = await context.fleet; const soClient = fleetContext.internalSoClient; - const esClient = coreContext.elasticsearch.client.asInternalUser; const { full: withPackagePolicies = false, ignoreMissing = false, ids } = request.body; const items = await agentPolicyService.getByIDs(soClient, ids, { withPackagePolicies, @@ -157,8 +157,9 @@ export const bulkGetAgentPoliciesHandler: FleetRequestHandler< ? items.map(sanitizeItemForReadAgentOnly) : items, }; - - await populateAssignedAgentsCount(esClient, soClient, items); + if (fleetContext.authz.fleet.readAgents) { + await populateAssignedAgentsCount(fleetContext.agentClient.asCurrentUser, items); + } return response.ok({ body }); } catch (error) { @@ -179,12 +180,13 @@ export const getOneAgentPolicyHandler: FleetRequestHandler< > = async (context, request, response) => { try { const [coreContext, fleetContext] = await Promise.all([context.core, context.fleet]); - const esClient = coreContext.elasticsearch.client.asInternalUser; const soClient = coreContext.savedObjects.client; const agentPolicy = await agentPolicyService.get(soClient, request.params.agentPolicyId); if (agentPolicy) { - await populateAssignedAgentsCount(esClient, soClient, [agentPolicy]); + if (fleetContext.authz.fleet.readAgents) { + await populateAssignedAgentsCount(fleetContext.agentClient.asCurrentUser, [agentPolicy]); + } const body: GetOneAgentPolicyResponse = { item: !fleetContext.authz.fleet.readAgentPolicies ? sanitizeItemForReadAgentOnly(agentPolicy) diff --git a/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts index 60d8a2de45119..016a731d1c67e 100644 --- a/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts +++ b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts @@ -7,6 +7,7 @@ import { type RequestHandler, SavedObjectsErrorHelpers } from '@kbn/core/server'; import type { TypeOf } from '@kbn/config-schema'; +import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; import type { GetEnrollmentAPIKeysRequestSchema, @@ -23,19 +24,25 @@ import type { import * as APIKeyService from '../../services/api_keys'; import { agentPolicyService } from '../../services/agent_policy'; import { defaultFleetErrorHandler, AgentPolicyNotFoundError } from '../../errors'; +import { appContextService } from '../../services'; export const getEnrollmentApiKeysHandler: RequestHandler< undefined, TypeOf > = async (context, request, response) => { + const { useSpaceAwareness } = appContextService.getExperimentalFeatures(); // Use kibana_system and depend on authz checks on HTTP layer to prevent abuse const esClient = (await context.core).elasticsearch.client.asInternalUser; + const soClient = (await context.core).savedObjects.client; try { const { items, total, page, perPage } = await APIKeyService.listEnrollmentApiKeys(esClient, { page: request.query.page, perPage: request.query.perPage, kuery: request.query.kuery, + spaceId: useSpaceAwareness + ? soClient.getCurrentNamespace() ?? DEFAULT_NAMESPACE_STRING + : undefined, }); const body: GetEnrollmentAPIKeysResponse = { list: items, // deprecated @@ -59,7 +66,7 @@ export const postEnrollmentApiKeyHandler: RequestHandler< const soClient = savedObjects.client; const esClient = elasticsearch.client.asInternalUser; try { - // validate policy id + // validate policy exists in the current space await agentPolicyService.get(soClient, request.body.policy_id).catch((err) => { if (SavedObjectsErrorHelpers.isNotFoundError(err)) { throw new AgentPolicyNotFoundError(`Agent policy "${request.body.policy_id}" not found`); @@ -85,9 +92,18 @@ export const postEnrollmentApiKeyHandler: RequestHandler< export const deleteEnrollmentApiKeyHandler: RequestHandler< TypeOf > = async (context, request, response) => { - const esClient = (await context.core).elasticsearch.client.asInternalUser; try { - await APIKeyService.deleteEnrollmentApiKey(esClient, request.params.keyId); + const { useSpaceAwareness } = appContextService.getExperimentalFeatures(); + const coreContext = await context.core; + const esClient = coreContext.elasticsearch.client.asInternalUser; + const currentNamespace = + coreContext.savedObjects.client.getCurrentNamespace() ?? DEFAULT_NAMESPACE_STRING; + await APIKeyService.deleteEnrollmentApiKey( + esClient, + request.params.keyId, + false, + useSpaceAwareness ? currentNamespace : undefined + ); const body: DeleteEnrollmentAPIKeyResponse = { action: 'deleted' }; @@ -106,9 +122,19 @@ export const getOneEnrollmentApiKeyHandler: RequestHandler< TypeOf > = async (context, request, response) => { // Use kibana_system and depend on authz checks on HTTP layer to prevent abuse - const esClient = (await context.core).elasticsearch.client.asInternalUser; + try { - const apiKey = await APIKeyService.getEnrollmentAPIKey(esClient, request.params.keyId); + const coreContext = await context.core; + const esClient = coreContext.elasticsearch.client.asInternalUser; + const currentNamespace = + coreContext.savedObjects.client.getCurrentNamespace() ?? DEFAULT_NAMESPACE_STRING; + const { useSpaceAwareness } = appContextService.getExperimentalFeatures(); + + const apiKey = await APIKeyService.getEnrollmentAPIKey( + esClient, + request.params.keyId, + useSpaceAwareness ? currentNamespace : undefined + ); const body: GetOneEnrollmentAPIKeyResponse = { item: apiKey }; return response.ok({ body }); diff --git a/x-pack/plugins/fleet/server/routes/health_check/handler.test.ts b/x-pack/plugins/fleet/server/routes/health_check/handler.test.ts new file mode 100644 index 0000000000000..d418b0d7c560b --- /dev/null +++ b/x-pack/plugins/fleet/server/routes/health_check/handler.test.ts @@ -0,0 +1,158 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import fetch from 'node-fetch'; + +import * as fleetServerService from '../../services/fleet_server_host'; + +import { postHealthCheckHandler } from '.'; + +jest.mock('node-fetch'); + +describe('Fleet server health_check handler', () => { + const mockContext = { + core: Promise.resolve({ + savedObjects: {}, + elasticsearch: { + client: {}, + }, + }), + } as any; + const mockedFetch = fetch as jest.MockedFunction; + const mockResponse = { + customError: jest.fn().mockImplementation((options) => options), + ok: jest.fn().mockImplementation((options) => { + return { ...options, statusCode: 200 }; + }), + badRequest: jest.fn().mockImplementation((options) => { + return { ...options, statusCode: 400 }; + }), + notFound: jest.fn().mockImplementation((options) => { + return { ...options, statusCode: 404 }; + }), + }; + + beforeEach(() => { + mockedFetch.mockReset(); + }); + + it('should return a bad request error if the requested fleet server host has no host_urls', async () => { + jest.spyOn(fleetServerService, 'getFleetServerHost').mockResolvedValue({ + id: 'default-fleet-server', + name: 'Default', + is_default: true, + host_urls: [], + } as any); + + const res = await postHealthCheckHandler( + mockContext, + { body: { id: 'default-fleet-server' } } as any, + mockResponse as any + ); + + expect(res).toEqual({ + body: { + message: `The requested host id default-fleet-server does not have associated host urls.`, + }, + statusCode: 400, + }); + }); + + it('should return a bad request error if body contains deprecated parameter `host`', async () => { + const res = await postHealthCheckHandler( + mockContext, + { body: { host: 'https://localhost:8220' } } as any, + mockResponse as any + ); + + expect(res).toEqual({ + body: { + message: `Property 'host' is deprecated. Please use id instead.`, + }, + statusCode: 400, + }); + }); + + it('should return 200 and active status when fetch response is `active`', async () => { + const activeRes = { + status: 'ONLINE', + host_id: 'default-fleet-server', + name: 'Default', + }; + + jest.spyOn(fleetServerService, 'getFleetServerHost').mockResolvedValue({ + id: 'default-fleet-server', + name: 'Default', + is_default: true, + host_urls: ['https://localhost:8220'], + } as any); + + mockedFetch.mockResolvedValueOnce({ + json: () => activeRes, + status: 200, + ok: true, + } as any); + + const res = await postHealthCheckHandler( + mockContext, + { body: { id: 'default-fleet-server' } } as any, + mockResponse as any + ); + + expect(res).toEqual({ + body: { + host: 'https://localhost:8220', + host_id: 'default-fleet-server', + name: 'Default', + status: 'ONLINE', + }, + statusCode: 200, + }); + }); + + it('should return an error when host id is not found', async () => { + jest + .spyOn(fleetServerService, 'getFleetServerHost') + .mockRejectedValue({ output: { statusCode: 404 }, isBoom: true }); + + const res = await postHealthCheckHandler( + mockContext, + { body: { id: 'non-existent' } } as any, + mockResponse as any + ); + + expect(res).toEqual({ + body: { + message: `The requested host id non-existent does not exist.`, + }, + statusCode: 404, + }); + }); + + it('should return status `offline` when fetch request gets aborted', async () => { + jest.spyOn(fleetServerService, 'getFleetServerHost').mockResolvedValue({ + id: 'default-fleet-server', + name: 'Default', + is_default: true, + host_urls: ['https://localhost:8220'], + } as any); + mockedFetch.mockRejectedValue({ message: 'user aborted', name: 'AbortError' }); + + const res = await postHealthCheckHandler( + mockContext, + { body: { id: 'default-fleet-server' } } as any, + mockResponse as any + ); + + expect(res).toEqual({ + body: { + host_id: 'default-fleet-server', + status: 'OFFLINE', + }, + statusCode: 200, + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/routes/health_check/index.ts b/x-pack/plugins/fleet/server/routes/health_check/index.ts index 7f30d7f92a859..54f4f68b2e7c3 100644 --- a/x-pack/plugins/fleet/server/routes/health_check/index.ts +++ b/x-pack/plugins/fleet/server/routes/health_check/index.ts @@ -9,21 +9,24 @@ import https from 'https'; import type { TypeOf } from '@kbn/config-schema'; import fetch from 'node-fetch'; +import { getFleetServerHost } from '../../services/fleet_server_host'; + import { API_VERSIONS } from '../../../common/constants'; import type { FleetAuthzRouter } from '../../services/security'; import { APP_API_ROUTES } from '../../constants'; import type { FleetRequestHandler } from '../../types'; + import { defaultFleetErrorHandler } from '../../errors'; import { PostHealthCheckRequestSchema } from '../../types'; export const registerRoutes = (router: FleetAuthzRouter) => { - // get fleet server health check by host + // get fleet server health check by host id router.versioned .post({ path: APP_API_ROUTES.HEALTH_CHECK_PATTERN, fleetAuthz: { - fleet: { all: true }, + fleet: { allSettings: true }, }, description: `Check Fleet Server health`, }) @@ -41,15 +44,39 @@ export const postHealthCheckHandler: FleetRequestHandler< undefined, TypeOf > = async (context, request, response) => { + const abortController = new AbortController(); + const { id, host: deprecatedField } = request.body; + const coreContext = await context.core; + const soClient = coreContext.savedObjects.client; + + if (deprecatedField) { + return response.badRequest({ + body: { + message: `Property 'host' is deprecated. Please use id instead.`, + }, + }); + } try { - const abortController = new AbortController(); - const { host } = request.body; + const fleetServerHost = await getFleetServerHost(soClient, id); + + if ( + !fleetServerHost || + !fleetServerHost?.host_urls || + fleetServerHost?.host_urls?.length === 0 + ) { + return response.badRequest({ + body: { + message: `The requested host id ${id} does not have associated host urls.`, + }, + }); + } // Sometimes when the host is not online, the request hangs // Setting a timeout to abort the request after 5s setTimeout(() => { abortController.abort(); }, 5000); + const host = fleetServerHost.host_urls[0]; const res = await fetch(`${host}/api/status`, { headers: { @@ -66,10 +93,18 @@ export const postHealthCheckHandler: FleetRequestHandler< return response.ok({ body }); } catch (error) { + if (error.isBoom && error.output.statusCode === 404) { + return response.notFound({ + body: { + message: `The requested host id ${request.body.id} does not exist.`, + }, + }); + } + // when the request is aborted, return offline status - if (error.name === 'AbortError') { + if (error.name === 'AbortError' || error.message.includes('user aborted')) { return response.ok({ - body: { name: 'fleet-server', status: `OFFLINE`, host: request.body.host }, + body: { status: `OFFLINE`, host_id: request.body.id }, }); } return defaultFleetErrorHandler({ error, response }); diff --git a/x-pack/plugins/fleet/server/routes/settings/enrollment_settings_handler.test.ts b/x-pack/plugins/fleet/server/routes/settings/enrollment_settings_handler.test.ts index b41debffb3e6b..0a39101db8481 100644 --- a/x-pack/plugins/fleet/server/routes/settings/enrollment_settings_handler.test.ts +++ b/x-pack/plugins/fleet/server/routes/settings/enrollment_settings_handler.test.ts @@ -16,6 +16,9 @@ jest.mock('../../services', () => ({ get: jest.fn(), getByIDs: jest.fn(), }, + appContextService: { + getInternalUserSOClientWithoutSpaceExtension: jest.fn(), + }, downloadSourceService: { list: jest.fn().mockResolvedValue({ items: [ diff --git a/x-pack/plugins/fleet/server/routes/settings/enrollment_settings_handler.ts b/x-pack/plugins/fleet/server/routes/settings/enrollment_settings_handler.ts index 1662fed3fc31b..132777867becb 100644 --- a/x-pack/plugins/fleet/server/routes/settings/enrollment_settings_handler.ts +++ b/x-pack/plugins/fleet/server/routes/settings/enrollment_settings_handler.ts @@ -18,7 +18,7 @@ import type { } from '../../../common/types'; import type { FleetRequestHandler, GetEnrollmentSettingsRequestSchema } from '../../types'; import { defaultFleetErrorHandler } from '../../errors'; -import { agentPolicyService, downloadSourceService } from '../../services'; +import { agentPolicyService, appContextService, downloadSourceService } from '../../services'; import { getFleetServerHostsForAgentPolicy } from '../../services/fleet_server_host'; import { getFleetProxy } from '../../services/fleet_proxies'; import { getFleetServerPolicies, hasFleetServersForPolicies } from '../../services/fleet_server'; @@ -53,8 +53,8 @@ export const getEnrollmentSettingsHandler: FleetRequestHandler< settingsResponse.fleet_server.policies = fleetServerPolicies; settingsResponse.fleet_server.has_active = await hasFleetServersForPolicies( esClient, - soClient, - fleetServerPolicies.map((p) => p.id), + appContextService.getInternalUserSOClientWithoutSpaceExtension(), + fleetServerPolicies, true ); } @@ -115,6 +115,7 @@ export const getFleetServerOrAgentPolicies = async ( has_fleet_server: policy.has_fleet_server, fleet_server_host_id: policy.fleet_server_host_id, download_source_id: policy.download_source_id, + space_id: policy.space_id, }); // If an agent policy is specified, return only that policy @@ -136,7 +137,9 @@ export const getFleetServerOrAgentPolicies = async ( } // If an agent policy is not specified, return all fleet server policies - const fleetServerPolicies = (await getFleetServerPolicies(soClient)).map(mapPolicy); + const fleetServerPolicies = ( + await getFleetServerPolicies(appContextService.getInternalUserSOClientWithoutSpaceExtension()) + ).map(mapPolicy); return { fleetServerPolicies }; }; diff --git a/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts index 323c7b71ae487..bb9bf0b507ca9 100644 --- a/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts @@ -14,6 +14,7 @@ import { RegistryError } from '../../errors'; import { createAppContextStartContractMock, createPackagePolicyServiceMock, + createUninstallTokenServiceMock, xpackMocks, } from '../../mocks'; import { agentServiceMock } from '../../services/agents/agent_service.mock'; @@ -45,6 +46,9 @@ describe('FleetSetupHandler', () => { context = { ...xpackMocks.createRequestHandlerContext(), fleet: { + uninstallTokenService: { + asCurrentUser: createUninstallTokenServiceMock(), + }, agentClient: { asCurrentUser: agentServiceMock.createClient(), asInternalUser: agentServiceMock.createClient(), @@ -129,6 +133,9 @@ describe('FleetStatusHandler', () => { context = { ...xpackMocks.createRequestHandlerContext(), fleet: { + uninstallTokenService: { + asCurrentUser: createUninstallTokenServiceMock(), + }, agentClient: { asCurrentUser: agentServiceMock.createClient(), asInternalUser: agentServiceMock.createClient(), diff --git a/x-pack/plugins/fleet/server/routes/setup/handlers.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.ts index 58555f233142a..019fb2af5276b 100644 --- a/x-pack/plugins/fleet/server/routes/setup/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/setup/handlers.ts @@ -16,10 +16,9 @@ import { isSecretStorageEnabled } from '../../services/secrets'; export const getFleetStatusHandler: FleetRequestHandler = async (context, request, response) => { const coreContext = await context.core; - const fleetContext = await context.fleet; const esClient = coreContext.elasticsearch.client.asInternalUser; - const soClient = fleetContext.internalSoClient; + const soClient = appContextService.getInternalUserSOClientWithoutSpaceExtension(); try { const isApiKeysEnabled = await appContextService diff --git a/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.test.ts b/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.test.ts index 31caa167ba59d..b51f19291c58a 100644 --- a/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.test.ts @@ -95,8 +95,8 @@ describe('uninstall token handlers', () => { >; const mockAgentPolicyService = agentPolicyService as jest.Mocked; - beforeEach(() => { - const uninstallTokenService = appContextService.getUninstallTokenService()!; + beforeEach(async () => { + const uninstallTokenService = (await context.fleet).uninstallTokenService.asCurrentUser; getTokenMetadataMock = uninstallTokenService.getTokenMetadata as jest.Mock; mockAgentPolicyService.list.mockResolvedValue({ items: [createAgentPolicyMock()], @@ -118,22 +118,6 @@ describe('uninstall token handlers', () => { }); }); - it('should return internal error when uninstallTokenService is unavailable', async () => { - appContextService.stop(); - appContextService.start({ - ...appContextStartContractMock, - // @ts-expect-error - uninstallTokenService: undefined, - }); - - await getUninstallTokensMetadataHandler(context, request, response); - - expect(response.customError).toHaveBeenCalledWith({ - statusCode: 500, - body: { message: 'Uninstall Token Service is unavailable.' }, - }); - }); - it('should return internal error when uninstallTokenService throws error', async () => { getTokenMetadataMock.mockRejectedValue(Error('something happened')); @@ -158,8 +142,8 @@ describe('uninstall token handlers', () => { let getTokenMock: jest.Mock; let request: KibanaRequest>; - beforeEach(() => { - const uninstallTokenService = appContextService.getUninstallTokenService()!; + beforeEach(async () => { + const uninstallTokenService = (await context.fleet).uninstallTokenService.asCurrentUser; getTokenMock = uninstallTokenService.getToken as jest.Mock; const requestOptions: GetUninstallTokenRequest = { @@ -183,22 +167,6 @@ describe('uninstall token handlers', () => { }); }); - it('should return internal error when uninstallTokenService is unavailable', async () => { - appContextService.stop(); - appContextService.start({ - ...appContextStartContractMock, - // @ts-expect-error - uninstallTokenService: undefined, - }); - - await getUninstallTokenHandler(context, request, response); - - expect(response.customError).toHaveBeenCalledWith({ - statusCode: 500, - body: { message: 'Uninstall Token Service is unavailable.' }, - }); - }); - it('should return internal error when uninstallTokenService throws error', async () => { getTokenMock.mockRejectedValue(Error('something happened')); diff --git a/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts b/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts index 189a1f8bc80f7..5e61d5b9b01a2 100644 --- a/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/uninstall_token/handlers.ts @@ -6,9 +6,8 @@ */ import type { TypeOf } from '@kbn/config-schema'; -import type { CustomHttpResponseOptions, ResponseError } from '@kbn/core-http-server'; -import { appContextService, agentPolicyService } from '../../services'; +import { agentPolicyService } from '../../services'; import type { FleetRequestHandler } from '../../types'; import type { GetUninstallTokensMetadataRequestSchema, @@ -18,33 +17,25 @@ import { defaultFleetErrorHandler } from '../../errors'; import type { GetUninstallTokenResponse } from '../../../common/types/rest_spec/uninstall_token'; import { AGENT_POLICY_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT } from '../../constants'; -const UNINSTALL_TOKEN_SERVICE_UNAVAILABLE_ERROR: CustomHttpResponseOptions = { - statusCode: 500, - body: { message: 'Uninstall Token Service is unavailable.' }, -}; - export const getUninstallTokensMetadataHandler: FleetRequestHandler< unknown, TypeOf > = async (context, request, response) => { - const uninstallTokenService = appContextService.getUninstallTokenService(); - if (!uninstallTokenService) { - return response.customError(UNINSTALL_TOKEN_SERVICE_UNAVAILABLE_ERROR); - } + try { + const [fleetContext, coreContext] = await Promise.all([context.fleet, context.core]); + const uninstallTokenService = fleetContext.uninstallTokenService.asCurrentUser; - const { page = 1, perPage = 20, policyId, search } = request.query; + const { page = 1, perPage = 20, policyId, search } = request.query; - if (policyId && search) { - return response.badRequest({ - body: { - message: 'Query parameters `policyId` and `search` cannot be used at the same time.', - }, - }); - } + if (policyId && search) { + return response.badRequest({ + body: { + message: 'Query parameters `policyId` and `search` cannot be used at the same time.', + }, + }); + } - try { - const fleetContext = await context.fleet; - const soClient = fleetContext.internalSoClient; + const soClient = coreContext.savedObjects.client; const { items: managedPolicies } = await agentPolicyService.list(soClient, { fields: ['id'], @@ -80,10 +71,8 @@ export const getUninstallTokensMetadataHandler: FleetRequestHandler< export const getUninstallTokenHandler: FleetRequestHandler< TypeOf > = async (context, request, response) => { - const uninstallTokenService = appContextService.getUninstallTokenService(); - if (!uninstallTokenService) { - return response.customError(UNINSTALL_TOKEN_SERVICE_UNAVAILABLE_ERROR); - } + const [fleetContext] = await Promise.all([context.fleet, context.core]); + const uninstallTokenService = fleetContext.uninstallTokenService.asCurrentUser; try { const { uninstallTokenId } = request.params; diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index 5a084ac211e5e..029352e145ca6 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -38,8 +38,6 @@ import { policyHasSyntheticsIntegration, } from '../../common/services'; -import { populateAssignedAgentsCount } from '../routes/agent_policy/handlers'; - import type { HTTPAuthorizationHeader } from '../../common/http_authorization_header'; import { @@ -349,7 +347,10 @@ class AgentPolicyService { options ); - await appContextService.getUninstallTokenService()?.generateTokenForPolicyId(newSo.id); + await appContextService + .getUninstallTokenService() + ?.scoped(soClient.getCurrentNamespace()) + ?.generateTokenForPolicyId(newSo.id); await this.triggerAgentPolicyUpdatedEvent(esClient, 'created', newSo.id, { skipDeploy: options.skipDeploy, spaceId: soClient.getCurrentNamespace(), @@ -413,10 +414,20 @@ class AgentPolicyService { public async getByIDs( soClient: SavedObjectsClientContract, - ids: string[], + ids: Array, options: { fields?: string[]; withPackagePolicies?: boolean; ignoreMissing?: boolean } = {} ): Promise { - const objects = ids.map((id) => ({ ...options, id, type: SAVED_OBJECT_TYPE })); + const objects = ids.map((id) => { + if (typeof id === 'string') { + return { ...options, id, type: SAVED_OBJECT_TYPE }; + } + return { + ...options, + id: id.id, + namespaces: id.spaceId ? [id.spaceId] : undefined, + type: SAVED_OBJECT_TYPE, + }; + }); const bulkGetResponse = await soClient.bulkGet(objects); const agentPolicies = await pMap( @@ -431,7 +442,6 @@ class AgentPolicyService { throw new FleetError(agentPolicySO.error.message); } } - const agentPolicy = mapAgentPolicySavedObjectToAgentPolicy(agentPolicySO); if (options.withPackagePolicies) { const agentPolicyWithPackagePolicies = await this.get( @@ -485,8 +495,6 @@ class AgentPolicyService { kuery, withPackagePolicies = false, fields, - esClient, - withAgentCount = false, } = options; const baseFindParams = { @@ -526,6 +534,21 @@ class AgentPolicyService { agentPolicy.package_policies = (await packagePolicyService.findAllForAgentPolicy(soClient, agentPolicySO.id)) || []; } + if (options.withAgentCount) { + await getAgentsByKuery( + appContextService.getInternalUserESClient(), + appContextService.getInternalUserSOClientForSpaceId(agentPolicy.space_id), + { + showInactive: true, + perPage: 0, + page: 1, + kuery: `${AGENTS_PREFIX}.policy_id:${agentPolicy.id}`, + } + ).then(({ total }) => (agentPolicy.agents = total)); + } else { + agentPolicy.agents = 0; + } + return agentPolicy; }, { concurrency: 50 } @@ -538,11 +561,6 @@ class AgentPolicyService { savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE, }); } - if (esClient && withAgentCount) { - await populateAssignedAgentsCount(esClient, soClient, agentPolicies); - } else { - agentPolicies.forEach((item) => (item.agents = 0)); - } return { items: agentPolicies, diff --git a/x-pack/plugins/fleet/server/services/agents/agent_service.test.ts b/x-pack/plugins/fleet/server/services/agents/agent_service.test.ts index 05ca64eaeaf7b..2db66f04d8a9b 100644 --- a/x-pack/plugins/fleet/server/services/agents/agent_service.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/agent_service.test.ts @@ -9,6 +9,12 @@ jest.mock('../security'); jest.mock('./crud'); jest.mock('./status'); jest.mock('./versions'); +jest.mock('../app_context', () => ({ + appContextService: { + getInternalUserSOClientForSpaceId: jest.fn(), + getSavedObjects: jest.fn(), + }, +})); import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; import { @@ -22,6 +28,7 @@ import { FleetUnauthorizedError } from '../../errors'; import { getAuthzFromRequest } from '../security'; import type { FleetAuthz } from '../../../common'; import { createFleetAuthzMock } from '../../../common/mocks'; +import { appContextService } from '../app_context'; import type { AgentClient } from './agent_service'; import { AgentServiceImpl } from './agent_service'; @@ -37,18 +44,24 @@ const mockGetAgentStatusForAgentPolicy = getAgentStatusForAgentPolicy as jest.Mo const mockgetLatestAvailableAgentVersion = getLatestAvailableAgentVersion as jest.Mock; describe('AgentService', () => { + let mockedScopedSoClient: jest.Mocked; + beforeEach(() => { jest.resetAllMocks(); + mockedScopedSoClient = savedObjectsClientMock.create(); + jest.mocked(appContextService.getSavedObjects).mockReturnValue({ + getScopedClient: jest.fn().mockReturnValue(mockedScopedSoClient), + } as any); + jest + .mocked(appContextService.getInternalUserSOClientForSpaceId) + .mockReturnValue(mockedScopedSoClient); }); describe('asScoped', () => { describe('without required privilege', () => { - const agentClient = new AgentServiceImpl( - elasticsearchServiceMock.createElasticsearchClient(), - savedObjectsClientMock.create() - ).asScoped(httpServerMock.createKibanaRequest()); + let agentClient: AgentClient; - beforeEach(() => + beforeEach(() => { mockGetAuthzFromRequest.mockReturnValue( Promise.resolve({ fleet: { @@ -77,8 +90,12 @@ describe('AgentService', () => { writeIntegrationPolicies: false, }, }) - ) - ); + ); + agentClient = new AgentServiceImpl( + elasticsearchServiceMock.createElasticsearchClient(), + savedObjectsClientMock.create() + ).asScoped(httpServerMock.createKibanaRequest()); + }); it('rejects on listAgents', async () => { await expect(agentClient.listAgents({ showInactive: true })).rejects.toThrowError( @@ -124,32 +141,65 @@ describe('AgentService', () => { describe('with required privilege', () => { const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); const mockSoClient = savedObjectsClientMock.create(); - const agentClient = new AgentServiceImpl(mockEsClient, mockSoClient).asScoped( - httpServerMock.createKibanaRequest() - ); beforeEach(() => mockGetAuthzFromRequest.mockReturnValue(Promise.resolve(createFleetAuthzMock())) ); + expectApisToCallServicesSuccessfully( + mockEsClient, + () => mockedScopedSoClient, + () => + new AgentServiceImpl(mockEsClient, mockSoClient).asScoped( + httpServerMock.createKibanaRequest() + ), + 'default' + ); + }); - expectApisToCallServicesSuccessfully(mockEsClient, mockSoClient, agentClient); + describe('with required privilege in a non default space', () => { + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + const mockSoClient = savedObjectsClientMock.create(); + + beforeEach(() => { + mockGetAuthzFromRequest.mockReturnValue(Promise.resolve(createFleetAuthzMock())); + jest.mocked(mockedScopedSoClient.getCurrentNamespace).mockReturnValue('test'); + }); + expectApisToCallServicesSuccessfully( + mockEsClient, + () => mockedScopedSoClient, + () => + new AgentServiceImpl(mockEsClient, mockSoClient).asScoped( + httpServerMock.createKibanaRequest() + ), + 'test' + ); }); }); describe('asInternalUser', () => { const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); const mockSoClient = savedObjectsClientMock.create(); - const agentClient = new AgentServiceImpl(mockEsClient, mockSoClient).asInternalUser; - - expectApisToCallServicesSuccessfully(mockEsClient, mockSoClient, agentClient); + expectApisToCallServicesSuccessfully( + mockEsClient, + () => mockSoClient, + () => new AgentServiceImpl(mockEsClient, mockSoClient).asInternalUser + ); }); }); function expectApisToCallServicesSuccessfully( mockEsClient: ElasticsearchClient, - mockSoClient: jest.Mocked, - agentClient: AgentClient + getExpectedSoClient: () => jest.Mocked, + agentClientFactory: () => AgentClient, + spaceId?: string ) { + let agentClient: AgentClient; + let mockSoClient: jest.Mocked; + beforeEach(() => { + mockSoClient = getExpectedSoClient(); + agentClient = agentClientFactory(); + }); + test('client.listAgents calls getAgentsByKuery and returns results', async () => { mockGetAgentsByKuery.mockResolvedValue('getAgentsByKuery success'); await expect(agentClient.listAgents({ showInactive: true })).resolves.toEqual( @@ -157,6 +207,7 @@ function expectApisToCallServicesSuccessfully( ); expect(mockGetAgentsByKuery).toHaveBeenCalledWith(mockEsClient, mockSoClient, { showInactive: true, + spaceId, }); }); @@ -183,7 +234,8 @@ function expectApisToCallServicesSuccessfully( mockEsClient, mockSoClient, 'foo-id', - 'foo-filter' + 'foo-filter', + spaceId ); }); diff --git a/x-pack/plugins/fleet/server/services/agents/agent_service.ts b/x-pack/plugins/fleet/server/services/agents/agent_service.ts index 512db893d1c63..9af97cc307c08 100644 --- a/x-pack/plugins/fleet/server/services/agents/agent_service.ts +++ b/x-pack/plugins/fleet/server/services/agents/agent_service.ts @@ -19,11 +19,12 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { SortResults } from '@elastic/elasticsearch/lib/api/types'; +import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; + import type { AgentStatus, ListWithKuery } from '../../types'; import type { Agent, GetAgentStatusResponse } from '../../../common/types'; - import { getAuthzFromRequest } from '../security'; - +import { appContextService } from '../app_context'; import { FleetUnauthorizedError } from '../../errors'; import { getAgentsByKuery, getAgentById } from './crud'; @@ -88,6 +89,7 @@ export interface AgentClient { total: number; page: number; perPage: number; + statusSummary?: Record; aggregations?: Record; }>; @@ -104,7 +106,8 @@ class AgentClientImpl implements AgentClient { constructor( private readonly internalEsClient: ElasticsearchClient, private readonly soClient: SavedObjectsClientContract, - private readonly preflightCheck?: () => void | Promise + private readonly preflightCheck?: () => void | Promise, + private readonly spaceId?: string ) {} public async listAgents( @@ -114,7 +117,10 @@ class AgentClientImpl implements AgentClient { } ) { await this.#runPreflight(); - return getAgentsByKuery(this.internalEsClient, this.soClient, options); + return getAgentsByKuery(this.internalEsClient, this.soClient, { + ...options, + spaceId: this.spaceId, + }); } public async getAgent(agentId: string) { @@ -133,7 +139,8 @@ class AgentClientImpl implements AgentClient { this.internalEsClient, this.soClient, agentPolicyId, - filterKuery + filterKuery, + this.spaceId ); } @@ -161,14 +168,23 @@ export class AgentServiceImpl implements AgentService { public asScoped(req: KibanaRequest) { const preflightCheck = async () => { const authz = await getAuthzFromRequest(req); - if (!authz.fleet.all) { + if (!authz.fleet.all && !authz.fleet.readAgents) { throw new FleetUnauthorizedError( `User does not have adequate permissions to access Fleet agents.` ); } }; - return new AgentClientImpl(this.internalEsClient, this.soClient, preflightCheck); + const soClient = appContextService.getInternalUserSOClientForSpaceId( + appContextService.getSavedObjects().getScopedClient(req).getCurrentNamespace() + ); + + return new AgentClientImpl( + this.internalEsClient, + soClient, + preflightCheck, + soClient.getCurrentNamespace() ?? DEFAULT_NAMESPACE_STRING + ); } public get asInternalUser() { diff --git a/x-pack/plugins/fleet/server/services/agents/crud.ts b/x-pack/plugins/fleet/server/services/agents/crud.ts index 67b4b964796a6..e6bcd5f954f2e 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.ts @@ -4,16 +4,16 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + +import { groupBy } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { SortResults } from '@elastic/elasticsearch/lib/api/types'; import type { SavedObjectsClientContract, ElasticsearchClient } from '@kbn/core/server'; import type { KueryNode } from '@kbn/es-query'; import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; - +import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import type { AggregationsAggregationContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { groupBy } from 'lodash'; - import type { AgentSOAttributes, Agent, ListWithKuery } from '../../types'; import { appContextService, agentPolicyService } from '..'; import type { AgentStatus, FleetServerAgent } from '../../../common/types'; @@ -37,7 +37,9 @@ import { getLatestAvailableAgentVersion } from './versions'; const INACTIVE_AGENT_CONDITION = `status:inactive OR status:unenrolled`; const ACTIVE_AGENT_CONDITION = `NOT (${INACTIVE_AGENT_CONDITION})`; -function _joinFilters(filters: Array): KueryNode | undefined { +export function _joinFilters( + filters: Array +): KueryNode | undefined { try { return filters .filter((filter) => filter !== undefined) @@ -214,6 +216,7 @@ export async function getAgentsByKuery( soClient: SavedObjectsClientContract, options: ListWithKuery & { showInactive: boolean; + spaceId?: string; getStatusSummary?: boolean; sortField?: string; sortOrder?: 'asc' | 'desc'; @@ -241,9 +244,19 @@ export async function getAgentsByKuery( searchAfter, pitId, aggregations, + spaceId, } = options; const filters = []; + const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness; + if (useSpaceAwareness && spaceId) { + if (spaceId === DEFAULT_SPACE_ID) { + filters.push(`namespaces:"${DEFAULT_SPACE_ID}" or not namespaces:*`); + } else { + filters.push(`namespaces:"${spaceId}"`); + } + } + if (kuery && kuery !== '') { filters.push(kuery); } diff --git a/x-pack/plugins/fleet/server/services/agents/helpers.ts b/x-pack/plugins/fleet/server/services/agents/helpers.ts index 21e4e864e3b6f..2ddad0c24abab 100644 --- a/x-pack/plugins/fleet/server/services/agents/helpers.ts +++ b/x-pack/plugins/fleet/server/services/agents/helpers.ts @@ -60,6 +60,7 @@ export function searchHitToAgent( const agent: Agent = { id: hit._id, type: hit._source?.type!, + namespaces: hit._source?.namespaces, active: hit._source?.active!, enrolled_at: hit._source?.enrolled_at!, unenrolled_at: hit._source?.unenrolled_at!, diff --git a/x-pack/plugins/fleet/server/services/agents/status.ts b/x-pack/plugins/fleet/server/services/agents/status.ts index f4d0e312ba027..31fb9f5fe9218 100644 --- a/x-pack/plugins/fleet/server/services/agents/status.ts +++ b/x-pack/plugins/fleet/server/services/agents/status.ts @@ -6,10 +6,9 @@ */ import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; - import { toElasticsearchQuery } from '@kbn/es-query'; import { fromKueryExpression } from '@kbn/es-query'; - +import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import type { AggregationsTermsAggregateBase, AggregationsTermsBucketBase, @@ -45,13 +44,27 @@ export async function getAgentStatusForAgentPolicy( esClient: ElasticsearchClient, soClient: SavedObjectsClientContract, agentPolicyId?: string, - filterKuery?: string + filterKuery?: string, + spaceId?: string ) { const logger = appContextService.getLogger(); const runtimeFields = await buildAgentStatusRuntimeField(soClient); const clauses: QueryDslQueryContainer[] = []; + const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness; + if (useSpaceAwareness && spaceId) { + if (spaceId === DEFAULT_SPACE_ID) { + clauses.push( + toElasticsearchQuery( + fromKueryExpression(`namespaces:"${DEFAULT_SPACE_ID}" or not namespaces:*`) + ) + ); + } else { + clauses.push(toElasticsearchQuery(fromKueryExpression(`namespaces:"${spaceId}"`))); + } + } + if (filterKuery) { const kueryAsElasticsearchQuery = toElasticsearchQuery( fromKueryExpression(removeSOAttributes(filterKuery)) diff --git a/x-pack/plugins/fleet/server/services/agents/update_agent_tags.test.ts b/x-pack/plugins/fleet/server/services/agents/update_agent_tags.test.ts index d962279f0ca32..b118b8ff03ac8 100644 --- a/x-pack/plugins/fleet/server/services/agents/update_agent_tags.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/update_agent_tags.test.ts @@ -22,6 +22,7 @@ jest.mock('../app_context', () => { getLogger: () => loggerMock.create(), getConfig: () => {}, getMessageSigningService: jest.fn(), + getExperimentalFeatures: jest.fn().mockResolvedValue({}), }, }; }); diff --git a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.test.ts b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.test.ts index ef2209c173510..6d37dd44a7f5e 100644 --- a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.test.ts +++ b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.test.ts @@ -17,7 +17,11 @@ import { agentPolicyService } from '../agent_policy'; import { auditLoggingService } from '../audit_logging'; import { appContextService } from '../app_context'; -import { deleteEnrollmentApiKey, generateEnrollmentAPIKey } from './enrollment_api_key'; +import { + deleteEnrollmentApiKey, + generateEnrollmentAPIKey, + getEnrollmentAPIKey, +} from './enrollment_api_key'; jest.mock('../audit_logging'); jest.mock('../agent_policy'); @@ -147,4 +151,49 @@ describe('enrollment api keys', () => { }); }); }); + + describe('getEnrollementApiKey', () => { + it('should not allow to retrieve a key in the current space', async () => { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + + esClient.get.mockResolvedValue({ + _id: 'test-id', + _index: ENROLLMENT_API_KEYS_INDEX, + _source: { + active: true, + created_at: new Date().toISOString(), + api_key_id: 'test-enrollment-api-key-id', + namespaces: ['test'], + }, + found: true, + }); + + const enrollmentKey = await getEnrollmentAPIKey( + esClient, + 'test-enrollment-api-key-id', + 'test' + ); + expect(enrollmentKey).toBeDefined(); + }); + it('should not allow to retrieve a key in a different space', async () => { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + + esClient.get.mockResolvedValue({ + _id: 'test-id', + _index: ENROLLMENT_API_KEYS_INDEX, + _source: { + active: true, + created_at: new Date().toISOString(), + api_key_id: 'test-enrollment-api-key-id', + }, + found: true, + }); + + await expect( + getEnrollmentAPIKey(esClient, 'test-enrollment-api-key-id', 'test') + ).rejects.toThrowError( + 'Enrollment api key test-enrollment-api-key-id not found in namespace' + ); + }); + }); }); diff --git a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts index 02e1e9e25e194..e89917143732f 100644 --- a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts +++ b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts @@ -11,8 +11,8 @@ import { i18n } from '@kbn/i18n'; import { errors } from '@elastic/elasticsearch'; import type { SavedObjectsClientContract, ElasticsearchClient } from '@kbn/core/server'; -import { toElasticsearchQuery, fromKueryExpression } from '@kbn/es-query'; - +import { toElasticsearchQuery } from '@kbn/es-query'; +import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import type { ESSearchResponse as SearchResponse } from '@kbn/es-types'; import type { EnrollmentAPIKey, FleetServerEnrollmentAPIKey } from '../../types'; @@ -20,9 +20,8 @@ import { FleetError, EnrollmentKeyNameExistsError, EnrollmentKeyNotFoundError } import { ENROLLMENT_API_KEYS_INDEX } from '../../constants'; import { agentPolicyService } from '../agent_policy'; import { escapeSearchQueryPhrase } from '../saved_object'; - import { auditLoggingService } from '../audit_logging'; - +import { _joinFilters } from '../agents'; import { appContextService } from '../app_context'; import { invalidateAPIKeys } from './security'; @@ -38,10 +37,38 @@ export async function listEnrollmentApiKeys( kuery?: string; query?: ReturnType; showInactive?: boolean; + spaceId?: string; } ): Promise<{ items: EnrollmentAPIKey[]; total: any; page: any; perPage: any }> { - const { page = 1, perPage = 20, kuery } = options; - const query = options.query ?? (kuery && toElasticsearchQuery(fromKueryExpression(kuery))); + const { page = 1, perPage = 20, kuery, spaceId } = options; + // const query = options.query ?? (kuery && toElasticsearchQuery(fromKueryExpression(kuery))); + + let query: ReturnType | undefined; + if (options.query && spaceId) { + throw new Error('not implemented (query should only be used in Fleet internal usage)'); + } + if (!options.query) { + const filters: string[] = []; + + if (kuery) { + filters.push(kuery); + } + + const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness; + if (useSpaceAwareness && spaceId) { + if (spaceId === DEFAULT_SPACE_ID) { + // TODO use constant + filters.push(`namespaces:"${DEFAULT_SPACE_ID}" or not namespaces:*`); + } else { + filters.push(`namespaces:"${spaceId}"`); + } + } + + const kueryNode = _joinFilters(filters); + query = kueryNode ? toElasticsearchQuery(kueryNode) : undefined; + } else { + query = options.query; + } const res = await esClient.search>({ index: ENROLLMENT_API_KEYS_INDEX, @@ -80,7 +107,8 @@ export async function hasEnrollementAPIKeysForPolicy( export async function getEnrollmentAPIKey( esClient: ElasticsearchClient, - id: string + id: string, + spaceId?: string ): Promise { try { const body = await esClient.get({ @@ -88,6 +116,16 @@ export async function getEnrollmentAPIKey( id, }); + if (spaceId) { + if (spaceId === DEFAULT_SPACE_ID) { + if (body._source?.namespaces && !body._source?.namespaces.includes(DEFAULT_SPACE_ID)) { + throw new EnrollmentKeyNotFoundError(`Enrollment api key ${id} not found in namespace`); + } + } else if (!body._source?.namespaces?.includes(spaceId)) { + throw new EnrollmentKeyNotFoundError(`Enrollment api key ${id} not found in namespace`); + } + } + // @ts-expect-error esDocToEnrollmentApiKey doesn't accept optional _source return esDocToEnrollmentApiKey(body); } catch (e) { @@ -106,12 +144,13 @@ export async function getEnrollmentAPIKey( export async function deleteEnrollmentApiKey( esClient: ElasticsearchClient, id: string, - forceDelete = false + forceDelete = false, + spaceId?: string ) { const logger = appContextService.getLogger(); logger.debug(`Deleting enrollment API key ${id}`); - const enrollmentApiKey = await getEnrollmentAPIKey(esClient, id); + const enrollmentApiKey = await getEnrollmentAPIKey(esClient, id, spaceId); auditLoggingService.writeCustomAuditLog({ message: `User deleting enrollment API key [id=${enrollmentApiKey.id}] [api_key_id=${enrollmentApiKey.api_key_id}]`, diff --git a/x-pack/plugins/fleet/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts index 533b57f6f7653..9770d6a696e85 100644 --- a/x-pack/plugins/fleet/server/services/app_context.ts +++ b/x-pack/plugins/fleet/server/services/app_context.ts @@ -22,6 +22,7 @@ import type { PluginStart as DataPluginStart } from '@kbn/data-plugin/server'; import type { EncryptedSavedObjectsClient, EncryptedSavedObjectsPluginSetup, + EncryptedSavedObjectsPluginStart, } from '@kbn/encrypted-saved-objects-plugin/server'; import type { SecurityPluginStart, SecurityPluginSetup } from '@kbn/security-plugin/server'; @@ -47,14 +48,16 @@ import type { } from '../types'; import type { FleetAppContext } from '../plugin'; import type { TelemetryEventsSender } from '../telemetry/sender'; +import { UNINSTALL_TOKENS_SAVED_OBJECT_TYPE } from '../constants'; import type { MessageSigningServiceInterface } from '..'; -import type { BulkActionsResolver } from './agents'; -import type { UninstallTokenServiceInterface } from './security/uninstall_token_service'; +import type { BulkActionsResolver } from './agents/bulk_actions_resolver'; +import { type UninstallTokenServiceInterface } from './security/uninstall_token_service'; class AppContextService { private encryptedSavedObjects: EncryptedSavedObjectsClient | undefined; private encryptedSavedObjectsSetup: EncryptedSavedObjectsPluginSetup | undefined; + private encryptedSavedObjectsStart: EncryptedSavedObjectsPluginStart | undefined; private data: DataPluginStart | undefined; private esClient: ElasticsearchClient | undefined; private experimentalFeatures?: ExperimentalFeatures; @@ -80,6 +83,7 @@ class AppContextService { public start(appContext: FleetAppContext) { this.data = appContext.data; this.esClient = appContext.elasticsearch.client.asInternalUser; + this.encryptedSavedObjectsStart = appContext.encryptedSavedObjectsStart; this.encryptedSavedObjects = appContext.encryptedSavedObjectsStart?.getClient(); this.encryptedSavedObjectsSetup = appContext.encryptedSavedObjectsSetup; this.securitySetup = appContext.securitySetup; @@ -190,6 +194,7 @@ class AppContextService { // soClient as kibana internal users, be careful on how you use it, security is not enabled return appContextService.getSavedObjects().getScopedClient(request, { + includedHiddenTypes: [UNINSTALL_TOKENS_SAVED_OBJECT_TYPE], excludedExtensions: [SECURITY_EXTENSION_ID], }); } @@ -209,6 +214,7 @@ class AppContextService { // soClient as kibana internal users, be careful on how you use it, security is not enabled return appContextService.getSavedObjects().getScopedClient(request, { + includedHiddenTypes: [UNINSTALL_TOKENS_SAVED_OBJECT_TYPE], excludedExtensions: [SECURITY_EXTENSION_ID], }); } @@ -253,6 +259,10 @@ class AppContextService { return this.encryptedSavedObjectsSetup; } + public getEncryptedSavedObjectsStart() { + return this.encryptedSavedObjectsStart; + } + public getKibanaVersion() { return this.kibanaVersion; } 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 f3d59c9607a38..82989715be0e1 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 @@ -24,7 +24,12 @@ describe('tagKibanaAssets', () => { description: '', name: 'Managed', }; - const managedTagPayloadArg2 = { id: 'fleet-managed-default', overwrite: true, refresh: false }; + const managedTagPayloadArg2 = { + id: 'fleet-managed-default', + overwrite: true, + refresh: false, + managed: true, + }; beforeEach(() => { savedObjectTagAssignmentService.updateTagAssignments.mockReset(); @@ -55,7 +60,7 @@ describe('tagKibanaAssets', () => { description: '', color: '#0077CC', }, - { id: 'fleet-managed-default', overwrite: true, refresh: false } + { id: 'fleet-managed-default', overwrite: true, refresh: false, managed: true } ); expect(savedObjectTagClient.create).toHaveBeenCalledWith( { @@ -63,7 +68,7 @@ describe('tagKibanaAssets', () => { description: '', color: '#4DD2CA', }, - { id: 'fleet-pkg-system-default', overwrite: true, refresh: false } + { id: 'fleet-pkg-system-default', overwrite: true, refresh: false, managed: true } ); expect(savedObjectTagAssignmentService.updateTagAssignments).toHaveBeenCalledWith({ tags: ['fleet-managed-default', 'fleet-pkg-system-default'], @@ -201,7 +206,7 @@ describe('tagKibanaAssets', () => { description: '', color: '#0077CC', }, - { id: 'fleet-managed-default', overwrite: true, refresh: false } + { id: 'fleet-managed-default', overwrite: true, refresh: false, managed: true } ); expect(savedObjectTagClient.create).toHaveBeenCalledWith( @@ -210,7 +215,7 @@ describe('tagKibanaAssets', () => { description: '', color: '#4DD2CA', }, - { id: 'fleet-pkg-system-default', overwrite: true, refresh: false } + { id: 'fleet-pkg-system-default', overwrite: true, refresh: false, managed: true } ); expect(savedObjectTagAssignmentService.updateTagAssignments).toHaveBeenCalledWith({ tags: ['managed', 'fleet-pkg-system-default'], @@ -248,7 +253,7 @@ describe('tagKibanaAssets', () => { description: '', color: '#0077CC', }, - { id: 'fleet-managed-default', overwrite: true, refresh: false } + { id: 'fleet-managed-default', overwrite: true, refresh: false, managed: true } ); expect(savedObjectTagClient.create).not.toHaveBeenCalledWith( @@ -257,7 +262,7 @@ describe('tagKibanaAssets', () => { description: '', color: '#4DD2CA', }, - { id: 'system', overwrite: true, refresh: false } + { id: 'system', overwrite: true, refresh: false, managed: true } ); expect(savedObjectTagAssignmentService.updateTagAssignments).toHaveBeenCalledWith({ tags: ['fleet-managed-default', 'system'], @@ -339,7 +344,7 @@ describe('tagKibanaAssets', () => { description: '', name: 'TestPackage', }, - { id: 'fleet-pkg-test-pkg-default', overwrite: true, refresh: false } + { id: 'fleet-pkg-test-pkg-default', overwrite: true, refresh: false, managed: true } ); expect(savedObjectTagClient.create).toHaveBeenCalledWith( { @@ -351,6 +356,7 @@ describe('tagKibanaAssets', () => { id: FOO_TAG_ID, overwrite: true, refresh: false, + managed: true, } ); expect(savedObjectTagAssignmentService.updateTagAssignments).toHaveBeenCalledTimes(3); @@ -436,7 +442,7 @@ describe('tagKibanaAssets', () => { description: '', name: 'TestPackage', }, - { id: 'fleet-pkg-test-pkg-default', overwrite: true, refresh: false } + { id: 'fleet-pkg-test-pkg-default', overwrite: true, refresh: false, managed: true } ); expect(savedObjectTagClient.create).toHaveBeenCalledWith( { @@ -448,6 +454,7 @@ describe('tagKibanaAssets', () => { id: BAR_TAG_ID, overwrite: true, refresh: false, + managed: true, } ); expect(savedObjectTagAssignmentService.updateTagAssignments).toHaveBeenCalledTimes(3); @@ -538,7 +545,7 @@ describe('tagKibanaAssets', () => { description: '', name: 'TestPackage', }, - { id: 'fleet-pkg-test-pkg-default', overwrite: true, refresh: false } + { id: 'fleet-pkg-test-pkg-default', overwrite: true, refresh: false, managed: true } ); expect(savedObjectTagClient.create).toHaveBeenCalledWith( { @@ -550,6 +557,7 @@ describe('tagKibanaAssets', () => { id: MY_CUSTOM_TAG_ID, overwrite: true, refresh: false, + managed: true, } ); expect(savedObjectTagAssignmentService.updateTagAssignments).toHaveBeenCalledTimes(3); @@ -684,7 +692,7 @@ describe('tagKibanaAssets', () => { description: '', color: '#4DD2CA', }, - { id: 'fleet-pkg-test-pkg-default', overwrite: true, refresh: false } + { id: 'fleet-pkg-test-pkg-default', overwrite: true, refresh: false, managed: true } ); }); @@ -728,7 +736,7 @@ describe('tagKibanaAssets', () => { description: '', name: 'TestPackage', }, - { id: 'fleet-pkg-test-pkg-default', overwrite: true, refresh: false } + { id: 'fleet-pkg-test-pkg-default', overwrite: true, refresh: false, managed: true } ); expect(savedObjectTagClient.create).toHaveBeenCalledWith( { @@ -736,7 +744,7 @@ describe('tagKibanaAssets', () => { description: 'Tag defined in package-spec', name: 'Security Solution', }, - { id: 'security-solution-default', overwrite: true, refresh: false } + { id: 'security-solution-default', overwrite: true, refresh: false, managed: true } ); }); @@ -779,7 +787,12 @@ describe('tagKibanaAssets', () => { description: '', name: 'TestPackage', }, - { id: 'fleet-pkg-test-pkg-my-secondary-space', overwrite: true, refresh: false } + { + id: 'fleet-pkg-test-pkg-my-secondary-space', + overwrite: true, + refresh: false, + managed: true, + } ); expect(savedObjectTagClient.create).toHaveBeenCalledWith( { @@ -787,7 +800,12 @@ describe('tagKibanaAssets', () => { description: 'Tag defined in package-spec', name: 'Security Solution', }, - { id: 'security-solution-my-secondary-space', overwrite: true, refresh: false } + { + id: 'security-solution-my-secondary-space', + overwrite: true, + refresh: false, + managed: true, + } ); }); }); 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 5e7d4477bbbd3..86127c19e2811 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 @@ -179,7 +179,7 @@ async function ensureManagedTag( description: '', color: MANAGED_TAG_COLOR, }, - { id: managedTagId, overwrite: true, refresh: false } + { id: managedTagId, overwrite: true, refresh: false, managed: true } ); return managedTagId; @@ -206,7 +206,7 @@ async function ensurePackageTag( description: '', color: PACKAGE_TAG_COLOR, }, - { id: packageTagId, overwrite: true, refresh: false } + { id: packageTagId, overwrite: true, refresh: false, managed: true } ); return packageTagId; @@ -232,7 +232,7 @@ async function getPackageSpecTags( description: 'Tag defined in package-spec', color: getRandomColor(), }, - { id: uniqueTagId, overwrite: true, refresh: false } + { id: uniqueTagId, overwrite: true, refresh: false, managed: true } ); } const assetTypes = getAssetTypesObjectReferences(tag?.asset_types, taggableAssets); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.test.ts new file mode 100644 index 0000000000000..17958699365e6 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.test.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { elasticsearchServiceMock, savedObjectsClientMock } from '@kbn/core/server/mocks'; +import { times } from 'lodash'; + +import { appContextService } from '../..'; + +import { createAppContextStartContractMock } from '../../../mocks'; + +import { fetchFindLatestPackageOrThrow } from '../registry'; + +import { bulkInstallPackages } from './bulk_install_packages'; + +const mockedFetchFindLatestPackageOrThrow = jest.mocked(fetchFindLatestPackageOrThrow); + +jest.mock('../registry', () => { + return { + fetchFindLatestPackageOrThrow: jest.fn().mockResolvedValue({}), + }; +}); + +describe('bulkInstallPackages', () => { + let mockContract: ReturnType; + const mockSoClient = savedObjectsClientMock.create(); + const mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + + beforeEach(() => { + mockContract = createAppContextStartContractMock(); + appContextService.start(mockContract); + }); + + describe('only makes one installation attempt per unique package name', () => { + test('with plain strings', async () => { + const NUM_PACKAGES = 50000; + + const packagesToInstall = times(NUM_PACKAGES, () => 'test_package'); + + await bulkInstallPackages({ + savedObjectsClient: mockSoClient, + packagesToInstall, + esClient: mockEsClient, + spaceId: 'default', + }); + + expect(mockedFetchFindLatestPackageOrThrow).toHaveBeenCalledTimes(1); + }); + + test('with objects', async () => { + const NUM_PACKAGES = 50000; + + const packagesToInstall = times(NUM_PACKAGES, () => ({ + name: 'test_package', + version: 'latest', + })); + + await bulkInstallPackages({ + savedObjectsClient: mockSoClient, + packagesToInstall, + esClient: mockEsClient, + spaceId: 'default', + }); + + expect(mockedFetchFindLatestPackageOrThrow).toHaveBeenCalledTimes(1); + }); + test('with a mixture of plain strings and objects', async () => { + const NUM_PACKAGES = 20000; + + const stringPackagesToInstall = times(NUM_PACKAGES, () => 'test_package_1'); + const objectPackagesToInstall = times(NUM_PACKAGES, () => ({ + name: 'test_package_2', + version: 'latest', + })); + + await bulkInstallPackages({ + savedObjectsClient: mockSoClient, + packagesToInstall: [...stringPackagesToInstall, ...objectPackagesToInstall], + esClient: mockEsClient, + spaceId: 'default', + }); + + expect(mockedFetchFindLatestPackageOrThrow).toHaveBeenCalledTimes(2); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/fleet_server/index.test.ts b/x-pack/plugins/fleet/server/services/fleet_server/index.test.ts index 0a4ce31504383..f00d78cd59ad9 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server/index.test.ts +++ b/x-pack/plugins/fleet/server/services/fleet_server/index.test.ts @@ -130,6 +130,7 @@ describe('getFleetServerPolicies', () => { version: '1.0.0', }, policy_id: 'fs-policy-1', + policy_ids: ['fs-policy-1'], }, { id: 'package-policy-2', @@ -140,6 +141,7 @@ describe('getFleetServerPolicies', () => { version: '1.0.0', }, policy_id: 'fs-policy-2', + policy_ids: ['fs-policy-2'], }, { id: 'package-policy-3', @@ -150,6 +152,7 @@ describe('getFleetServerPolicies', () => { version: '1.0.0', }, policy_id: 'agent-policy-2', + policy_ids: ['agent-policy-2'], }, ]; const mockFleetServerPolicies = [ @@ -218,7 +221,7 @@ describe('hasActiveFleetServersForPolicies', () => { const hasFs = await hasFleetServersForPolicies( mockEsClient, mockSoClient, - ['policy-1'], + [{ id: 'policy-1' }], true ); expect(hasFs).toBe(true); @@ -241,7 +244,7 @@ describe('hasActiveFleetServersForPolicies', () => { const hasFs = await hasFleetServersForPolicies( mockEsClient, mockSoClient, - ['policy-1'], + [{ id: 'policy-1' }], true ); expect(hasFs).toBe(true); @@ -264,7 +267,7 @@ describe('hasActiveFleetServersForPolicies', () => { const hasFs = await hasFleetServersForPolicies( mockEsClient, mockSoClient, - ['policy-1'], + [{ id: 'policy-1' }], true ); expect(hasFs).toBe(false); @@ -286,7 +289,9 @@ describe('hasActiveFleetServersForPolicies', () => { online: 0, error: 0, }); - const hasFs = await hasFleetServersForPolicies(mockEsClient, mockSoClient, ['policy-1']); + const hasFs = await hasFleetServersForPolicies(mockEsClient, mockSoClient, [ + { id: 'policy-1' }, + ]); expect(hasFs).toBe(true); }); }); diff --git a/x-pack/plugins/fleet/server/services/fleet_server/index.ts b/x-pack/plugins/fleet/server/services/fleet_server/index.ts index 88ba7ccc710d5..004a0deeea7b7 100644 --- a/x-pack/plugins/fleet/server/services/fleet_server/index.ts +++ b/x-pack/plugins/fleet/server/services/fleet_server/index.ts @@ -6,15 +6,15 @@ */ import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; +import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import semverGte from 'semver/functions/gte'; import semverCoerce from 'semver/functions/coerce'; +import { uniqBy } from 'lodash'; import type { AgentPolicy } from '../../../common/types'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE, FLEET_SERVER_PACKAGE } from '../../../common/constants'; - import { SO_SEARCH_LIMIT } from '../../constants'; import { getAgentsByKuery, getAgentStatusById } from '../agents'; - import { packagePolicyService } from '../package_policy'; import { agentPolicyService } from '../agent_policy'; import { getAgentStatusForAgentPolicy } from '../agents'; @@ -28,16 +28,20 @@ export const getFleetServerPolicies = async ( ): Promise => { const fleetServerPackagePolicies = await packagePolicyService.list(soClient, { kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${FLEET_SERVER_PACKAGE}`, + spaceId: '*', }); // Extract associated fleet server agent policy IDs - const fleetServerAgentPolicyIds = [ - ...new Set(fleetServerPackagePolicies.items.flatMap((p) => p.policy_ids)), - ]; + const fleetServerAgentPolicyIds = fleetServerPackagePolicies.items.flatMap((p) => + p.policy_ids?.map((id) => ({ id, spaceId: p.spaceId } ?? [])) + ); // Retrieve associated agent policies const fleetServerAgentPolicies = fleetServerAgentPolicyIds.length - ? await agentPolicyService.getByIDs(soClient, fleetServerAgentPolicyIds) + ? await agentPolicyService.getByIDs( + soClient, + uniqBy(fleetServerAgentPolicyIds, (p) => `${p?.spaceId ?? ''}:${p.id}`) + ) : []; return fleetServerAgentPolicies; @@ -51,15 +55,24 @@ export const getFleetServerPolicies = async ( export const hasFleetServersForPolicies = async ( esClient: ElasticsearchClient, soClient: SavedObjectsClientContract, - agentPolicyIds: string[], + agentPolicies: Array>, activeOnly: boolean = false ): Promise => { - if (agentPolicyIds.length > 0) { + if (agentPolicies.length > 0) { const agentStatusesRes = await getAgentStatusForAgentPolicy( esClient, soClient, undefined, - agentPolicyIds.map((id) => `policy_id:${id}`).join(' or ') + agentPolicies + .map(({ id, space_id: spaceId }) => { + const space = + spaceId && spaceId !== DEFAULT_SPACE_ID + ? `namespaces:"${spaceId}"` + : `not namespaces:* or namespaces:"${DEFAULT_SPACE_ID}"`; + + return `(policy_id:${id} and (${space}))`; + }) + .join(' or ') ); return activeOnly @@ -79,7 +92,7 @@ export async function hasFleetServers( return await hasFleetServersForPolicies( esClient, soClient, - (await getFleetServerPolicies(soClient)).map((policy) => policy.id) + await getFleetServerPolicies(soClient) ); } diff --git a/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.test.ts b/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.test.ts index 649fa6fc848c5..f8fe92890d429 100644 --- a/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.test.ts +++ b/x-pack/plugins/fleet/server/services/metrics/fleet_metrics_task.test.ts @@ -9,6 +9,7 @@ import { coreMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import type { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server'; import { TaskStatus } from '@kbn/task-manager-plugin/server'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { CoreSetup } from '@kbn/core/server'; import type { ElasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; @@ -197,6 +198,18 @@ describe('fleet metrics task', () => { }); }); + it('should not run if task is outdated', async () => { + const result = await runTask({ ...MOCK_TASK_INSTANCE, id: 'old-id' }); + + expect(esClient.index).not.toHaveBeenCalled(); + expect(esClient.bulk).not.toHaveBeenCalled(); + + expect(appContextService.getLogger().info).toHaveBeenCalledWith( + 'Outdated task version: Got [old-id] from task instance. Current version is [Fleet-Metrics-Task:1.1.1]' + ); + expect(result).toEqual(getDeleteTaskRunResult()); + }); + it('should log errors from bulk create', async () => { esClient.bulk.mockResolvedValue({ errors: true, 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 0d1c0bd142169..e99b6d1fce68c 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 @@ -4,12 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { ConcreteTaskInstance, TaskManagerStartContract, TaskManagerSetupContract, } from '@kbn/task-manager-plugin/server'; -import { throwUnrecoverableError } from '@kbn/task-manager-plugin/server'; import type { ElasticsearchClient } from '@kbn/core/server'; import { withSpan } from '@kbn/apm-utils'; @@ -68,8 +68,12 @@ export class FleetMetricsTask { } // Check that this task is current if (taskInstance.id !== this.taskId) { - throwUnrecoverableError(new Error('Outdated task version for task: ' + taskInstance.id)); - return; + appContextService + .getLogger() + .info( + `Outdated task version: Got [${taskInstance.id}] from task instance. Current version is [${this.taskId}]` + ); + return getDeleteTaskRunResult(); } if (!this.esClient) { appContextService.getLogger().debug('esClient not set, skipping Fleet metrics task'); diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index cae84f2c8f5b1..dd4d26e28ca7e 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -726,7 +726,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { public async list( soClient: SavedObjectsClientContract, - options: ListWithKuery + options: ListWithKuery & { spaceId?: string } ): Promise> { const { page = 1, perPage = 20, sortField = 'updated_at', sortOrder = 'desc', kuery } = options; @@ -737,6 +737,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { page, perPage, filter: kuery ? normalizeKuery(SAVED_OBJECT_TYPE, kuery) : undefined, + namespaces: options.spaceId ? [options.spaceId] : undefined, }); for (const packagePolicy of packagePolicies?.saved_objects ?? []) { @@ -752,6 +753,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { id: packagePolicySO.id, version: packagePolicySO.version, ...packagePolicySO.attributes, + spaceId: packagePolicySO.namespaces?.[0], })), total: packagePolicies?.total, page, diff --git a/x-pack/plugins/fleet/server/services/package_policy_service.ts b/x-pack/plugins/fleet/server/services/package_policy_service.ts index 56e68537afb7e..5f5e775bf910a 100644 --- a/x-pack/plugins/fleet/server/services/package_policy_service.ts +++ b/x-pack/plugins/fleet/server/services/package_policy_service.ts @@ -105,7 +105,7 @@ export interface PackagePolicyClient { list( soClient: SavedObjectsClientContract, - options: ListWithKuery + options: ListWithKuery & { spaceId?: string } ): Promise>; listIds( diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts index b6dc2fb859d25..21caf5088f457 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts @@ -300,6 +300,9 @@ jest.mock('./app_context', () => ({ ), getUninstallTokenService: () => ({ generateTokenForPolicyId: jest.fn(), + scoped: jest.fn().mockReturnValue({ + generateTokenForPolicyId: jest.fn(), + }), }), getExternalCallbacks: jest.fn(), getCloud: jest.fn(), diff --git a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts index 944eab70d23c3..211026bf6e487 100644 --- a/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts +++ b/x-pack/plugins/fleet/server/services/security/uninstall_token_service/index.ts @@ -32,12 +32,11 @@ import type { import { isResponseError } from '@kbn/es-errors'; -import type { AgentPolicySOAttributes } from '../../../types'; +import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; +import type { AgentPolicySOAttributes } from '../../../types'; import { UninstallTokenError } from '../../../../common/errors'; - import type { GetUninstallTokensMetadataResponse } from '../../../../common/types/rest_spec/uninstall_token'; - import type { UninstallToken, UninstallTokenMetadata, @@ -168,18 +167,37 @@ export interface UninstallTokenServiceInterface { * */ checkTokenValidityForAllPolicies(): Promise; + + scoped(spaceId?: string): UninstallTokenServiceInterface; } export class UninstallTokenService implements UninstallTokenServiceInterface { private _soClient: SavedObjectsClientContract | undefined; + private isScoped = false; + + constructor( + private esoClient: EncryptedSavedObjectsClient, + soClient?: SavedObjectsClientContract + ) { + if (soClient) { + this._soClient = soClient; + this.isScoped = true; + } + } - constructor(private esoClient: EncryptedSavedObjectsClient) {} + public scoped(spaceId?: string) { + return new UninstallTokenService( + this.esoClient, + appContextService.getInternalUserSOClientForSpaceId(spaceId) + ); + } public async getToken(id: string): Promise { - const filter = `${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.id: "${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}:${id}"`; - + const namespacePrefix = this.soClient.getCurrentNamespace() + ? `${this.soClient.getCurrentNamespace()}:` + : ''; + const filter = `${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.id: "${namespacePrefix}${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}:${id}"`; const tokenObjects = await this.getDecryptedTokenObjects({ filter }); - return tokenObjects.length === 1 ? this.convertTokenObjectToToken( await this.getPolicyIdNameDictionary([tokenObjects[0].attributes.policy_id]), @@ -262,8 +280,11 @@ export class UninstallTokenService implements UninstallTokenServiceInterface { this.assertCreatedAt(_source.created_at); const policyId = _source[UNINSTALL_TOKENS_SAVED_OBJECT_TYPE].policy_id; + const namespacePrefix = this.soClient.getCurrentNamespace() + ? `${this.soClient.getCurrentNamespace()}:` + : ''; return { - id: _id.replace(`${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}:`, ''), + id: _id.replace(`${namespacePrefix}${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}:`, ''), policy_id: policyId, policy_name: policyIdNameDictionary[policyId] ?? null, created_at: _source.created_at, @@ -356,6 +377,9 @@ export class UninstallTokenService implements UninstallTokenServiceInterface { { type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, perPage: SO_SEARCH_LIMIT, + namespaces: this.isScoped + ? [this.soClient.getCurrentNamespace() || DEFAULT_SPACE_ID] + : undefined, ...options, } ); @@ -401,7 +425,6 @@ export class UninstallTokenService implements UninstallTokenServiceInterface { exclude?: AggregationsTermsExclude ): Promise>> { const bucketSize = 10000; - const query: SavedObjectsCreatePointInTimeFinderOptions = { type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, perPage: 0, @@ -451,7 +474,6 @@ export class UninstallTokenService implements UninstallTokenServiceInterface { // sorting and paginating buckets is done here instead of ES, // because SO query doesn't support `bucket_sort` aggResults.sort((a, b) => getCreatedAt(b) - getCreatedAt(a)); - return aggResults.map((bucket) => bucket.latest.hits.hits[0]); } @@ -578,6 +600,9 @@ export class UninstallTokenService implements UninstallTokenServiceInterface { await this.soClient.bulkCreate>( policyIdsBatch.map((policyId) => ({ type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, + initialNamespaces: this.soClient.getCurrentNamespace() + ? [this.soClient.getCurrentNamespace() as string] + : undefined, attributes: this.isEncryptionAvailable ? { policy_id: policyId, diff --git a/x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts b/x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts index ca23ef3bb8fe0..2d986b1e7bc6c 100644 --- a/x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts +++ b/x-pack/plugins/fleet/server/services/telemetry/fleet_usage_sender.ts @@ -4,12 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { ConcreteTaskInstance, TaskManagerStartContract, TaskManagerSetupContract, } from '@kbn/task-manager-plugin/server'; -import { throwUnrecoverableError } from '@kbn/task-manager-plugin/server'; import type { CoreSetup } from '@kbn/core/server'; import { withSpan } from '@kbn/apm-utils'; @@ -70,8 +70,12 @@ export class FleetUsageSender { } // Check that this task is current if (taskInstance.id !== this.taskId) { - throwUnrecoverableError(new Error('Outdated task version for task: ' + taskInstance.id)); - return; + appContextService + .getLogger() + .info( + `Outdated task version: Got [${taskInstance.id}] from task instance. Current version is [${this.taskId}]` + ); + return getDeleteTaskRunResult(); } appContextService.getLogger().info('Running Fleet Usage telemetry send task'); diff --git a/x-pack/plugins/fleet/server/services/telemetry/fleet_usages_schema.ts b/x-pack/plugins/fleet/server/services/telemetry/fleet_usages_schema.ts index 912d0c0413c06..1720470b65ad8 100644 --- a/x-pack/plugins/fleet/server/services/telemetry/fleet_usages_schema.ts +++ b/x-pack/plugins/fleet/server/services/telemetry/fleet_usages_schema.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { RootSchema } from '@kbn/analytics-client'; +import type { RootSchema } from '@kbn/core/server'; export const fleetAgentsSchema: RootSchema = { agents_per_version: { @@ -346,6 +346,19 @@ export const fleetUsagesSchema: RootSchema = { _meta: { description: 'Output types of agent policies' }, }, }, + count_with_global_data_tags: { + type: 'long', + _meta: { + description: 'Number of agent policies using global data tags', + }, + }, + avg_number_global_data_tags_per_policy: { + type: 'long', + _meta: { + description: + 'Average number of global data tags defined per agent policy (accross policies using global data tags)', + }, + }, }, }, agent_checkin_status: { diff --git a/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.test.ts b/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.test.ts index 909a78f74ae34..fc426e3574ae6 100644 --- a/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.test.ts +++ b/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.test.ts @@ -9,6 +9,7 @@ import { coreMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import type { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server'; import { TaskStatus } from '@kbn/task-manager-plugin/server'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { CoreSetup } from '@kbn/core/server'; import type { ElasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { loggingSystemMock } from '@kbn/core/server/mocks'; @@ -206,5 +207,14 @@ describe('check deleted files task', () => { { signal: abortController.signal } ); }); + + it('should not run if task is outdated', async () => { + const result = await runTask({ ...MOCK_TASK_INSTANCE, id: 'old-id' }); + + expect(esClient.search).not.toHaveBeenCalled(); + expect(esClient.updateByQuery).not.toHaveBeenCalled(); + + expect(result).toEqual(getDeleteTaskRunResult()); + }); }); }); diff --git a/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.ts b/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.ts index a7611d73cd313..b4541fbf21084 100644 --- a/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.ts +++ b/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.ts @@ -11,7 +11,7 @@ import type { TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; -import { throwUnrecoverableError } from '@kbn/task-manager-plugin/server'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { LoggerFactory } from '@kbn/core/server'; import { errors } from '@elastic/elasticsearch'; @@ -102,7 +102,10 @@ export class CheckDeletedFilesTask { // Check that this task is current if (taskInstance.id !== this.taskId) { - throwUnrecoverableError(new Error('Outdated task version')); + this.logger.info( + `Outdated task version: Got [${taskInstance.id}] from task instance. Current version is [${this.taskId}]` + ); + return getDeleteTaskRunResult(); } this.logger.info(`[runTask()] started`); diff --git a/x-pack/plugins/fleet/server/types/request_context.ts b/x-pack/plugins/fleet/server/types/request_context.ts index bc0e1c8886bff..dac735c9a3ed1 100644 --- a/x-pack/plugins/fleet/server/types/request_context.ts +++ b/x-pack/plugins/fleet/server/types/request_context.ts @@ -17,6 +17,7 @@ import type { import type { FleetAuthz } from '../../common/authz'; import type { AgentClient } from '../services'; import type { PackagePolicyClient } from '../services/package_policy_service'; +import type { UninstallTokenServiceInterface } from '../services/security/uninstall_token_service'; /** @internal */ export type FleetRequestHandlerContext = CustomRequestHandlerContext<{ @@ -32,6 +33,9 @@ export type FleetRequestHandlerContext = CustomRequestHandlerContext<{ asCurrentUser: PackagePolicyClient; asInternalUser: PackagePolicyClient; }; + uninstallTokenService: { + asCurrentUser: UninstallTokenServiceInterface; + }; /** * Saved Objects client configured to use kibana_system privileges instead of end-user privileges. Should only be * used by routes that have additional privilege checks for authorization (such as requiring superuser). diff --git a/x-pack/plugins/fleet/server/types/rest_spec/health_check.ts b/x-pack/plugins/fleet/server/types/rest_spec/health_check.ts index 8199a1b2c815a..0a3ca309ddf51 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/health_check.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/health_check.ts @@ -9,6 +9,8 @@ import { schema } from '@kbn/config-schema'; export const PostHealthCheckRequestSchema = { body: schema.object({ - host: schema.uri({ scheme: ['http', 'https'] }), + id: schema.string(), + // deprecated + host: schema.maybe(schema.uri({ scheme: ['http', 'https'] })), }), }; diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json index 8bee11fed5d0e..7a8f58732902f 100644 --- a/x-pack/plugins/fleet/tsconfig.json +++ b/x-pack/plugins/fleet/tsconfig.json @@ -86,7 +86,6 @@ "@kbn/core-saved-objects-api-server-mocks", "@kbn/core-saved-objects-api-server", "@kbn/logging", - "@kbn/analytics-client", "@kbn/core-logging-server-mocks", "@kbn/ml-is-populated-object", "@kbn/utils", diff --git a/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx b/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx index 9cebc018daa11..479c3e546dc9c 100644 --- a/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx +++ b/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx @@ -177,7 +177,7 @@ describe('SearchBar', () => { await focusAndUpdate(); expect(searchService.find).toHaveBeenCalledTimes(1); - // + simulateTypeChar('d'); await assertSearchResults(['Visualize • Kibana', 'Map • Kibana']); diff --git a/x-pack/plugins/global_search_bar/public/telemetry/event_types.ts b/x-pack/plugins/global_search_bar/public/telemetry/event_types.ts index bae58ca05b950..0b95e531d7f9c 100644 --- a/x-pack/plugins/global_search_bar/public/telemetry/event_types.ts +++ b/x-pack/plugins/global_search_bar/public/telemetry/event_types.ts @@ -5,8 +5,7 @@ * 2.0. */ -import { RootSchema } from '@kbn/analytics-client'; -import { EventTypeOpts } from '@kbn/core/public'; +import { RootSchema, EventTypeOpts } from '@kbn/core/public'; import { EventMetric, FieldType } from '../types'; const fields: Record>> = { diff --git a/x-pack/plugins/global_search_bar/tsconfig.json b/x-pack/plugins/global_search_bar/tsconfig.json index 1e66c8225abea..f566b0d86eddc 100644 --- a/x-pack/plugins/global_search_bar/tsconfig.json +++ b/x-pack/plugins/global_search_bar/tsconfig.json @@ -14,7 +14,6 @@ "@kbn/i18n", "@kbn/saved-objects-tagging-oss-plugin", "@kbn/core-chrome-browser", - "@kbn/analytics-client", "@kbn/react-kibana-context-render", ], "exclude": [ diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts index 235e5090a2695..8bd8672b8fbba 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts @@ -209,6 +209,9 @@ const registerHttpRequestMockHelpers = ( const setCreateIndexResponse = (response?: HttpResponse, error?: ResponseError) => mockResponse('PUT', `${INTERNAL_API_BASE_PATH}/indices/create`, response, error); + const setInferenceModels = (response?: HttpResponse, error?: ResponseError) => + mockResponse('GET', `${API_BASE_PATH}/inference/all`, response, error); + return { setLoadTemplatesResponse, setLoadIndicesResponse, @@ -238,6 +241,7 @@ const registerHttpRequestMockHelpers = ( setGetFieldsFromIndices, setGetPrivilegesResponse, setCreateEnrichPolicy, + setInferenceModels, }; }; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts index 5a15772d5e16a..91dd4d26c2a5b 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/index_details_page.helpers.ts @@ -13,7 +13,7 @@ import { } from '@kbn/test-jest-helpers'; import { HttpSetup } from '@kbn/core/public'; import { act } from 'react-dom/test-utils'; - +import { keys } from '@elastic/eui'; import { IndexDetailsTabId } from '../../../common/constants'; import { IndexDetailsPage } from '../../../public/application/sections/home/index_list/details_page'; import { WithAppDependencies } from '../helpers'; @@ -56,6 +56,12 @@ export interface IndexDetailsPageTestBed extends TestBed { setSearchBarValue: (searchValue: string) => Promise; findSearchResult: () => string; isSemanticTextBannerVisible: () => boolean; + selectSemanticTextField: (name: string, type: string) => Promise; + isReferenceFieldVisible: () => void; + selectInferenceIdButtonExists: () => void; + openSelectInferencePopover: () => void; + expectDefaultInferenceModelToExists: () => void; + expectCustomInferenceModelToExists: (customInference: string) => Promise; }; settings: { getCodeBlockContent: () => string; @@ -228,7 +234,7 @@ export const setup = async ({ component.update(); }, selectFilterFieldType: async (fieldType: string) => { - expect(testBed.exists('indexDetailsMappingsSelectFilter-text')).toBe(true); + expect(testBed.exists(fieldType)).toBe(true); await act(async () => { find(fieldType).simulate('click'); }); @@ -287,6 +293,7 @@ export const setup = async ({ await act(async () => { expect(exists('createFieldForm.addButton')).toBe(true); + expect(find('createFieldForm.addButton').props().disabled).toBeFalsy(); find('createFieldForm.addButton').simulate('click'); }); @@ -294,6 +301,41 @@ export const setup = async ({ } } }, + selectSemanticTextField: async (name: string, type: string) => { + expect(exists('comboBoxSearchInput')).toBe(true); + + const { form } = testBed; + form.setInputValue('nameParameterInput', name); + form.setInputValue('comboBoxSearchInput', type); + await act(async () => { + find('comboBoxSearchInput').simulate('keydown', { key: keys.ENTER }); + }); + // select semantic_text field + await act(async () => { + expect(exists('fieldTypesOptions-semantic_text')).toBe(true); + find('fieldTypesOptions-semantic_text').simulate('click'); + expect(exists('fieldTypesOptions-semantic_text')).toBe(false); + }); + }, + isReferenceFieldVisible: async () => { + expect(exists('referenceField.select')).toBe(true); + }, + selectInferenceIdButtonExists: async () => { + expect(exists('selectInferenceId')).toBe(true); + expect(exists('inferenceIdButton')).toBe(true); + find('inferenceIdButton').simulate('click'); + }, + openSelectInferencePopover: async () => { + expect(exists('addInferenceEndpointButton')).toBe(true); + expect(exists('manageInferenceEndpointButton')).toBe(true); + }, + expectDefaultInferenceModelToExists: async () => { + expect(exists('default-inference_elser_model_2')).toBe(true); + expect(exists('default-inference_e5')).toBe(true); + }, + expectCustomInferenceModelToExists: async (customInference: string) => { + expect(exists(customInference)).toBe(true); + }, }; const settings = { 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 37e2799678e79..18f43807041ff 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 @@ -18,6 +18,7 @@ import { breadcrumbService, IndexManagementBreadcrumb, } from '../../../public/application/services/breadcrumbs'; +import { documentationService } from '../../../public/application/services/documentation'; import { humanizeTimeStamp } from '../../../public/application/sections/home/data_stream_list/humanize_time_stamp'; import { createDataStreamPayload } from '../home/data_streams_tab.helpers'; import { @@ -58,6 +59,7 @@ describe('', () => { let httpSetup: ReturnType['httpSetup']; let httpRequestsMockHelpers: ReturnType['httpRequestsMockHelpers']; jest.spyOn(breadcrumbService, 'setBreadcrumbs'); + jest.spyOn(documentationService, 'setup'); beforeEach(async () => { const mockEnvironment = setupEnvironment(); @@ -571,14 +573,9 @@ describe('', () => { }, }; beforeEach(async () => { - httpRequestsMockHelpers.setUpdateIndexMappingsResponse(testIndexName, { - acknowledged: true, - }); - await act(async () => { testBed = await setup({ httpSetup }); }); - testBed.component.update(); await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Mappings); await testBed.actions.mappings.clickAddFieldButton(); @@ -634,43 +631,6 @@ describe('', () => { ); }); - it('can add a semantic_text field and can save mappings', async () => { - const mockIndexMappingResponseForSemanticText: any = { - ...testIndexMappings.mappings, - properties: { - ...testIndexMappings.mappings.properties, - sem: { - type: 'semantic_text', - inference_id: 'my-elser', - }, - }, - }; - httpRequestsMockHelpers.setLoadIndexMappingResponse(testIndexName, { - mappings: mockIndexMappingResponseForSemanticText, - }); - await testBed.actions.mappings.addNewMappingFieldNameAndType([ - { name: 'sem', type: 'semantic_text' }, - ]); - await testBed.actions.mappings.clickSaveMappingsButton(); - // add field button is available again - expect(testBed.exists('indexDetailsMappingsAddField')).toBe(true); - expect(testBed.find('semField-datatype').props()['data-type-value']).toBe('semantic_text'); - expect(httpSetup.get).toHaveBeenCalledTimes(5); - expect(httpSetup.get).toHaveBeenLastCalledWith( - `${API_BASE_PATH}/mapping/${testIndexName}`, - requestOptions - ); - // refresh mappings and page re-renders - expect(testBed.exists('indexDetailsMappingsAddField')).toBe(true); - expect(testBed.actions.mappings.isSearchBarDisabled()).toBe(false); - const treeViewContent = testBed.actions.mappings.getTreeViewContent('semField'); - expect(treeViewContent).toContain('sem'); - await testBed.actions.mappings.clickToggleViewButton(); - const jsonContent = testBed.actions.mappings.getCodeBlockContent(); - expect(jsonContent).toEqual( - JSON.stringify({ mappings: mockIndexMappingResponseForSemanticText }, null, 2) - ); - }); it('there is a callout with error message when save mappings fail', async () => { const error = { statusCode: 400, @@ -685,6 +645,116 @@ describe('', () => { await testBed.actions.mappings.clickSaveMappingsButton(); expect(testBed.actions.mappings.isSaveMappingsErrorDisplayed()).toBe(true); }); + describe('Add Semantic text field', () => { + const customInferenceModel = 'my-elser-model'; + beforeEach(async () => { + httpRequestsMockHelpers.setInferenceModels({ + data: [ + { + model_id: customInferenceModel, + task_type: 'sparse_embedding', + service: 'elser', + service_settings: { + num_allocations: 1, + num_threads: 1, + model_id: '.elser_model_2', + }, + task_settings: {}, + }, + ], + }); + await act(async () => { + testBed = await setup({ + httpSetup, + dependencies: { + config: { enableSemanticText: true }, + docLinks: { + links: { + ml: '', + enterpriseSearch: '', + }, + }, + plugins: { + ml: { + mlApi: { + trainedModels: { + getTrainedModels: jest.fn().mockResolvedValue([ + { + model_id: '.elser_model_2', + model_type: 'pytorch', + model_package: { + packaged_model_id: customInferenceModel, + model_repository: 'https://ml-models.elastic.co', + minimum_version: '11.0.0', + size: 438123914, + sha256: '', + metadata: {}, + tags: [], + vocabulary_file: 'elser_model_2.vocab.json', + }, + description: 'Elastic Learned Sparse EncodeR v2', + tags: ['elastic'], + }, + ]), + getTrainedModelStats: jest.fn().mockResolvedValue({ + count: 1, + trained_model_stats: [ + { + model_id: '.elser_model_2', + + deployment_stats: { + deployment_id: customInferenceModel, + model_id: '.elser_model_2', + threads_per_allocation: 1, + number_of_allocations: 1, + queue_capacity: 1024, + state: 'started', + }, + }, + { + model_id: '.elser_model_2', + + deployment_stats: { + deployment_id: '.elser_model_2', + model_id: '.elser_model_2', + threads_per_allocation: 1, + number_of_allocations: 1, + queue_capacity: 1024, + state: 'started', + }, + }, + ], + }), + }, + }, + }, + }, + }, + }); + }); + testBed.component.update(); + await testBed.actions.clickIndexDetailsTab(IndexDetailsSection.Mappings); + await testBed.actions.mappings.clickAddFieldButton(); + }); + it('can select semantic_text field', async () => { + await testBed.actions.mappings.selectSemanticTextField( + 'semantic_text_name', + 'Semantic text' + ); + + testBed.actions.mappings.isReferenceFieldVisible(); + testBed.actions.mappings.selectInferenceIdButtonExists(); + testBed.actions.mappings.openSelectInferencePopover(); + testBed.actions.mappings.expectDefaultInferenceModelToExists(); + testBed.actions.mappings.expectCustomInferenceModelToExists( + `custom-inference_${customInferenceModel}` + ); + + // can cancel new field + expect(testBed.exists('cancelButton')).toBe(true); + testBed.find('cancelButton').simulate('click'); + }); + }); }); describe('error loading mappings', () => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/select_inference_id.test.tsx similarity index 80% rename from x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.test.tsx rename to x-pack/plugins/index_management/__jest__/client_integration/index_details_page/select_inference_id.test.tsx index 4222cf493a9bf..c734943ec4592 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_details_page/select_inference_id.test.tsx @@ -7,13 +7,13 @@ import { registerTestBed } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; -import { SelectInferenceId } from './select_inference_id'; +import { SelectInferenceId } from '../../../public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id'; const onChangeMock = jest.fn(); const setValueMock = jest.fn(); const setNewInferenceEndpointMock = jest.fn(); -jest.mock('../../../../../app_context', () => ({ +jest.mock('../../../public/application/app_context', () => ({ useAppContext: jest.fn().mockReturnValue({ core: { application: {} }, docLinks: {}, @@ -30,6 +30,17 @@ jest.mock('../../../../../app_context', () => ({ }), })); +jest.mock( + '../../../public/application/components/component_templates/component_templates_context', + () => ({ + useComponentTemplatesContext: jest.fn().mockReturnValue({ + toasts: { + addError: jest.fn(), + addSuccess: jest.fn(), + }, + }), + }) +); describe('SelectInferenceId', () => { let exists: any; let find: any; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index 33f97c6f61c8f..1347aaeade4f8 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -324,6 +324,7 @@ describe('Mappings editor: core', () => { _routing: { required: false, }, + subobjects: true, }; await act(async () => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx index 571449a5de29e..b326779d8a76c 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx @@ -19,6 +19,7 @@ import { SourceFieldSection } from './source_field_section'; import { MetaFieldSection } from './meta_field_section'; import { RoutingSection } from './routing_section'; import { MapperSizePluginSection } from './mapper_size_plugin_section'; +import { SubobjectsSection } from './subobjects_section'; import { configurationFormSchema } from './configuration_form_schema'; interface Props { @@ -42,6 +43,7 @@ const formSerializer = (formData: GenericObject, sourceFieldMode?: string) => { metaField, _routing, _size, + subobjects, } = formData; const dynamic = dynamicMappingsEnabled ? true : throwErrorsForUnmappedFields ? 'strict' : false; @@ -55,6 +57,7 @@ const formSerializer = (formData: GenericObject, sourceFieldMode?: string) => { _meta: metaField, _routing, _size, + subobjects, }; return serialized; @@ -77,6 +80,7 @@ const formDeserializer = (formData: GenericObject) => { _routing, // For the Mapper Size plugin _size, + subobjects, } = formData; return { @@ -95,6 +99,7 @@ const formDeserializer = (formData: GenericObject) => { metaField: _meta ?? {}, _routing, _size, + subobjects, }; }; @@ -177,6 +182,8 @@ export const ConfigurationForm = React.memo(({ value, esNodesPlugins }: Props) = )} {isMapperSizeSectionVisible && } + + ); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form_schema.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form_schema.tsx index 843c5aed6ece5..a9913b9474b36 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form_schema.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form_schema.tsx @@ -186,4 +186,10 @@ export const configurationFormSchema: FormSchema = { defaultValue: false, }, }, + subobjects: { + label: i18n.translate('xpack.idxMgmt.mappingsEditor.configuration.subobjectsLabel', { + defaultMessage: 'Allow objects to hold further subobjects', + }), + defaultValue: true, + }, }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/subobjects_section.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/subobjects_section.tsx new file mode 100644 index 0000000000000..f858474a45086 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/subobjects_section.tsx @@ -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 React from 'react'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiLink, EuiCode } from '@elastic/eui'; + +import { documentationService } from '../../../../services/documentation'; +import { UseField, FormRow, ToggleField } from '../../shared_imports'; + +export const SubobjectsSection = () => { + return ( + + {i18n.translate('xpack.idxMgmt.mappingsEditor.subobjectsParameterName', { + defaultMessage: 'subobjects', + })} + + ), + false: ( + + {i18n.translate('xpack.idxMgmt.mappingsEditor.subobjectsParameterFalseValue', { + defaultMessage: 'false', + })} + + ), + docsLink: ( + + {i18n.translate('xpack.idxMgmt.mappingsEditor.subobjectsDocumentionLink', { + defaultMessage: 'Learn more.', + })} + + ), + }} + /> + } + > + + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index.ts index b946f4c262bdd..d22b128205399 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index.ts @@ -74,6 +74,8 @@ export * from './meta_parameter'; export * from './ignore_above_parameter'; +export * from './subobjects_parameter'; + export const PARAMETER_SERIALIZERS = [relationsSerializer, dynamicSerializer]; export const PARAMETER_DESERIALIZERS = [relationsDeserializer, dynamicDeserializer]; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/reference_field_selects.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/reference_field_selects.tsx index e8b939ab81f5d..b8f5e866b68a9 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/reference_field_selects.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/reference_field_selects.tsx @@ -32,7 +32,11 @@ export const ReferenceFieldSelects = ({ Object.keys(data.mappings.properties).forEach((key) => { const field = data.mappings.properties[key]; if (field.type === 'text') { - referenceFieldOptions.push({ value: key, inputDisplay: key }); + referenceFieldOptions.push({ + value: key, + inputDisplay: key, + 'data-test-subj': `select-reference-field-${key}`, + }); } }); } @@ -47,14 +51,15 @@ export const ReferenceFieldSelects = ({ return subscription.unsubscribe; }, [subscribe, onChange]); - return ( -
+ {(field) => ( )} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx index 1d09c2469c164..24b99ef9e7c08 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/select_inference_id.tsx @@ -35,28 +35,40 @@ import { ModelConfig, Service, } from '@kbn/inference_integration_flyout/types'; -import { FormattedMessage } from '@kbn/i18n-react'; import { InferenceFlyoutWrapper } from '@kbn/inference_integration_flyout/components/inference_flyout_wrapper'; import { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_models'; -import { extractErrorProperties } from '@kbn/ml-error-utils'; import { getFieldConfig } from '../../../lib'; import { useAppContext } from '../../../../../app_context'; import { Form, UseField, useForm } from '../../../shared_imports'; import { useLoadInferenceModels } from '../../../../../services/api'; import { getTrainedModelStats } from '../../../../../../hooks/use_details_page_mappings_model_management'; import { InferenceToModelIdMap } from '../fields'; +import { useMLModelNotificationToasts } from '../../../../../../hooks/use_ml_model_status_toasts'; +import { + CustomInferenceEndpointConfig, + DefaultInferenceModels, + DeploymentState, +} from '../../../types'; const inferenceServiceTypeElasticsearchModelMap: Record = { elser: ElasticsearchModelDefaultOptions.elser, elasticsearch: ElasticsearchModelDefaultOptions.e5, }; - +const uncheckSelectedModelOption = (options: EuiSelectableOption[]) => { + const checkedOption = options.find(({ checked }) => checked === 'on'); + if (checkedOption) { + checkedOption.checked = undefined; + } +}; interface Props { onChange(value: string): void; 'data-test-subj'?: string; setValue: (value: string) => void; - setNewInferenceEndpoint: (newInferenceEndpoint: InferenceToModelIdMap) => void; + setNewInferenceEndpoint: ( + newInferenceEndpoint: InferenceToModelIdMap, + customInferenceEndpointConfig: CustomInferenceEndpointConfig + ) => void; } export const SelectInferenceId = ({ onChange, @@ -76,16 +88,14 @@ export const SelectInferenceId = ({ }); }, [ml]); - const { form } = useForm({ defaultValue: { main: 'elser_model_2' } }); + const { form } = useForm({ defaultValue: { main: DefaultInferenceModels.elser_model_2 } }); const { subscribe } = form; const [isInferenceFlyoutVisible, setIsInferenceFlyoutVisible] = useState(false); - const [inferenceAddError, setInferenceAddError] = useState(undefined); const [availableTrainedModels, setAvailableTrainedModels] = useState< TrainedModelConfigResponse[] >([]); const onFlyoutClose = useCallback(() => { - setInferenceAddError(undefined); setIsInferenceFlyoutVisible(!isInferenceFlyoutVisible); }, [isInferenceFlyoutVisible]); useEffect(() => { @@ -111,16 +121,27 @@ export const SelectInferenceId = ({ const fieldConfigModelId = getFieldConfig('inference_id'); const defaultInferenceIds: EuiSelectableOption[] = useMemo(() => { - return [{ checked: 'on', label: 'elser_model_2' }, { label: 'e5' }]; + return [ + { + checked: 'on', + label: 'elser_model_2', + 'data-test-subj': 'default-inference_elser_model_2', + }, + { + label: 'e5', + 'data-test-subj': 'default-inference_e5', + }, + ]; }, []); - const { isLoading, data: models, resendRequest } = useLoadInferenceModels(); + const { isLoading, data: models } = useLoadInferenceModels(); const [options, setOptions] = useState([...defaultInferenceIds]); const inferenceIdOptionsFromModels = useMemo(() => { const inferenceIdOptions = models?.map((model: InferenceAPIConfigResponse) => ({ label: model.model_id, + 'data-test-subj': `custom-inference_${model.model_id}`, })) || []; return inferenceIdOptions; @@ -136,40 +157,48 @@ export const SelectInferenceId = ({ }; setOptions(Object.values(mergedOptions)); }, [inferenceIdOptionsFromModels, defaultInferenceIds]); - const [isCreateInferenceApiLoading, setIsCreateInferenceApiLoading] = useState(false); + + const { showErrorToasts } = useMLModelNotificationToasts(); const onSaveInferenceCallback = useCallback( async (inferenceId: string, taskType: InferenceTaskType, modelConfig: ModelConfig) => { - setIsCreateInferenceApiLoading(true); + setIsInferenceFlyoutVisible(!isInferenceFlyoutVisible); try { - await ml?.mlApi?.inferenceModels?.createInferenceEndpoint( - inferenceId, - taskType, - modelConfig - ); - setIsInferenceFlyoutVisible(!isInferenceFlyoutVisible); - setIsCreateInferenceApiLoading(false); - setInferenceAddError(undefined); + const isDeployable = + modelConfig.service === Service.elser || modelConfig.service === Service.elasticsearch; + + const newOption: EuiSelectableOption[] = [ + { + label: inferenceId, + checked: 'on', + 'data-test-subj': `custom-inference_${inferenceId}`, + }, + ]; + // uncheck selected endpoint id + uncheckSelectedModelOption(options); + + setOptions([...options, ...newOption]); + const trainedModelStats = await ml?.mlApi?.trainedModels.getTrainedModelStats(); const defaultEndpointId = inferenceServiceTypeElasticsearchModelMap[modelConfig.service] || ''; const newModelId: InferenceToModelIdMap = {}; newModelId[inferenceId] = { trainedModelId: defaultEndpointId, - isDeployable: - modelConfig.service === Service.elser || modelConfig.service === Service.elasticsearch, - isDeployed: getTrainedModelStats(trainedModelStats)[defaultEndpointId] === 'deployed', - defaultInferenceEndpoint: false, + isDeployable, + isDeployed: + getTrainedModelStats(trainedModelStats)[defaultEndpointId] === DeploymentState.DEPLOYED, + }; + const customInferenceEndpointConfig: CustomInferenceEndpointConfig = { + taskType, + modelConfig, }; - resendRequest(); - setNewInferenceEndpoint(newModelId); + setNewInferenceEndpoint(newModelId, customInferenceEndpointConfig); } catch (error) { - const errorObj = extractErrorProperties(error); - setInferenceAddError(errorObj.message); - setIsCreateInferenceApiLoading(false); + showErrorToasts(error); } }, - [isInferenceFlyoutVisible, resendRequest, ml, setNewInferenceEndpoint] + [isInferenceFlyoutVisible, ml, setNewInferenceEndpoint, options, showErrorToasts] ); useEffect(() => { const subscription = subscribe((updateData) => { @@ -182,7 +211,7 @@ export const SelectInferenceId = ({ }, [subscribe, onChange]); const selectedOptionLabel = options.find((option) => option.checked)?.label; useEffect(() => { - setValue(selectedOptionLabel ?? 'elser_model_2'); + setValue(selectedOptionLabel ?? DefaultInferenceModels.elser_model_2); }, [selectedOptionLabel, setValue]); const [isInferencePopoverVisible, setIsInferencePopoverVisible] = useState(false); const [inferenceEndpointError, setInferenceEndpointError] = useState( @@ -304,7 +333,7 @@ export const SelectInferenceId = ({ data-test-subj={dataTestSubj} searchable isLoading={isLoading} - singleSelection + singleSelection="always" searchProps={{ compressed: true, placeholder: i18n.translate( @@ -340,32 +369,6 @@ export const SelectInferenceId = ({ - - - - - - - - ) - } onInferenceEndpointChange={onInferenceEndpointChange} inferenceEndpointError={inferenceEndpointError} trainedModels={trainedModels} @@ -374,7 +377,6 @@ export const SelectInferenceId = ({ isInferenceFlyoutVisible={isInferenceFlyoutVisible} supportedNlpModels={docLinks.links.enterpriseSearch.supportedNlpModels} nlpImportModel={docLinks.links.ml.nlpImportModel} - isCreateInferenceApiLoading={isCreateInferenceApiLoading} setInferenceEndpointError={setInferenceEndpointError} /> )} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/subobjects_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/subobjects_parameter.tsx new file mode 100644 index 0000000000000..c5eea5fa111a3 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/subobjects_parameter.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; + +import { documentationService } from '../../../../../services/documentation'; +import { EditFieldFormRow } from '../fields/edit_field'; + +export const SubobjectsParameter = () => { + return ( + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx index f91be7bf55fe2..4a3f600753dd4 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx @@ -14,14 +14,20 @@ import { EuiSpacer, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { ElasticsearchModelDefaultOptions } from '@kbn/inference_integration_flyout/types'; import { MlPluginStart } from '@kbn/ml-plugin/public'; import classNames from 'classnames'; -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { EUI_SIZE, TYPE_DEFINITION } from '../../../../constants'; import { fieldSerializer } from '../../../../lib'; import { useDispatch, useMappingsState } from '../../../../mappings_state_context'; import { Form, FormDataProvider, UseField, useForm, useFormData } from '../../../../shared_imports'; -import { Field, MainType, NormalizedFields } from '../../../../types'; +import { + CustomInferenceEndpointConfig, + Field, + MainType, + NormalizedFields, +} from '../../../../types'; import { NameParameter, SubTypeParameter, TypeParameter } from '../../field_parameters'; import { ReferenceFieldSelects } from '../../field_parameters/reference_field_selects'; import { SelectInferenceId } from '../../field_parameters/select_inference_id'; @@ -32,10 +38,9 @@ import { useSemanticText } from './semantic_text/use_semantic_text'; const formWrapper = (props: any) => ; export interface InferenceToModelIdMap { [key: string]: { - trainedModelId?: string; + trainedModelId: ElasticsearchModelDefaultOptions | string; isDeployed: boolean; isDeployable: boolean; - defaultInferenceEndpoint: boolean; }; } @@ -88,7 +93,9 @@ export const CreateField = React.memo(function CreateFieldComponent({ return subscription.unsubscribe; }, [dispatch, subscribe]); - + const [customInferenceEndpointConfig, setCustomInferenceEndpointConfig] = useState< + CustomInferenceEndpointConfig | undefined + >(undefined); const cancel = () => { if (isAddingFields && onCancelAddingNewFields) { onCancelAddingNewFields(); @@ -125,7 +132,7 @@ export const CreateField = React.memo(function CreateFieldComponent({ form.reset(); if (data.type === 'semantic_text' && !clickOutside) { - handleSemanticText(data); + handleSemanticText(data, customInferenceEndpointConfig); } else { dispatch({ type: 'field.add', value: data }); } @@ -283,7 +290,10 @@ export const CreateField = React.memo(function CreateFieldComponent({ }} {/* Field inference_id for semantic_text field type */} - + {renderFormActions()} @@ -311,16 +321,20 @@ function ReferenceFieldCombo({ indexName }: { indexName?: string }) { interface InferenceProps { setValue: (value: string) => void; + setCustomInferenceEndpointConfig: (config: CustomInferenceEndpointConfig) => void; } -function InferenceIdCombo({ setValue }: InferenceProps) { +function InferenceIdCombo({ setValue, setCustomInferenceEndpointConfig }: InferenceProps) { const { inferenceToModelIdMap } = useMappingsState(); const dispatch = useDispatch(); const [{ type }] = useFormData({ watch: 'type' }); // update new inferenceEndpoint const setNewInferenceEndpoint = useCallback( - (newInferenceEndpoint: InferenceToModelIdMap) => { + ( + newInferenceEndpoint: InferenceToModelIdMap, + customInferenceEndpointConfig: CustomInferenceEndpointConfig + ) => { dispatch({ type: 'inferenceToModelIdMap.update', value: { @@ -330,8 +344,9 @@ function InferenceIdCombo({ setValue }: InferenceProps) { }, }, }); + setCustomInferenceEndpointConfig(customInferenceEndpointConfig); }, - [dispatch, inferenceToModelIdMap] + [dispatch, inferenceToModelIdMap, setCustomInferenceEndpointConfig] ); if (type === undefined || type[0]?.value !== 'semantic_text') { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.test.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.test.ts index 062c0b93c303e..4833562e58e31 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.test.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.test.ts @@ -6,7 +6,7 @@ */ import { renderHook } from '@testing-library/react-hooks'; -import { Field } from '../../../../../types'; +import { CustomInferenceEndpointConfig, Field } from '../../../../../types'; import { useSemanticText } from './use_semantic_text'; import { act } from 'react-dom/test-utils'; @@ -15,22 +15,54 @@ const mlMock: any = { inferenceModels: { createInferenceEndpoint: jest.fn().mockResolvedValue({}), }, - trainedModels: { - startModelAllocation: jest.fn().mockResolvedValue({}), - getTrainedModels: jest.fn().mockResolvedValue([ - { - fully_defined: true, - }, - ]), - }, }, }; -const mockFieldData = { - name: 'name', - type: 'semantic_text', - inferenceId: 'elser_model_2', -} as Field; +const mockField: Record = { + elser_model_2: { + name: 'name', + type: 'semantic_text', + inferenceId: 'elser_model_2', + }, + e5: { + name: 'name', + type: 'semantic_text', + inferenceId: 'e5', + }, + openai: { + name: 'name', + type: 'semantic_text', + inferenceId: 'openai', + }, + my_elser_endpoint: { + name: 'name', + type: 'semantic_text', + inferenceId: 'my_elser_endpoint', + }, +}; + +const mockConfig: Record = { + openai: { + taskType: 'text_embedding', + modelConfig: { + service: 'openai', + service_settings: { + api_key: 'test', + model_id: 'text-embedding-ada-002', + }, + }, + }, + elser: { + taskType: 'sparse_embedding', + modelConfig: { + service: 'elser', + service_settings: { + num_allocations: 1, + num_threads: 1, + }, + }, + }, +}; const mockDispatch = jest.fn(); @@ -38,13 +70,21 @@ jest.mock('../../../../../mappings_state_context', () => ({ useMappingsState: jest.fn().mockReturnValue({ inferenceToModelIdMap: { e5: { - defaultInferenceEndpoint: false, isDeployed: false, isDeployable: true, trainedModelId: '.multilingual-e5-small', }, elser_model_2: { - defaultInferenceEndpoint: true, + isDeployed: false, + isDeployable: true, + trainedModelId: '.elser_model_2', + }, + openai: { + isDeployed: false, + isDeployable: false, + trainedModelId: '', + }, + my_elser_endpoint: { isDeployed: false, isDeployable: true, trainedModelId: '.elser_model_2', @@ -63,24 +103,108 @@ jest.mock('../../../../../../component_templates/component_templates_context', ( }), })); +jest.mock('../../../../../../../services/api', () => ({ + getInferenceModels: jest.fn().mockResolvedValue({ + data: [ + { + model_id: 'e5', + task_type: 'text_embedding', + service: 'elasticsearch', + service_settings: { + num_allocations: 1, + num_threads: 1, + model_id: '.multilingual-e5-small', + }, + task_settings: {}, + }, + ], + }), +})); + describe('useSemanticText', () => { - let form: any; + let mockForm: any; beforeEach(() => { jest.clearAllMocks(); - form = { - getFields: jest.fn().mockReturnValue({ - referenceField: { value: 'title' }, - name: { value: 'sem' }, - type: { value: [{ value: 'semantic_text' }] }, - inferenceId: { value: 'e5' }, - }), + mockForm = { + form: { + getFields: jest.fn().mockReturnValue({ + referenceField: { value: 'title' }, + name: { value: 'sem' }, + type: { value: [{ value: 'semantic_text' }] }, + inferenceId: { value: 'e5' }, + }), + }, + thirdPartyModel: { + getFields: jest.fn().mockReturnValue({ + referenceField: { value: 'title' }, + name: { value: 'semantic_text_openai_endpoint' }, + type: { value: [{ value: 'semantic_text' }] }, + inferenceId: { value: 'openai' }, + }), + }, + elasticModelEndpointCreatedfromFlyout: { + getFields: jest.fn().mockReturnValue({ + referenceField: { value: 'title' }, + name: { value: 'semantic_text_elserServiceType_endpoint' }, + type: { value: [{ value: 'semantic_text' }] }, + inferenceId: { value: 'my_elser_endpoint' }, + }), + }, }; }); + it('should handle semantic text with third party model correctly', async () => { + const { result } = renderHook(() => + useSemanticText({ + form: mockForm.thirdPartyModel, + setErrorsInTrainedModelDeployment: jest.fn(), + ml: mlMock, + }) + ); + await act(async () => { + result.current.setInferenceValue('openai'); + result.current.handleSemanticText(mockField.openai, mockConfig.openai); + }); + expect(mockDispatch).toHaveBeenCalledWith({ + type: 'field.addSemanticText', + value: mockField.openai, + }); + expect(mlMock.mlApi.inferenceModels.createInferenceEndpoint).toHaveBeenCalledWith( + 'openai', + 'text_embedding', + mockConfig.openai.modelConfig + ); + }); + it('should handle semantic text with inference endpoint created from flyout correctly', async () => { + const { result } = renderHook(() => + useSemanticText({ + form: mockForm.elasticModelEndpointCreatedfromFlyout, + setErrorsInTrainedModelDeployment: jest.fn(), + ml: mlMock, + }) + ); + await act(async () => { + result.current.setInferenceValue('my_elser_endpoint'); + result.current.handleSemanticText(mockField.my_elser_endpoint, mockConfig.elser); + }); + expect(mockDispatch).toHaveBeenCalledWith({ + type: 'field.addSemanticText', + value: mockField.my_elser_endpoint, + }); + expect(mlMock.mlApi.inferenceModels.createInferenceEndpoint).toHaveBeenCalledWith( + 'my_elser_endpoint', + 'sparse_embedding', + mockConfig.elser.modelConfig + ); + }); it('should populate the values from the form', () => { const { result } = renderHook(() => - useSemanticText({ form, setErrorsInTrainedModelDeployment: jest.fn(), ml: mlMock }) + useSemanticText({ + form: mockForm.form, + setErrorsInTrainedModelDeployment: jest.fn(), + ml: mlMock, + }) ); expect(result.current.referenceFieldComboValue).toBe('title'); @@ -91,23 +215,26 @@ describe('useSemanticText', () => { it('should handle semantic text correctly', async () => { const { result } = renderHook(() => - useSemanticText({ form, setErrorsInTrainedModelDeployment: jest.fn(), ml: mlMock }) + useSemanticText({ + form: mockForm.form, + setErrorsInTrainedModelDeployment: jest.fn(), + ml: mlMock, + }) ); await act(async () => { - result.current.handleSemanticText(mockFieldData); + result.current.handleSemanticText(mockField.elser_model_2); }); - expect(mlMock.mlApi.trainedModels.startModelAllocation).toHaveBeenCalledWith('.elser_model_2'); expect(mockDispatch).toHaveBeenCalledWith({ type: 'field.addSemanticText', - value: mockFieldData, + value: mockField.elser_model_2, }); expect(mlMock.mlApi.inferenceModels.createInferenceEndpoint).toHaveBeenCalledWith( 'elser_model_2', - 'text_embedding', + 'sparse_embedding', { - service: 'elasticsearch', + service: 'elser', service_settings: { num_allocations: 1, num_threads: 1, @@ -116,68 +243,42 @@ describe('useSemanticText', () => { } ); }); - - it('should invoke the download api if the model does not exist', async () => { - const mlMockWithModelNotDownloaded: any = { - mlApi: { - inferenceModels: { - createInferenceEndpoint: jest.fn(), - }, - trainedModels: { - startModelAllocation: jest.fn(), - getTrainedModels: jest.fn().mockResolvedValue([ - { - fully_defined: false, - }, - ]), - installElasticTrainedModelConfig: jest.fn().mockResolvedValue({}), - }, - }, - }; + it('does not call create inference endpoint api, if default endpoint already exists', async () => { const { result } = renderHook(() => useSemanticText({ - form, + form: mockForm.form, setErrorsInTrainedModelDeployment: jest.fn(), - ml: mlMockWithModelNotDownloaded, + ml: mlMock, }) ); await act(async () => { - result.current.handleSemanticText(mockFieldData); + result.current.setInferenceValue('e5'); + result.current.handleSemanticText(mockField.e5); }); - expect( - mlMockWithModelNotDownloaded.mlApi.trainedModels.installElasticTrainedModelConfig - ).toHaveBeenCalledWith('.elser_model_2'); - expect( - mlMockWithModelNotDownloaded.mlApi.trainedModels.startModelAllocation - ).toHaveBeenCalledWith('.elser_model_2'); - expect( - mlMockWithModelNotDownloaded.mlApi.inferenceModels.createInferenceEndpoint - ).toHaveBeenCalledWith('elser_model_2', 'text_embedding', { - service: 'elasticsearch', - service_settings: { - num_allocations: 1, - num_threads: 1, - model_id: '.elser_model_2', - }, + expect(mockDispatch).toHaveBeenCalledWith({ + type: 'field.addSemanticText', + value: mockField.e5, }); + + expect(mlMock.mlApi.inferenceModels.createInferenceEndpoint).not.toBeCalled(); }); it('handles errors correctly', async () => { const mockError = new Error('Test error'); - mlMock.mlApi?.trainedModels.startModelAllocation.mockImplementationOnce(() => { + mlMock.mlApi?.inferenceModels.createInferenceEndpoint.mockImplementationOnce(() => { throw mockError; }); const setErrorsInTrainedModelDeployment = jest.fn(); const { result } = renderHook(() => - useSemanticText({ form, setErrorsInTrainedModelDeployment, ml: mlMock }) + useSemanticText({ form: mockForm.form, setErrorsInTrainedModelDeployment, ml: mlMock }) ); await act(async () => { - result.current.handleSemanticText(mockFieldData); + result.current.handleSemanticText(mockField.elser_model_2); }); expect(setErrorsInTrainedModelDeployment).toHaveBeenCalledWith(expect.any(Function)); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.ts index dda1bfe794c97..01a37275a54dd 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/semantic_text/use_semantic_text.ts @@ -7,30 +7,39 @@ import { i18n } from '@kbn/i18n'; import { useCallback } from 'react'; -import { MlPluginStart, TrainedModelConfigResponse } from '@kbn/ml-plugin/public'; +import { MlPluginStart } from '@kbn/ml-plugin/public'; import React, { useEffect, useState } from 'react'; -import { useComponentTemplatesContext } from '../../../../../../component_templates/component_templates_context'; +import { ElasticsearchModelDefaultOptions } from '@kbn/inference_integration_flyout/types'; +import { InferenceTaskType } from '@elastic/elasticsearch/lib/api/types'; +import { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils'; import { useDispatch, useMappingsState } from '../../../../../mappings_state_context'; import { FormHook } from '../../../../../shared_imports'; -import { Field } from '../../../../../types'; +import { CustomInferenceEndpointConfig, DefaultInferenceModels, Field } from '../../../../../types'; +import { useMLModelNotificationToasts } from '../../../../../../../../hooks/use_ml_model_status_toasts'; +import { getInferenceModels } from '../../../../../../../services/api'; interface UseSemanticTextProps { form: FormHook; ml?: MlPluginStart; setErrorsInTrainedModelDeployment: React.Dispatch> | undefined; } +interface DefaultInferenceEndpointConfig { + taskType: InferenceTaskType; + service: string; +} export function useSemanticText(props: UseSemanticTextProps) { const { form, setErrorsInTrainedModelDeployment, ml } = props; const { inferenceToModelIdMap } = useMappingsState(); - const { toasts } = useComponentTemplatesContext(); const dispatch = useDispatch(); - const [referenceFieldComboValue, setReferenceFieldComboValue] = useState(); const [nameValue, setNameValue] = useState(); const [inferenceIdComboValue, setInferenceIdComboValue] = useState(); const [semanticFieldType, setSemanticTextFieldType] = useState(); - const [inferenceValue, setInferenceValue] = useState('elser_model_2'); + const [inferenceValue, setInferenceValue] = useState( + DefaultInferenceModels.elser_model_2 + ); + const { showSuccessToasts, showErrorToasts } = useMLModelNotificationToasts(); const useFieldEffect = ( semanticTextform: FormHook, @@ -65,113 +74,92 @@ export function useSemanticText(props: UseSemanticTextProps) { } }, [form, inferenceId, inferenceToModelIdMap]); - const isModelDownloaded = useCallback( - async (modelId: string) => { + const createInferenceEndpoint = useCallback( + async ( + trainedModelId: ElasticsearchModelDefaultOptions | string, + data: Field, + customInferenceEndpointConfig?: CustomInferenceEndpointConfig + ) => { + if (data.inferenceId === undefined) { + throw new Error( + i18n.translate('xpack.idxMgmt.mappingsEditor.createField.undefinedInferenceIdError', { + defaultMessage: 'InferenceId is undefined while creating the inference endpoint.', + }) + ); + } + const defaultInferenceEndpointConfig: DefaultInferenceEndpointConfig = { + service: + trainedModelId === ElasticsearchModelDefaultOptions.elser ? 'elser' : 'elasticsearch', + taskType: + trainedModelId === ElasticsearchModelDefaultOptions.elser + ? 'sparse_embedding' + : 'text_embedding', + }; + + const modelConfig = customInferenceEndpointConfig + ? customInferenceEndpointConfig.modelConfig + : { + service: defaultInferenceEndpointConfig.service, + service_settings: { + num_allocations: 1, + num_threads: 1, + model_id: trainedModelId, + }, + }; + const taskType: InferenceTaskType = + customInferenceEndpointConfig?.taskType ?? defaultInferenceEndpointConfig.taskType; try { - const response: TrainedModelConfigResponse[] | undefined = - await ml?.mlApi?.trainedModels.getTrainedModels(modelId, { - include: 'definition_status', - }); - return !!response?.[0]?.fully_defined; + await ml?.mlApi?.inferenceModels?.createInferenceEndpoint( + data.inferenceId, + taskType, + modelConfig + ); } catch (error) { - if (error.body.statusCode !== 404) { - throw error; - } + throw error; } - return false; }, - [ml?.mlApi?.trainedModels] + [ml?.mlApi?.inferenceModels] ); - const createInferenceEndpoint = ( - trainedModelId: string, - defaultInferenceEndpoint: boolean, - data: Field + const handleSemanticText = async ( + data: Field, + customInferenceEndpointConfig?: CustomInferenceEndpointConfig ) => { - if (data.inferenceId === undefined) { - throw new Error( - i18n.translate('xpack.idxMgmt.mappingsEditor.createField.undefinedInferenceIdError', { - defaultMessage: 'InferenceId is undefined while creating the inference endpoint.', - }) - ); - } - - if (trainedModelId && defaultInferenceEndpoint) { - const modelConfig = { - service: 'elasticsearch', - service_settings: { - num_allocations: 1, - num_threads: 1, - model_id: trainedModelId, - }, - }; - - ml?.mlApi?.inferenceModels?.createInferenceEndpoint( - data.inferenceId, - 'text_embedding', - modelConfig - ); - } - }; - - const handleSemanticText = async (data: Field) => { data.inferenceId = inferenceValue; if (data.inferenceId === undefined) { return; } - const inferenceData = inferenceToModelIdMap?.[data.inferenceId]; - if (!inferenceData) { return; } - const { trainedModelId, defaultInferenceEndpoint, isDeployed, isDeployable } = inferenceData; + const { trainedModelId } = inferenceData; + dispatch({ type: 'field.addSemanticText', value: data }); - if (isDeployable && trainedModelId) { - try { - const modelDownloaded: boolean = await isModelDownloaded(trainedModelId); + try { + // if model exists already, do not create inference endpoint + const inferenceModels = await getInferenceModels(); + const inferenceModel: InferenceAPIConfigResponse[] = inferenceModels.data.some( + (e: InferenceAPIConfigResponse) => e.model_id === inferenceValue + ); + if (inferenceModel) { + return; + } - if (isDeployed) { - createInferenceEndpoint(trainedModelId, defaultInferenceEndpoint, data); - } else if (modelDownloaded) { - ml?.mlApi?.trainedModels - .startModelAllocation(trainedModelId) - .then(() => createInferenceEndpoint(trainedModelId, defaultInferenceEndpoint, data)); - } else { - ml?.mlApi?.trainedModels - .installElasticTrainedModelConfig(trainedModelId) - .then(() => ml?.mlApi?.trainedModels.startModelAllocation(trainedModelId)) - .then(() => createInferenceEndpoint(trainedModelId, defaultInferenceEndpoint, data)); - } - toasts?.addSuccess({ - title: i18n.translate( - 'xpack.idxMgmt.mappingsEditor.createField.modelDeploymentStartedNotification', - { - defaultMessage: 'Model deployment started', - } - ), - text: i18n.translate( - 'xpack.idxMgmt.mappingsEditor.createField.modelDeploymentNotification', - { - defaultMessage: '1 model is being deployed on your ml_node.', - } - ), - }); - } catch (error) { + if (trainedModelId) { + // show toasts only if it's elastic models + showSuccessToasts(); + } + + await createInferenceEndpoint(trainedModelId, data, customInferenceEndpointConfig); + } catch (error) { + // trainedModelId is empty string when it's a third party model + if (trainedModelId) { setErrorsInTrainedModelDeployment?.((prevItems) => [...prevItems, trainedModelId]); - toasts?.addError(error.body && error.body.message ? new Error(error.body.message) : error, { - title: i18n.translate( - 'xpack.idxMgmt.mappingsEditor.createField.modelDeploymentErrorTitle', - { - defaultMessage: 'Model deployment failed', - } - ), - }); } + showErrorToasts(error); } - - dispatch({ type: 'field.addSemanticText', value: data }); }; return { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/object_type.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/object_type.tsx index 2417561a15519..d8d5953d33421 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/object_type.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/object_type.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { NormalizedField } from '../../../../types'; -import { DynamicParameter, EnabledParameter } from '../../field_parameters'; +import { DynamicParameter, EnabledParameter, SubobjectsParameter } from '../../field_parameters'; import { BasicParametersSection } from '../edit_field'; interface Props { @@ -23,6 +23,8 @@ export const ObjectType = ({ field }: Props) => { /> + + ); }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/field_options.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/field_options.tsx index ec6b3e9cabd34..0fc2e49d0a9cf 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/field_options.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/field_options.tsx @@ -26,6 +26,7 @@ export const FIELD_TYPES_OPTIONS = Object.entries(MAIN_DATA_TYPE_DEFINITION).map ([dataType, { label }]) => ({ value: dataType, label, + 'data-test-subj': `fieldTypesOptions-${dataType}`, }) ) as ComboBoxOption[]; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx index 881318b52cdc5..790b4560000f7 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/parameters_definition.tsx @@ -1119,4 +1119,10 @@ export const PARAMETERS_DEFINITION: { [key in ParameterName]: ParameterDefinitio }, schema: t.boolean, }, + subobjects: { + fieldConfig: { + defaultValue: true, + }, + schema: t.boolean, + }, }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/extract_mappings_definition.test.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/extract_mappings_definition.test.ts index 33205bbf9d716..3e67f35a5fccc 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/extract_mappings_definition.test.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/extract_mappings_definition.test.ts @@ -126,6 +126,7 @@ describe('extractMappingsDefinition', () => { _size: { enabled: true, }, + subobjects: true, }; expect(extractMappingsDefinition(mappings)).toBe(mappings); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/mappings_validator.test.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/mappings_validator.test.ts index 94af72c5e2902..288b2f3ca37fd 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/mappings_validator.test.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/mappings_validator.test.ts @@ -40,6 +40,7 @@ describe('Mappings configuration validator', () => { _size: { enabled: true, }, + subobjects: true, }; const { errors } = validateMappings(mappings, [MapperSizePluginId]); @@ -59,6 +60,7 @@ describe('Mappings configuration validator', () => { }, properties: { title: { type: 'text' } }, dynamic_templates: [], + subobjects: true, unknown: 123, }; @@ -85,6 +87,7 @@ describe('Mappings configuration validator', () => { _size: { enabled: true, }, + subobjects: true, }; const { value, errors } = validateMappings(mappings, []); @@ -93,6 +96,7 @@ describe('Mappings configuration validator', () => { dynamic: true, properties: {}, dynamic_templates: [], + subobjects: true, }); expect(errors).not.toBe(undefined); @@ -309,6 +313,7 @@ describe('Properties validator', () => { depth_limit: true, dims: false, max_shingle_size: 'string_not_allowed', + subobjects: 'abc', }, // All the parameters in "goodField" have the correct format // and should still be there after the validation ran. @@ -361,6 +366,7 @@ describe('Properties validator', () => { depth_limit: 20, dims: 'abc', max_shingle_size: 2, + subobjects: true, }, goodField2: { type: 'object', diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/mappings_validator.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/mappings_validator.ts index 08494cdd3ca64..c4e0f122e1492 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/mappings_validator.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/mappings_validator.ts @@ -238,6 +238,7 @@ export const mappingsConfigurationSchema = t.exact( _size: t.interface({ enabled: t.boolean, }), + subobjects: t.boolean, }) ); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts index b8a024b0e98e5..767d29aaaeda8 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/document_fields.ts @@ -7,6 +7,8 @@ import { ReactNode } from 'react'; +import { InferenceTaskType } from '@elastic/elasticsearch/lib/api/types'; +import { ModelConfig } from '@kbn/inference_integration_flyout'; import { GenericObject } from './mappings_editor'; import { PARAMETERS_DEFINITION } from '../constants'; @@ -164,7 +166,8 @@ export type ParameterName = | 'value' | 'meta' | 'time_series_metric' - | 'time_series_dimension'; + | 'time_series_dimension' + | 'subobjects'; export interface Parameter { fieldConfig: FieldConfig; @@ -245,3 +248,16 @@ export interface NormalizedRuntimeField { export interface NormalizedRuntimeFields { [id: string]: NormalizedRuntimeField; } +export enum DefaultInferenceModels { + elser_model_2 = 'elser_model_2', + e5 = 'e5', +} + +export enum DeploymentState { + 'DEPLOYED' = 'deployed', + 'NOT_DEPLOYED' = 'not_deployed', +} +export interface CustomInferenceEndpointConfig { + taskType: InferenceTaskType; + modelConfig: ModelConfig; +} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts index d08c80835c620..a0e7247c39bc1 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts @@ -36,6 +36,7 @@ export interface MappingsConfiguration { }; _meta?: string; _size?: { enabled: boolean }; + subobjects?: boolean; } export interface MappingsTemplates { diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/trained_models_deployment_modal.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/trained_models_deployment_modal.tsx index 4521a64c1dac4..8c2bfbbbef96d 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/trained_models_deployment_modal.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/trained_models_deployment_modal.tsx @@ -84,9 +84,9 @@ export function TrainedModelsDeploymentModal({ onCancel={closeModal} onConfirm={refreshModal} cancelButtonText={i18n.translate( - 'xpack.idxMgmt.indexDetails.trainedModelsDeploymentModal.cancelButtonLabel', + 'xpack.idxMgmt.indexDetails.trainedModelsDeploymentModal.closeButtonLabel', { - defaultMessage: 'Cancel', + defaultMessage: 'Close', } )} confirmButtonText={i18n.translate( diff --git a/x-pack/plugins/index_management/public/application/services/documentation.ts b/x-pack/plugins/index_management/public/application/services/documentation.ts index 71d16b84e6639..58aba69351883 100644 --- a/x-pack/plugins/index_management/public/application/services/documentation.ts +++ b/x-pack/plugins/index_management/public/application/services/documentation.ts @@ -56,6 +56,7 @@ class DocumentationService { private mappingSourceFields: string = ''; private mappingSourceFieldsDisable: string = ''; private mappingStore: string = ''; + private mappingSubobjects: string = ''; private mappingTermVector: string = ''; private mappingTypesRemoval: string = ''; private percolate: string = ''; @@ -115,6 +116,7 @@ class DocumentationService { this.mappingSourceFields = links.elasticsearch.mappingSourceFields; this.mappingSourceFieldsDisable = links.elasticsearch.mappingSourceFieldsDisable; this.mappingStore = links.elasticsearch.mappingStore; + this.mappingSubobjects = links.elasticsearch.mappingSubobjects; this.mappingTermVector = links.elasticsearch.mappingTermVector; this.mappingTypesRemoval = links.elasticsearch.mappingTypesRemoval; this.percolate = links.query.percolate; @@ -329,6 +331,10 @@ class DocumentationService { return this.mappingEnabled; } + public getSubobjectsLink() { + return this.mappingSubobjects; + } + public getRuntimeFields() { return this.runtimeFields; } diff --git a/x-pack/plugins/index_management/public/application/shared/parse_mappings.ts b/x-pack/plugins/index_management/public/application/shared/parse_mappings.ts index 239b58169a713..3090d910034b2 100644 --- a/x-pack/plugins/index_management/public/application/shared/parse_mappings.ts +++ b/x-pack/plugins/index_management/public/application/shared/parse_mappings.ts @@ -35,6 +35,7 @@ export const parseMappings = ( dynamic_date_formats, dynamic_templates, /* eslint-enable @typescript-eslint/naming-convention */ + subobjects, } = mappingsDefinition; const parsed = { @@ -47,6 +48,7 @@ export const parseMappings = ( numeric_detection, date_detection, dynamic_date_formats, + subobjects, }, fields: properties, templates: { diff --git a/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.test.ts b/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.test.ts index 98ecbb23b989a..1517b9664f3ea 100644 --- a/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.test.ts +++ b/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.test.ts @@ -104,13 +104,11 @@ const inferenceToModelIdMap = { trainedModelId: '.elser_model_2', isDeployed: true, isDeployable: true, - defaultInferenceEndpoint: false, }, e5: { trainedModelId: '.multilingual-e5-small', isDeployed: true, isDeployable: true, - defaultInferenceEndpoint: false, }, } as InferenceToModelIdMap; @@ -127,13 +125,11 @@ describe('useDetailsPageMappingsModelManagement', () => { value: { inferenceToModelIdMap: { e5: { - defaultInferenceEndpoint: false, isDeployed: false, isDeployable: true, trainedModelId: '.multilingual-e5-small', }, elser_model_2: { - defaultInferenceEndpoint: true, isDeployed: true, isDeployable: true, trainedModelId: '.elser_model_2', diff --git a/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.ts b/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.ts index fb27053d35547..38cf20b9bf534 100644 --- a/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.ts +++ b/x-pack/plugins/index_management/public/hooks/use_details_page_mappings_model_management.ts @@ -13,14 +13,18 @@ import { useAppContext } from '../application/app_context'; import { InferenceToModelIdMap } from '../application/components/mappings_editor/components/document_fields/fields'; import { deNormalize } from '../application/components/mappings_editor/lib'; import { useDispatch } from '../application/components/mappings_editor/mappings_state_context'; -import { NormalizedFields } from '../application/components/mappings_editor/types'; +import { + DefaultInferenceModels, + DeploymentState, + NormalizedFields, +} from '../application/components/mappings_editor/types'; import { getInferenceModels } from '../application/services/api'; interface InferenceModel { data: InferenceAPIConfigResponse[]; } -type DeploymentStatusType = Record; +type DeploymentStatusType = Record; const getCustomInferenceIdMap = ( deploymentStatsByModelId: DeploymentStatusType, @@ -39,7 +43,6 @@ const getCustomInferenceIdMap = ( trainedModelId, isDeployable: model.service === Service.elser || model.service === Service.elasticsearch, isDeployed: deploymentStatsByModelId[trainedModelId] === 'deployed', - defaultInferenceEndpoint: false, }; return inferenceMap; }, {}); @@ -50,7 +53,9 @@ export const getTrainedModelStats = (modelStats?: InferenceStatsResponse): Deplo modelStats?.trained_model_stats.reduce((acc, modelStat) => { if (modelStat.model_id) { acc[modelStat.model_id] = - modelStat?.deployment_stats?.state === 'started' ? 'deployed' : 'not_deployed'; + modelStat?.deployment_stats?.state === 'started' + ? DeploymentState.DEPLOYED + : DeploymentState.NOT_DEPLOYED; } return acc; }, {}) || {} @@ -59,17 +64,18 @@ export const getTrainedModelStats = (modelStats?: InferenceStatsResponse): Deplo const getDefaultInferenceIds = (deploymentStatsByModelId: DeploymentStatusType) => { return { - elser_model_2: { - trainedModelId: '.elser_model_2', + [DefaultInferenceModels.elser_model_2]: { + trainedModelId: ElasticsearchModelDefaultOptions.elser, isDeployable: true, - isDeployed: deploymentStatsByModelId['.elser_model_2'] === 'deployed', - defaultInferenceEndpoint: true, + isDeployed: + deploymentStatsByModelId[ElasticsearchModelDefaultOptions.elser] === + DeploymentState.DEPLOYED, }, - e5: { - trainedModelId: '.multilingual-e5-small', + [DefaultInferenceModels.e5]: { + trainedModelId: ElasticsearchModelDefaultOptions.e5, isDeployable: true, - isDeployed: deploymentStatsByModelId['.multilingual-e5-small'] === 'deployed', - defaultInferenceEndpoint: true, + isDeployed: + deploymentStatsByModelId[ElasticsearchModelDefaultOptions.e5] === DeploymentState.DEPLOYED, }, }; }; diff --git a/x-pack/plugins/index_management/public/hooks/use_ml_model_status_toasts.ts b/x-pack/plugins/index_management/public/hooks/use_ml_model_status_toasts.ts new file mode 100644 index 0000000000000..c9a0c37a37fc9 --- /dev/null +++ b/x-pack/plugins/index_management/public/hooks/use_ml_model_status_toasts.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { ErrorType, extractErrorProperties, MLRequestFailure } from '@kbn/ml-error-utils'; +import { useComponentTemplatesContext } from '../application/components/component_templates/component_templates_context'; + +export function useMLModelNotificationToasts() { + const { toasts } = useComponentTemplatesContext(); + const showSuccessToasts = () => { + return toasts.addSuccess({ + title: i18n.translate( + 'xpack.idxMgmt.mappingsEditor.createField.modelDeploymentStartedNotification', + { + defaultMessage: 'Model deployment started', + } + ), + text: i18n.translate('xpack.idxMgmt.mappingsEditor.createField.modelDeploymentNotification', { + defaultMessage: '1 model is being deployed on your ml_node.', + }), + }); + }; + const showErrorToasts = (error: ErrorType) => { + const errorObj = extractErrorProperties(error); + return toasts.addError(new MLRequestFailure(errorObj, error), { + title: i18n.translate('xpack.idxMgmt.mappingsEditor.createField.modelDeploymentErrorTitle', { + defaultMessage: 'Model deployment failed', + }), + }); + }; + return { showSuccessToasts, showErrorToasts }; +} diff --git a/x-pack/plugins/index_management/tsconfig.json b/x-pack/plugins/index_management/tsconfig.json index 202a755a8b21d..ec199f7b11f53 100644 --- a/x-pack/plugins/index_management/tsconfig.json +++ b/x-pack/plugins/index_management/tsconfig.json @@ -49,9 +49,9 @@ "@kbn/utility-types", "@kbn/inference_integration_flyout", "@kbn/ml-plugin", - "@kbn/ml-error-utils", "@kbn/react-kibana-context-render", - "@kbn/react-kibana-mount" + "@kbn/react-kibana-mount", + "@kbn/ml-error-utils", ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/integration_assistant/README.md b/x-pack/plugins/integration_assistant/README.md new file mode 100644 index 0000000000000..d05a0bf757c66 --- /dev/null +++ b/x-pack/plugins/integration_assistant/README.md @@ -0,0 +1,66 @@ +# Integration Assistant + +## Overview + +Team owner: Security Integrations Scalability + +This is a new Kibana plugin created to help users with automatically generating integration packages based on provided log samples and relevant information + +## Features + +Exposes 4 API's that can be consumed by any frontend plugin, which are: + +- ECS Mapping API +- Categorization API +- Related Fields API +- Build Integration API +- Optional Test Pipeline API (Used to update pipeline results if the ingest pipeline is changed by a user in the UI). + +## Development + +### Backend + +#### Overview + +The backend part of the plugin utilizes langraph extensively to parse the provided log samples and generate the integration package. + +One instance of langraph is created that will include one or more `nodes` in which each node represents a step in the integration package generation process. + +Each node links to a specific function, usually a `handler` specified in its own file under each graph folder that will be executed when the node is reached. + +#### Structure + +**Graphs** + +The graph components are split into logical parts and are placed in separate folders for each graph under the `./server/graphs` directory. + +Each graph folder needs to contains at least one `graph.ts`, which exports a function that returns the compiled graph object. + +Each exported graph function is then linked up to one or more API routes. + +**Routes** + +All routes are defined under `./server/routes` in its own file, and then included in the `./server/routes/register_routes.ts` file. + +**Integration Builder** + +The integration builder is the last step in the expected API flow (ECS Mapping -> Categorization -> Related Fields -> Integration Builder). +With the provided package and data stream details, an optional logo and a list of sample logs, the API will build out the entire folder structure and files required for the integration package, archive it and return it as a `Buffer`. + +**Templates** + +Currently the templates are stored as `nunjucks` files as they were converted from `jinja2` templates, which use the exact same format. Longer term this will most likely be switched to the Kibana forked Handlebars templating engine. + +The templates are stored in the `./server/templates` directory and are used to generate the integration package files while running the Integration Builder API. + +One template (pipeline.yml.njk) is used by the ECS Mapping API to generate the boilerplate ingest pipeline structure we want to use for all generated integrations. + +## Tests + +All mocks/fixtures are placed in the top `./__jest__` directory of the plugin. If many mocks/fixtures are required, try to split them up into separate file(s). + +Tests can be run with: + +```bash +node scripts/jest x-pack/plugins/integration_assistant/ --coverage +``` diff --git a/x-pack/plugins/integration_assistant/__jest__/fixtures/categorization.ts b/x-pack/plugins/integration_assistant/__jest__/fixtures/categorization.ts new file mode 100644 index 0000000000000..62fa18f5523c1 --- /dev/null +++ b/x-pack/plugins/integration_assistant/__jest__/fixtures/categorization.ts @@ -0,0 +1,289 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { Pipeline } from '../../common'; + +export const categorizationInitialPipeline: Pipeline = { + description: 'Pipeline to process mysql_enterprise audit logs', + processors: [ + { + set: { + field: 'ecs.version', + value: '8.11.0', + }, + }, + { + rename: { + field: 'message', + target_field: 'event.original', + ignore_missing: true, + if: 'ctx.event?.original == null', + }, + }, + { + remove: { + field: 'event.original', + tag: 'remove_original_event', + if: 'ctx?.tags == null || !(ctx.tags.contains("preserve_original_event"))', + ignore_failure: true, + ignore_missing: true, + }, + }, + ], +}; + +export const categorizationExpectedResults = { + docs: [ + { + key: 'value', + anotherKey: 'anotherValue', + }, + ], + pipeline: { + description: 'Pipeline to process mysql_enterprise audit logs', + processors: [ + { + set: { + field: 'ecs.version', + value: '8.11.0', + }, + }, + { + append: { + field: 'event.type', + value: ['change'], + if: "ctx.mysql_enterprise?.audit?.general_data?.sql_command == 'create_db'", + }, + }, + { + append: { + field: 'event.category', + value: ['database'], + if: "ctx.mysql_enterprise?.audit?.general_data?.sql_command == 'create_db'", + }, + }, + { + rename: { + field: 'message', + target_field: 'event.original', + ignore_missing: true, + if: 'ctx.event?.original == null', + }, + }, + { + remove: { + field: 'event.original', + tag: 'remove_original_event', + if: 'ctx?.tags == null || !(ctx.tags.contains("preserve_original_event"))', + ignore_failure: true, + ignore_missing: true, + }, + }, + ], + }, +}; + +export const categorizationInitialMockedResponse = [ + { + append: { + field: 'event.type', + value: ['creation'], + if: "ctx.mysql_enterprise?.audit?.general_data?.sql_command == 'create_db'", + }, + }, + { + append: { + field: 'event.category', + value: ['database'], + if: "ctx.mysql_enterprise.audit.general_data.sql_command == 'create_db'", + }, + }, +]; + +export const categorizationErrorMockedResponse = [ + { + append: { + field: 'event.type', + value: ['creation'], + if: "ctx.mysql_enterprise?.audit?.general_data?.sql_command == 'create_db'", + }, + }, + { + append: { + field: 'event.category', + value: ['database'], + if: "ctx.mysql_enterprise?.audit?.general_data?.sql_command == 'create_db'", + }, + }, +]; + +export const categorizationInvalidMockedResponse = [ + { + append: { + field: 'event.type', + value: ['change'], + if: "ctx.mysql_enterprise?.audit?.general_data?.sql_command == 'create_db'", + }, + }, + { + append: { + field: 'event.category', + value: ['database'], + if: "ctx.mysql_enterprise?.audit?.general_data?.sql_command == 'create_db'", + }, + }, +]; + +export const categorizationReviewMockedResponse = [ + { + append: { + field: 'event.type', + value: ['change'], + if: "ctx.mysql_enterprise?.audit?.general_data?.sql_command == 'create_db'", + }, + }, + { + append: { + field: 'event.category', + value: ['database'], + if: "ctx.mysql_enterprise?.audit?.general_data?.sql_command == 'create_db'", + }, + }, +]; + +export const testPipelineError: { pipelineResults: object[]; errors: object[] } = { + pipelineResults: [], + errors: [{ error: 'Sample error message 1' }, { error: 'Sample error message 2' }], +}; + +export const testPipelineValidResult: { pipelineResults: object[]; errors: object[] } = { + pipelineResults: [{ key: 'value', anotherKey: 'anotherValue' }], + errors: [], +}; + +export const testPipelineInvalidEcs: { pipelineResults: object[]; errors: object[] } = { + pipelineResults: [ + { event: { type: ['database'], category: ['creation'] }, anotherKey: 'anotherValue' }, + ], + errors: [], +}; + +export const categorizationTestState = { + rawSamples: ['{"test1": "test1"}'], + samples: ['{ "test1": "test1" }'], + formattedSamples: '{"test1": "test1"}', + ecsTypes: 'testtypes', + ecsCategories: 'testcategories', + exAnswer: 'testanswer', + lastExecutedChain: 'testchain', + packageName: 'testpackage', + dataStreamName: 'testdatastream', + errors: { test: 'testerror' }, + pipelineResults: [{ test: 'testresult' }], + finalized: false, + reviewed: false, + currentPipeline: { test: 'testpipeline' }, + currentProcessors: [ + { + append: { + field: 'event.type', + value: ['creation'], + if: "ctx.mysql_enterprise?.audit?.general_data?.sql_command == 'create_db'", + }, + }, + { + append: { + field: 'event.category', + value: ['database'], + if: "ctx.mysql_enterprise.audit.general_data.sql_command == 'create_db'", + }, + }, + ], + invalidCategorization: { test: 'testinvalid' }, + initialPipeline: categorizationInitialPipeline, + results: { test: 'testresults' }, +}; + +export const categorizationMockProcessors = [ + { + append: { + field: 'event.type', + value: ['creation'], + if: "ctx.mysql_enterprise?.audit?.general_data?.sql_command == 'create_db'", + }, + }, + { + append: { + field: 'event.category', + value: ['database'], + if: "ctx.mysql_enterprise.audit.general_data.sql_command == 'create_db'", + }, + }, +]; + +export const categorizationExpectedHandlerResponse = { + currentPipeline: { + description: 'Pipeline to process mysql_enterprise audit logs', + processors: [ + { + set: { + field: 'ecs.version', + value: '8.11.0', + }, + }, + { + append: { + field: 'event.type', + value: ['creation'], + if: "ctx.mysql_enterprise?.audit?.general_data?.sql_command == 'create_db'", + }, + }, + { + append: { + field: 'event.category', + value: ['database'], + if: "ctx.mysql_enterprise.audit.general_data.sql_command == 'create_db'", + }, + }, + { + rename: { + field: 'message', + target_field: 'event.original', + ignore_missing: true, + if: 'ctx.event?.original == null', + }, + }, + { + remove: { + field: 'event.original', + tag: 'remove_original_event', + if: 'ctx?.tags == null || !(ctx.tags.contains("preserve_original_event"))', + ignore_failure: true, + ignore_missing: true, + }, + }, + ], + }, + currentProcessors: [ + { + append: { + field: 'event.type', + value: ['creation'], + if: "ctx.mysql_enterprise?.audit?.general_data?.sql_command == 'create_db'", + }, + }, + { + append: { + field: 'event.category', + value: ['database'], + if: "ctx.mysql_enterprise.audit.general_data.sql_command == 'create_db'", + }, + }, + ], + reviewed: false, + lastExecutedChain: 'error', +}; diff --git a/x-pack/plugins/integration_assistant/__jest__/fixtures/ecs_mapping.ts b/x-pack/plugins/integration_assistant/__jest__/fixtures/ecs_mapping.ts new file mode 100644 index 0000000000000..f0ae923def0ef --- /dev/null +++ b/x-pack/plugins/integration_assistant/__jest__/fixtures/ecs_mapping.ts @@ -0,0 +1,448 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor 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 ecsMappingExpectedResults = { + mapping: { + mysql_enterprise: { + audit: { + test_array: null, + timestamp: { + target: '@timestamp', + confidence: 0.99, + type: 'date', + date_formats: ['yyyy-MM-dd HH:mm:ss'], + }, + id: null, + class: null, + cpu_usage: { + target: 'host.cpu.usage', + confidence: 0.99, + type: 'number', + date_formats: [], + }, + bytes: { + target: 'network.bytes', + confidence: 0.99, + type: 'number', + date_formats: [], + }, + account: { + user: { + target: 'user.name', + type: 'string', + date_formats: [], + confidence: 1, + }, + ip: { + target: 'source.ip', + type: 'string', + date_formats: [], + confidence: 1, + }, + }, + event: { + target: 'event.action', + confidence: 0.8, + type: 'string', + date_formats: [], + }, + }, + }, + }, + pipeline: { + description: 'Pipeline to process mysql_enterprise audit logs', + processors: [ + { + set: { + field: 'ecs.version', + tag: 'set_ecs_version', + value: '8.11.0', + }, + }, + { + rename: { + field: 'message', + target_field: 'event.original', + tag: 'rename_message', + ignore_missing: true, + if: 'ctx.event?.original == null', + }, + }, + { + remove: { + field: 'message', + ignore_missing: true, + tag: 'remove_message', + if: 'ctx.event?.original != null', + }, + }, + { + json: { + field: 'event.original', + tag: 'json_original', + target_field: 'mysql_enterprise.audit', + }, + }, + { + date: { + field: 'mysql_enterprise.audit.timestamp', + target_field: '@timestamp', + formats: ['yyyy-MM-dd HH:mm:ss'], + if: 'ctx.mysql_enterprise?.audit?.timestamp != null', + }, + }, + { + rename: { + field: 'mysql_enterprise.audit.cpu_usage', + target_field: 'host.cpu.usage', + ignore_missing: true, + }, + }, + { + rename: { + field: 'mysql_enterprise.audit.bytes', + target_field: 'network.bytes', + ignore_missing: true, + }, + }, + { + rename: { + field: 'mysql_enterprise.audit.account.user', + target_field: 'user.name', + ignore_missing: true, + }, + }, + { + convert: { + field: 'mysql_enterprise.audit.account.ip', + target_field: 'source.ip', + ignore_missing: true, + ignore_failure: true, + type: 'ip', + }, + }, + { + rename: { + field: 'mysql_enterprise.audit.event', + target_field: 'event.action', + ignore_missing: true, + }, + }, + { + script: { + description: 'Drops null/empty values recursively.', + tag: 'script_drop_null_empty_values', + lang: 'painless', + source: + 'boolean dropEmptyFields(Object object) {\n if (object == null || object == "") {\n return true;\n } else if (object instanceof Map) {\n ((Map) object).values().removeIf(value -> dropEmptyFields(value));\n return (((Map) object).size() == 0);\n } else if (object instanceof List) {\n ((List) object).removeIf(value -> dropEmptyFields(value));\n return (((List) object).length == 0);\n }\n return false;\n}\ndropEmptyFields(ctx);\n', + }, + }, + { + geoip: { + field: 'source.ip', + tag: 'geoip_source_ip', + target_field: 'source.geo', + ignore_missing: true, + }, + }, + { + geoip: { + ignore_missing: true, + database_file: 'GeoLite2-ASN.mmdb', + field: 'source.ip', + tag: 'geoip_source_asn', + target_field: 'source.as', + properties: ['asn', 'organization_name'], + }, + }, + { + rename: { + field: 'source.as.asn', + tag: 'rename_source_as_asn', + target_field: 'source.as.number', + ignore_missing: true, + }, + }, + { + rename: { + field: 'source.as.organization_name', + tag: 'rename_source_as_organization_name', + target_field: 'source.as.organization.name', + ignore_missing: true, + }, + }, + { + geoip: { + field: 'destination.ip', + tag: 'geoip_destination_ip', + target_field: 'destination.geo', + ignore_missing: true, + }, + }, + { + geoip: { + database_file: 'GeoLite2-ASN.mmdb', + field: 'destination.ip', + tag: 'geoip_destination_asn', + target_field: 'destination.as', + properties: ['asn', 'organization_name'], + ignore_missing: true, + }, + }, + { + rename: { + field: 'destination.as.asn', + tag: 'rename_destination_as_asn', + target_field: 'destination.as.number', + ignore_missing: true, + }, + }, + { + rename: { + field: 'destination.as.organization_name', + tag: 'rename_destination_as_organization_name', + target_field: 'destination.as.organization.name', + ignore_missing: true, + }, + }, + { + remove: { + field: ['mysql_enterprise.audit.account.ip'], + ignore_missing: true, + tag: 'remove_fields', + }, + }, + { + remove: { + field: 'event.original', + tag: 'remove_original_event', + if: 'ctx?.tags == null || !(ctx.tags.contains("preserve_original_event"))', + ignore_failure: true, + ignore_missing: true, + }, + }, + ], + on_failure: [ + { + append: { + field: 'error.message', + value: + 'Processor {{{_ingest.on_failure_processor_type}}} with tag {{{_ingest.on_failure_processor_tag}}} in pipeline {{{_ingest.on_failure_pipeline}}} failed with message: {{{_ingest.on_failure_message}}}', + }, + }, + { + set: { + field: 'event.kind', + value: 'pipeline_error', + }, + }, + ], + }, +}; + +export const ecsInitialMappingMockedResponse = { + mysql_enterprise: { + audit: { + test_array: null, + timestamp: { + target: 'event.action', + confidence: 0.99, + type: 'string', + date_formats: ['yyyy-MM-dd HH:mm:ss'], + }, + class: null, + id: { + target: 'file.code_signature.trusted', + confidence: 0.99, + type: 'boolean', + date_formats: [], + }, + cpu_usage: { + target: 'host.cpu.usage', + confidence: 0.99, + type: 'number', + date_formats: [], + }, + bytes: { + target: 'network.bytes', + confidence: 0.99, + type: 'number', + date_formats: [], + }, + account: { + user: { + target: 'user.name', + type: 'string', + date_formats: [], + confidence: 1.0, + }, + ip: { + target: 'source.ip', + type: 'string', + date_formats: [], + confidence: 1.0, + }, + }, + event: { + target: 'event.action', + confidence: 0.8, + type: 'string', + date_formats: [], + }, + }, + }, +}; + +export const ecsDuplicateMockedResponse = { + mysql_enterprise: { + audit: { + test_array: null, + timestamp: { + target: '@timestamp', + confidence: 0.99, + type: 'date', + date_formats: ['yyyy-MM-dd HH:mm:ss'], + }, + id: null, + bytes: { + target: 'network.bytes', + confidence: 0.99, + type: 'number', + date_formats: [], + }, + account: { + user: { + target: 'user.name', + type: 'string', + date_formats: [], + confidence: 1.0, + }, + ip: { + target: 'source.ip', + type: 'string', + date_formats: [], + confidence: 1.0, + }, + }, + }, + }, +}; + +export const ecsMissingKeysMockedResponse = { + mysql_enterprise: { + audit: { + test_array: null, + timestamp: { + target: '@timestamp', + confidence: 0.99, + type: 'date', + date_formats: ['yyyy-MM-dd HH:mm:ss'], + }, + id: null, + class: null, + cpu_usage: { + target: 'host.cpu.usage', + confidence: 0.99, + type: 'number', + date_formats: [], + }, + bytes: { + target: 'network.bytes', + confidence: 0.99, + type: 'number', + date_formats: [], + }, + account: { + user: { + target: 'user.name', + type: 'string', + date_formats: [], + confidence: 1.0, + }, + ip: { + target: 'source.ip', + type: 'string', + date_formats: [], + confidence: 1.0, + }, + }, + event: { + target: 'invalid.ecs.field', + confidence: 0.8, + type: 'string', + date_formats: [], + }, + }, + }, +}; + +export const ecsInvalidMappingMockedResponse = { + mysql_enterprise: { + audit: { + test_array: null, + timestamp: { + target: '@timestamp', + confidence: 0.99, + type: 'date', + date_formats: ['yyyy-MM-dd HH:mm:ss'], + }, + id: null, + class: null, + cpu_usage: { + target: 'host.cpu.usage', + confidence: 0.99, + type: 'number', + date_formats: [], + }, + bytes: { + target: 'network.bytes', + confidence: 0.99, + type: 'number', + date_formats: [], + }, + account: { + user: { + target: 'user.name', + type: 'string', + date_formats: [], + confidence: 1.0, + }, + ip: { + target: 'source.ip', + type: 'string', + date_formats: [], + confidence: 1.0, + }, + }, + event: { + target: 'event.action', + confidence: 0.8, + type: 'string', + date_formats: [], + }, + }, + }, +}; + +export const ecsTestState = { + ecs: 'teststring', + exAnswer: 'testanswer', + finalized: false, + currentPipeline: { test: 'testpipeline' }, + duplicateFields: [], + missingKeys: [], + invalidEcsFields: [], + results: { test: 'testresults' }, + logFormat: 'testlogformat', + ecsVersion: 'testversion', + currentMapping: { test1: 'test1' }, + lastExecutedChain: 'testchain', + rawSamples: ['{"test1": "test1"}'], + samples: ['{ "test1": "test1" }'], + packageName: 'testpackage', + dataStreamName: 'testdatastream', + formattedSamples: '{"test1": "test1"}', +}; diff --git a/x-pack/plugins/integration_assistant/__jest__/fixtures/index.ts b/x-pack/plugins/integration_assistant/__jest__/fixtures/index.ts new file mode 100644 index 0000000000000..7e3e155e67b8a --- /dev/null +++ b/x-pack/plugins/integration_assistant/__jest__/fixtures/index.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 { Pipeline } from '../../common'; +const currentPipelineMock: Pipeline = { + description: 'Pipeline to process mysql_enterprise audit logs', + processors: [ + { + set: { + field: 'ecs.version', + value: '8.11.0', + }, + }, + { + rename: { + field: 'message', + target_field: 'event.original', + ignore_missing: true, + if: 'ctx.event?.original == null', + }, + }, + { + remove: { + field: 'event.original', + tag: 'remove_original_event', + if: 'ctx?.tags == null || !(ctx.tags.contains("preserve_original_event"))', + ignore_failure: true, + ignore_missing: true, + }, + }, + ], +}; + +export const mockedRequest = { + rawSamples: [ + '{ "timestamp": "2020-10-19 19:31:31", "cpu_usage": 0.1, "class": "general", "event": "status", "test_array": ["test1", "test2"]}', + '{ "timestamp": "2020-10-19 19:32:10", "cpu_usage": 0.2, "class": "connection", "event": "disconnect", "bytes": 16, "account": { "user": "audit_test_user2", "ip": "10.10.10.10" }}', + ], + packageName: 'mysql_enterprise', + dataStreamName: 'audit', +}; + +export const mockedRequestWithPipeline = { + rawSamples: [ + '{ "timestamp": "2020-10-19 19:31:31", "cpu_usage": 0.1, "class": "general", "event": "status", "test_array": ["test1", "test2"]}', + '{ "timestamp": "2020-10-19 19:32:10", "cpu_usage": 0.2, "class": "connection", "event": "disconnect", "bytes": 16, "account": { "user": "audit_test_user2", "ip": "10.10.10.10" }}', + ], + packageName: 'mysql_enterprise', + dataStreamName: 'audit', + currentPipeline: currentPipelineMock, +}; diff --git a/x-pack/plugins/integration_assistant/__jest__/fixtures/related.ts b/x-pack/plugins/integration_assistant/__jest__/fixtures/related.ts new file mode 100644 index 0000000000000..f34133a4f520f --- /dev/null +++ b/x-pack/plugins/integration_assistant/__jest__/fixtures/related.ts @@ -0,0 +1,277 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Pipeline } from '../../common'; + +export const relatedInitialPipeline: Pipeline = { + description: 'Pipeline to process mysql_enterprise audit logs', + processors: [ + { + set: { + field: 'ecs.version', + value: '8.11.0', + }, + }, + { + rename: { + field: 'message', + target_field: 'event.original', + ignore_missing: true, + if: 'ctx.event?.original == null', + }, + }, + { + remove: { + field: 'event.original', + tag: 'remove_original_event', + if: 'ctx?.tags == null || !(ctx.tags.contains("preserve_original_event"))', + ignore_failure: true, + ignore_missing: true, + }, + }, + ], +}; + +export const relatedExpectedResults = { + docs: [ + { + key: 'value', + anotherKey: 'anotherValue', + }, + ], + pipeline: { + description: 'Pipeline to process mysql_enterprise audit logs', + processors: [ + { + set: { + field: 'ecs.version', + value: '8.11.0', + }, + }, + { + append: { + field: 'related.ip', + value: ['{{{source.ip}}}'], + allow_duplicates: false, + if: 'ctx.source?.ip != null', + }, + }, + { + append: { + field: 'related.ip', + value: ['{{{destination.ip}}}'], + allow_duplicates: false, + if: 'ctx.destination?.ip != null', + }, + }, + { + rename: { + field: 'message', + target_field: 'event.original', + ignore_missing: true, + if: 'ctx.event?.original == null', + }, + }, + { + remove: { + field: 'event.original', + tag: 'remove_original_event', + if: 'ctx?.tags == null || !(ctx.tags.contains("preserve_original_event"))', + ignore_failure: true, + ignore_missing: true, + }, + }, + ], + }, +}; + +export const relatedInitialMockedResponse = [ + { + append: { + field: 'related.ip', + value: ['{{{source.ip}?.split(":")[0]}}'], + allow_duplicates: false, + if: 'ctx.source?.ip != null', + }, + }, + { + append: { + field: 'related.ip', + value: ['{{{destination.ip}}}'], + allow_duplicates: false, + if: 'ctx.destination?.ip != null', + }, + }, +]; + +export const relatedErrorMockedResponse = [ + { + append: { + field: 'related.ip', + value: ['{{{source.ip}}}'], + allow_duplicates: false, + if: 'ctx.source?.ip != null', + }, + }, + { + append: { + field: 'related.ip', + value: ['{{{destination.ip}}}'], + allow_duplicates: false, + if: 'ctx.destination?.ip != null', + }, + }, +]; + +export const relatedReviewMockedResponse = [ + { + append: { + field: 'related.ip', + value: ['{{{source.ip}}}'], + allow_duplicates: false, + if: 'ctx.source?.ip != null', + }, + }, + { + append: { + field: 'related.ip', + value: ['{{{destination.ip}}}'], + allow_duplicates: false, + if: 'ctx.destination?.ip != null', + }, + }, +]; + +export const testPipelineError: { pipelineResults: object[]; errors: object[] } = { + pipelineResults: [], + errors: [{ error: 'Sample error message 1' }, { error: 'Sample error message 2' }], +}; + +export const testPipelineValidResult: { pipelineResults: object[]; errors: object[] } = { + pipelineResults: [{ key: 'value', anotherKey: 'anotherValue' }], + errors: [], +}; + +export const relatedTestState = { + rawSamples: ['{"test1": "test1"}'], + samples: ['{ "test1": "test1" }'], + formattedSamples: '{"test1": "test1"}', + ecs: 'testtypes', + exAnswer: 'testanswer', + packageName: 'testpackage', + dataStreamName: 'testdatastream', + errors: { test: 'testerror' }, + pipelineResults: [{ test: 'testresult' }], + finalized: false, + reviewed: false, + currentPipeline: { test: 'testpipeline' }, + currentProcessors: [ + { + append: { + field: 'related.ip', + value: ['{{{source.ip}?.split(":")[0]}}'], + allow_duplicates: false, + if: 'ctx.source?.ip != null', + }, + }, + { + append: { + field: 'related.ip', + value: ['{{{destination.ip}}}'], + allow_duplicates: false, + if: 'ctx.destination?.ip != null', + }, + }, + ], + initialPipeline: relatedInitialPipeline, + results: { test: 'testresults' }, + lastExecutedChain: 'testchain', +}; + +export const relatedMockProcessors = [ + { + append: { + field: 'related.ip', + value: ['{{{source.ip}?.split(":")[0]}}'], + allow_duplicates: false, + if: 'ctx.source?.ip != null', + }, + }, + { + append: { + field: 'related.ip', + value: ['{{{destination.ip}}}'], + allow_duplicates: false, + if: 'ctx.destination?.ip != null', + }, + }, +]; + +export const relatedExpectedHandlerResponse = { + currentPipeline: { + description: 'Pipeline to process mysql_enterprise audit logs', + processors: [ + { + set: { + field: 'ecs.version', + value: '8.11.0', + }, + }, + { + append: { + field: 'related.ip', + value: ['{{{source.ip}?.split(":")[0]}}'], + allow_duplicates: false, + if: 'ctx.source?.ip != null', + }, + }, + { + append: { + field: 'related.ip', + value: ['{{{destination.ip}}}'], + allow_duplicates: false, + if: 'ctx.destination?.ip != null', + }, + }, + { + rename: { + field: 'message', + target_field: 'event.original', + ignore_missing: true, + if: 'ctx.event?.original == null', + }, + }, + { + remove: { + field: 'event.original', + tag: 'remove_original_event', + if: 'ctx?.tags == null || !(ctx.tags.contains("preserve_original_event"))', + ignore_failure: true, + ignore_missing: true, + }, + }, + ], + }, + currentProcessors: [ + { + append: { + field: 'event.type', + value: ['creation'], + if: "ctx.mysql_enterprise?.audit?.general_data?.sql_command == 'create_db'", + }, + }, + { + append: { + field: 'event.category', + value: ['database'], + if: "ctx.mysql_enterprise.audit.general_data.sql_command == 'create_db'", + }, + }, + ], + reviewed: false, + lastExecutedChain: 'error', +}; diff --git a/x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.schema.yaml b/x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.schema.yaml new file mode 100644 index 0000000000000..798ead34114a6 --- /dev/null +++ b/x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.schema.yaml @@ -0,0 +1,33 @@ +openapi: 3.0.3 +info: + title: Integration Assistatnt Build Integrarion API endpoint + version: "1" +paths: + /api/integration_assistant/build: + post: + summary: Builds Integration with the given input samples + operationId: BuildIntegration + x-codegen-enabled: false + description: Build Integration for the given input samples + tags: + - Build Integration API + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - integration + properties: + integration: + $ref: "../model/common_attributes.schema.yaml#/components/schemas/Integration" + responses: + 200: + description: Indicates a successful call. + content: + application/octet-stream: + schema: + # a binary file of any type + type: string + format: binary diff --git a/x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.ts b/x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.ts new file mode 100644 index 0000000000000..728b6d7403696 --- /dev/null +++ b/x-pack/plugins/integration_assistant/common/api/build_integration/build_integration.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +import { Integration } from '../model/common_attributes'; + +export type BuildIntegrationRequestBody = z.infer; +export const BuildIntegrationRequestBody = z.object({ + integration: Integration, +}); +export type BuildIntegrationRequestBodyInput = z.input; diff --git a/x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.schema.yaml b/x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.schema.yaml new file mode 100644 index 0000000000000..d46213e5c8afb --- /dev/null +++ b/x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.schema.yaml @@ -0,0 +1,43 @@ +openapi: 3.0.3 +info: + title: Integration Assistatnt Categorization API endpoint + version: "1" +paths: + /api/integration_assistant/categorization: + post: + summary: Builds Categorization processors based on the samples + operationId: Categorization + x-codegen-enabled: false + description: Perform Categorization for the given ecs mappings. + tags: + - Categorization API + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - packageName + - datastreamName + - rawSamples + - currentPipeline + - connectorId + properties: + packageName: + $ref: "../model/common_attributes.schema.yaml#/components/schemas/PackageName" + datastreamName: + $ref: "../model/common_attributes.schema.yaml#/components/schemas/DatastreamName" + rawSamples: + $ref: "../model/common_attributes.schema.yaml#/components/schemas/RawSamples" + currentPipeline: + $ref: "../model/common_attributes.schema.yaml#/components/schemas/Pipeline" + connectorId: + $ref: "../model/common_attributes.schema.yaml#/components/schemas/Connector" + responses: + 200: + description: Indicates a successful call. + content: + application/json: + schema: + $ref: "../model/response_schemas.schema.yaml#/components/schemas/CategorizationAPIResponse" diff --git a/x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.ts b/x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.ts new file mode 100644 index 0000000000000..e3c81c95b7404 --- /dev/null +++ b/x-pack/plugins/integration_assistant/common/api/categorization/categorization_route.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +import { + Connector, + DatastreamName, + PackageName, + Pipeline, + RawSamples, +} from '../model/common_attributes'; +import { CategorizationAPIResponse } from '../model/response_schemas'; + +export type CategorizationRequestBody = z.infer; +export const CategorizationRequestBody = z.object({ + packageName: PackageName, + datastreamName: DatastreamName, + rawSamples: RawSamples, + currentPipeline: Pipeline, + connectorId: Connector, +}); +export type CategorizationRequestBodyInput = z.input; + +export type CategorizationResponse = z.infer; +export const CategorizationResponse = CategorizationAPIResponse; diff --git a/x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.schema.yaml b/x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.schema.yaml new file mode 100644 index 0000000000000..22785fc40bbf2 --- /dev/null +++ b/x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.schema.yaml @@ -0,0 +1,34 @@ +openapi: 3.0.3 +info: + title: Integration Assistatnt Check Pipeline API endpoint + version: "1" +paths: + /api/integration_assistant/pipeline: + post: + summary: Checks if the pipeline is valid for the given samples + operationId: CheckPipeline + x-codegen-enabled: false + description: Check latest pipeline against the input samples. + tags: + - Check Pipeline API + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - rawSamples + - pipeline + properties: + rawSamples: + $ref: "../model/common_attributes.schema.yaml#/components/schemas/RawSamples" + pipeline: + $ref: "../model/common_attributes.schema.yaml#/components/schemas/Pipeline" + responses: + 200: + description: Indicates a successful call. + content: + application/json: + schema: + $ref: "../model/response_schemas.schema.yaml#/components/schemas/CheckPipelineAPIResponse" diff --git a/x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.ts b/x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.ts new file mode 100644 index 0000000000000..39197c3675b21 --- /dev/null +++ b/x-pack/plugins/integration_assistant/common/api/check_pipeline/check_pipeline.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +import { Pipeline, RawSamples } from '../model/common_attributes'; +import { CheckPipelineAPIResponse } from '../model/response_schemas'; + +export type CheckPipelineRequestBody = z.infer; +export const CheckPipelineRequestBody = z.object({ + rawSamples: RawSamples, + pipeline: Pipeline, +}); +export type CheckPipelineRequestBodyInput = z.input; + +export type CheckPipelineResponse = z.infer; +export const CheckPipelineResponse = CheckPipelineAPIResponse; diff --git a/x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.schema.yaml b/x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.schema.yaml new file mode 100644 index 0000000000000..996635404e0b9 --- /dev/null +++ b/x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.schema.yaml @@ -0,0 +1,42 @@ +openapi: 3.0.3 +info: + title: Integration Assistatnt ECS Mapping API endpoint + version: "1" +paths: + /api/integration_assistant/ecs: + post: + summary: Builds ECS Mapping based on the input samples + operationId: EcsMapping + x-codegen-enabled: false + description: Perform ECS mapping for the given input JSON samples + tags: + - ECS Mapping API + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - packageName + - datastreamName + - rawSamples + - connectorId + properties: + packageName: + $ref: "../model/common_attributes.schema.yaml#/components/schemas/PackageName" + datastreamName: + $ref: "../model/common_attributes.schema.yaml#/components/schemas/DatastreamName" + rawSamples: + $ref: "../model/common_attributes.schema.yaml#/components/schemas/RawSamples" + mapping: + $ref: "../model/common_attributes.schema.yaml#/components/schemas/Mapping" + connectorId: + $ref: "../model/common_attributes.schema.yaml#/components/schemas/Connector" + responses: + 200: + description: Indicates a successful call. + content: + application/json: + schema: + $ref: "../model/response_schemas.schema.yaml#/components/schemas/EcsMappingAPIResponse" diff --git a/x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.ts b/x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.ts new file mode 100644 index 0000000000000..0a265c75da493 --- /dev/null +++ b/x-pack/plugins/integration_assistant/common/api/ecs/ecs_route.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +import { + Connector, + DatastreamName, + Mapping, + PackageName, + RawSamples, +} from '../model/common_attributes'; +import { EcsMappingAPIResponse } from '../model/response_schemas'; + +export type EcsMappingRequestBody = z.infer; +export const EcsMappingRequestBody = z.object({ + packageName: PackageName, + datastreamName: DatastreamName, + rawSamples: RawSamples, + mapping: Mapping.optional(), + connectorId: Connector, +}); +export type EcsMappingRequestBodyInput = z.input; + +export type EcsMappingResponse = z.infer; +export const EcsMappingResponse = EcsMappingAPIResponse; diff --git a/x-pack/plugins/integration_assistant/common/api/model/common_attributes.schema.yaml b/x-pack/plugins/integration_assistant/common/api/model/common_attributes.schema.yaml new file mode 100644 index 0000000000000..eb8c303edb5ab --- /dev/null +++ b/x-pack/plugins/integration_assistant/common/api/model/common_attributes.schema.yaml @@ -0,0 +1,156 @@ +openapi: 3.0.3 +info: + title: Common Rule Attributes + version: "not applicable" +paths: {} +components: + x-codegen-enabled: false + schemas: + PackageName: + type: string + minLength: 1 + description: Package name for the integration to be built. + + DatastreamName: + type: string + minLength: 1 + description: Datastream name for the integration to be built. + + RawSamples: + type: array + items: + type: string + description: String array containing the json raw samples that are used for ecs mapping. + + Mapping: + type: object + description: mapping object to ECS Mapping Request. + + Connector: + type: string + description: LLM Connector to be used in each API request. + + Docs: + type: array + description: An array of processed documents. + items: + type: object + + Pipeline: + type: object + description: The pipeline object. + required: + - processors + properties: + name: + type: string + description: The name of the pipeline. + description: + type: string + description: The description of the pipeline. + version: + type: integer + description: The version of the pipeline. + processors: + type: array + items: + $ref: "../model/processor_attributes.schema.yaml#/components/schemas/ESProcessorItem" + description: The processors to execute. + on_failure: + type: array + items: + $ref: "../model/processor_attributes.schema.yaml#/components/schemas/ESProcessorItem" + description: The processors to execute if the pipeline fails. + + InputType: + type: string + description: The input type for the datastream to pull logs from. + enum: + - aws_cloudwatch + - aws_s3 + - azure_blob_storage + - azure_eventhub + - cloudfoundry + - filestream + - gcp_pubsub + - gcs + - http_endpoint + - journald + - kafka + - tcp + - udp + + Datastream: + type: object + description: The datastream object. + required: + - name + - title + - description + - inputTypes + - rawSamples + - pipeline + - docs + properties: + name: + type: string + description: The name of the datastream. + title: + type: string + description: The title of the datastream. + description: + type: string + description: The description of the datastream. + inputTypes: + type: array + items: + $ref: "#/components/schemas/InputType" + description: The input types of the datastream. + rawSamples: + $ref: "#/components/schemas/RawSamples" + description: The raw samples of the datastream. + pipeline: + $ref: "#/components/schemas/Pipeline" + description: The pipeline of the datastream. + docs: + $ref: "#/components/schemas/Docs" + description: The documents of the datastream. + + Integration: + type: object + description: The integration object. + required: + - name + - title + - description + - dataStreams + properties: + name: + type: string + description: The name of the integration. + title: + type: string + description: The title of the integration. + description: + type: string + description: The description of the integration. + dataStreams: + type: array + items: + $ref: "#/components/schemas/Datastream" + description: The datastreams of the integration. + logo: + type: string + description: The logo of the integration. + + PipelineResults: + type: array + description: An array of pipeline results. + items: + type: object + + Errors: + type: array + description: An array of errors. + items: + type: object diff --git a/x-pack/plugins/integration_assistant/common/api/model/common_attributes.ts b/x-pack/plugins/integration_assistant/common/api/model/common_attributes.ts new file mode 100644 index 0000000000000..426224a0622d0 --- /dev/null +++ b/x-pack/plugins/integration_assistant/common/api/model/common_attributes.ts @@ -0,0 +1,169 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { ESProcessorItem } from './processor_attributes'; + +/** + * Package name for the integration to be built. + */ +export type PackageName = z.infer; +export const PackageName = z.string().min(1); + +/** + * Datastream name for the integration to be built. + */ +export type DatastreamName = z.infer; +export const DatastreamName = z.string().min(1); + +/** + * String array containing the json raw samples that are used for ecs mapping. + */ +export type RawSamples = z.infer; +export const RawSamples = z.array(z.string()); + +/** + * mapping object to ECS Mapping Request. + */ +export type Mapping = z.infer; +export const Mapping = z.object({}); + +/** + * LLM Connector to be used in each API request. + */ +export type Connector = z.infer; +export const Connector = z.string(); + +/** + * An array of processed documents. + */ +export type Docs = z.infer; +export const Docs = z.array(z.object({})); + +/** + * The pipeline object. + */ +export type Pipeline = z.infer; +export const Pipeline = z.object({ + /** + * The name of the pipeline. + */ + name: z.string().optional(), + /** + * The description of the pipeline. + */ + description: z.string().optional(), + /** + * The version of the pipeline. + */ + version: z.number().int().optional(), + /** + * The processors to execute. + */ + processors: z.array(ESProcessorItem), + /** + * The processors to execute if the pipeline fails. + */ + on_failure: z.array(ESProcessorItem).optional(), +}); + +/** + * The input type for the datastream to pull logs from. + */ +export type InputType = z.infer; +export const InputType = z.enum([ + 'aws_cloudwatch', + 'aws_s3', + 'azure_blob_storage', + 'azure_eventhub', + 'cloudfoundry', + 'filestream', + 'gcp_pubsub', + 'gcs', + 'http_endpoint', + 'journald', + 'kafka', + 'tcp', + 'udp', +]); +export type InputTypeEnum = typeof InputType.enum; +export const InputTypeEnum = InputType.enum; + +/** + * The datastream object. + */ +export type Datastream = z.infer; +export const Datastream = z.object({ + /** + * The name of the datastream. + */ + name: z.string(), + /** + * The title of the datastream. + */ + title: z.string(), + /** + * The description of the datastream. + */ + description: z.string(), + /** + * The input types of the datastream. + */ + inputTypes: z.array(InputType), + /** + * The raw samples of the datastream. + */ + rawSamples: RawSamples, + /** + * The pipeline of the datastream. + */ + pipeline: Pipeline, + /** + * The documents of the datastream. + */ + docs: Docs, +}); + +/** + * The integration object. + */ +export type Integration = z.infer; +export const Integration = z.object({ + /** + * The name of the integration. + */ + name: z.string(), + /** + * The title of the integration. + */ + title: z.string(), + /** + * The description of the integration. + */ + description: z.string(), + /** + * The datastreams of the integration. + */ + dataStreams: z.array(Datastream), + /** + * The logo of the integration. + */ + logo: z.string().optional(), +}); + +/** + * An array of pipeline results. + */ +export type PipelineResults = z.infer; +export const PipelineResults = z.array(z.object({})); + +/** + * An array of errors. + */ +export type Errors = z.infer; +export const Errors = z.array(z.object({})); diff --git a/x-pack/plugins/integration_assistant/common/api/model/processor_attributes.schema.yaml b/x-pack/plugins/integration_assistant/common/api/model/processor_attributes.schema.yaml new file mode 100644 index 0000000000000..a0c930361737a --- /dev/null +++ b/x-pack/plugins/integration_assistant/common/api/model/processor_attributes.schema.yaml @@ -0,0 +1,36 @@ +openapi: 3.0.3 +info: + title: Common Rule Attributes + version: "not applicable" +paths: {} +components: + x-codegen-enabled: false + schemas: + ESProcessorItem: + type: object + description: Processor item for the Elasticsearch processor. + additionalProperties: + $ref: "#/components/schemas/ESProcessorOptions" + + ESProcessorOptions: + type: object + description: Processor options for the Elasticsearch processor. + properties: + on_failure: + type: array + items: + $ref: "#/components/schemas/ESProcessorItem" + description: An array of items to execute if the processor fails. + ignore_failure: + type: boolean + description: If true, the processor continues to the next processor if the current processor fails. + ignore_missing: + type: boolean + description: If true, the processor continues to the next processor if the field is missing. + if: + type: string + description: Conditionally execute the processor. + tag: + type: string + description: A tag to assign to the document after processing. + additionalProperties: true diff --git a/x-pack/plugins/integration_assistant/common/api/model/processor_attributes.ts b/x-pack/plugins/integration_assistant/common/api/model/processor_attributes.ts new file mode 100644 index 0000000000000..a46f04e3fa130 --- /dev/null +++ b/x-pack/plugins/integration_assistant/common/api/model/processor_attributes.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +/** + * Processor item for the Elasticsearch processor. + */ +export type ESProcessorItem = Record; +export const ESProcessorItem: z.ZodType = z + .object({}) + .catchall(z.lazy(() => ESProcessorOptions)); + +/** + * Processor options for the Elasticsearch processor. + */ +export interface ESProcessorOptions { + on_failure?: ESProcessorItem[]; + ignore_failure?: boolean; + ignore_missing?: boolean; + if?: string; + tag?: string; + [key: string]: unknown; +} +export const ESProcessorOptions = z + .object({ + /** + * An array of items to execute if the processor fails. + */ + on_failure: z.array(ESProcessorItem).optional(), + /** + * If true, the processor continues to the next processor if the current processor fails. + */ + ignore_failure: z.boolean().optional(), + /** + * If true, the processor continues to the next processor if the field is missing. + */ + ignore_missing: z.boolean().optional(), + /** + * Conditionally execute the processor. + */ + if: z.string().optional(), + /** + * A tag to assign to the document after processing. + */ + tag: z.string().optional(), + }) + .catchall(z.unknown()); diff --git a/x-pack/plugins/integration_assistant/common/api/model/response_schemas.schema.yaml b/x-pack/plugins/integration_assistant/common/api/model/response_schemas.schema.yaml new file mode 100644 index 0000000000000..100581cd21ceb --- /dev/null +++ b/x-pack/plugins/integration_assistant/common/api/model/response_schemas.schema.yaml @@ -0,0 +1,65 @@ +openapi: 3.0.3 +info: + title: Response Schemas. + version: "not applicable" +paths: {} +components: + x-codegen-enabled: false + schemas: + EcsMappingAPIResponse: + type: object + required: + - results + properties: + results: + type: object + required: + - mapping + - pipeline + properties: + mapping: + $ref: "./common_attributes.schema.yaml#/components/schemas/Mapping" + pipeline: + $ref: "./common_attributes.schema.yaml#/components/schemas/Pipeline" + + CategorizationAPIResponse: + type: object + required: + - results + properties: + results: + type: object + required: + - docs + - pipeline + properties: + docs: + $ref: "./common_attributes.schema.yaml#/components/schemas/Docs" + pipeline: + $ref: "./common_attributes.schema.yaml#/components/schemas/Pipeline" + + RelatedAPIResponse: + type: object + required: + - results + properties: + results: + type: object + required: + - docs + - pipeline + properties: + docs: + $ref: "./common_attributes.schema.yaml#/components/schemas/Docs" + pipeline: + $ref: "./common_attributes.schema.yaml#/components/schemas/Pipeline" + + CheckPipelineAPIResponse: + type: object + required: + - pipelineResults + properties: + pipelineResults: + $ref: "./common_attributes.schema.yaml#/components/schemas/PipelineResults" + errors: + $ref: "./common_attributes.schema.yaml#/components/schemas/Errors" diff --git a/x-pack/plugins/integration_assistant/common/api/model/response_schemas.ts b/x-pack/plugins/integration_assistant/common/api/model/response_schemas.ts new file mode 100644 index 0000000000000..f8a42d2081488 --- /dev/null +++ b/x-pack/plugins/integration_assistant/common/api/model/response_schemas.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Response Schemas. + * version: not applicable + */ + +import { z } from 'zod'; + +import { Docs, Errors, Mapping, Pipeline, PipelineResults } from './common_attributes'; + +export type EcsMappingAPIResponse = z.infer; +export const EcsMappingAPIResponse = z.object({ + results: z.object({ + mapping: Mapping, + pipeline: Pipeline, + }), +}); + +export type CategorizationAPIResponse = z.infer; +export const CategorizationAPIResponse = z.object({ + results: z.object({ + docs: Docs, + pipeline: Pipeline, + }), +}); + +export type RelatedAPIResponse = z.infer; +export const RelatedAPIResponse = z.object({ + results: z.object({ + docs: Docs, + pipeline: Pipeline, + }), +}); + +export type CheckPipelineAPIResponse = z.infer; +export const CheckPipelineAPIResponse = z.object({ + pipelineResults: PipelineResults, + errors: Errors.optional(), +}); diff --git a/x-pack/plugins/integration_assistant/common/api/related/related_route.schema.yaml b/x-pack/plugins/integration_assistant/common/api/related/related_route.schema.yaml new file mode 100644 index 0000000000000..3172d9f9ba812 --- /dev/null +++ b/x-pack/plugins/integration_assistant/common/api/related/related_route.schema.yaml @@ -0,0 +1,43 @@ +openapi: 3.0.3 +info: + title: Integration Assistatnt Related API endpoint + version: "1" +paths: + /api/integration_assistant/related: + post: + summary: Builds related.* fields for integration with the given input samples + operationId: Related + x-codegen-enabled: false + description: Add Related mappings for the given samples. + tags: + - Related API + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - packageName + - datastreamName + - rawSamples + - currentPipeline + - connectorId + properties: + packageName: + $ref: "../model/common_attributes.schema.yaml#/components/schemas/PackageName" + datastreamName: + $ref: "../model/common_attributes.schema.yaml#/components/schemas/DatastreamName" + rawSamples: + $ref: "../model/common_attributes.schema.yaml#/components/schemas/RawSamples" + currentPipeline: + $ref: "../model/common_attributes.schema.yaml#/components/schemas/Pipeline" + connectorId: + $ref: "../model/common_attributes.schema.yaml#/components/schemas/Connector" + responses: + 200: + description: Indicates a successful call. + content: + application/json: + schema: + $ref: "../model/response_schemas.schema.yaml#/components/schemas/RelatedAPIResponse" diff --git a/x-pack/plugins/integration_assistant/common/api/related/related_route.ts b/x-pack/plugins/integration_assistant/common/api/related/related_route.ts new file mode 100644 index 0000000000000..2b8c46866cc7b --- /dev/null +++ b/x-pack/plugins/integration_assistant/common/api/related/related_route.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; + +import { + Connector, + DatastreamName, + PackageName, + Pipeline, + RawSamples, +} from '../model/common_attributes'; +import { RelatedAPIResponse } from '../model/response_schemas'; + +export type RelatedRequestBody = z.infer; +export const RelatedRequestBody = z.object({ + packageName: PackageName, + datastreamName: DatastreamName, + rawSamples: RawSamples, + currentPipeline: Pipeline, + connectorId: Connector, +}); +export type RelatedRequestBodyInput = z.input; + +export type RelatedResponse = z.infer; +export const RelatedResponse = RelatedAPIResponse; diff --git a/x-pack/plugins/integration_assistant/common/constants.ts b/x-pack/plugins/integration_assistant/common/constants.ts new file mode 100644 index 0000000000000..59b0a2cd9b094 --- /dev/null +++ b/x-pack/plugins/integration_assistant/common/constants.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// Plugin information +export const PLUGIN_ID = 'integrationAssistant'; + +// Public App Routes +export const INTEGRATION_ASSISTANT_APP_ROUTE = '/app/integration_assistant'; + +// Server API Routes +export const INTEGRATION_ASSISTANT_BASE_PATH = '/api/integration_assistant'; +export const ECS_GRAPH_PATH = `${INTEGRATION_ASSISTANT_BASE_PATH}/ecs`; +export const CATEGORIZATION_GRAPH_PATH = `${INTEGRATION_ASSISTANT_BASE_PATH}/categorization`; +export const RELATED_GRAPH_PATH = `${INTEGRATION_ASSISTANT_BASE_PATH}/related`; +export const INTEGRATION_BUILDER_PATH = `${INTEGRATION_ASSISTANT_BASE_PATH}/build`; +export const TEST_PIPELINE_PATH = `${INTEGRATION_ASSISTANT_BASE_PATH}/pipeline`; diff --git a/x-pack/plugins/integration_assistant/common/ecs.ts b/x-pack/plugins/integration_assistant/common/ecs.ts new file mode 100644 index 0000000000000..bdc2b885febe1 --- /dev/null +++ b/x-pack/plugins/integration_assistant/common/ecs.ts @@ -0,0 +1,1950 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +interface EcsFields { + [key: string]: string; +} + +export const ECS_FULL: EcsFields = { + '@timestamp': 'Date/time when the event originated.', + 'agent.build.original': 'Extended build information for the agent.', + 'agent.ephemeral_id': 'Ephemeral identifier of this agent.', + 'agent.id': 'Unique identifier of this agent.', + 'agent.name': 'Custom name of the agent.', + 'agent.type': 'Type of the agent.', + 'agent.version': 'Version of the agent.', + 'client.address': 'Client network address.', + 'client.as.number': 'Unique number allocated to the autonomous system.', + 'client.as.organization.name': 'Organization name.', + 'client.bytes': 'Bytes sent from the client to the server.', + 'client.domain': 'The domain name of the client.', + 'client.geo.city_name': 'City name.', + 'client.geo.continent_code': 'Continent code.', + 'client.geo.continent_name': 'Name of the continent.', + 'client.geo.country_iso_code': 'Country ISO code.', + 'client.geo.country_name': 'Country name.', + 'client.geo.location': 'Longitude and latitude.', + 'client.geo.name': 'User-defined description of a location.', + 'client.geo.postal_code': 'Postal code.', + 'client.geo.region_iso_code': 'Region ISO code.', + 'client.geo.region_name': 'Region name.', + 'client.geo.timezone': 'Time zone.', + 'client.ip': 'IP address of the client.', + 'client.mac': 'MAC address of the client.', + 'client.nat.ip': 'Client NAT ip address', + 'client.nat.port': 'Client NAT port', + 'client.packets': 'Packets sent from the client to the server.', + 'client.port': 'Port of the client.', + 'client.registered_domain': 'The highest registered client domain, stripped of the subdomain.', + 'client.subdomain': 'The subdomain of the domain.', + 'client.top_level_domain': 'The effective top level domain (com, org, net, co.uk).', + 'client.user.domain': 'Name of the directory the user is a member of.', + 'client.user.email': 'User email address.', + 'client.user.full_name': 'Users full name, if available.', + 'client.user.group.domain': 'Name of the directory the group is a member of.', + 'client.user.group.id': 'Unique identifier for the group on the system/platform.', + 'client.user.group.name': 'Name of the group.', + 'client.user.hash': 'Unique user hash to correlate information for a user in anonymized form.', + 'client.user.id': 'Unique identifier of the user.', + 'client.user.name': 'Short name or login of the user.', + 'client.user.roles': 'Array of user roles at the time of the event.', + 'cloud.account.id': 'The cloud account or organization id.', + 'cloud.account.name': 'The cloud account name.', + 'cloud.availability_zone': + 'Availability zone in which this host, resource, or service is located.', + 'cloud.instance.id': 'Instance ID of the host machine.', + 'cloud.instance.name': 'Instance name of the host machine.', + 'cloud.machine.type': 'Machine type of the host machine.', + 'cloud.origin.account.id': 'The cloud account or organization id.', + 'cloud.origin.account.name': 'The cloud account name.', + 'cloud.origin.availability_zone': + 'Availability zone in which this host, resource, or service is located.', + 'cloud.origin.instance.id': 'Instance ID of the host machine.', + 'cloud.origin.instance.name': 'Instance name of the host machine.', + 'cloud.origin.machine.type': 'Machine type of the host machine.', + 'cloud.origin.project.id': 'The cloud project id.', + 'cloud.origin.project.name': 'The cloud project name.', + 'cloud.origin.provider': 'Name of the cloud provider.', + 'cloud.origin.region': 'Region in which this host, resource, or service is located.', + 'cloud.origin.service.name': 'The cloud service name.', + 'cloud.project.id': 'The cloud project id.', + 'cloud.project.name': 'The cloud project name.', + 'cloud.provider': 'Name of the cloud provider.', + 'cloud.region': 'Region in which this host, resource, or service is located.', + 'cloud.service.name': 'The cloud service name.', + 'cloud.target.account.id': 'The cloud account or organization id.', + 'cloud.target.account.name': 'The cloud account name.', + 'cloud.target.availability_zone': + 'Availability zone in which this host, resource, or service is located.', + 'cloud.target.instance.id': 'Instance ID of the host machine.', + 'cloud.target.instance.name': 'Instance name of the host machine.', + 'cloud.target.machine.type': 'Machine type of the host machine.', + 'cloud.target.project.id': 'The cloud project id.', + 'cloud.target.project.name': 'The cloud project name.', + 'cloud.target.provider': 'Name of the cloud provider.', + 'cloud.target.region': 'Region in which this host, resource, or service is located.', + 'cloud.target.service.name': 'The cloud service name.', + 'container.cpu.usage': 'Percent CPU used, between 0 and 1.', + 'container.disk.read.bytes': 'The number of bytes read by all disks.', + 'container.disk.write.bytes': 'The number of bytes written on all disks.', + 'container.id': 'Unique container id.', + 'container.image.hash.all': 'An array of digests of the image the container was built on.', + 'container.image.name': 'Name of the image the container was built on.', + 'container.image.tag': 'Container image tags.', + 'container.labels': 'Image labels.', + 'container.memory.usage': 'Percent memory used, between 0 and 1.', + 'container.name': 'Container name.', + 'container.network.egress.bytes': 'The number of bytes sent on all network interfaces.', + 'container.network.ingress.bytes': 'The number of bytes received on all network interfaces.', + 'container.runtime': 'Runtime managing this container.', + 'container.security_context.privileged': + 'Indicates whether the container is running in privileged mode.', + 'data_stream.dataset': + 'The field can contain anything that makes sense to signify the source of the data.', + 'data_stream.namespace': + 'A user defined namespace. Namespaces are useful to allow grouping of data.', + 'data_stream.type': 'An overarching type for the data stream.', + 'destination.address': 'Destination network address.', + 'destination.as.number': 'Unique number allocated to the autonomous system.', + 'destination.as.organization.name': 'Organization name.', + 'destination.bytes': 'Bytes sent from the destination to the source.', + 'destination.domain': 'The domain name of the destination.', + 'destination.geo.city_name': 'City name.', + 'destination.geo.continent_code': 'Continent code.', + 'destination.geo.continent_name': 'Name of the continent.', + 'destination.geo.country_iso_code': 'Country ISO code.', + 'destination.geo.country_name': 'Country name.', + 'destination.geo.location': 'Longitude and latitude.', + 'destination.geo.name': 'User-defined description of a location.', + 'destination.geo.postal_code': 'Postal code.', + 'destination.geo.region_iso_code': 'Region ISO code.', + 'destination.geo.region_name': 'Region name.', + 'destination.geo.timezone': 'Time zone.', + 'destination.ip': 'IP address of the destination.', + 'destination.mac': 'MAC address of the destination.', + 'destination.nat.ip': 'Destination NAT ip', + 'destination.nat.port': 'Destination NAT Port', + 'destination.packets': 'Packets sent from the destination to the source.', + 'destination.port': 'Port of the destination.', + 'destination.registered_domain': + 'The highest registered destination domain, stripped of the subdomain.', + 'destination.subdomain': 'The subdomain of the domain.', + 'destination.top_level_domain': 'The effective top level domain (com, org, net, co.uk).', + 'destination.user.domain': 'Name of the directory the user is a member of.', + 'destination.user.email': 'User email address.', + 'destination.user.full_name': 'Users full name, if available.', + 'destination.user.group.domain': 'Name of the directory the group is a member of.', + 'destination.user.group.id': 'Unique identifier for the group on the system/platform.', + 'destination.user.group.name': 'Name of the group.', + 'destination.user.hash': + 'Unique user hash to correlate information for a user in anonymized form.', + 'destination.user.id': 'Unique identifier of the user.', + 'destination.user.name': 'Short name or login of the user.', + 'destination.user.roles': 'Array of user roles at the time of the event.', + 'device.id': 'The unique identifier of a device.', + 'device.manufacturer': 'The vendor name of the device manufacturer.', + 'device.model.identifier': 'The machine readable identifier of the device model.', + 'device.model.name': 'The human readable marketing name of the device model.', + 'dll.code_signature.digest_algorithm': 'Hashing algorithm used to sign the process.', + 'dll.code_signature.exists': 'Boolean to capture if a signature is present.', + 'dll.code_signature.signing_id': 'The identifier used to sign the process.', + 'dll.code_signature.status': 'Additional information about the certificate status.', + 'dll.code_signature.subject_name': 'Subject name of the code signer', + 'dll.code_signature.team_id': 'The team identifier used to sign the process.', + 'dll.code_signature.timestamp': 'When the signature was generated and signed.', + 'dll.code_signature.trusted': 'Stores the trust status of the certificate chain.', + 'dll.code_signature.valid': + 'Boolean to capture if the digital signature is verified against the binary content.', + 'dll.hash.md5': 'MD5 hash.', + 'dll.hash.sha1': 'SHA1 hash.', + 'dll.hash.sha256': 'SHA256 hash.', + 'dll.hash.sha384': 'SHA384 hash.', + 'dll.hash.sha512': 'SHA512 hash.', + 'dll.hash.ssdeep': 'SSDEEP hash.', + 'dll.hash.tlsh': 'TLSH hash.', + 'dll.name': 'Name of the library.', + 'dll.path': 'Full file path of the library.', + 'dll.pe.architecture': 'CPU architecture target for the file.', + 'dll.pe.company': 'Internal company name of the file, provided at compile-time.', + 'dll.pe.description': 'Internal description of the file, provided at compile-time.', + 'dll.pe.file_version': 'Process name.', + 'dll.pe.go_import_hash': 'A hash of the Go language imports in a PE file.', + 'dll.pe.go_imports': 'List of imported Go language element names and types.', + 'dll.pe.go_imports_names_entropy': 'Shannon entropy calculation from the list of Go imports.', + 'dll.pe.go_imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of Go imports.', + 'dll.pe.go_stripped': 'Whether the file is a stripped or obfuscated Go executable.', + 'dll.pe.imphash': 'A hash of the imports in a PE file.', + 'dll.pe.import_hash': 'A hash of the imports in a PE file.', + 'dll.pe.imports': 'List of imported element names and types.', + 'dll.pe.imports_names_entropy': + 'Shannon entropy calculation from the list of imported element names and types.', + 'dll.pe.imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of imported element names and types.', + 'dll.pe.original_file_name': 'Internal name of the file, provided at compile-time.', + 'dll.pe.pehash': 'A hash of the PE header and data from one or more PE sections.', + 'dll.pe.product': 'Internal product name of the file, provided at compile-time.', + 'dll.pe.sections': 'Section information of the PE file.', + 'dll.pe.sections.entropy': 'Shannon entropy calculation from the section.', + 'dll.pe.sections.name': 'PE Section List name.', + 'dll.pe.sections.physical_size': 'PE Section List physical size.', + 'dll.pe.sections.var_entropy': 'Variance for Shannon entropy calculation from the section.', + 'dll.pe.sections.virtual_size': + 'PE Section List virtual size. This is always the same as `physical_size`.', + 'dns.answers': 'Array of DNS answers.', + 'dns.answers.class': 'The class of DNS data contained in this resource record.', + 'dns.answers.data': 'The data describing the resource.', + 'dns.answers.name': 'The domain name to which this resource record pertains.', + 'dns.answers.ttl': + 'The time interval in seconds that this resource record may be cached before it should be discarded.', + 'dns.answers.type': 'The type of data contained in this resource record.', + 'dns.header_flags': 'Array of DNS header flags.', + 'dns.id': + 'The DNS packet identifier assigned by the program that generated the query. The identifier is copied to the response.', + 'dns.op_code': 'The DNS operation code that specifies the kind of query in the message.', + 'dns.question.class': 'The class of records being queried.', + 'dns.question.name': 'The name being queried.', + 'dns.question.registered_domain': 'The highest registered domain, stripped of the subdomain.', + 'dns.question.subdomain': 'The subdomain of the domain.', + 'dns.question.top_level_domain': 'The effective top level domain (com, org, net, co.uk).', + 'dns.question.type': 'The type of record being queried.', + 'dns.resolved_ip': 'Array containing all IPs seen in answers.data', + 'dns.response_code': 'The DNS response code.', + 'dns.type': 'The type of DNS event captured, query or answer.', + 'ecs.version': 'ECS version this event conforms to.', + 'email.attachments': 'List of objects describing the attachments.', + 'email.attachments.file.extension': 'Attachment file extension.', + 'email.attachments.file.hash.md5': 'MD5 hash.', + 'email.attachments.file.hash.sha1': 'SHA1 hash.', + 'email.attachments.file.hash.sha256': 'SHA256 hash.', + 'email.attachments.file.hash.sha384': 'SHA384 hash.', + 'email.attachments.file.hash.sha512': 'SHA512 hash.', + 'email.attachments.file.hash.ssdeep': 'SSDEEP hash.', + 'email.attachments.file.hash.tlsh': 'TLSH hash.', + 'email.attachments.file.mime_type': 'MIME type of the attachment file.', + 'email.attachments.file.name': 'Name of the attachment file.', + 'email.attachments.file.size': 'Attachment file size.', + 'email.bcc.address': 'Email address of BCC recipient', + 'email.cc.address': 'Email address of CC recipient', + 'email.content_type': 'MIME type of the email message.', + 'email.delivery_timestamp': 'Date and time when message was delivered.', + 'email.direction': 'Direction of the message.', + 'email.from.address': 'The senders email address.', + 'email.local_id': 'Unique identifier given by the source.', + 'email.message_id': 'Value from the Message-ID header.', + 'email.origination_timestamp': 'Date and time the email was composed.', + 'email.reply_to.address': 'Address replies should be delivered to.', + 'email.sender.address': 'Address of the message sender.', + 'email.subject': 'The subject of the email message.', + 'email.to.address': 'Email address of recipient', + 'email.x_mailer': 'Application that drafted email.', + 'error.code': 'Error code describing the error.', + 'error.id': 'Unique identifier for the error.', + 'error.message': 'Error message.', + 'error.stack_trace': 'The stack trace of this error in plain text.', + 'error.type': 'The type of the error, for example the class name of the exception.', + 'event.action': 'The action captured by the event.', + 'event.agent_id_status': 'Validation status of the events agent.id field.', + 'event.category': 'Event category. The second categorization field in the hierarchy.', + 'event.code': 'Identification code for this event.', + 'event.created': 'Time when the event was first read by an agent or by your pipeline.', + 'event.dataset': 'Name of the dataset.', + 'event.duration': 'Duration of the event in nanoseconds.', + 'event.end': + '`event.end` contains the date when the event ended or when the activity was last observed.', + 'event.hash': + 'Hash (perhaps logstash fingerprint) of raw field to be able to demonstrate log integrity.', + 'event.id': 'Unique ID to describe the event.', + 'event.ingested': 'Timestamp when an event arrived in the central data store.', + 'event.kind': 'The kind of the event. The highest categorization field in the hierarchy.', + 'event.module': 'Name of the module this data is coming from.', + 'event.original': 'Raw text message of entire event.', + 'event.outcome': + 'The outcome of the event. The lowest level categorization field in the hierarchy.', + 'event.provider': 'Source of the event.', + 'event.reason': 'Reason why this event happened, according to the source', + 'event.reference': 'Event reference URL', + 'event.risk_score': + 'Risk score or priority of the event (e.g. security solutions). Use your systems original value here.', + 'event.risk_score_norm': 'Normalized risk score or priority of the event (0-100).', + 'event.sequence': 'Sequence number of the event.', + 'event.severity': 'Numeric severity of the event.', + 'event.start': + '`event.start` contains the date when the event started or when the activity was first observed.', + 'event.timezone': 'Event time zone.', + 'event.type': 'Event type. The third categorization field in the hierarchy.', + 'event.url': 'Event investigation URL', + 'faas.coldstart': 'Boolean value indicating a cold start of a function.', + 'faas.execution': 'The execution ID of the current function execution.', + 'faas.id': 'The unique identifier of a serverless function.', + 'faas.name': 'The name of a serverless function.', + 'faas.trigger.request_id': 'The ID of the trigger request , message, event, etc.', + 'faas.trigger.type': 'The trigger for the function execution.', + 'faas.version': 'The version of a serverless function.', + 'file.accessed': 'Last time the file was accessed.', + 'file.attributes': 'Array of file attributes.', + 'file.code_signature.digest_algorithm': 'Hashing algorithm used to sign the process.', + 'file.code_signature.exists': 'Boolean to capture if a signature is present.', + 'file.code_signature.signing_id': 'The identifier used to sign the process.', + 'file.code_signature.status': 'Additional information about the certificate status.', + 'file.code_signature.subject_name': 'Subject name of the code signer', + 'file.code_signature.team_id': 'The team identifier used to sign the process.', + 'file.code_signature.timestamp': 'When the signature was generated and signed.', + 'file.code_signature.trusted': 'Stores the trust status of the certificate chain.', + 'file.code_signature.valid': + 'Boolean to capture if the digital signature is verified against the binary content.', + 'file.created': 'File creation time.', + 'file.ctime': 'Last time the file attributes or metadata changed.', + 'file.device': 'Device that is the source of the file.', + 'file.directory': 'Directory where the file is located.', + 'file.drive_letter': 'Drive letter where the file is located.', + 'file.elf.architecture': 'Machine architecture of the ELF file.', + 'file.elf.byte_order': 'Byte sequence of ELF file.', + 'file.elf.cpu_type': 'CPU type of the ELF file.', + 'file.elf.creation_date': 'Build or compile date.', + 'file.elf.exports': 'List of exported element names and types.', + 'file.elf.go_import_hash': 'A hash of the Go language imports in an ELF file.', + 'file.elf.go_imports': 'List of imported Go language element names and types.', + 'file.elf.go_imports_names_entropy': 'Shannon entropy calculation from the list of Go imports.', + 'file.elf.go_imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of Go imports.', + 'file.elf.go_stripped': 'Whether the file is a stripped or obfuscated Go executable.', + 'file.elf.header.abi_version': 'Version of the ELF Application Binary Interface (ABI).', + 'file.elf.header.class': 'Header class of the ELF file.', + 'file.elf.header.data': 'Data table of the ELF header.', + 'file.elf.header.entrypoint': 'Header entrypoint of the ELF file.', + 'file.elf.header.object_version': '"0x1" for original ELF files.', + 'file.elf.header.os_abi': 'Application Binary Interface (ABI) of the Linux OS.', + 'file.elf.header.type': 'Header type of the ELF file.', + 'file.elf.header.version': 'Version of the ELF header.', + 'file.elf.import_hash': 'A hash of the imports in an ELF file.', + 'file.elf.imports': 'List of imported element names and types.', + 'file.elf.imports_names_entropy': + 'Shannon entropy calculation from the list of imported element names and types.', + 'file.elf.imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of imported element names and types.', + 'file.elf.sections': 'Section information of the ELF file.', + 'file.elf.sections.chi2': 'Chi-square probability distribution of the section.', + 'file.elf.sections.entropy': 'Shannon entropy calculation from the section.', + 'file.elf.sections.flags': 'ELF Section List flags.', + 'file.elf.sections.name': 'ELF Section List name.', + 'file.elf.sections.physical_offset': 'ELF Section List offset.', + 'file.elf.sections.physical_size': 'ELF Section List physical size.', + 'file.elf.sections.type': 'ELF Section List type.', + 'file.elf.sections.var_entropy': 'Variance for Shannon entropy calculation from the section.', + 'file.elf.sections.virtual_address': 'ELF Section List virtual address.', + 'file.elf.sections.virtual_size': 'ELF Section List virtual size.', + 'file.elf.segments': 'ELF object segment list.', + 'file.elf.segments.sections': 'ELF object segment sections.', + 'file.elf.segments.type': 'ELF object segment type.', + 'file.elf.shared_libraries': 'List of shared libraries used by this ELF object.', + 'file.elf.telfhash': 'telfhash hash for ELF file.', + 'file.extension': 'File extension, excluding the leading dot.', + 'file.fork_name': 'A fork is additional data associated with a filesystem object.', + 'file.gid': 'Primary group ID (GID) of the file.', + 'file.group': 'Primary group name of the file.', + 'file.hash.md5': 'MD5 hash.', + 'file.hash.sha1': 'SHA1 hash.', + 'file.hash.sha256': 'SHA256 hash.', + 'file.hash.sha384': 'SHA384 hash.', + 'file.hash.sha512': 'SHA512 hash.', + 'file.hash.ssdeep': 'SSDEEP hash.', + 'file.hash.tlsh': 'TLSH hash.', + 'file.inode': 'Inode representing the file in the filesystem.', + 'file.macho.go_import_hash': 'A hash of the Go language imports in a Mach-O file.', + 'file.macho.go_imports': 'List of imported Go language element names and types.', + 'file.macho.go_imports_names_entropy': 'Shannon entropy calculation from the list of Go imports.', + 'file.macho.go_imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of Go imports.', + 'file.macho.go_stripped': 'Whether the file is a stripped or obfuscated Go executable.', + 'file.macho.import_hash': 'A hash of the imports in a Mach-O file.', + 'file.macho.imports': 'List of imported element names and types.', + 'file.macho.imports_names_entropy': + 'Shannon entropy calculation from the list of imported element names and types.', + 'file.macho.imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of imported element names and types.', + 'file.macho.sections': 'Section information of the Mach-O file.', + 'file.macho.sections.entropy': 'Shannon entropy calculation from the section.', + 'file.macho.sections.name': 'Mach-O Section List name.', + 'file.macho.sections.physical_size': 'Mach-O Section List physical size.', + 'file.macho.sections.var_entropy': 'Variance for Shannon entropy calculation from the section.', + 'file.macho.sections.virtual_size': + 'Mach-O Section List virtual size. This is always the same as `physical_size`.', + 'file.macho.symhash': 'A hash of the imports in a Mach-O file.', + 'file.mime_type': 'Media type of file, document, or arrangement of bytes.', + 'file.mode': 'Mode of the file in octal representation.', + 'file.mtime': 'Last time the file content was modified.', + 'file.name': 'Name of the file including the extension, without the directory.', + 'file.owner': 'File owners username.', + 'file.path': 'Full path to the file, including the file name.', + 'file.pe.architecture': 'CPU architecture target for the file.', + 'file.pe.company': 'Internal company name of the file, provided at compile-time.', + 'file.pe.description': 'Internal description of the file, provided at compile-time.', + 'file.pe.file_version': 'Process name.', + 'file.pe.go_import_hash': 'A hash of the Go language imports in a PE file.', + 'file.pe.go_imports': 'List of imported Go language element names and types.', + 'file.pe.go_imports_names_entropy': 'Shannon entropy calculation from the list of Go imports.', + 'file.pe.go_imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of Go imports.', + 'file.pe.go_stripped': 'Whether the file is a stripped or obfuscated Go executable.', + 'file.pe.imphash': 'A hash of the imports in a PE file.', + 'file.pe.import_hash': 'A hash of the imports in a PE file.', + 'file.pe.imports': 'List of imported element names and types.', + 'file.pe.imports_names_entropy': + 'Shannon entropy calculation from the list of imported element names and types.', + 'file.pe.imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of imported element names and types.', + 'file.pe.original_file_name': 'Internal name of the file, provided at compile-time.', + 'file.pe.pehash': 'A hash of the PE header and data from one or more PE sections.', + 'file.pe.product': 'Internal product name of the file, provided at compile-time.', + 'file.pe.sections': 'Section information of the PE file.', + 'file.pe.sections.entropy': 'Shannon entropy calculation from the section.', + 'file.pe.sections.name': 'PE Section List name.', + 'file.pe.sections.physical_size': 'PE Section List physical size.', + 'file.pe.sections.var_entropy': 'Variance for Shannon entropy calculation from the section.', + 'file.pe.sections.virtual_size': + 'PE Section List virtual size. This is always the same as `physical_size`.', + 'file.size': 'File size in bytes.', + 'file.target_path': 'Target path for symlinks.', + 'file.type': 'File type (file, dir, or symlink).', + 'file.uid': 'The user ID (UID) or security identifier (SID) of the file owner.', + 'file.x509.alternative_names': 'List of subject alternative names (SAN).', + 'file.x509.issuer.common_name': 'List of common name (CN) of issuing certificate authority.', + 'file.x509.issuer.country': 'List of country (C) codes', + 'file.x509.issuer.distinguished_name': + 'Distinguished name (DN) of issuing certificate authority.', + 'file.x509.issuer.locality': 'List of locality names (L)', + 'file.x509.issuer.organization': 'List of organizations (O) of issuing certificate authority.', + 'file.x509.issuer.organizational_unit': + 'List of organizational units (OU) of issuing certificate authority.', + 'file.x509.issuer.state_or_province': 'List of state or province names (ST, S, or P)', + 'file.x509.not_after': 'Time at which the certificate is no longer considered valid.', + 'file.x509.not_before': 'Time at which the certificate is first considered valid.', + 'file.x509.public_key_algorithm': 'Algorithm used to generate the public key.', + 'file.x509.public_key_curve': + 'The curve used by the elliptic curve public key algorithm. This is algorithm specific', + 'file.x509.public_key_exponent': + 'Exponent used to derive the public key. This is algorithm specific', + 'file.x509.public_key_size': 'The size of the public key space in bits.', + 'file.x509.serial_number': 'Unique serial number issued by the certificate authority.', + 'file.x509.signature_algorithm': 'Identifier for certificate signature algorithm.', + 'file.x509.subject.common_name': 'List of common names (CN) of subject.', + 'file.x509.subject.country': 'List of country (C) code', + 'file.x509.subject.distinguished_name': + 'Distinguished name (DN) of the certificate subject entity', + 'file.x509.subject.locality': 'List of locality names (L)', + 'file.x509.subject.organization': 'List of organizations (O) of subject.', + 'file.x509.subject.organizational_unit': 'List of organizational units (OU) of subject.', + 'file.x509.subject.state_or_province': 'List of state or province names (ST, S, or P)', + 'file.x509.version_number': 'Version of x509 format.', + 'group.domain': 'Name of the directory the group is a member of.', + 'group.id': 'Unique identifier for the group on the system/platform.', + 'group.name': 'Name of the group.', + 'host.architecture': 'Operating system architecture.', + 'host.boot.id': 'Linux boot uuid taken from /proc/sys/kernel/random/boot_id', + 'host.cpu.usage': 'Percent CPU used, between 0 and 1.', + 'host.disk.read.bytes': 'The number of bytes read by all disks.', + 'host.disk.write.bytes': 'The number of bytes written on all disks.', + 'host.domain': 'Name of the directory the group is a member of.', + 'host.geo.city_name': 'City name.', + 'host.geo.continent_code': 'Continent code.', + 'host.geo.continent_name': 'Name of the continent.', + 'host.geo.country_iso_code': 'Country ISO code.', + 'host.geo.country_name': 'Country name.', + 'host.geo.location': 'Longitude and latitude.', + 'host.geo.name': 'User-defined description of a location.', + 'host.geo.postal_code': 'Postal code.', + 'host.geo.region_iso_code': 'Region ISO code.', + 'host.geo.region_name': 'Region name.', + 'host.geo.timezone': 'Time zone.', + 'host.hostname': 'Hostname of the host.', + 'host.id': 'Unique host id.', + 'host.ip': 'Host ip addresses.', + 'host.mac': 'Host MAC addresses.', + 'host.name': 'Name of the host.', + 'host.network.egress.bytes': 'The number of bytes sent on all network interfaces.', + 'host.network.egress.packets': 'The number of packets sent on all network interfaces.', + 'host.network.ingress.bytes': 'The number of bytes received on all network interfaces.', + 'host.network.ingress.packets': 'The number of packets received on all network interfaces.', + 'host.os.family': 'OS family (such as redhat, debian, freebsd, windows).', + 'host.os.full': 'Operating system name, including the version or code name.', + 'host.os.kernel': 'Operating system kernel version as a raw string.', + 'host.os.name': 'Operating system name, without the version.', + 'host.os.platform': 'Operating system platform (such centos, ubuntu, windows).', + 'host.os.type': + 'Which commercial OS family (one of: linux, macos, unix, windows, ios or android).', + 'host.os.version': 'Operating system version as a raw string.', + 'host.pid_ns_ino': 'Pid namespace inode', + 'host.risk.calculated_level': + 'A risk classification level calculated by an internal system as part of entity analytics and entity risk scoring', + 'host.risk.calculated_score': + 'A risk classification score calculated by an internal system as part of entity analytics and entity risk scoring', + 'host.risk.calculated_score_norm': 'A normalized risk score calculated by an internal system', + 'host.risk.static_level': + 'A risk classification level obtained from outside the system, such as from some external Threat Intelligence Platform', + 'host.risk.static_score': + 'A risk classification score obtained from outside the system, such as from some external Threat Intelligence Platform', + 'host.risk.static_score_norm': 'A normalized risk score calculated by an external system.', + 'host.type': 'Type of host.', + 'host.uptime': 'Seconds the host has been up.', + 'http.request.body.bytes': 'Size in bytes of the request body.', + 'http.request.body.content': 'The full HTTP request body.', + 'http.request.bytes': 'Total size in bytes of the request (body and headers).', + 'http.request.id': 'HTTP request ID.', + 'http.request.method': 'HTTP request method.', + 'http.request.mime_type': 'Mime type of the body of the request.', + 'http.request.referrer': 'Referrer for this HTTP request.', + 'http.response.body.bytes': 'Size in bytes of the response body.', + 'http.response.body.content': 'The full HTTP response body.', + 'http.response.bytes': 'Total size in bytes of the response (body and headers).', + 'http.response.mime_type': 'Mime type of the body of the response.', + 'http.response.status_code': 'HTTP response status code.', + 'http.version': 'HTTP version.', + labels: 'Custom key/value pairs.', + 'log.file.path': 'Full path to the log file this event came from.', + 'log.level': 'Log level of the log event.', + 'log.logger': 'Name of the logger.', + 'log.origin.file.line': 'The line number of the file which originated the log event.', + 'log.origin.file.name': 'The code file which originated the log event.', + 'log.origin.function': 'The function which originated the log event.', + 'log.syslog': 'Syslog metadata', + 'log.syslog.appname': 'The device or application that originated the Syslog message.', + 'log.syslog.facility.code': 'Syslog numeric facility of the event.', + 'log.syslog.facility.name': 'Syslog text-based facility of the event.', + 'log.syslog.hostname': 'The host that originated the Syslog message.', + 'log.syslog.msgid': 'An identifier for the type of Syslog message.', + 'log.syslog.priority': 'Syslog priority of the event.', + 'log.syslog.procid': 'The process name or ID that originated the Syslog message.', + 'log.syslog.severity.code': 'Syslog numeric severity of the event.', + 'log.syslog.severity.name': 'Syslog text-based severity of the event.', + 'log.syslog.structured_data': 'Structured data expressed in RFC 5424 messages.', + 'log.syslog.version': 'Syslog protocol version.', + message: 'Log message optimized for viewing in a log viewer.', + 'network.application': 'Application level protocol name.', + 'network.bytes': 'Total bytes transferred in both directions.', + 'network.community_id': 'A hash of source and destination IPs and ports.', + 'network.direction': 'Direction of the network traffic.', + 'network.forwarded_ip': 'Host IP address when the source IP address is the proxy.', + 'network.iana_number': 'IANA Protocol Number.', + 'network.inner': 'Inner VLAN tag information', + 'network.inner.vlan.id': 'VLAN ID as reported by the observer.', + 'network.inner.vlan.name': 'Optional VLAN name as reported by the observer.', + 'network.name': 'Name given by operators to sections of their network.', + 'network.packets': 'Total packets transferred in both directions.', + 'network.protocol': 'Application protocol name.', + 'network.transport': 'Protocol Name corresponding to the field `iana_number`.', + 'network.type': 'In the OSI Model this would be the Network Layer. ipv4, ipv6, ipsec, pim, etc', + 'network.vlan.id': 'VLAN ID as reported by the observer.', + 'network.vlan.name': 'Optional VLAN name as reported by the observer.', + 'observer.egress': 'Object field for egress information', + 'observer.egress.interface.alias': 'Interface alias', + 'observer.egress.interface.id': 'Interface ID', + 'observer.egress.interface.name': 'Interface name', + 'observer.egress.vlan.id': 'VLAN ID as reported by the observer.', + 'observer.egress.vlan.name': 'Optional VLAN name as reported by the observer.', + 'observer.egress.zone': 'Observer Egress zone', + 'observer.geo.city_name': 'City name.', + 'observer.geo.continent_code': 'Continent code.', + 'observer.geo.continent_name': 'Name of the continent.', + 'observer.geo.country_iso_code': 'Country ISO code.', + 'observer.geo.country_name': 'Country name.', + 'observer.geo.location': 'Longitude and latitude.', + 'observer.geo.name': 'User-defined description of a location.', + 'observer.geo.postal_code': 'Postal code.', + 'observer.geo.region_iso_code': 'Region ISO code.', + 'observer.geo.region_name': 'Region name.', + 'observer.geo.timezone': 'Time zone.', + 'observer.hostname': 'Hostname of the observer.', + 'observer.ingress': 'Object field for ingress information', + 'observer.ingress.interface.alias': 'Interface alias', + 'observer.ingress.interface.id': 'Interface ID', + 'observer.ingress.interface.name': 'Interface name', + 'observer.ingress.vlan.id': 'VLAN ID as reported by the observer.', + 'observer.ingress.vlan.name': 'Optional VLAN name as reported by the observer.', + 'observer.ingress.zone': 'Observer ingress zone', + 'observer.ip': 'IP addresses of the observer.', + 'observer.mac': 'MAC addresses of the observer.', + 'observer.name': 'Custom name of the observer.', + 'observer.os.family': 'OS family (such as redhat, debian, freebsd, windows).', + 'observer.os.full': 'Operating system name, including the version or code name.', + 'observer.os.kernel': 'Operating system kernel version as a raw string.', + 'observer.os.name': 'Operating system name, without the version.', + 'observer.os.platform': 'Operating system platform (such centos, ubuntu, windows).', + 'observer.os.type': + 'Which commercial OS family (one of: linux, macos, unix, windows, ios or android).', + 'observer.os.version': 'Operating system version as a raw string.', + 'observer.product': 'The product name of the observer.', + 'observer.serial_number': 'Observer serial number.', + 'observer.type': 'The type of the observer the data is coming from.', + 'observer.vendor': 'Vendor name of the observer.', + 'observer.version': 'Observer version.', + 'orchestrator.api_version': 'API version being used to carry out the action', + 'orchestrator.cluster.id': 'Unique ID of the cluster.', + 'orchestrator.cluster.name': 'Name of the cluster.', + 'orchestrator.cluster.url': 'URL of the API used to manage the cluster.', + 'orchestrator.cluster.version': 'The version of the cluster.', + 'orchestrator.namespace': 'Namespace in which the action is taking place.', + 'orchestrator.organization': + 'Organization affected by the event (for multi-tenant orchestrator setups).', + 'orchestrator.resource.annotation': 'The list of annotations added to the resource.', + 'orchestrator.resource.id': 'Unique ID of the resource being acted upon.', + 'orchestrator.resource.ip': + 'IP address assigned to the resource associated with the event being observed.', + 'orchestrator.resource.label': 'The list of labels added to the resource.', + 'orchestrator.resource.name': 'Name of the resource being acted upon.', + 'orchestrator.resource.parent.type': + 'Type or kind of the parent resource associated with the event being observed.', + 'orchestrator.resource.type': 'Type of resource being acted upon.', + 'orchestrator.type': 'Orchestrator cluster type (e.g. kubernetes, nomad or cloudfoundry).', + 'organization.id': 'Unique identifier for the organization.', + 'organization.name': 'Organization name.', + 'package.architecture': 'Package architecture.', + 'package.build_version': 'Build version information', + 'package.checksum': 'Checksum of the installed package for verification.', + 'package.description': 'Description of the package.', + 'package.install_scope': 'Indicating how the package was installed, e.g. user-local, global.', + 'package.installed': 'Time when package was installed.', + 'package.license': 'Package license', + 'package.name': 'Package name', + 'package.path': 'Path where the package is installed.', + 'package.reference': 'Package home page or reference URL', + 'package.size': 'Package size in bytes.', + 'package.type': 'Package type', + 'package.version': 'Package version', + 'process.args': 'Array of process arguments.', + 'process.args_count': 'Length of the process.args array.', + 'process.code_signature.digest_algorithm': 'Hashing algorithm used to sign the process.', + 'process.code_signature.exists': 'Boolean to capture if a signature is present.', + 'process.code_signature.signing_id': 'The identifier used to sign the process.', + 'process.code_signature.status': 'Additional information about the certificate status.', + 'process.code_signature.subject_name': 'Subject name of the code signer', + 'process.code_signature.team_id': 'The team identifier used to sign the process.', + 'process.code_signature.timestamp': 'When the signature was generated and signed.', + 'process.code_signature.trusted': 'Stores the trust status of the certificate chain.', + 'process.code_signature.valid': + 'Boolean to capture if the digital signature is verified against the binary content.', + 'process.command_line': 'Full command line that started the process.', + 'process.elf.architecture': 'Machine architecture of the ELF file.', + 'process.elf.byte_order': 'Byte sequence of ELF file.', + 'process.elf.cpu_type': 'CPU type of the ELF file.', + 'process.elf.creation_date': 'Build or compile date.', + 'process.elf.exports': 'List of exported element names and types.', + 'process.elf.go_import_hash': 'A hash of the Go language imports in an ELF file.', + 'process.elf.go_imports': 'List of imported Go language element names and types.', + 'process.elf.go_imports_names_entropy': + 'Shannon entropy calculation from the list of Go imports.', + 'process.elf.go_imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of Go imports.', + 'process.elf.go_stripped': 'Whether the file is a stripped or obfuscated Go executable.', + 'process.elf.header.abi_version': 'Version of the ELF Application Binary Interface (ABI).', + 'process.elf.header.class': 'Header class of the ELF file.', + 'process.elf.header.data': 'Data table of the ELF header.', + 'process.elf.header.entrypoint': 'Header entrypoint of the ELF file.', + 'process.elf.header.object_version': '"0x1" for original ELF files.', + 'process.elf.header.os_abi': 'Application Binary Interface (ABI) of the Linux OS.', + 'process.elf.header.type': 'Header type of the ELF file.', + 'process.elf.header.version': 'Version of the ELF header.', + 'process.elf.import_hash': 'A hash of the imports in an ELF file.', + 'process.elf.imports': 'List of imported element names and types.', + 'process.elf.imports_names_entropy': + 'Shannon entropy calculation from the list of imported element names and types.', + 'process.elf.imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of imported element names and types.', + 'process.elf.sections': 'Section information of the ELF file.', + 'process.elf.sections.chi2': 'Chi-square probability distribution of the section.', + 'process.elf.sections.entropy': 'Shannon entropy calculation from the section.', + 'process.elf.sections.flags': 'ELF Section List flags.', + 'process.elf.sections.name': 'ELF Section List name.', + 'process.elf.sections.physical_offset': 'ELF Section List offset.', + 'process.elf.sections.physical_size': 'ELF Section List physical size.', + 'process.elf.sections.type': 'ELF Section List type.', + 'process.elf.sections.var_entropy': 'Variance for Shannon entropy calculation from the section.', + 'process.elf.sections.virtual_address': 'ELF Section List virtual address.', + 'process.elf.sections.virtual_size': 'ELF Section List virtual size.', + 'process.elf.segments': 'ELF object segment list.', + 'process.elf.segments.sections': 'ELF object segment sections.', + 'process.elf.segments.type': 'ELF object segment type.', + 'process.elf.shared_libraries': 'List of shared libraries used by this ELF object.', + 'process.elf.telfhash': 'telfhash hash for ELF file.', + 'process.end': 'The time the process ended.', + 'process.entity_id': 'Unique identifier for the process.', + 'process.entry_leader.args': 'Array of process arguments.', + 'process.entry_leader.args_count': 'Length of the process.args array.', + 'process.entry_leader.attested_groups.name': 'Name of the group.', + 'process.entry_leader.attested_user.id': 'Unique identifier of the user.', + 'process.entry_leader.attested_user.name': 'Short name or login of the user.', + 'process.entry_leader.command_line': 'Full command line that started the process.', + 'process.entry_leader.entity_id': 'Unique identifier for the process.', + 'process.entry_leader.entry_meta.source.ip': 'IP address of the source.', + 'process.entry_leader.entry_meta.type': 'The entry type for the entry session leader.', + 'process.entry_leader.executable': 'Absolute path to the process executable.', + 'process.entry_leader.group.id': 'Unique identifier for the group on the system/platform.', + 'process.entry_leader.group.name': 'Name of the group.', + 'process.entry_leader.interactive': 'Whether the process is connected to an interactive shell.', + 'process.entry_leader.name': 'Process name.', + 'process.entry_leader.parent.entity_id': 'Unique identifier for the process.', + 'process.entry_leader.parent.pid': 'Process id.', + 'process.entry_leader.parent.session_leader.entity_id': 'Unique identifier for the process.', + 'process.entry_leader.parent.session_leader.pid': 'Process id.', + 'process.entry_leader.parent.session_leader.start': 'The time the process started.', + 'process.entry_leader.parent.session_leader.vpid': 'Virtual process id.', + 'process.entry_leader.parent.start': 'The time the process started.', + 'process.entry_leader.parent.vpid': 'Virtual process id.', + 'process.entry_leader.pid': 'Process id.', + 'process.entry_leader.real_group.id': 'Unique identifier for the group on the system/platform.', + 'process.entry_leader.real_group.name': 'Name of the group.', + 'process.entry_leader.real_user.id': 'Unique identifier of the user.', + 'process.entry_leader.real_user.name': 'Short name or login of the user.', + 'process.entry_leader.same_as_process': + 'This boolean is used to identify if a leader process is the same as the top level process.', + 'process.entry_leader.saved_group.id': 'Unique identifier for the group on the system/platform.', + 'process.entry_leader.saved_group.name': 'Name of the group.', + 'process.entry_leader.saved_user.id': 'Unique identifier of the user.', + 'process.entry_leader.saved_user.name': 'Short name or login of the user.', + 'process.entry_leader.start': 'The time the process started.', + 'process.entry_leader.supplemental_groups.id': + 'Unique identifier for the group on the system/platform.', + 'process.entry_leader.supplemental_groups.name': 'Name of the group.', + 'process.entry_leader.tty': 'Information about the controlling TTY device.', + 'process.entry_leader.tty.char_device.major': 'The TTY character devices major number.', + 'process.entry_leader.tty.char_device.minor': 'The TTY character devices minor number.', + 'process.entry_leader.user.id': 'Unique identifier of the user.', + 'process.entry_leader.user.name': 'Short name or login of the user.', + 'process.entry_leader.vpid': 'Virtual process id.', + 'process.entry_leader.working_directory': 'The working directory of the process.', + 'process.env_vars': 'Array of environment variable bindings.', + 'process.executable': 'Absolute path to the process executable.', + 'process.exit_code': 'The exit code of the process.', + 'process.group_leader.args': 'Array of process arguments.', + 'process.group_leader.args_count': 'Length of the process.args array.', + 'process.group_leader.command_line': 'Full command line that started the process.', + 'process.group_leader.entity_id': 'Unique identifier for the process.', + 'process.group_leader.executable': 'Absolute path to the process executable.', + 'process.group_leader.group.id': 'Unique identifier for the group on the system/platform.', + 'process.group_leader.group.name': 'Name of the group.', + 'process.group_leader.interactive': 'Whether the process is connected to an interactive shell.', + 'process.group_leader.name': 'Process name.', + 'process.group_leader.pid': 'Process id.', + 'process.group_leader.real_group.id': 'Unique identifier for the group on the system/platform.', + 'process.group_leader.real_group.name': 'Name of the group.', + 'process.group_leader.real_user.id': 'Unique identifier of the user.', + 'process.group_leader.real_user.name': 'Short name or login of the user.', + 'process.group_leader.same_as_process': + 'This boolean is used to identify if a leader process is the same as the top level process.', + 'process.group_leader.saved_group.id': 'Unique identifier for the group on the system/platform.', + 'process.group_leader.saved_group.name': 'Name of the group.', + 'process.group_leader.saved_user.id': 'Unique identifier of the user.', + 'process.group_leader.saved_user.name': 'Short name or login of the user.', + 'process.group_leader.start': 'The time the process started.', + 'process.group_leader.supplemental_groups.id': + 'Unique identifier for the group on the system/platform.', + 'process.group_leader.supplemental_groups.name': 'Name of the group.', + 'process.group_leader.tty': 'Information about the controlling TTY device.', + 'process.group_leader.tty.char_device.major': 'The TTY character devices major number.', + 'process.group_leader.tty.char_device.minor': 'The TTY character devices minor number.', + 'process.group_leader.user.id': 'Unique identifier of the user.', + 'process.group_leader.user.name': 'Short name or login of the user.', + 'process.group_leader.vpid': 'Virtual process id.', + 'process.group_leader.working_directory': 'The working directory of the process.', + 'process.hash.md5': 'MD5 hash.', + 'process.hash.sha1': 'SHA1 hash.', + 'process.hash.sha256': 'SHA256 hash.', + 'process.hash.sha384': 'SHA384 hash.', + 'process.hash.sha512': 'SHA512 hash.', + 'process.hash.ssdeep': 'SSDEEP hash.', + 'process.hash.tlsh': 'TLSH hash.', + 'process.interactive': 'Whether the process is connected to an interactive shell.', + 'process.io': 'A chunk of input or output (IO) from a single process.', + 'process.io.bytes_skipped': + 'An array of byte offsets and lengths denoting where IO data has been skipped.', + 'process.io.bytes_skipped.length': 'The length of bytes skipped.', + 'process.io.bytes_skipped.offset': + 'The byte offset into this events io.text (or io.bytes in the future) where length bytes were skipped.', + 'process.io.max_bytes_per_process_exceeded': + 'If true, the process producing the output has exceeded the max_kilobytes_per_process configuration setting.', + 'process.io.text': 'A chunk of output or input sanitized to UTF-8.', + 'process.io.total_bytes_captured': 'The total number of bytes captured in this event.', + 'process.io.total_bytes_skipped': + 'The total number of bytes that were not captured due to implementation restrictions such as buffer size limits.', + 'process.io.type': 'The type of object on which the IO action (read or write) was taken.', + 'process.macho.go_import_hash': 'A hash of the Go language imports in a Mach-O file.', + 'process.macho.go_imports': 'List of imported Go language element names and types.', + 'process.macho.go_imports_names_entropy': + 'Shannon entropy calculation from the list of Go imports.', + 'process.macho.go_imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of Go imports.', + 'process.macho.go_stripped': 'Whether the file is a stripped or obfuscated Go executable.', + 'process.macho.import_hash': 'A hash of the imports in a Mach-O file.', + 'process.macho.imports': 'List of imported element names and types.', + 'process.macho.imports_names_entropy': + 'Shannon entropy calculation from the list of imported element names and types.', + 'process.macho.imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of imported element names and types.', + 'process.macho.sections': 'Section information of the Mach-O file.', + 'process.macho.sections.entropy': 'Shannon entropy calculation from the section.', + 'process.macho.sections.name': 'Mach-O Section List name.', + 'process.macho.sections.physical_size': 'Mach-O Section List physical size.', + 'process.macho.sections.var_entropy': + 'Variance for Shannon entropy calculation from the section.', + 'process.macho.sections.virtual_size': + 'Mach-O Section List virtual size. This is always the same as `physical_size`.', + 'process.macho.symhash': 'A hash of the imports in a Mach-O file.', + 'process.name': 'Process name.', + 'process.parent.args': 'Array of process arguments.', + 'process.parent.args_count': 'Length of the process.args array.', + 'process.parent.code_signature.digest_algorithm': 'Hashing algorithm used to sign the process.', + 'process.parent.code_signature.exists': 'Boolean to capture if a signature is present.', + 'process.parent.code_signature.signing_id': 'The identifier used to sign the process.', + 'process.parent.code_signature.status': 'Additional information about the certificate status.', + 'process.parent.code_signature.subject_name': 'Subject name of the code signer', + 'process.parent.code_signature.team_id': 'The team identifier used to sign the process.', + 'process.parent.code_signature.timestamp': 'When the signature was generated and signed.', + 'process.parent.code_signature.trusted': 'Stores the trust status of the certificate chain.', + 'process.parent.code_signature.valid': + 'Boolean to capture if the digital signature is verified against the binary content.', + 'process.parent.command_line': 'Full command line that started the process.', + 'process.parent.elf.architecture': 'Machine architecture of the ELF file.', + 'process.parent.elf.byte_order': 'Byte sequence of ELF file.', + 'process.parent.elf.cpu_type': 'CPU type of the ELF file.', + 'process.parent.elf.creation_date': 'Build or compile date.', + 'process.parent.elf.exports': 'List of exported element names and types.', + 'process.parent.elf.go_import_hash': 'A hash of the Go language imports in an ELF file.', + 'process.parent.elf.go_imports': 'List of imported Go language element names and types.', + 'process.parent.elf.go_imports_names_entropy': + 'Shannon entropy calculation from the list of Go imports.', + 'process.parent.elf.go_imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of Go imports.', + 'process.parent.elf.go_stripped': 'Whether the file is a stripped or obfuscated Go executable.', + 'process.parent.elf.header.abi_version': 'Version of the ELF Application Binary Interface (ABI).', + 'process.parent.elf.header.class': 'Header class of the ELF file.', + 'process.parent.elf.header.data': 'Data table of the ELF header.', + 'process.parent.elf.header.entrypoint': 'Header entrypoint of the ELF file.', + 'process.parent.elf.header.object_version': '"0x1" for original ELF files.', + 'process.parent.elf.header.os_abi': 'Application Binary Interface (ABI) of the Linux OS.', + 'process.parent.elf.header.type': 'Header type of the ELF file.', + 'process.parent.elf.header.version': 'Version of the ELF header.', + 'process.parent.elf.import_hash': 'A hash of the imports in an ELF file.', + 'process.parent.elf.imports': 'List of imported element names and types.', + 'process.parent.elf.imports_names_entropy': + 'Shannon entropy calculation from the list of imported element names and types.', + 'process.parent.elf.imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of imported element names and types.', + 'process.parent.elf.sections': 'Section information of the ELF file.', + 'process.parent.elf.sections.chi2': 'Chi-square probability distribution of the section.', + 'process.parent.elf.sections.entropy': 'Shannon entropy calculation from the section.', + 'process.parent.elf.sections.flags': 'ELF Section List flags.', + 'process.parent.elf.sections.name': 'ELF Section List name.', + 'process.parent.elf.sections.physical_offset': 'ELF Section List offset.', + 'process.parent.elf.sections.physical_size': 'ELF Section List physical size.', + 'process.parent.elf.sections.type': 'ELF Section List type.', + 'process.parent.elf.sections.var_entropy': + 'Variance for Shannon entropy calculation from the section.', + 'process.parent.elf.sections.virtual_address': 'ELF Section List virtual address.', + 'process.parent.elf.sections.virtual_size': 'ELF Section List virtual size.', + 'process.parent.elf.segments': 'ELF object segment list.', + 'process.parent.elf.segments.sections': 'ELF object segment sections.', + 'process.parent.elf.segments.type': 'ELF object segment type.', + 'process.parent.elf.shared_libraries': 'List of shared libraries used by this ELF object.', + 'process.parent.elf.telfhash': 'telfhash hash for ELF file.', + 'process.parent.end': 'The time the process ended.', + 'process.parent.entity_id': 'Unique identifier for the process.', + 'process.parent.executable': 'Absolute path to the process executable.', + 'process.parent.exit_code': 'The exit code of the process.', + 'process.parent.group.id': 'Unique identifier for the group on the system/platform.', + 'process.parent.group.name': 'Name of the group.', + 'process.parent.group_leader.entity_id': 'Unique identifier for the process.', + 'process.parent.group_leader.pid': 'Process id.', + 'process.parent.group_leader.start': 'The time the process started.', + 'process.parent.group_leader.vpid': 'Virtual process id.', + 'process.parent.hash.md5': 'MD5 hash.', + 'process.parent.hash.sha1': 'SHA1 hash.', + 'process.parent.hash.sha256': 'SHA256 hash.', + 'process.parent.hash.sha384': 'SHA384 hash.', + 'process.parent.hash.sha512': 'SHA512 hash.', + 'process.parent.hash.ssdeep': 'SSDEEP hash.', + 'process.parent.hash.tlsh': 'TLSH hash.', + 'process.parent.interactive': 'Whether the process is connected to an interactive shell.', + 'process.parent.macho.go_import_hash': 'A hash of the Go language imports in a Mach-O file.', + 'process.parent.macho.go_imports': 'List of imported Go language element names and types.', + 'process.parent.macho.go_imports_names_entropy': + 'Shannon entropy calculation from the list of Go imports.', + 'process.parent.macho.go_imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of Go imports.', + 'process.parent.macho.go_stripped': 'Whether the file is a stripped or obfuscated Go executable.', + 'process.parent.macho.import_hash': 'A hash of the imports in a Mach-O file.', + 'process.parent.macho.imports': 'List of imported element names and types.', + 'process.parent.macho.imports_names_entropy': + 'Shannon entropy calculation from the list of imported element names and types.', + 'process.parent.macho.imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of imported element names and types.', + 'process.parent.macho.sections': 'Section information of the Mach-O file.', + 'process.parent.macho.sections.entropy': 'Shannon entropy calculation from the section.', + 'process.parent.macho.sections.name': 'Mach-O Section List name.', + 'process.parent.macho.sections.physical_size': 'Mach-O Section List physical size.', + 'process.parent.macho.sections.var_entropy': + 'Variance for Shannon entropy calculation from the section.', + 'process.parent.macho.sections.virtual_size': + 'Mach-O Section List virtual size. This is always the same as `physical_size`.', + 'process.parent.macho.symhash': 'A hash of the imports in a Mach-O file.', + 'process.parent.name': 'Process name.', + 'process.parent.pe.architecture': 'CPU architecture target for the file.', + 'process.parent.pe.company': 'Internal company name of the file, provided at compile-time.', + 'process.parent.pe.description': 'Internal description of the file, provided at compile-time.', + 'process.parent.pe.file_version': 'Process name.', + 'process.parent.pe.go_import_hash': 'A hash of the Go language imports in a PE file.', + 'process.parent.pe.go_imports': 'List of imported Go language element names and types.', + 'process.parent.pe.go_imports_names_entropy': + 'Shannon entropy calculation from the list of Go imports.', + 'process.parent.pe.go_imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of Go imports.', + 'process.parent.pe.go_stripped': 'Whether the file is a stripped or obfuscated Go executable.', + 'process.parent.pe.imphash': 'A hash of the imports in a PE file.', + 'process.parent.pe.import_hash': 'A hash of the imports in a PE file.', + 'process.parent.pe.imports': 'List of imported element names and types.', + 'process.parent.pe.imports_names_entropy': + 'Shannon entropy calculation from the list of imported element names and types.', + 'process.parent.pe.imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of imported element names and types.', + 'process.parent.pe.original_file_name': 'Internal name of the file, provided at compile-time.', + 'process.parent.pe.pehash': 'A hash of the PE header and data from one or more PE sections.', + 'process.parent.pe.product': 'Internal product name of the file, provided at compile-time.', + 'process.parent.pe.sections': 'Section information of the PE file.', + 'process.parent.pe.sections.entropy': 'Shannon entropy calculation from the section.', + 'process.parent.pe.sections.name': 'PE Section List name.', + 'process.parent.pe.sections.physical_size': 'PE Section List physical size.', + 'process.parent.pe.sections.var_entropy': + 'Variance for Shannon entropy calculation from the section.', + 'process.parent.pe.sections.virtual_size': + 'PE Section List virtual size. This is always the same as `physical_size`.', + 'process.parent.pgid': 'Deprecated identifier of the group of processes the process belongs to.', + 'process.parent.pid': 'Process id.', + 'process.parent.real_group.id': 'Unique identifier for the group on the system/platform.', + 'process.parent.real_group.name': 'Name of the group.', + 'process.parent.real_user.id': 'Unique identifier of the user.', + 'process.parent.real_user.name': 'Short name or login of the user.', + 'process.parent.saved_group.id': 'Unique identifier for the group on the system/platform.', + 'process.parent.saved_group.name': 'Name of the group.', + 'process.parent.saved_user.id': 'Unique identifier of the user.', + 'process.parent.saved_user.name': 'Short name or login of the user.', + 'process.parent.start': 'The time the process started.', + 'process.parent.supplemental_groups.id': + 'Unique identifier for the group on the system/platform.', + 'process.parent.supplemental_groups.name': 'Name of the group.', + 'process.parent.thread.capabilities.effective': + 'Array of capabilities used for permission checks.', + 'process.parent.thread.capabilities.permitted': 'Array of capabilities a thread could assume.', + 'process.parent.thread.id': 'Thread ID.', + 'process.parent.thread.name': 'Thread name.', + 'process.parent.title': 'Process title.', + 'process.parent.tty': 'Information about the controlling TTY device.', + 'process.parent.tty.char_device.major': 'The TTY character devices major number.', + 'process.parent.tty.char_device.minor': 'The TTY character devices minor number.', + 'process.parent.uptime': 'Seconds the process has been up.', + 'process.parent.user.id': 'Unique identifier of the user.', + 'process.parent.user.name': 'Short name or login of the user.', + 'process.parent.vpid': 'Virtual process id.', + 'process.parent.working_directory': 'The working directory of the process.', + 'process.pe.architecture': 'CPU architecture target for the file.', + 'process.pe.company': 'Internal company name of the file, provided at compile-time.', + 'process.pe.description': 'Internal description of the file, provided at compile-time.', + 'process.pe.file_version': 'Process name.', + 'process.pe.go_import_hash': 'A hash of the Go language imports in a PE file.', + 'process.pe.go_imports': 'List of imported Go language element names and types.', + 'process.pe.go_imports_names_entropy': 'Shannon entropy calculation from the list of Go imports.', + 'process.pe.go_imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of Go imports.', + 'process.pe.go_stripped': 'Whether the file is a stripped or obfuscated Go executable.', + 'process.pe.imphash': 'A hash of the imports in a PE file.', + 'process.pe.import_hash': 'A hash of the imports in a PE file.', + 'process.pe.imports': 'List of imported element names and types.', + 'process.pe.imports_names_entropy': + 'Shannon entropy calculation from the list of imported element names and types.', + 'process.pe.imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of imported element names and types.', + 'process.pe.original_file_name': 'Internal name of the file, provided at compile-time.', + 'process.pe.pehash': 'A hash of the PE header and data from one or more PE sections.', + 'process.pe.product': 'Internal product name of the file, provided at compile-time.', + 'process.pe.sections': 'Section information of the PE file.', + 'process.pe.sections.entropy': 'Shannon entropy calculation from the section.', + 'process.pe.sections.name': 'PE Section List name.', + 'process.pe.sections.physical_size': 'PE Section List physical size.', + 'process.pe.sections.var_entropy': 'Variance for Shannon entropy calculation from the section.', + 'process.pe.sections.virtual_size': + 'PE Section List virtual size. This is always the same as `physical_size`.', + 'process.pgid': 'Deprecated identifier of the group of processes the process belongs to.', + 'process.pid': 'Process id.', + 'process.previous.args': 'Array of process arguments.', + 'process.previous.args_count': 'Length of the process.args array.', + 'process.previous.executable': 'Absolute path to the process executable.', + 'process.real_group.id': 'Unique identifier for the group on the system/platform.', + 'process.real_group.name': 'Name of the group.', + 'process.real_user.id': 'Unique identifier of the user.', + 'process.real_user.name': 'Short name or login of the user.', + 'process.saved_group.id': 'Unique identifier for the group on the system/platform.', + 'process.saved_group.name': 'Name of the group.', + 'process.saved_user.id': 'Unique identifier of the user.', + 'process.saved_user.name': 'Short name or login of the user.', + 'process.session_leader.args': 'Array of process arguments.', + 'process.session_leader.args_count': 'Length of the process.args array.', + 'process.session_leader.command_line': 'Full command line that started the process.', + 'process.session_leader.entity_id': 'Unique identifier for the process.', + 'process.session_leader.executable': 'Absolute path to the process executable.', + 'process.session_leader.group.id': 'Unique identifier for the group on the system/platform.', + 'process.session_leader.group.name': 'Name of the group.', + 'process.session_leader.interactive': 'Whether the process is connected to an interactive shell.', + 'process.session_leader.name': 'Process name.', + 'process.session_leader.parent.entity_id': 'Unique identifier for the process.', + 'process.session_leader.parent.pid': 'Process id.', + 'process.session_leader.parent.session_leader.entity_id': 'Unique identifier for the process.', + 'process.session_leader.parent.session_leader.pid': 'Process id.', + 'process.session_leader.parent.session_leader.start': 'The time the process started.', + 'process.session_leader.parent.session_leader.vpid': 'Virtual process id.', + 'process.session_leader.parent.start': 'The time the process started.', + 'process.session_leader.parent.vpid': 'Virtual process id.', + 'process.session_leader.pid': 'Process id.', + 'process.session_leader.real_group.id': 'Unique identifier for the group on the system/platform.', + 'process.session_leader.real_group.name': 'Name of the group.', + 'process.session_leader.real_user.id': 'Unique identifier of the user.', + 'process.session_leader.real_user.name': 'Short name or login of the user.', + 'process.session_leader.same_as_process': + 'This boolean is used to identify if a leader process is the same as the top level process.', + 'process.session_leader.saved_group.id': + 'Unique identifier for the group on the system/platform.', + 'process.session_leader.saved_group.name': 'Name of the group.', + 'process.session_leader.saved_user.id': 'Unique identifier of the user.', + 'process.session_leader.saved_user.name': 'Short name or login of the user.', + 'process.session_leader.start': 'The time the process started.', + 'process.session_leader.supplemental_groups.id': + 'Unique identifier for the group on the system/platform.', + 'process.session_leader.supplemental_groups.name': 'Name of the group.', + 'process.session_leader.tty': 'Information about the controlling TTY device.', + 'process.session_leader.tty.char_device.major': 'The TTY character devices major number.', + 'process.session_leader.tty.char_device.minor': 'The TTY character devices minor number.', + 'process.session_leader.user.id': 'Unique identifier of the user.', + 'process.session_leader.user.name': 'Short name or login of the user.', + 'process.session_leader.vpid': 'Virtual process id.', + 'process.session_leader.working_directory': 'The working directory of the process.', + 'process.start': 'The time the process started.', + 'process.supplemental_groups.id': 'Unique identifier for the group on the system/platform.', + 'process.supplemental_groups.name': 'Name of the group.', + 'process.thread.capabilities.effective': 'Array of capabilities used for permission checks.', + 'process.thread.capabilities.permitted': 'Array of capabilities a thread could assume.', + 'process.thread.id': 'Thread ID.', + 'process.thread.name': 'Thread name.', + 'process.title': 'Process title.', + 'process.tty': 'Information about the controlling TTY device.', + 'process.tty.char_device.major': 'The TTY character devices major number.', + 'process.tty.char_device.minor': 'The TTY character devices minor number.', + 'process.tty.columns': 'The number of character columns per line. e.g terminal width', + 'process.tty.rows': 'The number of character rows in the terminal. e.g terminal height', + 'process.uptime': 'Seconds the process has been up.', + 'process.user.id': 'Unique identifier of the user.', + 'process.user.name': 'Short name or login of the user.', + 'process.vpid': 'Virtual process id.', + 'process.working_directory': 'The working directory of the process.', + 'registry.data.bytes': 'Original bytes written with base64 encoding.', + 'registry.data.strings': 'List of strings representing what was written to the registry.', + 'registry.data.type': 'Standard registry type for encoding contents', + 'registry.hive': 'Abbreviated name for the hive.', + 'registry.key': 'Hive-relative path of keys.', + 'registry.path': 'Full path, including hive, key and value', + 'registry.value': 'Name of the value written.', + 'related.hash': 'All the hashes seen on your event.', + 'related.hosts': 'All the host identifiers seen on your event.', + 'related.ip': 'All of the IPs seen on your event.', + 'related.user': 'All the user names or other user identifiers seen on the event.', + 'rule.author': 'Rule author', + 'rule.category': 'Rule category', + 'rule.description': 'Rule description', + 'rule.id': 'Rule ID', + 'rule.license': 'Rule license', + 'rule.name': 'Rule name', + 'rule.reference': 'Rule reference URL', + 'rule.ruleset': 'Rule ruleset', + 'rule.uuid': 'Rule UUID', + 'rule.version': 'Rule version', + 'server.address': 'Server network address.', + 'server.as.number': 'Unique number allocated to the autonomous system.', + 'server.as.organization.name': 'Organization name.', + 'server.bytes': 'Bytes sent from the server to the client.', + 'server.domain': 'The domain name of the server.', + 'server.geo.city_name': 'City name.', + 'server.geo.continent_code': 'Continent code.', + 'server.geo.continent_name': 'Name of the continent.', + 'server.geo.country_iso_code': 'Country ISO code.', + 'server.geo.country_name': 'Country name.', + 'server.geo.location': 'Longitude and latitude.', + 'server.geo.name': 'User-defined description of a location.', + 'server.geo.postal_code': 'Postal code.', + 'server.geo.region_iso_code': 'Region ISO code.', + 'server.geo.region_name': 'Region name.', + 'server.geo.timezone': 'Time zone.', + 'server.ip': 'IP address of the server.', + 'server.mac': 'MAC address of the server.', + 'server.nat.ip': 'Server NAT ip', + 'server.nat.port': 'Server NAT port', + 'server.packets': 'Packets sent from the server to the client.', + 'server.port': 'Port of the server.', + 'server.registered_domain': 'The highest registered server domain, stripped of the subdomain.', + 'server.subdomain': 'The subdomain of the domain.', + 'server.top_level_domain': 'The effective top level domain (com, org, net, co.uk).', + 'server.user.domain': 'Name of the directory the user is a member of.', + 'server.user.email': 'User email address.', + 'server.user.full_name': 'Users full name, if available.', + 'server.user.group.domain': 'Name of the directory the group is a member of.', + 'server.user.group.id': 'Unique identifier for the group on the system/platform.', + 'server.user.group.name': 'Name of the group.', + 'server.user.hash': 'Unique user hash to correlate information for a user in anonymized form.', + 'server.user.id': 'Unique identifier of the user.', + 'server.user.name': 'Short name or login of the user.', + 'server.user.roles': 'Array of user roles at the time of the event.', + 'service.address': 'Address of this service.', + 'service.environment': 'Environment of the service.', + 'service.ephemeral_id': 'Ephemeral identifier of this service.', + 'service.id': 'Unique identifier of the running service.', + 'service.name': 'Name of the service.', + 'service.node.name': 'Name of the service node.', + 'service.node.role': 'Deprecated role (singular) of the service node.', + 'service.node.roles': 'Roles of the service node.', + 'service.origin.address': 'Address of this service.', + 'service.origin.environment': 'Environment of the service.', + 'service.origin.ephemeral_id': 'Ephemeral identifier of this service.', + 'service.origin.id': 'Unique identifier of the running service.', + 'service.origin.name': 'Name of the service.', + 'service.origin.node.name': 'Name of the service node.', + 'service.origin.node.role': 'Deprecated role (singular) of the service node.', + 'service.origin.node.roles': 'Roles of the service node.', + 'service.origin.state': 'Current state of the service.', + 'service.origin.type': 'The type of the service.', + 'service.origin.version': 'Version of the service.', + 'service.state': 'Current state of the service.', + 'service.target.address': 'Address of this service.', + 'service.target.environment': 'Environment of the service.', + 'service.target.ephemeral_id': 'Ephemeral identifier of this service.', + 'service.target.id': 'Unique identifier of the running service.', + 'service.target.name': 'Name of the service.', + 'service.target.node.name': 'Name of the service node.', + 'service.target.node.role': 'Deprecated role (singular) of the service node.', + 'service.target.node.roles': 'Roles of the service node.', + 'service.target.state': 'Current state of the service.', + 'service.target.type': 'The type of the service.', + 'service.target.version': 'Version of the service.', + 'service.type': 'The type of the service.', + 'service.version': 'Version of the service.', + 'source.address': 'Source network address.', + 'source.as.number': 'Unique number allocated to the autonomous system.', + 'source.as.organization.name': 'Organization name.', + 'source.bytes': 'Bytes sent from the source to the destination.', + 'source.domain': 'The domain name of the source.', + 'source.geo.city_name': 'City name.', + 'source.geo.continent_code': 'Continent code.', + 'source.geo.continent_name': 'Name of the continent.', + 'source.geo.country_iso_code': 'Country ISO code.', + 'source.geo.country_name': 'Country name.', + 'source.geo.location': 'Longitude and latitude.', + 'source.geo.name': 'User-defined description of a location.', + 'source.geo.postal_code': 'Postal code.', + 'source.geo.region_iso_code': 'Region ISO code.', + 'source.geo.region_name': 'Region name.', + 'source.geo.timezone': 'Time zone.', + 'source.ip': 'IP address of the source.', + 'source.mac': 'MAC address of the source.', + 'source.nat.ip': 'Source NAT ip', + 'source.nat.port': 'Source NAT port', + 'source.packets': 'Packets sent from the source to the destination.', + 'source.port': 'Port of the source.', + 'source.registered_domain': 'The highest registered source domain, stripped of the subdomain.', + 'source.subdomain': 'The subdomain of the domain.', + 'source.top_level_domain': 'The effective top level domain (com, org, net, co.uk).', + 'source.user.domain': 'Name of the directory the user is a member of.', + 'source.user.email': 'User email address.', + 'source.user.full_name': 'Users full name, if available.', + 'source.user.group.domain': 'Name of the directory the group is a member of.', + 'source.user.group.id': 'Unique identifier for the group on the system/platform.', + 'source.user.group.name': 'Name of the group.', + 'source.user.hash': 'Unique user hash to correlate information for a user in anonymized form.', + 'source.user.id': 'Unique identifier of the user.', + 'source.user.name': 'Short name or login of the user.', + 'source.user.roles': 'Array of user roles at the time of the event.', + 'span.id': 'Unique identifier of the span within the scope of its trace.', + tags: 'List of keywords used to tag each event.', + 'threat.enrichments': 'List of objects containing indicators enriching the event.', + 'threat.enrichments.indicator': 'Object containing indicators enriching the event.', + 'threat.enrichments.indicator.as.number': 'Unique number allocated to the autonomous system.', + 'threat.enrichments.indicator.as.organization.name': 'Organization name.', + 'threat.enrichments.indicator.confidence': 'Indicator confidence rating', + 'threat.enrichments.indicator.description': 'Indicator description', + 'threat.enrichments.indicator.email.address': 'Indicator email address', + 'threat.enrichments.indicator.file.accessed': 'Last time the file was accessed.', + 'threat.enrichments.indicator.file.attributes': 'Array of file attributes.', + 'threat.enrichments.indicator.file.code_signature.digest_algorithm': + 'Hashing algorithm used to sign the process.', + 'threat.enrichments.indicator.file.code_signature.exists': + 'Boolean to capture if a signature is present.', + 'threat.enrichments.indicator.file.code_signature.signing_id': + 'The identifier used to sign the process.', + 'threat.enrichments.indicator.file.code_signature.status': + 'Additional information about the certificate status.', + 'threat.enrichments.indicator.file.code_signature.subject_name': + 'Subject name of the code signer', + 'threat.enrichments.indicator.file.code_signature.team_id': + 'The team identifier used to sign the process.', + 'threat.enrichments.indicator.file.code_signature.timestamp': + 'When the signature was generated and signed.', + 'threat.enrichments.indicator.file.code_signature.trusted': + 'Stores the trust status of the certificate chain.', + 'threat.enrichments.indicator.file.code_signature.valid': + 'Boolean to capture if the digital signature is verified against the binary content.', + 'threat.enrichments.indicator.file.created': 'File creation time.', + 'threat.enrichments.indicator.file.ctime': 'Last time the file attributes or metadata changed.', + 'threat.enrichments.indicator.file.device': 'Device that is the source of the file.', + 'threat.enrichments.indicator.file.directory': 'Directory where the file is located.', + 'threat.enrichments.indicator.file.drive_letter': 'Drive letter where the file is located.', + 'threat.enrichments.indicator.file.elf.architecture': 'Machine architecture of the ELF file.', + 'threat.enrichments.indicator.file.elf.byte_order': 'Byte sequence of ELF file.', + 'threat.enrichments.indicator.file.elf.cpu_type': 'CPU type of the ELF file.', + 'threat.enrichments.indicator.file.elf.creation_date': 'Build or compile date.', + 'threat.enrichments.indicator.file.elf.exports': 'List of exported element names and types.', + 'threat.enrichments.indicator.file.elf.go_import_hash': + 'A hash of the Go language imports in an ELF file.', + 'threat.enrichments.indicator.file.elf.go_imports': + 'List of imported Go language element names and types.', + 'threat.enrichments.indicator.file.elf.go_imports_names_entropy': + 'Shannon entropy calculation from the list of Go imports.', + 'threat.enrichments.indicator.file.elf.go_imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of Go imports.', + 'threat.enrichments.indicator.file.elf.go_stripped': + 'Whether the file is a stripped or obfuscated Go executable.', + 'threat.enrichments.indicator.file.elf.header.abi_version': + 'Version of the ELF Application Binary Interface (ABI).', + 'threat.enrichments.indicator.file.elf.header.class': 'Header class of the ELF file.', + 'threat.enrichments.indicator.file.elf.header.data': 'Data table of the ELF header.', + 'threat.enrichments.indicator.file.elf.header.entrypoint': 'Header entrypoint of the ELF file.', + 'threat.enrichments.indicator.file.elf.header.object_version': '"0x1" for original ELF files.', + 'threat.enrichments.indicator.file.elf.header.os_abi': + 'Application Binary Interface (ABI) of the Linux OS.', + 'threat.enrichments.indicator.file.elf.header.type': 'Header type of the ELF file.', + 'threat.enrichments.indicator.file.elf.header.version': 'Version of the ELF header.', + 'threat.enrichments.indicator.file.elf.import_hash': 'A hash of the imports in an ELF file.', + 'threat.enrichments.indicator.file.elf.imports': 'List of imported element names and types.', + 'threat.enrichments.indicator.file.elf.imports_names_entropy': + 'Shannon entropy calculation from the list of imported element names and types.', + 'threat.enrichments.indicator.file.elf.imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of imported element names and types.', + 'threat.enrichments.indicator.file.elf.sections': 'Section information of the ELF file.', + 'threat.enrichments.indicator.file.elf.sections.chi2': + 'Chi-square probability distribution of the section.', + 'threat.enrichments.indicator.file.elf.sections.entropy': + 'Shannon entropy calculation from the section.', + 'threat.enrichments.indicator.file.elf.sections.flags': 'ELF Section List flags.', + 'threat.enrichments.indicator.file.elf.sections.name': 'ELF Section List name.', + 'threat.enrichments.indicator.file.elf.sections.physical_offset': 'ELF Section List offset.', + 'threat.enrichments.indicator.file.elf.sections.physical_size': 'ELF Section List physical size.', + 'threat.enrichments.indicator.file.elf.sections.type': 'ELF Section List type.', + 'threat.enrichments.indicator.file.elf.sections.var_entropy': + 'Variance for Shannon entropy calculation from the section.', + 'threat.enrichments.indicator.file.elf.sections.virtual_address': + 'ELF Section List virtual address.', + 'threat.enrichments.indicator.file.elf.sections.virtual_size': 'ELF Section List virtual size.', + 'threat.enrichments.indicator.file.elf.segments': 'ELF object segment list.', + 'threat.enrichments.indicator.file.elf.segments.sections': 'ELF object segment sections.', + 'threat.enrichments.indicator.file.elf.segments.type': 'ELF object segment type.', + 'threat.enrichments.indicator.file.elf.shared_libraries': + 'List of shared libraries used by this ELF object.', + 'threat.enrichments.indicator.file.elf.telfhash': 'telfhash hash for ELF file.', + 'threat.enrichments.indicator.file.extension': 'File extension, excluding the leading dot.', + 'threat.enrichments.indicator.file.fork_name': + 'A fork is additional data associated with a filesystem object.', + 'threat.enrichments.indicator.file.gid': 'Primary group ID (GID) of the file.', + 'threat.enrichments.indicator.file.group': 'Primary group name of the file.', + 'threat.enrichments.indicator.file.hash.md5': 'MD5 hash.', + 'threat.enrichments.indicator.file.hash.sha1': 'SHA1 hash.', + 'threat.enrichments.indicator.file.hash.sha256': 'SHA256 hash.', + 'threat.enrichments.indicator.file.hash.sha384': 'SHA384 hash.', + 'threat.enrichments.indicator.file.hash.sha512': 'SHA512 hash.', + 'threat.enrichments.indicator.file.hash.ssdeep': 'SSDEEP hash.', + 'threat.enrichments.indicator.file.hash.tlsh': 'TLSH hash.', + 'threat.enrichments.indicator.file.inode': 'Inode representing the file in the filesystem.', + 'threat.enrichments.indicator.file.mime_type': + 'Media type of file, document, or arrangement of bytes.', + 'threat.enrichments.indicator.file.mode': 'Mode of the file in octal representation.', + 'threat.enrichments.indicator.file.mtime': 'Last time the file content was modified.', + 'threat.enrichments.indicator.file.name': + 'Name of the file including the extension, without the directory.', + 'threat.enrichments.indicator.file.owner': 'File owners username.', + 'threat.enrichments.indicator.file.path': 'Full path to the file, including the file name.', + 'threat.enrichments.indicator.file.pe.architecture': 'CPU architecture target for the file.', + 'threat.enrichments.indicator.file.pe.company': + 'Internal company name of the file, provided at compile-time.', + 'threat.enrichments.indicator.file.pe.description': + 'Internal description of the file, provided at compile-time.', + 'threat.enrichments.indicator.file.pe.file_version': 'Process name.', + 'threat.enrichments.indicator.file.pe.go_import_hash': + 'A hash of the Go language imports in a PE file.', + 'threat.enrichments.indicator.file.pe.go_imports': + 'List of imported Go language element names and types.', + 'threat.enrichments.indicator.file.pe.go_imports_names_entropy': + 'Shannon entropy calculation from the list of Go imports.', + 'threat.enrichments.indicator.file.pe.go_imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of Go imports.', + 'threat.enrichments.indicator.file.pe.go_stripped': + 'Whether the file is a stripped or obfuscated Go executable.', + 'threat.enrichments.indicator.file.pe.imphash': 'A hash of the imports in a PE file.', + 'threat.enrichments.indicator.file.pe.import_hash': 'A hash of the imports in a PE file.', + 'threat.enrichments.indicator.file.pe.imports': 'List of imported element names and types.', + 'threat.enrichments.indicator.file.pe.imports_names_entropy': + 'Shannon entropy calculation from the list of imported element names and types.', + 'threat.enrichments.indicator.file.pe.imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of imported element names and types.', + 'threat.enrichments.indicator.file.pe.original_file_name': + 'Internal name of the file, provided at compile-time.', + 'threat.enrichments.indicator.file.pe.pehash': + 'A hash of the PE header and data from one or more PE sections.', + 'threat.enrichments.indicator.file.pe.product': + 'Internal product name of the file, provided at compile-time.', + 'threat.enrichments.indicator.file.pe.sections': 'Section information of the PE file.', + 'threat.enrichments.indicator.file.pe.sections.entropy': + 'Shannon entropy calculation from the section.', + 'threat.enrichments.indicator.file.pe.sections.name': 'PE Section List name.', + 'threat.enrichments.indicator.file.pe.sections.physical_size': 'PE Section List physical size.', + 'threat.enrichments.indicator.file.pe.sections.var_entropy': + 'Variance for Shannon entropy calculation from the section.', + 'threat.enrichments.indicator.file.pe.sections.virtual_size': + 'PE Section List virtual size. This is always the same as `physical_size`.', + 'threat.enrichments.indicator.file.size': 'File size in bytes.', + 'threat.enrichments.indicator.file.target_path': 'Target path for symlinks.', + 'threat.enrichments.indicator.file.type': 'File type (file, dir, or symlink).', + 'threat.enrichments.indicator.file.uid': + 'The user ID (UID) or security identifier (SID) of the file owner.', + 'threat.enrichments.indicator.file.x509.alternative_names': + 'List of subject alternative names (SAN).', + 'threat.enrichments.indicator.file.x509.issuer.common_name': + 'List of common name (CN) of issuing certificate authority.', + 'threat.enrichments.indicator.file.x509.issuer.country': 'List of country (C) codes', + 'threat.enrichments.indicator.file.x509.issuer.distinguished_name': + 'Distinguished name (DN) of issuing certificate authority.', + 'threat.enrichments.indicator.file.x509.issuer.locality': 'List of locality names (L)', + 'threat.enrichments.indicator.file.x509.issuer.organization': + 'List of organizations (O) of issuing certificate authority.', + 'threat.enrichments.indicator.file.x509.issuer.organizational_unit': + 'List of organizational units (OU) of issuing certificate authority.', + 'threat.enrichments.indicator.file.x509.issuer.state_or_province': + 'List of state or province names (ST, S, or P)', + 'threat.enrichments.indicator.file.x509.not_after': + 'Time at which the certificate is no longer considered valid.', + 'threat.enrichments.indicator.file.x509.not_before': + 'Time at which the certificate is first considered valid.', + 'threat.enrichments.indicator.file.x509.public_key_algorithm': + 'Algorithm used to generate the public key.', + 'threat.enrichments.indicator.file.x509.public_key_curve': + 'The curve used by the elliptic curve public key algorithm. This is algorithm specific.', + 'threat.enrichments.indicator.file.x509.public_key_exponent': + 'Exponent used to derive the public key. This is algorithm specific.', + 'threat.enrichments.indicator.file.x509.public_key_size': + 'The size of the public key space in bits.', + 'threat.enrichments.indicator.file.x509.serial_number': + 'Unique serial number issued by the certificate authority.', + 'threat.enrichments.indicator.file.x509.signature_algorithm': + 'Identifier for certificate signature algorithm.', + 'threat.enrichments.indicator.file.x509.subject.common_name': + 'List of common names (CN) of subject.', + 'threat.enrichments.indicator.file.x509.subject.country': 'List of country (C) code', + 'threat.enrichments.indicator.file.x509.subject.distinguished_name': + 'Distinguished name (DN) of the certificate subject entity.', + 'threat.enrichments.indicator.file.x509.subject.locality': 'List of locality names (L)', + 'threat.enrichments.indicator.file.x509.subject.organization': + 'List of organizations (O) of subject.', + 'threat.enrichments.indicator.file.x509.subject.organizational_unit': + 'List of organizational units (OU) of subject.', + 'threat.enrichments.indicator.file.x509.subject.state_or_province': + 'List of state or province names (ST, S, or P)', + 'threat.enrichments.indicator.file.x509.version_number': 'Version of x509 format.', + 'threat.enrichments.indicator.first_seen': 'Date/time indicator was first reported.', + 'threat.enrichments.indicator.geo.city_name': 'City name.', + 'threat.enrichments.indicator.geo.continent_code': 'Continent code.', + 'threat.enrichments.indicator.geo.continent_name': 'Name of the continent.', + 'threat.enrichments.indicator.geo.country_iso_code': 'Country ISO code.', + 'threat.enrichments.indicator.geo.country_name': 'Country name.', + 'threat.enrichments.indicator.geo.location': 'Longitude and latitude.', + 'threat.enrichments.indicator.geo.name': 'User-defined description of a location.', + 'threat.enrichments.indicator.geo.postal_code': 'Postal code.', + 'threat.enrichments.indicator.geo.region_iso_code': 'Region ISO code.', + 'threat.enrichments.indicator.geo.region_name': 'Region name.', + 'threat.enrichments.indicator.geo.timezone': 'Time zone.', + 'threat.enrichments.indicator.ip': 'Indicator IP address', + 'threat.enrichments.indicator.last_seen': 'Date/time indicator was last reported.', + 'threat.enrichments.indicator.marking.tlp': 'Indicator TLP marking', + 'threat.enrichments.indicator.marking.tlp_version': 'Indicator TLP version', + 'threat.enrichments.indicator.modified_at': 'Date/time indicator was last updated.', + 'threat.enrichments.indicator.name': 'Indicator display name', + 'threat.enrichments.indicator.port': 'Indicator port', + 'threat.enrichments.indicator.provider': 'Indicator provider', + 'threat.enrichments.indicator.reference': 'Indicator reference URL', + 'threat.enrichments.indicator.registry.data.bytes': + 'Original bytes written with base64 encoding.', + 'threat.enrichments.indicator.registry.data.strings': + 'List of strings representing what was written to the registry.', + 'threat.enrichments.indicator.registry.data.type': 'Standard registry type for encoding contents', + 'threat.enrichments.indicator.registry.hive': 'Abbreviated name for the hive.', + 'threat.enrichments.indicator.registry.key': 'Hive-relative path of keys.', + 'threat.enrichments.indicator.registry.path': 'Full path, including hive, key and value', + 'threat.enrichments.indicator.registry.value': 'Name of the value written.', + 'threat.enrichments.indicator.scanner_stats': 'Scanner statistics', + 'threat.enrichments.indicator.sightings': 'Number of times indicator observed', + 'threat.enrichments.indicator.type': 'Type of indicator', + 'threat.enrichments.indicator.url.domain': 'Domain of the url.', + 'threat.enrichments.indicator.url.extension': + 'File extension from the request url, excluding the leading dot.', + 'threat.enrichments.indicator.url.fragment': 'Portion of the url after the `#`.', + 'threat.enrichments.indicator.url.full': 'Full unparsed URL.', + 'threat.enrichments.indicator.url.original': + 'Unmodified original url as seen in the event source.', + 'threat.enrichments.indicator.url.password': 'Password of the request.', + 'threat.enrichments.indicator.url.path': 'Path of the request, such as "/search".', + 'threat.enrichments.indicator.url.port': 'Port of the request, such as 443.', + 'threat.enrichments.indicator.url.query': 'Query string of the request.', + 'threat.enrichments.indicator.url.registered_domain': + 'The highest registered url domain, stripped of the subdomain.', + 'threat.enrichments.indicator.url.scheme': 'Scheme of the url.', + 'threat.enrichments.indicator.url.subdomain': 'The subdomain of the domain.', + 'threat.enrichments.indicator.url.top_level_domain': + 'The effective top level domain (com, org, net, co.uk).', + 'threat.enrichments.indicator.url.username': 'Username of the request.', + 'threat.enrichments.indicator.x509.alternative_names': 'List of subject alternative names (SAN).', + 'threat.enrichments.indicator.x509.issuer.common_name': + 'List of common name (CN) of issuing certificate authority.', + 'threat.enrichments.indicator.x509.issuer.country': 'List of country (C) codes', + 'threat.enrichments.indicator.x509.issuer.distinguished_name': + 'Distinguished name (DN) of issuing certificate authority.', + 'threat.enrichments.indicator.x509.issuer.locality': 'List of locality names (L)', + 'threat.enrichments.indicator.x509.issuer.organization': + 'List of organizations (O) of issuing certificate authority.', + 'threat.enrichments.indicator.x509.issuer.organizational_unit': + 'List of organizational units (OU) of issuing certificate authority.', + 'threat.enrichments.indicator.x509.issuer.state_or_province': + 'List of state or province names (ST, S, or P)', + 'threat.enrichments.indicator.x509.not_after': + 'Time at which the certificate is no longer considered valid.', + 'threat.enrichments.indicator.x509.not_before': + 'Time at which the certificate is first considered valid.', + 'threat.enrichments.indicator.x509.public_key_algorithm': + 'Algorithm used to generate the public key.', + 'threat.enrichments.indicator.x509.public_key_curve': + 'The curve used by the elliptic curve public key algorithm. This is algorithm specific.', + 'threat.enrichments.indicator.x509.public_key_exponent': + 'Exponent used to derive the public key. This is algorithm specific.', + 'threat.enrichments.indicator.x509.public_key_size': 'The size of the public key space in bits.', + 'threat.enrichments.indicator.x509.serial_number': + 'Unique serial number issued by the certificate authority.', + 'threat.enrichments.indicator.x509.signature_algorithm': + 'Identifier for certificate signature algorithm.', + 'threat.enrichments.indicator.x509.subject.common_name': 'List of common names (CN) of subject.', + 'threat.enrichments.indicator.x509.subject.country': 'List of country (C) code', + 'threat.enrichments.indicator.x509.subject.distinguished_name': + 'Distinguished name (DN) of the certificate subject entity.', + 'threat.enrichments.indicator.x509.subject.locality': 'List of locality names (L)', + 'threat.enrichments.indicator.x509.subject.organization': 'List of organizations (O) of subject.', + 'threat.enrichments.indicator.x509.subject.organizational_unit': + 'List of organizational units (OU) of subject.', + 'threat.enrichments.indicator.x509.subject.state_or_province': + 'List of state or province names (ST, S, or P)', + 'threat.enrichments.indicator.x509.version_number': 'Version of x509 format.', + 'threat.enrichments.matched.atomic': 'Matched indicator value', + 'threat.enrichments.matched.field': 'Matched indicator field', + 'threat.enrichments.matched.id': 'Matched indicator identifier', + 'threat.enrichments.matched.index': 'Matched indicator index', + 'threat.enrichments.matched.occurred': 'Date of match', + 'threat.enrichments.matched.type': 'Type of indicator match', + 'threat.feed.dashboard_id': 'Feed dashboard ID.', + 'threat.feed.description': 'Description of the threat feed.', + 'threat.feed.name': 'Name of the threat feed.', + 'threat.feed.reference': 'Reference for the threat feed.', + 'threat.framework': 'Threat classification framework.', + 'threat.group.alias': 'Alias of the group.', + 'threat.group.id': 'ID of the group.', + 'threat.group.name': 'Name of the group.', + 'threat.group.reference': 'Reference URL of the group.', + 'threat.indicator.as.number': 'Unique number allocated to the autonomous system.', + 'threat.indicator.as.organization.name': 'Organization name.', + 'threat.indicator.confidence': 'Indicator confidence rating', + 'threat.indicator.description': 'Indicator description', + 'threat.indicator.email.address': 'Indicator email address', + 'threat.indicator.file.accessed': 'Last time the file was accessed.', + 'threat.indicator.file.attributes': 'Array of file attributes.', + 'threat.indicator.file.code_signature.digest_algorithm': + 'Hashing algorithm used to sign the process.', + 'threat.indicator.file.code_signature.exists': 'Boolean to capture if a signature is present.', + 'threat.indicator.file.code_signature.signing_id': 'The identifier used to sign the process.', + 'threat.indicator.file.code_signature.status': + 'Additional information about the certificate status.', + 'threat.indicator.file.code_signature.subject_name': 'Subject name of the code signer', + 'threat.indicator.file.code_signature.team_id': 'The team identifier used to sign the process.', + 'threat.indicator.file.code_signature.timestamp': 'When the signature was generated and signed.', + 'threat.indicator.file.code_signature.trusted': + 'Stores the trust status of the certificate chain.', + 'threat.indicator.file.code_signature.valid': + 'Boolean to capture if the digital signature is verified against the binary content.', + 'threat.indicator.file.created': 'File creation time.', + 'threat.indicator.file.ctime': 'Last time the file attributes or metadata changed.', + 'threat.indicator.file.device': 'Device that is the source of the file.', + 'threat.indicator.file.directory': 'Directory where the file is located.', + 'threat.indicator.file.drive_letter': 'Drive letter where the file is located.', + 'threat.indicator.file.elf.architecture': 'Machine architecture of the ELF file.', + 'threat.indicator.file.elf.byte_order': 'Byte sequence of ELF file.', + 'threat.indicator.file.elf.cpu_type': 'CPU type of the ELF file.', + 'threat.indicator.file.elf.creation_date': 'Build or compile date.', + 'threat.indicator.file.elf.exports': 'List of exported element names and types.', + 'threat.indicator.file.elf.go_import_hash': 'A hash of the Go language imports in an ELF file.', + 'threat.indicator.file.elf.go_imports': 'List of imported Go language element names and types.', + 'threat.indicator.file.elf.go_imports_names_entropy': + 'Shannon entropy calculation from the list of Go imports.', + 'threat.indicator.file.elf.go_imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of Go imports.', + 'threat.indicator.file.elf.go_stripped': + 'Whether the file is a stripped or obfuscated Go executable.', + 'threat.indicator.file.elf.header.abi_version': + 'Version of the ELF Application Binary Interface (ABI).', + 'threat.indicator.file.elf.header.class': 'Header class of the ELF file.', + 'threat.indicator.file.elf.header.data': 'Data table of the ELF header.', + 'threat.indicator.file.elf.header.entrypoint': 'Header entrypoint of the ELF file.', + 'threat.indicator.file.elf.header.object_version': '"0x1" for original ELF files.', + 'threat.indicator.file.elf.header.os_abi': 'Application Binary Interface (ABI) of the Linux OS.', + 'threat.indicator.file.elf.header.type': 'Header type of the ELF file.', + 'threat.indicator.file.elf.header.version': 'Version of the ELF header.', + 'threat.indicator.file.elf.import_hash': 'A hash of the imports in an ELF file.', + 'threat.indicator.file.elf.imports': 'List of imported element names and types.', + 'threat.indicator.file.elf.imports_names_entropy': + 'Shannon entropy calculation from the list of imported element names and types.', + 'threat.indicator.file.elf.imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of imported element names and types.', + 'threat.indicator.file.elf.sections': 'Section information of the ELF file.', + 'threat.indicator.file.elf.sections.chi2': 'Chi-square probability distribution of the section.', + 'threat.indicator.file.elf.sections.entropy': 'Shannon entropy calculation from the section.', + 'threat.indicator.file.elf.sections.flags': 'ELF Section List flags.', + 'threat.indicator.file.elf.sections.name': 'ELF Section List name.', + 'threat.indicator.file.elf.sections.physical_offset': 'ELF Section List offset.', + 'threat.indicator.file.elf.sections.physical_size': 'ELF Section List physical size.', + 'threat.indicator.file.elf.sections.type': 'ELF Section List type.', + 'threat.indicator.file.elf.sections.var_entropy': + 'Variance for Shannon entropy calculation from the section.', + 'threat.indicator.file.elf.sections.virtual_address': 'ELF Section List virtual address.', + 'threat.indicator.file.elf.sections.virtual_size': 'ELF Section List virtual size.', + 'threat.indicator.file.elf.segments': 'ELF object segment list.', + 'threat.indicator.file.elf.segments.sections': 'ELF object segment sections.', + 'threat.indicator.file.elf.segments.type': 'ELF object segment type.', + 'threat.indicator.file.elf.shared_libraries': 'List of shared libraries used by this ELF object.', + 'threat.indicator.file.elf.telfhash': 'telfhash hash for ELF file.', + 'threat.indicator.file.extension': 'File extension, excluding the leading dot.', + 'threat.indicator.file.fork_name': + 'A fork is additional data associated with a filesystem object.', + 'threat.indicator.file.gid': 'Primary group ID (GID) of the file.', + 'threat.indicator.file.group': 'Primary group name of the file.', + 'threat.indicator.file.hash.md5': 'MD5 hash.', + 'threat.indicator.file.hash.sha1': 'SHA1 hash.', + 'threat.indicator.file.hash.sha256': 'SHA256 hash.', + 'threat.indicator.file.hash.sha384': 'SHA384 hash.', + 'threat.indicator.file.hash.sha512': 'SHA512 hash.', + 'threat.indicator.file.hash.ssdeep': 'SSDEEP hash.', + 'threat.indicator.file.hash.tlsh': 'TLSH hash.', + 'threat.indicator.file.inode': 'Inode representing the file in the filesystem.', + 'threat.indicator.file.mime_type': 'Media type of file, document, or arrangement of bytes.', + 'threat.indicator.file.mode': 'Mode of the file in octal representation.', + 'threat.indicator.file.mtime': 'Last time the file content was modified.', + 'threat.indicator.file.name': 'Name of the file including the extension, without the directory.', + 'threat.indicator.file.owner': 'File owners username.', + 'threat.indicator.file.path': 'Full path to the file, including the file name.', + 'threat.indicator.file.pe.architecture': 'CPU architecture target for the file.', + 'threat.indicator.file.pe.company': + 'Internal company name of the file, provided at compile-time.', + 'threat.indicator.file.pe.description': + 'Internal description of the file, provided at compile-time.', + 'threat.indicator.file.pe.file_version': 'Process name.', + 'threat.indicator.file.pe.go_import_hash': 'A hash of the Go language imports in a PE file.', + 'threat.indicator.file.pe.go_imports': 'List of imported Go language element names and types.', + 'threat.indicator.file.pe.go_imports_names_entropy': + 'Shannon entropy calculation from the list of Go imports.', + 'threat.indicator.file.pe.go_imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of Go imports.', + 'threat.indicator.file.pe.go_stripped': + 'Whether the file is a stripped or obfuscated Go executable.', + 'threat.indicator.file.pe.imphash': 'A hash of the imports in a PE file.', + 'threat.indicator.file.pe.import_hash': 'A hash of the imports in a PE file.', + 'threat.indicator.file.pe.imports': 'List of imported element names and types.', + 'threat.indicator.file.pe.imports_names_entropy': + 'Shannon entropy calculation from the list of imported element names and types.', + 'threat.indicator.file.pe.imports_names_var_entropy': + 'Variance for Shannon entropy calculation from the list of imported element names and types.', + 'threat.indicator.file.pe.original_file_name': + 'Internal name of the file, provided at compile-time.', + 'threat.indicator.file.pe.pehash': + 'A hash of the PE header and data from one or more PE sections.', + 'threat.indicator.file.pe.product': + 'Internal product name of the file, provided at compile-time.', + 'threat.indicator.file.pe.sections': 'Section information of the PE file.', + 'threat.indicator.file.pe.sections.entropy': 'Shannon entropy calculation from the section.', + 'threat.indicator.file.pe.sections.name': 'PE Section List name.', + 'threat.indicator.file.pe.sections.physical_size': 'PE Section List physical size.', + 'threat.indicator.file.pe.sections.var_entropy': + 'Variance for Shannon entropy calculation from the section.', + 'threat.indicator.file.pe.sections.virtual_size': + 'PE Section List virtual size. This is always the same as `physical_size`.', + 'threat.indicator.file.size': 'File size in bytes.', + 'threat.indicator.file.target_path': 'Target path for symlinks.', + 'threat.indicator.file.type': 'File type (file, dir, or symlink).', + 'threat.indicator.file.uid': 'The user ID (UID) or security identifier (SID) of the file owner.', + 'threat.indicator.file.x509.alternative_names': 'List of subject alternative names (SAN).', + 'threat.indicator.file.x509.issuer.common_name': + 'List of common name (CN) of issuing certificate authority.', + 'threat.indicator.file.x509.issuer.country': 'List of country (C) codes', + 'threat.indicator.file.x509.issuer.distinguished_name': + 'Distinguished name (DN) of issuing certificate authority.', + 'threat.indicator.file.x509.issuer.locality': 'List of locality names (L)', + 'threat.indicator.file.x509.issuer.organization': + 'List of organizations (O) of issuing certificate authority.', + 'threat.indicator.file.x509.issuer.organizational_unit': + 'List of organizational units (OU) of issuing certificate authority.', + 'threat.indicator.file.x509.issuer.state_or_province': + 'List of state or province names (ST, S, or P)', + 'threat.indicator.file.x509.not_after': + 'Time at which the certificate is no longer considered valid.', + 'threat.indicator.file.x509.not_before': + 'Time at which the certificate is first considered valid.', + 'threat.indicator.file.x509.public_key_algorithm': 'Algorithm used to generate the public key.', + 'threat.indicator.file.x509.public_key_curve': + 'The curve used by the elliptic curve public key algorithm. This is algorithm specific.', + 'threat.indicator.file.x509.public_key_exponent': + 'Exponent used to derive the public key. This is algorithm specific.', + 'threat.indicator.file.x509.public_key_size': 'The size of the public key space in bits.', + 'threat.indicator.file.x509.serial_number': + 'Unique serial number issued by the certificate authority.', + 'threat.indicator.file.x509.signature_algorithm': + 'Identifier for certificate signature algorithm.', + 'threat.indicator.file.x509.subject.common_name': 'List of common names (CN) of subject.', + 'threat.indicator.file.x509.subject.country': 'List of country (C) code', + 'threat.indicator.file.x509.subject.distinguished_name': + 'Distinguished name (DN) of the certificate subject entity.', + 'threat.indicator.file.x509.subject.locality': 'List of locality names (L)', + 'threat.indicator.file.x509.subject.organization': 'List of organizations (O) of subject.', + 'threat.indicator.file.x509.subject.organizational_unit': + 'List of organizational units (OU) of subject.', + 'threat.indicator.file.x509.subject.state_or_province': + 'List of state or province names (ST, S, or P)', + 'threat.indicator.file.x509.version_number': 'Version of x509 format.', + 'threat.indicator.first_seen': 'Date/time indicator was first reported.', + 'threat.indicator.geo.city_name': 'City name.', + 'threat.indicator.geo.continent_code': 'Continent code.', + 'threat.indicator.geo.continent_name': 'Name of the continent.', + 'threat.indicator.geo.country_iso_code': 'Country ISO code.', + 'threat.indicator.geo.country_name': 'Country name.', + 'threat.indicator.geo.location': 'Longitude and latitude.', + 'threat.indicator.geo.name': 'User-defined description of a location.', + 'threat.indicator.geo.postal_code': 'Postal code.', + 'threat.indicator.geo.region_iso_code': 'Region ISO code.', + 'threat.indicator.geo.region_name': 'Region name.', + 'threat.indicator.geo.timezone': 'Time zone.', + 'threat.indicator.ip': 'Indicator IP address', + 'threat.indicator.last_seen': 'Date/time indicator was last reported.', + 'threat.indicator.marking.tlp': 'Indicator TLP marking', + 'threat.indicator.marking.tlp_version': 'Indicator TLP version', + 'threat.indicator.modified_at': 'Date/time indicator was last updated.', + 'threat.indicator.name': 'Indicator display name', + 'threat.indicator.port': 'Indicator port', + 'threat.indicator.provider': 'Indicator provider', + 'threat.indicator.reference': 'Indicator reference URL', + 'threat.indicator.registry.data.bytes': 'Original bytes written with base64 encoding.', + 'threat.indicator.registry.data.strings': + 'List of strings representing what was written to the registry.', + 'threat.indicator.registry.data.type': 'Standard registry type for encoding contents', + 'threat.indicator.registry.hive': 'Abbreviated name for the hive.', + 'threat.indicator.registry.key': 'Hive-relative path of keys.', + 'threat.indicator.registry.path': 'Full path, including hive, key and value', + 'threat.indicator.registry.value': 'Name of the value written.', + 'threat.indicator.scanner_stats': 'Scanner statistics', + 'threat.indicator.sightings': 'Number of times indicator observed', + 'threat.indicator.type': 'Type of indicator', + 'threat.indicator.url.domain': 'Domain of the url.', + 'threat.indicator.url.extension': + 'File extension from the request url, excluding the leading dot.', + 'threat.indicator.url.fragment': 'Portion of the url after the `#`.', + 'threat.indicator.url.full': 'Full unparsed URL.', + 'threat.indicator.url.original': 'Unmodified original url as seen in the event source.', + 'threat.indicator.url.password': 'Password of the request.', + 'threat.indicator.url.path': 'Path of the request, such as "/search".', + 'threat.indicator.url.port': 'Port of the request, such as 443.', + 'threat.indicator.url.query': 'Query string of the request.', + 'threat.indicator.url.registered_domain': + 'The highest registered url domain, stripped of the subdomain.', + 'threat.indicator.url.scheme': 'Scheme of the url.', + 'threat.indicator.url.subdomain': 'The subdomain of the domain.', + 'threat.indicator.url.top_level_domain': 'The effective top level domain (com, org, net, co.uk).', + 'threat.indicator.url.username': 'Username of the request.', + 'threat.indicator.x509.alternative_names': 'List of subject alternative names (SAN).', + 'threat.indicator.x509.issuer.common_name': + 'List of common name (CN) of issuing certificate authority.', + 'threat.indicator.x509.issuer.country': 'List of country (C) codes', + 'threat.indicator.x509.issuer.distinguished_name': + 'Distinguished name (DN) of issuing certificate authority.', + 'threat.indicator.x509.issuer.locality': 'List of locality names (L)', + 'threat.indicator.x509.issuer.organization': + 'List of organizations (O) of issuing certificate authority.', + 'threat.indicator.x509.issuer.organizational_unit': + 'List of organizational units (OU) of issuing certificate authority.', + 'threat.indicator.x509.issuer.state_or_province': 'List of state or province names (ST, S, or P)', + 'threat.indicator.x509.not_after': 'Time at which the certificate is no longer considered valid.', + 'threat.indicator.x509.not_before': 'Time at which the certificate is first considered valid.', + 'threat.indicator.x509.public_key_algorithm': 'Algorithm used to generate the public key.', + 'threat.indicator.x509.public_key_curve': + 'The curve used by the elliptic curve public key algorithm. This is algorithm specific.', + 'threat.indicator.x509.public_key_exponent': + 'Exponent used to derive the public key. This is algorithm specific.', + 'threat.indicator.x509.public_key_size': 'The size of the public key space in bits.', + 'threat.indicator.x509.serial_number': + 'Unique serial number issued by the certificate authority.', + 'threat.indicator.x509.signature_algorithm': 'Identifier for certificate signature algorithm.', + 'threat.indicator.x509.subject.common_name': 'List of common names (CN) of subject.', + 'threat.indicator.x509.subject.country': 'List of country (C) code', + 'threat.indicator.x509.subject.distinguished_name': + 'Distinguished name (DN) of the certificate subject entity.', + 'threat.indicator.x509.subject.locality': 'List of locality names (L)', + 'threat.indicator.x509.subject.organization': 'List of organizations (O) of subject.', + 'threat.indicator.x509.subject.organizational_unit': + 'List of organizational units (OU) of subject.', + 'threat.indicator.x509.subject.state_or_province': + 'List of state or province names (ST, S, or P)', + 'threat.indicator.x509.version_number': 'Version of x509 format.', + 'threat.software.alias': 'Alias of the software', + 'threat.software.id': 'ID of the software', + 'threat.software.name': 'Name of the software.', + 'threat.software.platforms': 'Platforms of the software.', + 'threat.software.reference': 'Software reference URL.', + 'threat.software.type': 'Software type.', + 'threat.tactic.id': 'Threat tactic id.', + 'threat.tactic.name': 'Threat tactic.', + 'threat.tactic.reference': 'Threat tactic URL reference.', + 'threat.technique.id': 'Threat technique id.', + 'threat.technique.name': 'Threat technique name.', + 'threat.technique.reference': 'Threat technique URL reference.', + 'threat.technique.subtechnique.id': 'Threat subtechnique id.', + 'threat.technique.subtechnique.name': 'Threat subtechnique name.', + 'threat.technique.subtechnique.reference': 'Threat subtechnique URL reference.', + 'tls.cipher': 'String indicating the cipher used during the current connection.', + 'tls.client.certificate': 'PEM-encoded stand-alone certificate offered by the client.', + 'tls.client.certificate_chain': + 'Array of PEM-encoded certificates that make up the certificate chain offered by the client.', + 'tls.client.hash.md5': + 'Certificate fingerprint using the MD5 digest of DER-encoded version of certificate offered by the client.', + 'tls.client.hash.sha1': + 'Certificate fingerprint using the SHA1 digest of DER-encoded version of certificate offered by the client.', + 'tls.client.hash.sha256': + 'Certificate fingerprint using the SHA256 digest of DER-encoded version of certificate offered by the client.', + 'tls.client.issuer': + 'Distinguished name of subject of the issuer of the x.509 certificate presented by the client.', + 'tls.client.ja3': + 'A hash that identifies clients based on how they perform an SSL/TLS handshake.', + 'tls.client.not_after': + 'Date/Time indicating when client certificate is no longer considered valid.', + 'tls.client.not_before': + 'Date/Time indicating when client certificate is first considered valid.', + 'tls.client.server_name': 'Hostname the client is trying to connect to. Also called the SNI.', + 'tls.client.subject': + 'Distinguished name of subject of the x.509 certificate presented by the client.', + 'tls.client.supported_ciphers': 'Array of ciphers offered by the client during the client hello.', + 'tls.client.x509.alternative_names': 'List of subject alternative names (SAN).', + 'tls.client.x509.issuer.common_name': + 'List of common name (CN) of issuing certificate authority.', + 'tls.client.x509.issuer.country': 'List of country (C) codes', + 'tls.client.x509.issuer.distinguished_name': + 'Distinguished name (DN) of issuing certificate authority.', + 'tls.client.x509.issuer.locality': 'List of locality names (L)', + 'tls.client.x509.issuer.organization': + 'List of organizations (O) of issuing certificate authority.', + 'tls.client.x509.issuer.organizational_unit': + 'List of organizational units (OU) of issuing certificate authority.', + 'tls.client.x509.issuer.state_or_province': 'List of state or province names (ST, S, or P)', + 'tls.client.x509.not_after': 'Time at which the certificate is no longer considered valid.', + 'tls.client.x509.not_before': 'Time at which the certificate is first considered valid.', + 'tls.client.x509.public_key_algorithm': 'Algorithm used to generate the public key.', + 'tls.client.x509.public_key_curve': + 'The curve used by the elliptic curve public key algorithm. This is algorithm specific.', + 'tls.client.x509.public_key_exponent': + 'Exponent used to derive the public key. This is algorithm specific.', + 'tls.client.x509.public_key_size': 'The size of the public key space in bits.', + 'tls.client.x509.serial_number': 'Unique serial number issued by the certificate authority.', + 'tls.client.x509.signature_algorithm': 'Identifier for certificate signature algorithm.', + 'tls.client.x509.subject.common_name': 'List of common names (CN) of subject.', + 'tls.client.x509.subject.country': 'List of country (C) code', + 'tls.client.x509.subject.distinguished_name': + 'Distinguished name (DN) of the certificate subject entity.', + 'tls.client.x509.subject.locality': 'List of locality names (L)', + 'tls.client.x509.subject.organization': 'List of organizations (O) of subject.', + 'tls.client.x509.subject.organizational_unit': 'List of organizational units (OU) of subject.', + 'tls.client.x509.subject.state_or_province': 'List of state or province names (ST, S, or P)', + 'tls.client.x509.version_number': 'Version of x509 format.', + 'tls.curve': 'String indicating the curve used for the given cipher, when applicable.', + 'tls.established': + 'Boolean flag indicating if the TLS negotiation was successful and transitioned to an encrypted tunnel.', + 'tls.next_protocol': 'String indicating the protocol being tunneled.', + 'tls.resumed': + 'Boolean flag indicating if this TLS connection was resumed from an existing TLS negotiation.', + 'tls.server.certificate': 'PEM-encoded stand-alone certificate offered by the server.', + 'tls.server.certificate_chain': + 'Array of PEM-encoded certificates that make up the certificate chain offered by the server.', + 'tls.server.hash.md5': + 'Certificate fingerprint using the MD5 digest of DER-encoded version of certificate offered by the server.', + 'tls.server.hash.sha1': + 'Certificate fingerprint using the SHA1 digest of DER-encoded version of certificate offered by the server.', + 'tls.server.hash.sha256': + 'Certificate fingerprint using the SHA256 digest of DER-encoded version of certificate offered by the server.', + 'tls.server.issuer': 'Subject of the issuer of the x.509 certificate presented by the server.', + 'tls.server.ja3s': + 'A hash that identifies servers based on how they perform an SSL/TLS handshake.', + 'tls.server.not_after': + 'Timestamp indicating when server certificate is no longer considered valid.', + 'tls.server.not_before': + 'Timestamp indicating when server certificate is first considered valid.', + 'tls.server.subject': 'Subject of the x.509 certificate presented by the server.', + 'tls.server.x509.alternative_names': 'List of subject alternative names (SAN).', + 'tls.server.x509.issuer.common_name': + 'List of common name (CN) of issuing certificate authority.', + 'tls.server.x509.issuer.country': 'List of country (C) codes', + 'tls.server.x509.issuer.distinguished_name': + 'Distinguished name (DN) of issuing certificate authority.', + 'tls.server.x509.issuer.locality': 'List of locality names (L)', + 'tls.server.x509.issuer.organization': + 'List of organizations (O) of issuing certificate authority.', + 'tls.server.x509.issuer.organizational_unit': + 'List of organizational units (OU) of issuing certificate authority.', + 'tls.server.x509.issuer.state_or_province': 'List of state or province names (ST, S, or P)', + 'tls.server.x509.not_after': 'Time at which the certificate is no longer considered valid.', + 'tls.server.x509.not_before': 'Time at which the certificate is first considered valid.', + 'tls.server.x509.public_key_algorithm': 'Algorithm used to generate the public key.', + 'tls.server.x509.public_key_curve': + 'The curve used by the elliptic curve public key algorithm. This is algorithm specific.', + 'tls.server.x509.public_key_exponent': + 'Exponent used to derive the public key. This is algorithm specific.', + 'tls.server.x509.public_key_size': 'The size of the public key space in bits.', + 'tls.server.x509.serial_number': 'Unique serial number issued by the certificate authority.', + 'tls.server.x509.signature_algorithm': 'Identifier for certificate signature algorithm.', + 'tls.server.x509.subject.common_name': 'List of common names (CN) of subject.', + 'tls.server.x509.subject.country': 'List of country (C) code', + 'tls.server.x509.subject.distinguished_name': + 'Distinguished name (DN) of the certificate subject entity.', + 'tls.server.x509.subject.locality': 'List of locality names (L)', + 'tls.server.x509.subject.organization': 'List of organizations (O) of subject.', + 'tls.server.x509.subject.organizational_unit': 'List of organizational units (OU) of subject.', + 'tls.server.x509.subject.state_or_province': 'List of state or province names (ST, S, or P)', + 'tls.server.x509.version_number': 'Version of x509 format.', + 'tls.version': 'Numeric part of the version parsed from the original string.', + 'tls.version_protocol': 'Normalized lowercase protocol name parsed from original string.', + 'trace.id': 'Unique identifier of the trace.', + 'transaction.id': 'Unique identifier of the transaction within the scope of its trace.', + 'url.domain': 'Domain of the url.', + 'url.extension': 'File extension from the request url, excluding the leading dot.', + 'url.fragment': 'Portion of the url after the `#`.', + 'url.full': 'Full unparsed URL.', + 'url.original': 'Unmodified original url as seen in the event source.', + 'url.password': 'Password of the request.', + 'url.path': 'Path of the request, such as "/search".', + 'url.port': 'Port of the request, such as 443.', + 'url.query': 'Query string of the request.', + 'url.registered_domain': 'The highest registered url domain, stripped of the subdomain.', + 'url.scheme': 'Scheme of the url.', + 'url.subdomain': 'The subdomain of the domain.', + 'url.top_level_domain': 'The effective top level domain (com, org, net, co.uk).', + 'url.username': 'Username of the request.', + 'user.changes.domain': 'Name of the directory the user is a member of.', + 'user.changes.email': 'User email address.', + 'user.changes.full_name': 'Users full name, if available.', + 'user.changes.group.domain': 'Name of the directory the group is a member of.', + 'user.changes.group.id': 'Unique identifier for the group on the system/platform.', + 'user.changes.group.name': 'Name of the group.', + 'user.changes.hash': 'Unique user hash to correlate information for a user in anonymized form.', + 'user.changes.id': 'Unique identifier of the user.', + 'user.changes.name': 'Short name or login of the user.', + 'user.changes.roles': 'Array of user roles at the time of the event.', + 'user.domain': 'Name of the directory the user is a member of.', + 'user.effective.domain': 'Name of the directory the user is a member of.', + 'user.effective.email': 'User email address.', + 'user.effective.full_name': 'Users full name, if available.', + 'user.effective.group.domain': 'Name of the directory the group is a member of.', + 'user.effective.group.id': 'Unique identifier for the group on the system/platform.', + 'user.effective.group.name': 'Name of the group.', + 'user.effective.hash': 'Unique user hash to correlate information for a user in anonymized form.', + 'user.effective.id': 'Unique identifier of the user.', + 'user.effective.name': 'Short name or login of the user.', + 'user.effective.roles': 'Array of user roles at the time of the event.', + 'user.email': 'User email address.', + 'user.full_name': 'Users full name, if available.', + 'user.group.domain': 'Name of the directory the group is a member of.', + 'user.group.id': 'Unique identifier for the group on the system/platform.', + 'user.group.name': 'Name of the group.', + 'user.hash': 'Unique user hash to correlate information for a user in anonymized form.', + 'user.id': 'Unique identifier of the user.', + 'user.name': 'Short name or login of the user.', + 'user.risk.calculated_level': + 'A risk classification level calculated by an internal system as part of entity analytics and entity risk scoring.', + 'user.risk.calculated_score': + 'A risk classification score calculated by an internal system as part of entity analytics and entity risk scoring.', + 'user.risk.calculated_score_norm': 'A normalized risk score calculated by an internal system.', + 'user.risk.static_level': + 'A risk classification level obtained from outside the system, such as from some external Threat Intelligence Platform.', + 'user.risk.static_score': + 'A risk classification score obtained from outside the system, such as from some external Threat Intelligence Platform.', + 'user.risk.static_score_norm': 'A normalized risk score calculated by an external system.', + 'user.roles': 'Array of user roles at the time of the event.', + 'user.target.domain': 'Name of the directory the user is a member of.', + 'user.target.email': 'User email address.', + 'user.target.full_name': 'Users full name, if available.', + 'user.target.group.domain': 'Name of the directory the group is a member of.', + 'user.target.group.id': 'Unique identifier for the group on the system/platform.', + 'user.target.group.name': 'Name of the group.', + 'user.target.hash': 'Unique user hash to correlate information for a user in anonymized form.', + 'user.target.id': 'Unique identifier of the user.', + 'user.target.name': 'Short name or login of the user.', + 'user.target.roles': 'Array of user roles at the time of the event.', + 'user_agent.device.name': 'Name of the device.', + 'user_agent.name': 'Name of the user agent.', + 'user_agent.original': 'Unparsed user_agent string.', + 'user_agent.os.family': 'OS family (such as redhat, debian, freebsd, windows).', + 'user_agent.os.full': 'Operating system name, including the version or code name.', + 'user_agent.os.kernel': 'Operating system kernel version as a raw string.', + 'user_agent.os.name': 'Operating system name, without the version.', + 'user_agent.os.platform': 'Operating system platform (such centos, ubuntu, windows).', + 'user_agent.os.type': + 'Which commercial OS family (one of: linux, macos, unix, windows, ios or android).', + 'user_agent.os.version': 'Operating system version as a raw string.', + 'user_agent.version': 'Version of the user agent.', + 'volume.bus_type': 'Bus type of the device.', + 'volume.default_access': 'Bus type of the device.', + 'volume.device_name': 'Device name of the volume.', + 'volume.device_type': 'Volume device type.', + 'volume.dos_name': 'DOS name of the device.', + 'volume.file_system_type': 'Volume device file system type.', + 'volume.mount_name': 'Mount name of the volume.', + 'volume.nt_name': 'NT name of the device.', + 'volume.product_id': 'ProductID of the device.', + 'volume.product_name': 'Produce name of the volume.', + 'volume.removable': 'Indicates if the volume is removable.', + 'volume.serial_number': 'Serial number of the device.', + 'volume.size': 'Size of the volume device in bytes.', + 'volume.vendor_id': 'VendorID of the device.', + 'volume.vendor_name': 'Vendor name of the device.', + 'volume.writable': 'Indicates if the volume is writable.', + 'vulnerability.category': 'Category of a vulnerability.', + 'vulnerability.classification': 'Classification of the vulnerability.', + 'vulnerability.description': 'Description of the vulnerability.', + 'vulnerability.enumeration': 'Identifier of the vulnerability.', + 'vulnerability.id': 'ID of the vulnerability.', + 'vulnerability.reference': 'Reference of the vulnerability.', + 'vulnerability.report_id': 'Scan identification number.', + 'vulnerability.scanner.vendor': 'Name of the scanner vendor.', + 'vulnerability.score.base': 'Vulnerability Base score.', + 'vulnerability.score.environmental': 'Vulnerability Environmental score.', + 'vulnerability.score.temporal': 'Vulnerability Temporal score.', + 'vulnerability.score.version': 'CVSS version.', + 'vulnerability.severity': 'Severity of the vulnerability.', +}; diff --git a/x-pack/plugins/integration_assistant/common/index.ts b/x-pack/plugins/integration_assistant/common/index.ts new file mode 100644 index 0000000000000..d5e72ede8e164 --- /dev/null +++ b/x-pack/plugins/integration_assistant/common/index.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { BuildIntegrationRequestBody } from './api/build_integration/build_integration'; +export { + CategorizationRequestBody, + CategorizationResponse, +} from './api/categorization/categorization_route'; +export { + CheckPipelineRequestBody, + CheckPipelineResponse, +} from './api/check_pipeline/check_pipeline'; +export { EcsMappingRequestBody, EcsMappingResponse } from './api/ecs/ecs_route'; +export { RelatedRequestBody, RelatedResponse } from './api/related/related_route'; + +export type { Datastream, InputType, Integration, Pipeline } from './api/model/common_attributes'; +export type { ESProcessorItem } from './api/model/processor_attributes'; + +export { + CATEGORIZATION_GRAPH_PATH, + ECS_GRAPH_PATH, + INTEGRATION_ASSISTANT_APP_ROUTE, + INTEGRATION_ASSISTANT_BASE_PATH, + INTEGRATION_BUILDER_PATH, + PLUGIN_ID, + RELATED_GRAPH_PATH, + TEST_PIPELINE_PATH, +} from './constants'; diff --git a/x-pack/plugins/integration_assistant/jest.config.js b/x-pack/plugins/integration_assistant/jest.config.js new file mode 100644 index 0000000000000..5da1e904b8894 --- /dev/null +++ b/x-pack/plugins/integration_assistant/jest.config.js @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/integration_assistant'], + coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/integration_assistant', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/x-pack/plugins/integration_assistant/{common,server}/**/*.{ts,tsx}', + '!/x-pack/plugins/integration_assistant/{__jest__}/**/*', + '!/x-pack/plugins/integration_assistant/*.test.{ts,tsx}', + '!/x-pack/plugins/integration_assistant/*.config.ts', + ], + setupFiles: ['jest-canvas-mock'], +}; diff --git a/x-pack/plugins/integration_assistant/kibana.jsonc b/x-pack/plugins/integration_assistant/kibana.jsonc new file mode 100644 index 0000000000000..9ce4f435c893b --- /dev/null +++ b/x-pack/plugins/integration_assistant/kibana.jsonc @@ -0,0 +1,15 @@ +{ + "type": "plugin", + "id": "@kbn/integration-assistant-plugin", + "owner": "@elastic/security-solution", + "description": "A simple example of how to use core's routing services test", + "plugin": { + "id": "integrationAssistant", + "server": true, + "browser": false, + "configPath": ["xpack", "integration_assistant"], + "requiredPlugins": ["actions", "licensing", "management", "features", "share", "fileUpload"], + "optionalPlugins": ["security", "usageCollection", "console"], + "extraPublicDirs": ["common"] + } +} diff --git a/x-pack/plugins/integration_assistant/server/config.ts b/x-pack/plugins/integration_assistant/server/config.ts new file mode 100644 index 0000000000000..c8c81b9f63743 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/config.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema, type TypeOf } from '@kbn/config-schema'; +import type { PluginConfigDescriptor } from '@kbn/core/server'; + +export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: false }), +}); +export type ServerlessSecuritySchema = TypeOf; + +export const config: PluginConfigDescriptor = { + schema: configSchema, +}; diff --git a/x-pack/plugins/integration_assistant/server/constants.ts b/x-pack/plugins/integration_assistant/server/constants.ts new file mode 100644 index 0000000000000..1c4ed1918d310 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/constants.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const ROUTE_HANDLER_TIMEOUT = 10 * 60 * 1000; // 10 * 60 seconds = 10 minutes +export const LANG_CHAIN_TIMEOUT = ROUTE_HANDLER_TIMEOUT - 10_000; // 9 minutes 50 seconds +export const CONNECTOR_TIMEOUT = LANG_CHAIN_TIMEOUT - 10_000; // 9 minutes 40 seconds diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/categorization.test.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/categorization.test.ts new file mode 100644 index 0000000000000..3ad0926297bbc --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/categorization.test.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 { FakeLLM } from '@langchain/core/utils/testing'; +import { handleCategorization } from './categorization'; +import type { CategorizationState } from '../../types'; +import { + categorizationTestState, + categorizationMockProcessors, + categorizationExpectedHandlerResponse, +} from '../../../__jest__/fixtures/categorization'; +import { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; + +const mockLlm = new FakeLLM({ + response: JSON.stringify(categorizationMockProcessors, null, 2), +}) as unknown as ActionsClientChatOpenAI | ActionsClientSimpleChatModel; + +const testState: CategorizationState = categorizationTestState; + +describe('Testing categorization handler', () => { + it('handleCategorization()', async () => { + const response = await handleCategorization(testState, mockLlm); + expect(response.currentPipeline).toStrictEqual( + categorizationExpectedHandlerResponse.currentPipeline + ); + expect(response.lastExecutedChain).toBe('categorization'); + }); +}); diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/categorization.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/categorization.ts new file mode 100644 index 0000000000000..ed1a88c3a1cfd --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/categorization.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 { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; +import { JsonOutputParser } from '@langchain/core/output_parsers'; +import type { ESProcessorItem, Pipeline } from '../../../common'; +import type { CategorizationState } from '../../types'; +import { combineProcessors } from '../../util/processors'; +import { CATEGORIZATION_MAIN_PROMPT } from './prompts'; + +export async function handleCategorization( + state: CategorizationState, + model: ActionsClientChatOpenAI | ActionsClientSimpleChatModel +) { + const categorizationMainPrompt = CATEGORIZATION_MAIN_PROMPT; + const outputParser = new JsonOutputParser(); + const categorizationMainGraph = categorizationMainPrompt.pipe(model).pipe(outputParser); + + const currentProcessors = (await categorizationMainGraph.invoke({ + pipeline_results: JSON.stringify(state.pipelineResults, null, 2), + ex_answer: state?.exAnswer, + ecs_categories: state?.ecsCategories, + ecs_types: state?.ecsTypes, + })) as ESProcessorItem[]; + + const currentPipeline = combineProcessors(state.initialPipeline as Pipeline, currentProcessors); + + return { + currentPipeline, + currentProcessors, + lastExecutedChain: 'categorization', + }; +} diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/constants.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/constants.ts new file mode 100644 index 0000000000000..ca875c15f026d --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/constants.ts @@ -0,0 +1,242 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export const ECS_CATEGORIES = { + api: 'Covers events from API calls, including those from OS and network protocols. Allowed event.type combinations: access, admin, allowed, change, creation, deletion, denied, end, info, start, user', + authentication: + 'Focuses on login and credential verification processes. Allowed event.type combinations: start, end, info', + configuration: + 'Deals with application, process, or system settings changes. Allowed event.type combinations: access, change, creation, deletion, info', + database: + 'Relates to data storage systems, such as SQL or Elasticsearch. Allowed event.type combinations: access, change, info, error', + driver: + 'Involves OS device driver activities. Allowed event.type combinations: change, end, info, start', + email: 'Covers events from email messages and protocols. Allowed event.type combinations: info', + file: 'Related to file creation, access, and deletion. Allowed event.type combinations: access, change, creation, deletion, info', + host: 'Provides information about hosts, excluding activity on them. Allowed event.type combinations: access, change, end, info, start', + iam: 'Concerns users, groups, and administration events. Allowed event.type combinations: admin, change, creation, deletion, group, info, user', + intrusion_detection: + 'Detects intrusions from IDS/IPS systems. Allowed event.type combinations: allowed, denied, info', + library: + 'Refers to the loading of libraries into processes. Allowed event.type combinations: start', + malware: 'Focuses on malware detection events and alerts. Allowed event.type combinations: info', + network: + 'Captures all network-related activities. Allowed event.type combinations: access, allowed, connection, denied, end, info, protocol, start', + package: + 'Concerns software packages on hosts. Allowed event.type combinations: access, change, deletion, info, installation, start', + process: + 'Addresses process-specific details. Allowed event.type combinations: access, change, end, info, start', + registry: + 'Focuses on Windows registry settings. Allowed event.type combinations: access, change, creation, deletion', + session: + 'Relates to persistent connections to hosts/services. Allowed event.type combinations: start, end, info', + threat: + "Describes threat actors' intentions and behaviors. Allowed event.type combinations: indicator", + vulnerability: 'Pertain to vulnerability scan outcomes. Allowed event.type combinations: info', + web: 'Concerns web server access events. access, error, Allowed event.type combinations: info', +}; + +export const ECS_TYPES = { + access: 'Used to indicate something was accessed. Examples include accessing databases or files.', + admin: + 'Pertains to events related to admin objects, like administrative changes in IAM not tied to specific users or groups.', + allowed: + 'Indicates that a certain action or event was permitted, like firewall connections that were permitted.', + change: + 'Used for events indicating that something has changed, such as modifications in files or processes.', + connection: + 'Mainly for network-related events, capturing details sufficient for flow or connection analysis, like Netflow or IPFIX events.', + creation: 'Denotes that something was created. A typical example is file creation.', + deletion: 'Indicates that something was removed or deleted, for instance, file deletions.', + denied: + 'Refers to events where something was denied or blocked, such as a network connection that was blocked by a firewall.', + end: 'Suggests that something has concluded or ended, like a process.', + error: + 'Used for events that describe errors, but not errors during event ingestion. For instance, database errors.', + group: + 'Pertains to group-related events within categories, like creation or modification of user groups in IAM.', + indicator: + 'Represents events that contain indicators of compromise (IOCs), commonly associated with threat detection.', + info: "Denotes purely informational events that don't imply a state change or an action. For example, system information logs.", + installation: 'Indicates that something was installed, typically software or packages.', + protocol: + 'Used for events containing detailed protocol analysis, beyond just naming the protocol, especially in network events.', + start: 'Signals the commencement of something, such as a process.', + user: 'Relates to user-centric events within categories, like user creation or deletion in IAM.', +}; + +export const EVENT_TYPES = [ + 'access', + 'admin', + 'allowed', + 'change', + 'connection', + 'creation', + 'deletion', + 'denied', + 'end', + 'error', + 'group', + 'indicator', + 'info', + 'installation', + 'protocol', + 'start', + 'user', +]; + +export const EVENT_CATEGORIES = [ + 'api', + 'authentication', + 'configuration', + 'database', + 'driver', + 'email', + 'file', + 'host', + 'iam', + 'intrusion_detection', + 'library', + 'malware', + 'network', + 'package', + 'process', + 'registry', + 'session', + 'threat', + 'vulnerability', + 'web', +]; + +export type EventCategories = + | 'api' + | 'authentication' + | 'configuration' + | 'database' + | 'driver' + | 'email' + | 'file' + | 'host' + | 'iam' + | 'intrusion_detection' + | 'library' + | 'network' + | 'package' + | 'process' + | 'registry' + | 'session' + | 'threat' + | 'user' + | 'vulnerability' + | 'web'; + +export const ECS_EVENT_TYPES_PER_CATEGORY: { + [key in EventCategories]: string[]; +} = { + api: [ + 'access', + 'admin', + 'allowed', + 'change', + 'creation', + 'deletion', + 'denied', + 'end', + 'info', + 'start', + 'user', + ], + authentication: ['start', 'end', 'info'], + configuration: ['access', 'change', 'creation', 'deletion', 'info'], + database: ['access', 'change', 'info', 'error'], + driver: ['change', 'end', 'info', 'start'], + email: ['access', 'change', 'creation', 'deletion', 'info', 'start'], + file: ['access', 'change', 'creation', 'deletion', 'info', 'start'], + host: ['access', 'change', 'creation', 'deletion', 'info', 'start'], + iam: ['access', 'change', 'creation', 'deletion', 'info', 'start'], + intrusion_detection: ['access', 'change', 'creation', 'deletion', 'info', 'start'], + library: ['access', 'change', 'creation', 'deletion', 'info', 'start'], + network: ['access', 'change', 'creation', 'deletion', 'info', 'start'], + package: ['access', 'change', 'creation', 'deletion', 'info', 'start'], + process: ['access', 'change', 'creation', 'deletion', 'info', 'start'], + registry: ['access', 'change', 'creation', 'deletion', 'info', 'start'], + session: ['access', 'change', 'creation', 'deletion', 'info', 'start'], + threat: ['access', 'change', 'creation', 'deletion', 'info', 'start'], + user: ['access', 'change', 'creation', 'deletion', 'info', 'start'], + vulnerability: ['access', 'change', 'creation', 'deletion', 'info', 'start'], + web: ['access', 'change', 'creation', 'deletion', 'info', 'start'], +}; + +export const CATEGORIZATION_EXAMPLE_PROCESSORS = ` +If condition that determines if ctx.checkpoint?.operation is not of a specific value: +{ + "append": { + "field": "event.category", + "value": "network", + "allow_duplicates": false, + "if": "ctx.checkpoint?.operation != 'Log In'" + } +} + +If condition that determines if ctx.checkpoint?.operation is of a specific value: +{ + "append": { + "field": "event.category", + "value": "authentication", + "allow_duplicates": false, + "if": "ctx.checkpoint?.operation == 'Log In'" + } +} + +Appending multiple values when either the value Accept or Allow is found in ctx.checkpoint?.rule_action: +{ + "append": { + "field": "event.type", + "value": [ + "allowed", + "connection" + ], + "allow_duplicates": false, + "if": "['Accept', 'Allow'].contains(ctx.checkpoint?.rule_action)" + } +} +`; + +export const CATEGORIZATION_EXAMPLE_ANSWER = [ + { append: { field: 'event.type', value: ['access'] } }, + { + append: { + field: 'event.type', + value: ['allowed', 'connection'], + allow_duplicates: false, + if: "['Accept', 'Allow'].contains(ctx.checkpoint?.rule_action)", + }, + }, + { + append: { + field: 'event.category', + value: ['network'], + allow_duplicates: false, + if: "['Accept', 'Allow'].contains(ctx.checkpoint?.rule_action)", + }, + }, + { + append: { + field: 'event.type', + value: ['start'], + allow_duplicates: false, + if: "ctx.checkpoint?.operation == 'Log In'", + }, + }, + { + append: { + field: 'event.category', + value: ['authentication'], + allow_duplicates: false, + if: "ctx.checkpoint?.operation == 'Log In'", + }, + }, +]; diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/errors.test.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/errors.test.ts new file mode 100644 index 0000000000000..18d8c1842080a --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/errors.test.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 { FakeLLM } from '@langchain/core/utils/testing'; +import { handleErrors } from './errors'; +import type { CategorizationState } from '../../types'; +import { + categorizationTestState, + categorizationMockProcessors, + categorizationExpectedHandlerResponse, +} from '../../../__jest__/fixtures/categorization'; +import { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; + +const mockLlm = new FakeLLM({ + response: JSON.stringify(categorizationMockProcessors, null, 2), +}) as unknown as ActionsClientChatOpenAI | ActionsClientSimpleChatModel; + +const testState: CategorizationState = categorizationTestState; + +describe('Testing categorization handler', () => { + it('handleErrors()', async () => { + const response = await handleErrors(testState, mockLlm); + expect(response.currentPipeline).toStrictEqual( + categorizationExpectedHandlerResponse.currentPipeline + ); + expect(response.lastExecutedChain).toBe('error'); + }); +}); diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/errors.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/errors.ts new file mode 100644 index 0000000000000..d8cb7beedc9bf --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/errors.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; +import { JsonOutputParser } from '@langchain/core/output_parsers'; +import type { ESProcessorItem, Pipeline } from '../../../common'; +import type { CategorizationState } from '../../types'; +import { combineProcessors } from '../../util/processors'; +import { CATEGORIZATION_ERROR_PROMPT } from './prompts'; + +export async function handleErrors( + state: CategorizationState, + model: ActionsClientChatOpenAI | ActionsClientSimpleChatModel +) { + const categorizationErrorPrompt = CATEGORIZATION_ERROR_PROMPT; + + const outputParser = new JsonOutputParser(); + const categorizationErrorGraph = categorizationErrorPrompt.pipe(model).pipe(outputParser); + + const currentProcessors = (await categorizationErrorGraph.invoke({ + current_processors: JSON.stringify(state.currentProcessors, null, 2), + ex_answer: state.exAnswer, + errors: JSON.stringify(state.errors, null, 2), + package_name: state.packageName, + data_stream_name: state.dataStreamName, + })) as ESProcessorItem[]; + + const currentPipeline = combineProcessors(state.initialPipeline as Pipeline, currentProcessors); + + return { + currentPipeline, + currentProcessors, + reviewed: false, + lastExecutedChain: 'error', + }; +} diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/graph.test.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/graph.test.ts new file mode 100644 index 0000000000000..4122d4540dbc0 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/graph.test.ts @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IScopedClusterClient } from '@kbn/core/server'; +import { FakeLLM } from '@langchain/core/utils/testing'; +import { getCategorizationGraph } from './graph'; +import { + categorizationExpectedResults, + categorizationErrorMockedResponse, + categorizationInitialMockedResponse, + categorizationInvalidMockedResponse, + categorizationReviewMockedResponse, + categorizationInitialPipeline, + testPipelineError, + testPipelineValidResult, + testPipelineInvalidEcs, +} from '../../../__jest__/fixtures/categorization'; +import { mockedRequestWithPipeline } from '../../../__jest__/fixtures'; +import { handleReview } from './review'; +import { handleCategorization } from './categorization'; +import { handleErrors } from './errors'; +import { handleInvalidCategorization } from './invalid'; +import { testPipeline, combineProcessors } from '../../util'; +import { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; + +const mockLlm = new FakeLLM({ + response: "I'll callback later.", +}) as unknown as ActionsClientChatOpenAI | ActionsClientSimpleChatModel; + +jest.mock('./errors'); +jest.mock('./review'); +jest.mock('./categorization'); +jest.mock('./invalid'); + +jest.mock('../../util/pipeline', () => ({ + testPipeline: jest.fn(), +})); + +describe('runCategorizationGraph', () => { + const mockClient = { + asCurrentUser: { + ingest: { + simulate: jest.fn(), + }, + }, + } as unknown as IScopedClusterClient; + beforeEach(() => { + // Mocked responses for each node that requires an LLM API call/response. + const mockInvokeCategorization = jest + .fn() + .mockResolvedValue(categorizationInitialMockedResponse); + const mockInvokeError = jest.fn().mockResolvedValue(categorizationErrorMockedResponse); + const mockInvokeInvalid = jest.fn().mockResolvedValue(categorizationInvalidMockedResponse); + const mockInvokeReview = jest.fn().mockResolvedValue(categorizationReviewMockedResponse); + + // We do not care about ES in these tests, the mock is just to prevent errors. + + // After this is triggered, the mock of TestPipeline will trigger the expected error, to route to error handler + (handleCategorization as jest.Mock).mockImplementation(async () => ({ + currentPipeline: categorizationInitialPipeline, + currentProcessors: await mockInvokeCategorization(), + reviewed: false, + finalized: false, + lastExecutedChain: 'categorization', + })); + // Error pipeline resolves it, though the responce includes an invalid categorization + (handleErrors as jest.Mock).mockImplementation(async () => ({ + currentPipeline: categorizationInitialPipeline, + currentProcessors: await mockInvokeError(), + reviewed: false, + finalized: false, + lastExecutedChain: 'error', + })); + // Invalid categorization is resolved and returned correctly, which routes it to a review + (handleInvalidCategorization as jest.Mock).mockImplementation(async () => ({ + currentPipeline: categorizationInitialPipeline, + currentProcessors: await mockInvokeInvalid(), + reviewed: false, + finalized: false, + lastExecutedChain: 'invalidCategorization', + })); + // After the review it should route to modelOutput and finish. + (handleReview as jest.Mock).mockImplementation(async () => { + const currentProcessors = await mockInvokeReview(); + const currentPipeline = combineProcessors(categorizationInitialPipeline, currentProcessors); + return { + currentProcessors, + currentPipeline, + reviewed: true, + finalized: false, + lastExecutedChain: 'review', + }; + }); + }); + + it('Ensures that the graph compiles', async () => { + try { + await getCategorizationGraph(mockClient, mockLlm); + } catch (error) { + // noop + } + }); + + it('Runs the whole graph, with mocked outputs from the LLM.', async () => { + const categorizationGraph = await getCategorizationGraph(mockClient, mockLlm); + + (testPipeline as jest.Mock) + .mockResolvedValueOnce(testPipelineValidResult) + .mockResolvedValueOnce(testPipelineError) + .mockResolvedValueOnce(testPipelineInvalidEcs) + .mockResolvedValueOnce(testPipelineValidResult) + .mockResolvedValueOnce(testPipelineValidResult) + .mockResolvedValueOnce(testPipelineValidResult); + + let response; + try { + response = await categorizationGraph.invoke(mockedRequestWithPipeline); + } catch (e) { + // noop + } + + expect(response.results).toStrictEqual(categorizationExpectedResults); + + // Check if the functions were called + expect(handleCategorization).toHaveBeenCalled(); + expect(handleErrors).toHaveBeenCalled(); + expect(handleInvalidCategorization).toHaveBeenCalled(); + expect(handleReview).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/graph.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/graph.ts new file mode 100644 index 0000000000000..6834fcf892a9e --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/graph.ts @@ -0,0 +1,193 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; +import type { StateGraphArgs } from '@langchain/langgraph'; +import { StateGraph, END, START } from '@langchain/langgraph'; +import type { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; +import type { CategorizationState } from '../../types'; +import { modifySamples, formatSamples } from '../../util/samples'; +import { handleCategorization } from './categorization'; +import { handleValidatePipeline } from '../../util/graph'; +import { handleCategorizationValidation } from './validate'; +import { handleInvalidCategorization } from './invalid'; +import { handleErrors } from './errors'; +import { handleReview } from './review'; +import { CATEGORIZATION_EXAMPLE_ANSWER, ECS_CATEGORIES, ECS_TYPES } from './constants'; + +const graphState: StateGraphArgs['channels'] = { + lastExecutedChain: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + rawSamples: { + value: (x: string[], y?: string[]) => y ?? x, + default: () => [], + }, + samples: { + value: (x: string[], y?: string[]) => y ?? x, + default: () => [], + }, + formattedSamples: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + ecsTypes: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + ecsCategories: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + exAnswer: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + packageName: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + dataStreamName: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + finalized: { + value: (x: boolean, y?: boolean) => y ?? x, + default: () => false, + }, + reviewed: { + value: (x: boolean, y?: boolean) => y ?? x, + default: () => false, + }, + errors: { + value: (x: object, y?: object) => y ?? x, + default: () => ({}), + }, + pipelineResults: { + value: (x: object[], y?: object[]) => y ?? x, + default: () => [{}], + }, + currentPipeline: { + value: (x: object, y?: object) => y ?? x, + default: () => ({}), + }, + currentProcessors: { + value: (x: object[], y?: object[]) => y ?? x, + default: () => [], + }, + invalidCategorization: { + value: (x: object, y?: object) => y ?? x, + default: () => ({}), + }, + initialPipeline: { + value: (x: object, y?: object) => y ?? x, + default: () => ({}), + }, + results: { + value: (x: object, y?: object) => y ?? x, + default: () => ({}), + }, +}; + +function modelInput(state: CategorizationState): Partial { + const samples = modifySamples(state); + const formattedSamples = formatSamples(samples); + const initialPipeline = JSON.parse(JSON.stringify(state.currentPipeline)); + return { + exAnswer: JSON.stringify(CATEGORIZATION_EXAMPLE_ANSWER, null, 2), + ecsCategories: JSON.stringify(ECS_CATEGORIES, null, 2), + ecsTypes: JSON.stringify(ECS_TYPES, null, 2), + samples, + formattedSamples, + initialPipeline, + finalized: false, + reviewed: false, + lastExecutedChain: 'modelInput', + }; +} + +function modelOutput(state: CategorizationState): Partial { + return { + finalized: true, + lastExecutedChain: 'modelOutput', + results: { + docs: state.pipelineResults, + pipeline: state.currentPipeline, + }, + }; +} + +function validationRouter(state: CategorizationState): string { + if (Object.keys(state.currentProcessors).length === 0) { + return 'categorization'; + } + return 'validateCategorization'; +} + +function chainRouter(state: CategorizationState): string { + if (Object.keys(state.errors).length > 0) { + return 'errors'; + } + if (Object.keys(state.invalidCategorization).length > 0) { + return 'invalidCategorization'; + } + if (!state.reviewed) { + return 'review'; + } + if (!state.finalized) { + return 'modelOutput'; + } + + return END; +} + +export async function getCategorizationGraph( + client: IScopedClusterClient, + model: ActionsClientChatOpenAI | ActionsClientSimpleChatModel +) { + const workflow = new StateGraph({ + channels: graphState, + }) + .addNode('modelInput', modelInput) + .addNode('modelOutput', modelOutput) + .addNode('handleCategorization', (state: CategorizationState) => + handleCategorization(state, model) + ) + .addNode('handleValidatePipeline', (state: CategorizationState) => + handleValidatePipeline(state, client) + ) + .addNode('handleCategorizationValidation', handleCategorizationValidation) + .addNode('handleInvalidCategorization', (state: CategorizationState) => + handleInvalidCategorization(state, model) + ) + .addNode('handleErrors', (state: CategorizationState) => handleErrors(state, model)) + .addNode('handleReview', (state: CategorizationState) => handleReview(state, model)) + .addEdge(START, 'modelInput') + .addEdge('modelOutput', END) + .addEdge('modelInput', 'handleValidatePipeline') + .addEdge('handleCategorization', 'handleValidatePipeline') + .addEdge('handleInvalidCategorization', 'handleValidatePipeline') + .addEdge('handleErrors', 'handleValidatePipeline') + .addEdge('handleReview', 'handleValidatePipeline') + .addConditionalEdges('handleValidatePipeline', validationRouter, { + categorization: 'handleCategorization', + validateCategorization: 'handleCategorizationValidation', + }) + .addConditionalEdges('handleCategorizationValidation', chainRouter, { + modelOutput: 'modelOutput', + errors: 'handleErrors', + invalidCategorization: 'handleInvalidCategorization', + review: 'handleReview', + }); + + const compiledCategorizationGraph = workflow.compile(); + return compiledCategorizationGraph; +} diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/index.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/index.ts new file mode 100644 index 0000000000000..03e8cb360de67 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export { getCategorizationGraph } from './graph'; diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/invalid.test.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/invalid.test.ts new file mode 100644 index 0000000000000..10560137093d8 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/invalid.test.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 { FakeLLM } from '@langchain/core/utils/testing'; +import { handleInvalidCategorization } from './invalid'; +import type { CategorizationState } from '../../types'; +import { + categorizationTestState, + categorizationMockProcessors, + categorizationExpectedHandlerResponse, +} from '../../../__jest__/fixtures/categorization'; +import { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; + +const mockLlm = new FakeLLM({ + response: JSON.stringify(categorizationMockProcessors, null, 2), +}) as unknown as ActionsClientChatOpenAI | ActionsClientSimpleChatModel; + +const testState: CategorizationState = categorizationTestState; + +describe('Testing categorization handler', () => { + it('handleInvalidCategorization()', async () => { + const response = await handleInvalidCategorization(testState, mockLlm); + expect(response.currentPipeline).toStrictEqual( + categorizationExpectedHandlerResponse.currentPipeline + ); + expect(response.lastExecutedChain).toBe('invalidCategorization'); + }); +}); diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/invalid.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/invalid.ts new file mode 100644 index 0000000000000..413694b594518 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/invalid.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; +import { JsonOutputParser } from '@langchain/core/output_parsers'; +import type { ESProcessorItem, Pipeline } from '../../../common'; +import type { CategorizationState } from '../../types'; +import { combineProcessors } from '../../util/processors'; +import { ECS_EVENT_TYPES_PER_CATEGORY } from './constants'; +import { CATEGORIZATION_VALIDATION_PROMPT } from './prompts'; + +export async function handleInvalidCategorization( + state: CategorizationState, + model: ActionsClientChatOpenAI | ActionsClientSimpleChatModel +) { + const categorizationInvalidPrompt = CATEGORIZATION_VALIDATION_PROMPT; + + const outputParser = new JsonOutputParser(); + const categorizationInvalidGraph = categorizationInvalidPrompt.pipe(model).pipe(outputParser); + + const currentProcessors = (await categorizationInvalidGraph.invoke({ + current_processors: JSON.stringify(state.currentProcessors, null, 2), + invalid_categorization: JSON.stringify(state.invalidCategorization, null, 2), + ex_answer: state.exAnswer, + compatible_types: JSON.stringify(ECS_EVENT_TYPES_PER_CATEGORY, null, 2), + })) as ESProcessorItem[]; + + const currentPipeline = combineProcessors(state.initialPipeline as Pipeline, currentProcessors); + + return { + currentPipeline, + currentProcessors, + reviewed: false, + lastExecutedChain: 'invalidCategorization', + }; +} diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/prompts.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/prompts.ts new file mode 100644 index 0000000000000..a03f373499aea --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/prompts.ts @@ -0,0 +1,204 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ChatPromptTemplate } from '@langchain/core/prompts'; + +export const CATEGORIZATION_MAIN_PROMPT = ChatPromptTemplate.fromMessages([ + [ + 'system', + `You are a helpful, expert assistant on Elasticsearch Ingest Pipelines, focusing on providing append processors that can be used to enrich samples with all relevant event.type and event.category values. +Here are some context for you to reference for your task, read it carefully as you will get questions about it later: + + +Event Category (event.category): +Purpose: It is the second level in the ECS category hierarchy, representing the primary category or "big bucket" for event classification. +Type: It's a keyword type and can have multiple values (list). +Relationship: Works alongside event.type, which acts as a subcategory. +Allowed categories and their descriptions: +{ecs_categories} + +Event Type (event.type): +Purpose: It is the third level in the ECS category hierarchy, represents a categorization "sub-bucket". +Type: It's a keyword type and can have multiple values (list). +Relationship: Works alongside event.category, which acts as a subcategory. +Allowed types and their descriptions: +{ecs_types} + +`, + ], + [ + 'human', + `Please help me by providing all relevant append processors for any detected event.category and event.type combinations that would fit the below pipeline results as an array of JSON objects. + + +{pipeline_results} + + +Go through each of the pipeline results above step by step and do the following to add all relevant event.type and event.category combinations. +1. Try to understand what is unique about each pipeline result, and what sort of event.categorization and event.type combinations that fit best, and if there is any unique values for each result. +2. For for each combination of event.category and event.type that you find, add a new append processor to your array of JSON objects. +3. If only certain results are relevant to the event.category and event.type combination, add an if condition similar to the above example processors, that describes what value or field needs to be available for this categorization to take place. The if condition should be inside the processor object. +4. Always check if the combination of event.category and event.type is common in the ecs context above. +5. Always make sure the value for event.category and event.type is strictly from the allowed categories and allowed types in the ecs context above. +6. The value argument for the append processor is an array of one or more types and categories. + +You ALWAYS follow these guidelines when writing your response: + +- You can add as many append processors you need to cover all the unique combinations that you detected. +- If conditions should always use a ? character when accessing nested fields, in case the field might not always be available, see example processors above. +- When an if condition is not needed the argument should not be used for the processor object. +- When using a range based if condition like > 0, you first need to check that the field is not null, for example: ctx.somefield?.production != null && ctx.somefield?.production > 0 +- Do not respond with anything except the array of processors as a valid JSON objects enclosed with 3 backticks (\`), see example response below. + + +Example response format: + +A: Please find the Categorization processors below: +\`\`\`json +{ex_answer} +\`\`\` +`, + ], + ['ai', 'Please find the Categorization processors below:'], +]); + +export const CATEGORIZATION_REVIEW_PROMPT = ChatPromptTemplate.fromMessages([ + [ + 'system', + `You are a helpful, expert assistant on Elasticsearch Ingest Pipelines, focusing on adding improvements to the provided array of processors and reviewing the current results. + +Here is some context that you can reference for your task, read it carefully as you will get questions about it later: + + +{current_processors} + + +{compatibility_matrix} + +`, + ], + [ + 'human', + `Testing my current pipeline returned me with the results: + +{pipeline_results} + + +Please review the pipeline results and the array of current processors, ensuring to identify all the possible event.type and event.category combinatinations that would match each pipeline result document. If any event.type or event.category is missing from any of the pipeline results, add them by updating the array of current processors and return the whole updated array of processors. + +For each pipeline result you review step by step, remember the below steps: +1. Check if each of the pipeline results have at least one event.category and event.type added to them. If not then try to correlate the results with the current processors and see if either a new append processor should be added to the list with a matching if condition, or if any of the if conditions should be modified as they are not matching that is in the results. +2. If the results have at least one event.category and event.type value, see if more of them could match, if so it could be added to the relevant append processor which added the initial values. +3. When adding more values to event.type and event.category please keep in mind the compatibility_matrix in the context to make sure only compatible event.type , event.category pairs that are compatible are created. +4. Ensure that all append processors has allow_duplicates: false, as seen in the example response. + +You ALWAYS follow these guidelines when writing your response: + +- You can use as many append processors as you need to add all relevant ECS categories and types combinations. +- If conditions should always use a ? character when accessing nested fields, in case the field might not always be available, see example processors above. +- When an if condition is not needed the argument should not be used for the processor object. +- If not updates are needed you respond with the initially provided current processors. +- Each append processor needs to have the allow_duplicates: false argument, as shown in the below example response. +- Do not respond with anything except updated array of processors as a valid JSON object enclosed with 3 backticks (\`), see example response below. + + +Example response format: + +A: Please find the updated ECS categorization append processors below: +\`\`\` +{ex_answer} +\`\`\` +`, + ], + ['ai', 'Please find the updated ECS categorization append processors below:'], +]); + +export const CATEGORIZATION_VALIDATION_PROMPT = ChatPromptTemplate.fromMessages([ + [ + 'system', + `You are a helpful, expert assistant on Elasticsearch Ingest Pipelines, focusing on resolving errors and issues with append processors used for categorization. +Here is some context that you can reference for your task, read it carefully as you will get questions about it later: + + +{current_processors} + + +{compatible_types} + + +{invalid_categorization} + +`, + ], + [ + 'human', + `Please go through each error above, carefully review the provided current processors, and resolve the most likely cause to the supplied error by returning an updated version of the current_processors. + +Follow these steps to help resolve the current ingest pipeline issues: +1. Try to fix all related errors before responding. +2. Apply all fixes to the provided array of current append processors. +3. If you do not know how to fix an error, then continue to the next and return the complete updated array of current append processors. + +You ALWAYS follow these guidelines when writing your response: + +- If the error complains about having event.type or event.category not in the allowed values , fix the corresponding append processors to use the allowed values mentioned in the error. +- If the error is about event.type not compatible with any event.category, please refer to the 'compatible_types' in the context to fix the corresponding append processors to use valid combination of event.type and event.category +- Do not respond with anything except the complete updated array of processors as a valid JSON object enclosed with 3 backticks (\`), see example response below. + + +Example response format: + +A: Please find the updated ECS categorization append processors below: +\`\`\`json +{ex_answer} +\`\`\` +`, + ], + ['ai', 'Please find the updated ECS categorization append processors below:'], +]); + +export const CATEGORIZATION_ERROR_PROMPT = ChatPromptTemplate.fromMessages([ + [ + 'system', + `You are a helpful, expert assistant on Elasticsearch Ingest Pipelines, focusing on resolving errors and issues with append processors used for categorization. +Here is some context that you can reference for your task, read it carefully as you will get questions about it later: + + +{current_processors} + + +{errors} + +`, + ], + [ + 'human', + `Please go through each error above, carefully review the provided current processors, and resolve the most likely cause to the supplied error by returning an updated version of the current_processors. + +Follow these steps to help resolve the current ingest pipeline issues: +1. Try to fix all related errors before responding. +2. Apply all fixes to the provided array of current append processors. +3. If you do not know how to fix an error, then continue to the next and return the complete updated array of current append processors. + +You ALWAYS follow these guidelines when writing your response: + +- When checking for the existance of multiple values in a single variable, use this format: "if": "['value1', 'value2'].contains(ctx.{package_name}?.{data_stream_name}?.field)" +- If conditions should never be in a format like "if": "true". If it exist in the current array of append processors, remove only the redundant if condition. +- If the error complains that it is a null point exception, always ensure the if conditions uses a ? when accessing nested fields. For example ctx.field1?.nestedfield1?.nestedfield2. +- If the error complains about having values not in the list of allowed values , fix the corresponding append processors to use the allowed values as mentioned in the error. +- Do not respond with anything except the complete updated array of processors as a valid JSON object enclosed with 3 backticks (\`), see example response below. + + +Example response format: + +A: Please find the updated ECS categorization append processors below: +\`\`\`json +{ex_answer} +\`\`\` +`, + ], + ['ai', 'Please find the updated ECS categorization append processors below:'], +]); diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/review.test.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/review.test.ts new file mode 100644 index 0000000000000..7775b69c5b6a8 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/review.test.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 { FakeLLM } from '@langchain/core/utils/testing'; +import { handleReview } from './review'; +import type { CategorizationState } from '../../types'; +import { + categorizationTestState, + categorizationMockProcessors, + categorizationExpectedHandlerResponse, +} from '../../../__jest__/fixtures/categorization'; +import { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; + +const mockLlm = new FakeLLM({ + response: JSON.stringify(categorizationMockProcessors, null, 2), +}) as unknown as ActionsClientChatOpenAI | ActionsClientSimpleChatModel; + +const testState: CategorizationState = categorizationTestState; + +describe('Testing categorization handler', () => { + it('handleReview()', async () => { + const response = await handleReview(testState, mockLlm); + expect(response.currentPipeline).toStrictEqual( + categorizationExpectedHandlerResponse.currentPipeline + ); + expect(response.lastExecutedChain).toBe('review'); + }); +}); diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/review.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/review.ts new file mode 100644 index 0000000000000..12b3880737237 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/review.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; +import { JsonOutputParser } from '@langchain/core/output_parsers'; +import { CATEGORIZATION_REVIEW_PROMPT } from './prompts'; + +import type { ESProcessorItem, Pipeline } from '../../../common'; +import type { CategorizationState } from '../../types'; +import { combineProcessors } from '../../util/processors'; +import { ECS_EVENT_TYPES_PER_CATEGORY } from './constants'; + +export async function handleReview( + state: CategorizationState, + model: ActionsClientChatOpenAI | ActionsClientSimpleChatModel +) { + const categorizationReviewPrompt = CATEGORIZATION_REVIEW_PROMPT; + const outputParser = new JsonOutputParser(); + const categorizationReview = categorizationReviewPrompt.pipe(model).pipe(outputParser); + + const currentProcessors = (await categorizationReview.invoke({ + current_processors: JSON.stringify(state.currentProcessors, null, 2), + pipeline_results: JSON.stringify(state.pipelineResults, null, 2), + ex_answer: state?.exAnswer, + package_name: state?.packageName, + compatibility_matrix: JSON.stringify(ECS_EVENT_TYPES_PER_CATEGORY, null, 2), + })) as ESProcessorItem[]; + + const currentPipeline = combineProcessors(state.initialPipeline as Pipeline, currentProcessors); + + return { + currentPipeline, + currentProcessors, + reviewed: true, + lastExecutedChain: 'review', + }; +} diff --git a/x-pack/plugins/integration_assistant/server/graphs/categorization/validate.ts b/x-pack/plugins/integration_assistant/server/graphs/categorization/validate.ts new file mode 100644 index 0000000000000..40e37cd3b0363 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/categorization/validate.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 type { CategorizationState } from '../../types'; +import { ECS_EVENT_TYPES_PER_CATEGORY, EVENT_CATEGORIES, EVENT_TYPES } from './constants'; + +import type { EventCategories } from './constants'; + +interface Event { + type?: string[]; + category?: string[]; +} + +interface PipelineResult { + event?: Event; +} + +interface CategorizationError { + error: string; +} + +export function handleCategorizationValidation(state: CategorizationState): { + invalidCategorization: CategorizationError[]; + lastExecutedChain: string; +} { + const errors: CategorizationError[] = []; + const pipelineResults = state.pipelineResults as PipelineResult[]; + + // Loops through the pipeline results to find invalid categories and types + for (const doc of pipelineResults) { + let types: string[] = []; + let categories: string[] = []; + if (doc?.event?.type) { + types = doc.event.type; + } + if (doc?.event?.category) { + categories = doc.event.category; + } + + const invalidCategories = findInvalidCategories(categories); + const invalidTypes = findInvalidTypes(types); + + if (invalidCategories.length > 0) { + errors.push(createErrorMessage('event.category', invalidCategories, EVENT_CATEGORIES)); + } + + if (invalidTypes.length > 0) { + errors.push(createErrorMessage('event.type', invalidTypes, EVENT_TYPES)); + } + + // Compatibility check is done only on valid categories and types + const validCategories = categories.filter((x) => !invalidCategories.includes(x)); + const validTypes = types.filter((x) => !invalidTypes.includes(x)); + + const compatibleErrors = getTypeCategoryIncompatibleError(validCategories, validTypes); + for (const ce of compatibleErrors) { + errors.push(ce); + } + } + + return { + invalidCategorization: errors, + lastExecutedChain: 'handleCategorizationValidation', + }; +} + +function createErrorMessage( + field: string, + errorList: string[], + allowedValues: string[] +): CategorizationError { + return { + error: `field ${field}'s values (${errorList.join( + ', ' + )}) is not one of the allowed values (${allowedValues.join(', ')})`, + }; +} + +function findInvalidCategories(categories: string[]): string[] { + const invalidCategories: string[] = []; + for (const c of categories) { + if (!EVENT_CATEGORIES.includes(c)) { + invalidCategories.push(c); + } + } + return invalidCategories; +} + +function findInvalidTypes(types: string[]): string[] { + const invalidTypes: string[] = []; + for (const t of types) { + if (!EVENT_TYPES.includes(t)) { + invalidTypes.push(t); + } + } + return invalidTypes; +} + +function getTypeCategoryIncompatibleError( + categories: string[], + types: string[] +): CategorizationError[] { + const errors: CategorizationError[] = []; + let unmatchedTypes = new Set(types); + const matchCategories = new Set(categories); + let categoryExists = false; + + for (const c of matchCategories) { + if (c in ECS_EVENT_TYPES_PER_CATEGORY) { + categoryExists = true; + const matchTypes = new Set(ECS_EVENT_TYPES_PER_CATEGORY[c as EventCategories]); + unmatchedTypes = new Set([...unmatchedTypes].filter((x) => !matchTypes.has(x))); + } + } + + if (categoryExists && unmatchedTypes.size > 0) { + errors.push({ + error: `event.type (${[...unmatchedTypes].join( + ', ' + )}) not compatible with any of the event.category (${[...matchCategories].join(', ')})`, + }); + } + + return errors; +} diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/constants.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/constants.ts new file mode 100644 index 0000000000000..607655541ca9d --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/constants.ts @@ -0,0 +1,1930 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +interface EcsFields { + [key: string]: string; +} + +export const ECS_TYPES: EcsFields = { + '@timestamp': 'date', + 'agent.build.original': 'keyword', + 'agent.ephemeral_id': 'keyword', + 'agent.id': 'keyword', + 'agent.name': 'keyword', + 'agent.type': 'keyword', + 'agent.version': 'keyword', + 'client.address': 'keyword', + 'client.as.number': 'long', + 'client.as.organization.name': 'keyword', + 'client.bytes': 'long', + 'client.domain': 'keyword', + 'client.geo.city_name': 'keyword', + 'client.geo.continent_code': 'keyword', + 'client.geo.continent_name': 'keyword', + 'client.geo.country_iso_code': 'keyword', + 'client.geo.country_name': 'keyword', + 'client.geo.location': 'geo_point', + 'client.geo.name': 'keyword', + 'client.geo.postal_code': 'keyword', + 'client.geo.region_iso_code': 'keyword', + 'client.geo.region_name': 'keyword', + 'client.geo.timezone': 'keyword', + 'client.ip': 'ip', + 'client.mac': 'keyword', + 'client.nat.ip': 'ip', + 'client.nat.port': 'long', + 'client.packets': 'long', + 'client.port': 'long', + 'client.registered_domain': 'keyword', + 'client.subdomain': 'keyword', + 'client.top_level_domain': 'keyword', + 'client.user.domain': 'keyword', + 'client.user.email': 'keyword', + 'client.user.full_name': 'keyword', + 'client.user.group.domain': 'keyword', + 'client.user.group.id': 'keyword', + 'client.user.group.name': 'keyword', + 'client.user.hash': 'keyword', + 'client.user.id': 'keyword', + 'client.user.name': 'keyword', + 'client.user.roles': 'keyword', + 'cloud.account.id': 'keyword', + 'cloud.account.name': 'keyword', + 'cloud.availability_zone': 'keyword', + 'cloud.instance.id': 'keyword', + 'cloud.instance.name': 'keyword', + 'cloud.machine.type': 'keyword', + 'cloud.origin.account.id': 'keyword', + 'cloud.origin.account.name': 'keyword', + 'cloud.origin.availability_zone': 'keyword', + 'cloud.origin.instance.id': 'keyword', + 'cloud.origin.instance.name': 'keyword', + 'cloud.origin.machine.type': 'keyword', + 'cloud.origin.project.id': 'keyword', + 'cloud.origin.project.name': 'keyword', + 'cloud.origin.provider': 'keyword', + 'cloud.origin.region': 'keyword', + 'cloud.origin.service.name': 'keyword', + 'cloud.project.id': 'keyword', + 'cloud.project.name': 'keyword', + 'cloud.provider': 'keyword', + 'cloud.region': 'keyword', + 'cloud.service.name': 'keyword', + 'cloud.target.account.id': 'keyword', + 'cloud.target.account.name': 'keyword', + 'cloud.target.availability_zone': 'keyword', + 'cloud.target.instance.id': 'keyword', + 'cloud.target.instance.name': 'keyword', + 'cloud.target.machine.type': 'keyword', + 'cloud.target.project.id': 'keyword', + 'cloud.target.project.name': 'keyword', + 'cloud.target.provider': 'keyword', + 'cloud.target.region': 'keyword', + 'cloud.target.service.name': 'keyword', + 'container.cpu.usage': 'scaled_float', + 'container.disk.read.bytes': 'long', + 'container.disk.write.bytes': 'long', + 'container.id': 'keyword', + 'container.image.hash.all': 'keyword', + 'container.image.name': 'keyword', + 'container.image.tag': 'keyword', + 'container.labels': 'object', + 'container.memory.usage': 'scaled_float', + 'container.name': 'keyword', + 'container.network.egress.bytes': 'long', + 'container.network.ingress.bytes': 'long', + 'container.runtime': 'keyword', + 'container.security_context.privileged': 'boolean', + 'data_stream.dataset': 'constant_keyword', + 'data_stream.namespace': 'constant_keyword', + 'data_stream.type': 'constant_keyword', + 'destination.address': 'keyword', + 'destination.as.number': 'long', + 'destination.as.organization.name': 'keyword', + 'destination.bytes': 'long', + 'destination.domain': 'keyword', + 'destination.geo.city_name': 'keyword', + 'destination.geo.continent_code': 'keyword', + 'destination.geo.continent_name': 'keyword', + 'destination.geo.country_iso_code': 'keyword', + 'destination.geo.country_name': 'keyword', + 'destination.geo.location': 'geo_point', + 'destination.geo.name': 'keyword', + 'destination.geo.postal_code': 'keyword', + 'destination.geo.region_iso_code': 'keyword', + 'destination.geo.region_name': 'keyword', + 'destination.geo.timezone': 'keyword', + 'destination.ip': 'ip', + 'destination.mac': 'keyword', + 'destination.nat.ip': 'ip', + 'destination.nat.port': 'long', + 'destination.packets': 'long', + 'destination.port': 'long', + 'destination.registered_domain': 'keyword', + 'destination.subdomain': 'keyword', + 'destination.top_level_domain': 'keyword', + 'destination.user.domain': 'keyword', + 'destination.user.email': 'keyword', + 'destination.user.full_name': 'keyword', + 'destination.user.group.domain': 'keyword', + 'destination.user.group.id': 'keyword', + 'destination.user.group.name': 'keyword', + 'destination.user.hash': 'keyword', + 'destination.user.id': 'keyword', + 'destination.user.name': 'keyword', + 'destination.user.roles': 'keyword', + 'device.id': 'keyword', + 'device.manufacturer': 'keyword', + 'device.model.identifier': 'keyword', + 'device.model.name': 'keyword', + 'dll.code_signature.digest_algorithm': 'keyword', + 'dll.code_signature.exists': 'boolean', + 'dll.code_signature.signing_id': 'keyword', + 'dll.code_signature.status': 'keyword', + 'dll.code_signature.subject_name': 'keyword', + 'dll.code_signature.team_id': 'keyword', + 'dll.code_signature.timestamp': 'date', + 'dll.code_signature.trusted': 'boolean', + 'dll.code_signature.valid': 'boolean', + 'dll.hash.md5': 'keyword', + 'dll.hash.sha1': 'keyword', + 'dll.hash.sha256': 'keyword', + 'dll.hash.sha384': 'keyword', + 'dll.hash.sha512': 'keyword', + 'dll.hash.ssdeep': 'keyword', + 'dll.hash.tlsh': 'keyword', + 'dll.name': 'keyword', + 'dll.path': 'keyword', + 'dll.pe.architecture': 'keyword', + 'dll.pe.company': 'keyword', + 'dll.pe.description': 'keyword', + 'dll.pe.file_version': 'keyword', + 'dll.pe.go_import_hash': 'keyword', + 'dll.pe.go_imports': 'flattened', + 'dll.pe.go_imports_names_entropy': 'long', + 'dll.pe.go_imports_names_var_entropy': 'long', + 'dll.pe.go_stripped': 'boolean', + 'dll.pe.imphash': 'keyword', + 'dll.pe.import_hash': 'keyword', + 'dll.pe.imports': 'flattened', + 'dll.pe.imports_names_entropy': 'long', + 'dll.pe.imports_names_var_entropy': 'long', + 'dll.pe.original_file_name': 'keyword', + 'dll.pe.pehash': 'keyword', + 'dll.pe.product': 'keyword', + 'dll.pe.sections': 'nested', + 'dll.pe.sections.entropy': 'long', + 'dll.pe.sections.name': 'keyword', + 'dll.pe.sections.physical_size': 'long', + 'dll.pe.sections.var_entropy': 'long', + 'dll.pe.sections.virtual_size': 'long', + 'dns.answers': 'object', + 'dns.answers.class': 'keyword', + 'dns.answers.data': 'keyword', + 'dns.answers.name': 'keyword', + 'dns.answers.ttl': 'long', + 'dns.answers.type': 'keyword', + 'dns.header_flags': 'keyword', + 'dns.id': 'keyword', + 'dns.op_code': 'keyword', + 'dns.question.class': 'keyword', + 'dns.question.name': 'keyword', + 'dns.question.registered_domain': 'keyword', + 'dns.question.subdomain': 'keyword', + 'dns.question.top_level_domain': 'keyword', + 'dns.question.type': 'keyword', + 'dns.resolved_ip': 'ip', + 'dns.response_code': 'keyword', + 'dns.type': 'keyword', + 'ecs.version': 'keyword', + 'email.attachments': 'nested', + 'email.attachments.file.extension': 'keyword', + 'email.attachments.file.hash.md5': 'keyword', + 'email.attachments.file.hash.sha1': 'keyword', + 'email.attachments.file.hash.sha256': 'keyword', + 'email.attachments.file.hash.sha384': 'keyword', + 'email.attachments.file.hash.sha512': 'keyword', + 'email.attachments.file.hash.ssdeep': 'keyword', + 'email.attachments.file.hash.tlsh': 'keyword', + 'email.attachments.file.mime_type': 'keyword', + 'email.attachments.file.name': 'keyword', + 'email.attachments.file.size': 'long', + 'email.bcc.address': 'keyword', + 'email.cc.address': 'keyword', + 'email.content_type': 'keyword', + 'email.delivery_timestamp': 'date', + 'email.direction': 'keyword', + 'email.from.address': 'keyword', + 'email.local_id': 'keyword', + 'email.message_id': 'wildcard', + 'email.origination_timestamp': 'date', + 'email.reply_to.address': 'keyword', + 'email.sender.address': 'keyword', + 'email.subject': 'keyword', + 'email.to.address': 'keyword', + 'email.x_mailer': 'keyword', + 'error.code': 'keyword', + 'error.id': 'keyword', + 'error.message': 'match_only_text', + 'error.stack_trace': 'wildcard', + 'error.type': 'keyword', + 'event.action': 'keyword', + 'event.agent_id_status': 'keyword', + 'event.category': 'keyword', + 'event.code': 'keyword', + 'event.created': 'date', + 'event.dataset': 'keyword', + 'event.duration': 'long', + 'event.end': 'date', + 'event.hash': 'keyword', + 'event.id': 'keyword', + 'event.ingested': 'date', + 'event.kind': 'keyword', + 'event.module': 'keyword', + 'event.original': 'keyword', + 'event.outcome': 'keyword', + 'event.provider': 'keyword', + 'event.reason': 'keyword', + 'event.reference': 'keyword', + 'event.risk_score': 'float', + 'event.risk_score_norm': 'float', + 'event.sequence': 'long', + 'event.severity': 'long', + 'event.start': 'date', + 'event.timezone': 'keyword', + 'event.type': 'keyword', + 'event.url': 'keyword', + 'faas.coldstart': 'boolean', + 'faas.execution': 'keyword', + 'faas.id': 'keyword', + 'faas.name': 'keyword', + 'faas.trigger.request_id': 'keyword', + 'faas.trigger.type': 'keyword', + 'faas.version': 'keyword', + 'file.accessed': 'date', + 'file.attributes': 'keyword', + 'file.code_signature.digest_algorithm': 'keyword', + 'file.code_signature.exists': 'boolean', + 'file.code_signature.signing_id': 'keyword', + 'file.code_signature.status': 'keyword', + 'file.code_signature.subject_name': 'keyword', + 'file.code_signature.team_id': 'keyword', + 'file.code_signature.timestamp': 'date', + 'file.code_signature.trusted': 'boolean', + 'file.code_signature.valid': 'boolean', + 'file.created': 'date', + 'file.ctime': 'date', + 'file.device': 'keyword', + 'file.directory': 'keyword', + 'file.drive_letter': 'keyword', + 'file.elf.architecture': 'keyword', + 'file.elf.byte_order': 'keyword', + 'file.elf.cpu_type': 'keyword', + 'file.elf.creation_date': 'date', + 'file.elf.exports': 'flattened', + 'file.elf.go_import_hash': 'keyword', + 'file.elf.go_imports': 'flattened', + 'file.elf.go_imports_names_entropy': 'long', + 'file.elf.go_imports_names_var_entropy': 'long', + 'file.elf.go_stripped': 'boolean', + 'file.elf.header.abi_version': 'keyword', + 'file.elf.header.class': 'keyword', + 'file.elf.header.data': 'keyword', + 'file.elf.header.entrypoint': 'long', + 'file.elf.header.object_version': 'keyword', + 'file.elf.header.os_abi': 'keyword', + 'file.elf.header.type': 'keyword', + 'file.elf.header.version': 'keyword', + 'file.elf.import_hash': 'keyword', + 'file.elf.imports': 'flattened', + 'file.elf.imports_names_entropy': 'long', + 'file.elf.imports_names_var_entropy': 'long', + 'file.elf.sections': 'nested', + 'file.elf.sections.chi2': 'long', + 'file.elf.sections.entropy': 'long', + 'file.elf.sections.flags': 'keyword', + 'file.elf.sections.name': 'keyword', + 'file.elf.sections.physical_offset': 'keyword', + 'file.elf.sections.physical_size': 'long', + 'file.elf.sections.type': 'keyword', + 'file.elf.sections.var_entropy': 'long', + 'file.elf.sections.virtual_address': 'long', + 'file.elf.sections.virtual_size': 'long', + 'file.elf.segments': 'nested', + 'file.elf.segments.sections': 'keyword', + 'file.elf.segments.type': 'keyword', + 'file.elf.shared_libraries': 'keyword', + 'file.elf.telfhash': 'keyword', + 'file.extension': 'keyword', + 'file.fork_name': 'keyword', + 'file.gid': 'keyword', + 'file.group': 'keyword', + 'file.hash.md5': 'keyword', + 'file.hash.sha1': 'keyword', + 'file.hash.sha256': 'keyword', + 'file.hash.sha384': 'keyword', + 'file.hash.sha512': 'keyword', + 'file.hash.ssdeep': 'keyword', + 'file.hash.tlsh': 'keyword', + 'file.inode': 'keyword', + 'file.macho.go_import_hash': 'keyword', + 'file.macho.go_imports': 'flattened', + 'file.macho.go_imports_names_entropy': 'long', + 'file.macho.go_imports_names_var_entropy': 'long', + 'file.macho.go_stripped': 'boolean', + 'file.macho.import_hash': 'keyword', + 'file.macho.imports': 'flattened', + 'file.macho.imports_names_entropy': 'long', + 'file.macho.imports_names_var_entropy': 'long', + 'file.macho.sections': 'nested', + 'file.macho.sections.entropy': 'long', + 'file.macho.sections.name': 'keyword', + 'file.macho.sections.physical_size': 'long', + 'file.macho.sections.var_entropy': 'long', + 'file.macho.sections.virtual_size': 'long', + 'file.macho.symhash': 'keyword', + 'file.mime_type': 'keyword', + 'file.mode': 'keyword', + 'file.mtime': 'date', + 'file.name': 'keyword', + 'file.owner': 'keyword', + 'file.path': 'keyword', + 'file.pe.architecture': 'keyword', + 'file.pe.company': 'keyword', + 'file.pe.description': 'keyword', + 'file.pe.file_version': 'keyword', + 'file.pe.go_import_hash': 'keyword', + 'file.pe.go_imports': 'flattened', + 'file.pe.go_imports_names_entropy': 'long', + 'file.pe.go_imports_names_var_entropy': 'long', + 'file.pe.go_stripped': 'boolean', + 'file.pe.imphash': 'keyword', + 'file.pe.import_hash': 'keyword', + 'file.pe.imports': 'flattened', + 'file.pe.imports_names_entropy': 'long', + 'file.pe.imports_names_var_entropy': 'long', + 'file.pe.original_file_name': 'keyword', + 'file.pe.pehash': 'keyword', + 'file.pe.product': 'keyword', + 'file.pe.sections': 'nested', + 'file.pe.sections.entropy': 'long', + 'file.pe.sections.name': 'keyword', + 'file.pe.sections.physical_size': 'long', + 'file.pe.sections.var_entropy': 'long', + 'file.pe.sections.virtual_size': 'long', + 'file.size': 'long', + 'file.target_path': 'keyword', + 'file.type': 'keyword', + 'file.uid': 'keyword', + 'file.x509.alternative_names': 'keyword', + 'file.x509.issuer.common_name': 'keyword', + 'file.x509.issuer.country': 'keyword', + 'file.x509.issuer.distinguished_name': 'keyword', + 'file.x509.issuer.locality': 'keyword', + 'file.x509.issuer.organization': 'keyword', + 'file.x509.issuer.organizational_unit': 'keyword', + 'file.x509.issuer.state_or_province': 'keyword', + 'file.x509.not_after': 'date', + 'file.x509.not_before': 'date', + 'file.x509.public_key_algorithm': 'keyword', + 'file.x509.public_key_curve': 'keyword', + 'file.x509.public_key_exponent': 'long', + 'file.x509.public_key_size': 'long', + 'file.x509.serial_number': 'keyword', + 'file.x509.signature_algorithm': 'keyword', + 'file.x509.subject.common_name': 'keyword', + 'file.x509.subject.country': 'keyword', + 'file.x509.subject.distinguished_name': 'keyword', + 'file.x509.subject.locality': 'keyword', + 'file.x509.subject.organization': 'keyword', + 'file.x509.subject.organizational_unit': 'keyword', + 'file.x509.subject.state_or_province': 'keyword', + 'file.x509.version_number': 'keyword', + 'group.domain': 'keyword', + 'group.id': 'keyword', + 'group.name': 'keyword', + 'host.architecture': 'keyword', + 'host.boot.id': 'keyword', + 'host.cpu.usage': 'scaled_float', + 'host.disk.read.bytes': 'long', + 'host.disk.write.bytes': 'long', + 'host.domain': 'keyword', + 'host.geo.city_name': 'keyword', + 'host.geo.continent_code': 'keyword', + 'host.geo.continent_name': 'keyword', + 'host.geo.country_iso_code': 'keyword', + 'host.geo.country_name': 'keyword', + 'host.geo.location': 'geo_point', + 'host.geo.name': 'keyword', + 'host.geo.postal_code': 'keyword', + 'host.geo.region_iso_code': 'keyword', + 'host.geo.region_name': 'keyword', + 'host.geo.timezone': 'keyword', + 'host.hostname': 'keyword', + 'host.id': 'keyword', + 'host.ip': 'ip', + 'host.mac': 'keyword', + 'host.name': 'keyword', + 'host.network.egress.bytes': 'long', + 'host.network.egress.packets': 'long', + 'host.network.ingress.bytes': 'long', + 'host.network.ingress.packets': 'long', + 'host.os.family': 'keyword', + 'host.os.full': 'keyword', + 'host.os.kernel': 'keyword', + 'host.os.name': 'keyword', + 'host.os.platform': 'keyword', + 'host.os.type': 'keyword', + 'host.os.version': 'keyword', + 'host.pid_ns_ino': 'keyword', + 'host.risk.calculated_level': 'keyword', + 'host.risk.calculated_score': 'float', + 'host.risk.calculated_score_norm': 'float', + 'host.risk.static_level': 'keyword', + 'host.risk.static_score': 'float', + 'host.risk.static_score_norm': 'float', + 'host.type': 'keyword', + 'host.uptime': 'long', + 'http.request.body.bytes': 'long', + 'http.request.body.content': 'wildcard', + 'http.request.bytes': 'long', + 'http.request.id': 'keyword', + 'http.request.method': 'keyword', + 'http.request.mime_type': 'keyword', + 'http.request.referrer': 'keyword', + 'http.response.body.bytes': 'long', + 'http.response.body.content': 'wildcard', + 'http.response.bytes': 'long', + 'http.response.mime_type': 'keyword', + 'http.response.status_code': 'long', + 'http.version': 'keyword', + labels: 'object', + 'log.file.path': 'keyword', + 'log.level': 'keyword', + 'log.logger': 'keyword', + 'log.origin.file.line': 'long', + 'log.origin.file.name': 'keyword', + 'log.origin.function': 'keyword', + 'log.syslog': 'object', + 'log.syslog.appname': 'keyword', + 'log.syslog.facility.code': 'long', + 'log.syslog.facility.name': 'keyword', + 'log.syslog.hostname': 'keyword', + 'log.syslog.msgid': 'keyword', + 'log.syslog.priority': 'long', + 'log.syslog.procid': 'keyword', + 'log.syslog.severity.code': 'long', + 'log.syslog.severity.name': 'keyword', + 'log.syslog.structured_data': 'flattened', + 'log.syslog.version': 'keyword', + message: 'match_only_text', + 'network.application': 'keyword', + 'network.bytes': 'long', + 'network.community_id': 'keyword', + 'network.direction': 'keyword', + 'network.forwarded_ip': 'ip', + 'network.iana_number': 'keyword', + 'network.inner': 'object', + 'network.inner.vlan.id': 'keyword', + 'network.inner.vlan.name': 'keyword', + 'network.name': 'keyword', + 'network.packets': 'long', + 'network.protocol': 'keyword', + 'network.transport': 'keyword', + 'network.type': 'keyword', + 'network.vlan.id': 'keyword', + 'network.vlan.name': 'keyword', + 'observer.egress': 'object', + 'observer.egress.interface.alias': 'keyword', + 'observer.egress.interface.id': 'keyword', + 'observer.egress.interface.name': 'keyword', + 'observer.egress.vlan.id': 'keyword', + 'observer.egress.vlan.name': 'keyword', + 'observer.egress.zone': 'keyword', + 'observer.geo.city_name': 'keyword', + 'observer.geo.continent_code': 'keyword', + 'observer.geo.continent_name': 'keyword', + 'observer.geo.country_iso_code': 'keyword', + 'observer.geo.country_name': 'keyword', + 'observer.geo.location': 'geo_point', + 'observer.geo.name': 'keyword', + 'observer.geo.postal_code': 'keyword', + 'observer.geo.region_iso_code': 'keyword', + 'observer.geo.region_name': 'keyword', + 'observer.geo.timezone': 'keyword', + 'observer.hostname': 'keyword', + 'observer.ingress': 'object', + 'observer.ingress.interface.alias': 'keyword', + 'observer.ingress.interface.id': 'keyword', + 'observer.ingress.interface.name': 'keyword', + 'observer.ingress.vlan.id': 'keyword', + 'observer.ingress.vlan.name': 'keyword', + 'observer.ingress.zone': 'keyword', + 'observer.ip': 'ip', + 'observer.mac': 'keyword', + 'observer.name': 'keyword', + 'observer.os.family': 'keyword', + 'observer.os.full': 'keyword', + 'observer.os.kernel': 'keyword', + 'observer.os.name': 'keyword', + 'observer.os.platform': 'keyword', + 'observer.os.type': 'keyword', + 'observer.os.version': 'keyword', + 'observer.product': 'keyword', + 'observer.serial_number': 'keyword', + 'observer.type': 'keyword', + 'observer.vendor': 'keyword', + 'observer.version': 'keyword', + 'orchestrator.api_version': 'keyword', + 'orchestrator.cluster.id': 'keyword', + 'orchestrator.cluster.name': 'keyword', + 'orchestrator.cluster.url': 'keyword', + 'orchestrator.cluster.version': 'keyword', + 'orchestrator.namespace': 'keyword', + 'orchestrator.organization': 'keyword', + 'orchestrator.resource.annotation': 'keyword', + 'orchestrator.resource.id': 'keyword', + 'orchestrator.resource.ip': 'ip', + 'orchestrator.resource.label': 'keyword', + 'orchestrator.resource.name': 'keyword', + 'orchestrator.resource.parent.type': 'keyword', + 'orchestrator.resource.type': 'keyword', + 'orchestrator.type': 'keyword', + 'organization.id': 'keyword', + 'organization.name': 'keyword', + 'package.architecture': 'keyword', + 'package.build_version': 'keyword', + 'package.checksum': 'keyword', + 'package.description': 'keyword', + 'package.install_scope': 'keyword', + 'package.installed': 'date', + 'package.license': 'keyword', + 'package.name': 'keyword', + 'package.path': 'keyword', + 'package.reference': 'keyword', + 'package.size': 'long', + 'package.type': 'keyword', + 'package.version': 'keyword', + 'process.args': 'keyword', + 'process.args_count': 'long', + 'process.code_signature.digest_algorithm': 'keyword', + 'process.code_signature.exists': 'boolean', + 'process.code_signature.signing_id': 'keyword', + 'process.code_signature.status': 'keyword', + 'process.code_signature.subject_name': 'keyword', + 'process.code_signature.team_id': 'keyword', + 'process.code_signature.timestamp': 'date', + 'process.code_signature.trusted': 'boolean', + 'process.code_signature.valid': 'boolean', + 'process.command_line': 'wildcard', + 'process.elf.architecture': 'keyword', + 'process.elf.byte_order': 'keyword', + 'process.elf.cpu_type': 'keyword', + 'process.elf.creation_date': 'date', + 'process.elf.exports': 'flattened', + 'process.elf.go_import_hash': 'keyword', + 'process.elf.go_imports': 'flattened', + 'process.elf.go_imports_names_entropy': 'long', + 'process.elf.go_imports_names_var_entropy': 'long', + 'process.elf.go_stripped': 'boolean', + 'process.elf.header.abi_version': 'keyword', + 'process.elf.header.class': 'keyword', + 'process.elf.header.data': 'keyword', + 'process.elf.header.entrypoint': 'long', + 'process.elf.header.object_version': 'keyword', + 'process.elf.header.os_abi': 'keyword', + 'process.elf.header.type': 'keyword', + 'process.elf.header.version': 'keyword', + 'process.elf.import_hash': 'keyword', + 'process.elf.imports': 'flattened', + 'process.elf.imports_names_entropy': 'long', + 'process.elf.imports_names_var_entropy': 'long', + 'process.elf.sections': 'nested', + 'process.elf.sections.chi2': 'long', + 'process.elf.sections.entropy': 'long', + 'process.elf.sections.flags': 'keyword', + 'process.elf.sections.name': 'keyword', + 'process.elf.sections.physical_offset': 'keyword', + 'process.elf.sections.physical_size': 'long', + 'process.elf.sections.type': 'keyword', + 'process.elf.sections.var_entropy': 'long', + 'process.elf.sections.virtual_address': 'long', + 'process.elf.sections.virtual_size': 'long', + 'process.elf.segments': 'nested', + 'process.elf.segments.sections': 'keyword', + 'process.elf.segments.type': 'keyword', + 'process.elf.shared_libraries': 'keyword', + 'process.elf.telfhash': 'keyword', + 'process.end': 'date', + 'process.entity_id': 'keyword', + 'process.entry_leader.args': 'keyword', + 'process.entry_leader.args_count': 'long', + 'process.entry_leader.attested_groups.name': 'keyword', + 'process.entry_leader.attested_user.id': 'keyword', + 'process.entry_leader.attested_user.name': 'keyword', + 'process.entry_leader.command_line': 'wildcard', + 'process.entry_leader.entity_id': 'keyword', + 'process.entry_leader.entry_meta.source.ip': 'ip', + 'process.entry_leader.entry_meta.type': 'keyword', + 'process.entry_leader.executable': 'keyword', + 'process.entry_leader.group.id': 'keyword', + 'process.entry_leader.group.name': 'keyword', + 'process.entry_leader.interactive': 'boolean', + 'process.entry_leader.name': 'keyword', + 'process.entry_leader.parent.entity_id': 'keyword', + 'process.entry_leader.parent.pid': 'long', + 'process.entry_leader.parent.session_leader.entity_id': 'keyword', + 'process.entry_leader.parent.session_leader.pid': 'long', + 'process.entry_leader.parent.session_leader.start': 'date', + 'process.entry_leader.parent.session_leader.vpid': 'long', + 'process.entry_leader.parent.start': 'date', + 'process.entry_leader.parent.vpid': 'long', + 'process.entry_leader.pid': 'long', + 'process.entry_leader.real_group.id': 'keyword', + 'process.entry_leader.real_group.name': 'keyword', + 'process.entry_leader.real_user.id': 'keyword', + 'process.entry_leader.real_user.name': 'keyword', + 'process.entry_leader.same_as_process': 'boolean', + 'process.entry_leader.saved_group.id': 'keyword', + 'process.entry_leader.saved_group.name': 'keyword', + 'process.entry_leader.saved_user.id': 'keyword', + 'process.entry_leader.saved_user.name': 'keyword', + 'process.entry_leader.start': 'date', + 'process.entry_leader.supplemental_groups.id': 'keyword', + 'process.entry_leader.supplemental_groups.name': 'keyword', + 'process.entry_leader.tty': 'object', + 'process.entry_leader.tty.char_device.major': 'long', + 'process.entry_leader.tty.char_device.minor': 'long', + 'process.entry_leader.user.id': 'keyword', + 'process.entry_leader.user.name': 'keyword', + 'process.entry_leader.vpid': 'long', + 'process.entry_leader.working_directory': 'keyword', + 'process.env_vars': 'keyword', + 'process.executable': 'keyword', + 'process.exit_code': 'long', + 'process.group_leader.args': 'keyword', + 'process.group_leader.args_count': 'long', + 'process.group_leader.command_line': 'wildcard', + 'process.group_leader.entity_id': 'keyword', + 'process.group_leader.executable': 'keyword', + 'process.group_leader.group.id': 'keyword', + 'process.group_leader.group.name': 'keyword', + 'process.group_leader.interactive': 'boolean', + 'process.group_leader.name': 'keyword', + 'process.group_leader.pid': 'long', + 'process.group_leader.real_group.id': 'keyword', + 'process.group_leader.real_group.name': 'keyword', + 'process.group_leader.real_user.id': 'keyword', + 'process.group_leader.real_user.name': 'keyword', + 'process.group_leader.same_as_process': 'boolean', + 'process.group_leader.saved_group.id': 'keyword', + 'process.group_leader.saved_group.name': 'keyword', + 'process.group_leader.saved_user.id': 'keyword', + 'process.group_leader.saved_user.name': 'keyword', + 'process.group_leader.start': 'date', + 'process.group_leader.supplemental_groups.id': 'keyword', + 'process.group_leader.supplemental_groups.name': 'keyword', + 'process.group_leader.tty': 'object', + 'process.group_leader.tty.char_device.major': 'long', + 'process.group_leader.tty.char_device.minor': 'long', + 'process.group_leader.user.id': 'keyword', + 'process.group_leader.user.name': 'keyword', + 'process.group_leader.vpid': 'long', + 'process.group_leader.working_directory': 'keyword', + 'process.hash.md5': 'keyword', + 'process.hash.sha1': 'keyword', + 'process.hash.sha256': 'keyword', + 'process.hash.sha384': 'keyword', + 'process.hash.sha512': 'keyword', + 'process.hash.ssdeep': 'keyword', + 'process.hash.tlsh': 'keyword', + 'process.interactive': 'boolean', + 'process.io': 'object', + 'process.io.bytes_skipped': 'object', + 'process.io.bytes_skipped.length': 'long', + 'process.io.bytes_skipped.offset': 'long', + 'process.io.max_bytes_per_process_exceeded': 'boolean', + 'process.io.text': 'wildcard', + 'process.io.total_bytes_captured': 'long', + 'process.io.total_bytes_skipped': 'long', + 'process.io.type': 'keyword', + 'process.macho.go_import_hash': 'keyword', + 'process.macho.go_imports': 'flattened', + 'process.macho.go_imports_names_entropy': 'long', + 'process.macho.go_imports_names_var_entropy': 'long', + 'process.macho.go_stripped': 'boolean', + 'process.macho.import_hash': 'keyword', + 'process.macho.imports': 'flattened', + 'process.macho.imports_names_entropy': 'long', + 'process.macho.imports_names_var_entropy': 'long', + 'process.macho.sections': 'nested', + 'process.macho.sections.entropy': 'long', + 'process.macho.sections.name': 'keyword', + 'process.macho.sections.physical_size': 'long', + 'process.macho.sections.var_entropy': 'long', + 'process.macho.sections.virtual_size': 'long', + 'process.macho.symhash': 'keyword', + 'process.name': 'keyword', + 'process.parent.args': 'keyword', + 'process.parent.args_count': 'long', + 'process.parent.code_signature.digest_algorithm': 'keyword', + 'process.parent.code_signature.exists': 'boolean', + 'process.parent.code_signature.signing_id': 'keyword', + 'process.parent.code_signature.status': 'keyword', + 'process.parent.code_signature.subject_name': 'keyword', + 'process.parent.code_signature.team_id': 'keyword', + 'process.parent.code_signature.timestamp': 'date', + 'process.parent.code_signature.trusted': 'boolean', + 'process.parent.code_signature.valid': 'boolean', + 'process.parent.command_line': 'wildcard', + 'process.parent.elf.architecture': 'keyword', + 'process.parent.elf.byte_order': 'keyword', + 'process.parent.elf.cpu_type': 'keyword', + 'process.parent.elf.creation_date': 'date', + 'process.parent.elf.exports': 'flattened', + 'process.parent.elf.go_import_hash': 'keyword', + 'process.parent.elf.go_imports': 'flattened', + 'process.parent.elf.go_imports_names_entropy': 'long', + 'process.parent.elf.go_imports_names_var_entropy': 'long', + 'process.parent.elf.go_stripped': 'boolean', + 'process.parent.elf.header.abi_version': 'keyword', + 'process.parent.elf.header.class': 'keyword', + 'process.parent.elf.header.data': 'keyword', + 'process.parent.elf.header.entrypoint': 'long', + 'process.parent.elf.header.object_version': 'keyword', + 'process.parent.elf.header.os_abi': 'keyword', + 'process.parent.elf.header.type': 'keyword', + 'process.parent.elf.header.version': 'keyword', + 'process.parent.elf.import_hash': 'keyword', + 'process.parent.elf.imports': 'flattened', + 'process.parent.elf.imports_names_entropy': 'long', + 'process.parent.elf.imports_names_var_entropy': 'long', + 'process.parent.elf.sections': 'nested', + 'process.parent.elf.sections.chi2': 'long', + 'process.parent.elf.sections.entropy': 'long', + 'process.parent.elf.sections.flags': 'keyword', + 'process.parent.elf.sections.name': 'keyword', + 'process.parent.elf.sections.physical_offset': 'keyword', + 'process.parent.elf.sections.physical_size': 'long', + 'process.parent.elf.sections.type': 'keyword', + 'process.parent.elf.sections.var_entropy': 'long', + 'process.parent.elf.sections.virtual_address': 'long', + 'process.parent.elf.sections.virtual_size': 'long', + 'process.parent.elf.segments': 'nested', + 'process.parent.elf.segments.sections': 'keyword', + 'process.parent.elf.segments.type': 'keyword', + 'process.parent.elf.shared_libraries': 'keyword', + 'process.parent.elf.telfhash': 'keyword', + 'process.parent.end': 'date', + 'process.parent.entity_id': 'keyword', + 'process.parent.executable': 'keyword', + 'process.parent.exit_code': 'long', + 'process.parent.group.id': 'keyword', + 'process.parent.group.name': 'keyword', + 'process.parent.group_leader.entity_id': 'keyword', + 'process.parent.group_leader.pid': 'long', + 'process.parent.group_leader.start': 'date', + 'process.parent.group_leader.vpid': 'long', + 'process.parent.hash.md5': 'keyword', + 'process.parent.hash.sha1': 'keyword', + 'process.parent.hash.sha256': 'keyword', + 'process.parent.hash.sha384': 'keyword', + 'process.parent.hash.sha512': 'keyword', + 'process.parent.hash.ssdeep': 'keyword', + 'process.parent.hash.tlsh': 'keyword', + 'process.parent.interactive': 'boolean', + 'process.parent.macho.go_import_hash': 'keyword', + 'process.parent.macho.go_imports': 'flattened', + 'process.parent.macho.go_imports_names_entropy': 'long', + 'process.parent.macho.go_imports_names_var_entropy': 'long', + 'process.parent.macho.go_stripped': 'boolean', + 'process.parent.macho.import_hash': 'keyword', + 'process.parent.macho.imports': 'flattened', + 'process.parent.macho.imports_names_entropy': 'long', + 'process.parent.macho.imports_names_var_entropy': 'long', + 'process.parent.macho.sections': 'nested', + 'process.parent.macho.sections.entropy': 'long', + 'process.parent.macho.sections.name': 'keyword', + 'process.parent.macho.sections.physical_size': 'long', + 'process.parent.macho.sections.var_entropy': 'long', + 'process.parent.macho.sections.virtual_size': 'long', + 'process.parent.macho.symhash': 'keyword', + 'process.parent.name': 'keyword', + 'process.parent.pe.architecture': 'keyword', + 'process.parent.pe.company': 'keyword', + 'process.parent.pe.description': 'keyword', + 'process.parent.pe.file_version': 'keyword', + 'process.parent.pe.go_import_hash': 'keyword', + 'process.parent.pe.go_imports': 'flattened', + 'process.parent.pe.go_imports_names_entropy': 'long', + 'process.parent.pe.go_imports_names_var_entropy': 'long', + 'process.parent.pe.go_stripped': 'boolean', + 'process.parent.pe.imphash': 'keyword', + 'process.parent.pe.import_hash': 'keyword', + 'process.parent.pe.imports': 'flattened', + 'process.parent.pe.imports_names_entropy': 'long', + 'process.parent.pe.imports_names_var_entropy': 'long', + 'process.parent.pe.original_file_name': 'keyword', + 'process.parent.pe.pehash': 'keyword', + 'process.parent.pe.product': 'keyword', + 'process.parent.pe.sections': 'nested', + 'process.parent.pe.sections.entropy': 'long', + 'process.parent.pe.sections.name': 'keyword', + 'process.parent.pe.sections.physical_size': 'long', + 'process.parent.pe.sections.var_entropy': 'long', + 'process.parent.pe.sections.virtual_size': 'long', + 'process.parent.pgid': 'long', + 'process.parent.pid': 'long', + 'process.parent.real_group.id': 'keyword', + 'process.parent.real_group.name': 'keyword', + 'process.parent.real_user.id': 'keyword', + 'process.parent.real_user.name': 'keyword', + 'process.parent.saved_group.id': 'keyword', + 'process.parent.saved_group.name': 'keyword', + 'process.parent.saved_user.id': 'keyword', + 'process.parent.saved_user.name': 'keyword', + 'process.parent.start': 'date', + 'process.parent.supplemental_groups.id': 'keyword', + 'process.parent.supplemental_groups.name': 'keyword', + 'process.parent.thread.capabilities.effective': 'keyword', + 'process.parent.thread.capabilities.permitted': 'keyword', + 'process.parent.thread.id': 'long', + 'process.parent.thread.name': 'keyword', + 'process.parent.title': 'keyword', + 'process.parent.tty': 'object', + 'process.parent.tty.char_device.major': 'long', + 'process.parent.tty.char_device.minor': 'long', + 'process.parent.uptime': 'long', + 'process.parent.user.id': 'keyword', + 'process.parent.user.name': 'keyword', + 'process.parent.vpid': 'long', + 'process.parent.working_directory': 'keyword', + 'process.pe.architecture': 'keyword', + 'process.pe.company': 'keyword', + 'process.pe.description': 'keyword', + 'process.pe.file_version': 'keyword', + 'process.pe.go_import_hash': 'keyword', + 'process.pe.go_imports': 'flattened', + 'process.pe.go_imports_names_entropy': 'long', + 'process.pe.go_imports_names_var_entropy': 'long', + 'process.pe.go_stripped': 'boolean', + 'process.pe.imphash': 'keyword', + 'process.pe.import_hash': 'keyword', + 'process.pe.imports': 'flattened', + 'process.pe.imports_names_entropy': 'long', + 'process.pe.imports_names_var_entropy': 'long', + 'process.pe.original_file_name': 'keyword', + 'process.pe.pehash': 'keyword', + 'process.pe.product': 'keyword', + 'process.pe.sections': 'nested', + 'process.pe.sections.entropy': 'long', + 'process.pe.sections.name': 'keyword', + 'process.pe.sections.physical_size': 'long', + 'process.pe.sections.var_entropy': 'long', + 'process.pe.sections.virtual_size': 'long', + 'process.pgid': 'long', + 'process.pid': 'long', + 'process.previous.args': 'keyword', + 'process.previous.args_count': 'long', + 'process.previous.executable': 'keyword', + 'process.real_group.id': 'keyword', + 'process.real_group.name': 'keyword', + 'process.real_user.id': 'keyword', + 'process.real_user.name': 'keyword', + 'process.saved_group.id': 'keyword', + 'process.saved_group.name': 'keyword', + 'process.saved_user.id': 'keyword', + 'process.saved_user.name': 'keyword', + 'process.session_leader.args': 'keyword', + 'process.session_leader.args_count': 'long', + 'process.session_leader.command_line': 'wildcard', + 'process.session_leader.entity_id': 'keyword', + 'process.session_leader.executable': 'keyword', + 'process.session_leader.group.id': 'keyword', + 'process.session_leader.group.name': 'keyword', + 'process.session_leader.interactive': 'boolean', + 'process.session_leader.name': 'keyword', + 'process.session_leader.parent.entity_id': 'keyword', + 'process.session_leader.parent.pid': 'long', + 'process.session_leader.parent.session_leader.entity_id': 'keyword', + 'process.session_leader.parent.session_leader.pid': 'long', + 'process.session_leader.parent.session_leader.start': 'date', + 'process.session_leader.parent.session_leader.vpid': 'long', + 'process.session_leader.parent.start': 'date', + 'process.session_leader.parent.vpid': 'long', + 'process.session_leader.pid': 'long', + 'process.session_leader.real_group.id': 'keyword', + 'process.session_leader.real_group.name': 'keyword', + 'process.session_leader.real_user.id': 'keyword', + 'process.session_leader.real_user.name': 'keyword', + 'process.session_leader.same_as_process': 'boolean', + 'process.session_leader.saved_group.id': 'keyword', + 'process.session_leader.saved_group.name': 'keyword', + 'process.session_leader.saved_user.id': 'keyword', + 'process.session_leader.saved_user.name': 'keyword', + 'process.session_leader.start': 'date', + 'process.session_leader.supplemental_groups.id': 'keyword', + 'process.session_leader.supplemental_groups.name': 'keyword', + 'process.session_leader.tty': 'object', + 'process.session_leader.tty.char_device.major': 'long', + 'process.session_leader.tty.char_device.minor': 'long', + 'process.session_leader.user.id': 'keyword', + 'process.session_leader.user.name': 'keyword', + 'process.session_leader.vpid': 'long', + 'process.session_leader.working_directory': 'keyword', + 'process.start': 'date', + 'process.supplemental_groups.id': 'keyword', + 'process.supplemental_groups.name': 'keyword', + 'process.thread.capabilities.effective': 'keyword', + 'process.thread.capabilities.permitted': 'keyword', + 'process.thread.id': 'long', + 'process.thread.name': 'keyword', + 'process.title': 'keyword', + 'process.tty': 'object', + 'process.tty.char_device.major': 'long', + 'process.tty.char_device.minor': 'long', + 'process.tty.columns': 'long', + 'process.tty.rows': 'long', + 'process.uptime': 'long', + 'process.user.id': 'keyword', + 'process.user.name': 'keyword', + 'process.vpid': 'long', + 'process.working_directory': 'keyword', + 'registry.data.bytes': 'keyword', + 'registry.data.strings': 'wildcard', + 'registry.data.type': 'keyword', + 'registry.hive': 'keyword', + 'registry.key': 'keyword', + 'registry.path': 'keyword', + 'registry.value': 'keyword', + 'related.hash': 'keyword', + 'related.hosts': 'keyword', + 'related.ip': 'ip', + 'related.user': 'keyword', + 'rule.author': 'keyword', + 'rule.category': 'keyword', + 'rule.description': 'keyword', + 'rule.id': 'keyword', + 'rule.license': 'keyword', + 'rule.name': 'keyword', + 'rule.reference': 'keyword', + 'rule.ruleset': 'keyword', + 'rule.uuid': 'keyword', + 'rule.version': 'keyword', + 'server.address': 'keyword', + 'server.as.number': 'long', + 'server.as.organization.name': 'keyword', + 'server.bytes': 'long', + 'server.domain': 'keyword', + 'server.geo.city_name': 'keyword', + 'server.geo.continent_code': 'keyword', + 'server.geo.continent_name': 'keyword', + 'server.geo.country_iso_code': 'keyword', + 'server.geo.country_name': 'keyword', + 'server.geo.location': 'geo_point', + 'server.geo.name': 'keyword', + 'server.geo.postal_code': 'keyword', + 'server.geo.region_iso_code': 'keyword', + 'server.geo.region_name': 'keyword', + 'server.geo.timezone': 'keyword', + 'server.ip': 'ip', + 'server.mac': 'keyword', + 'server.nat.ip': 'ip', + 'server.nat.port': 'long', + 'server.packets': 'long', + 'server.port': 'long', + 'server.registered_domain': 'keyword', + 'server.subdomain': 'keyword', + 'server.top_level_domain': 'keyword', + 'server.user.domain': 'keyword', + 'server.user.email': 'keyword', + 'server.user.full_name': 'keyword', + 'server.user.group.domain': 'keyword', + 'server.user.group.id': 'keyword', + 'server.user.group.name': 'keyword', + 'server.user.hash': 'keyword', + 'server.user.id': 'keyword', + 'server.user.name': 'keyword', + 'server.user.roles': 'keyword', + 'service.address': 'keyword', + 'service.environment': 'keyword', + 'service.ephemeral_id': 'keyword', + 'service.id': 'keyword', + 'service.name': 'keyword', + 'service.node.name': 'keyword', + 'service.node.role': 'keyword', + 'service.node.roles': 'keyword', + 'service.origin.address': 'keyword', + 'service.origin.environment': 'keyword', + 'service.origin.ephemeral_id': 'keyword', + 'service.origin.id': 'keyword', + 'service.origin.name': 'keyword', + 'service.origin.node.name': 'keyword', + 'service.origin.node.role': 'keyword', + 'service.origin.node.roles': 'keyword', + 'service.origin.state': 'keyword', + 'service.origin.type': 'keyword', + 'service.origin.version': 'keyword', + 'service.state': 'keyword', + 'service.target.address': 'keyword', + 'service.target.environment': 'keyword', + 'service.target.ephemeral_id': 'keyword', + 'service.target.id': 'keyword', + 'service.target.name': 'keyword', + 'service.target.node.name': 'keyword', + 'service.target.node.role': 'keyword', + 'service.target.node.roles': 'keyword', + 'service.target.state': 'keyword', + 'service.target.type': 'keyword', + 'service.target.version': 'keyword', + 'service.type': 'keyword', + 'service.version': 'keyword', + 'source.address': 'keyword', + 'source.as.number': 'long', + 'source.as.organization.name': 'keyword', + 'source.bytes': 'long', + 'source.domain': 'keyword', + 'source.geo.city_name': 'keyword', + 'source.geo.continent_code': 'keyword', + 'source.geo.continent_name': 'keyword', + 'source.geo.country_iso_code': 'keyword', + 'source.geo.country_name': 'keyword', + 'source.geo.location': 'geo_point', + 'source.geo.name': 'keyword', + 'source.geo.postal_code': 'keyword', + 'source.geo.region_iso_code': 'keyword', + 'source.geo.region_name': 'keyword', + 'source.geo.timezone': 'keyword', + 'source.ip': 'ip', + 'source.mac': 'keyword', + 'source.nat.ip': 'ip', + 'source.nat.port': 'long', + 'source.packets': 'long', + 'source.port': 'long', + 'source.registered_domain': 'keyword', + 'source.subdomain': 'keyword', + 'source.top_level_domain': 'keyword', + 'source.user.domain': 'keyword', + 'source.user.email': 'keyword', + 'source.user.full_name': 'keyword', + 'source.user.group.domain': 'keyword', + 'source.user.group.id': 'keyword', + 'source.user.group.name': 'keyword', + 'source.user.hash': 'keyword', + 'source.user.id': 'keyword', + 'source.user.name': 'keyword', + 'source.user.roles': 'keyword', + 'span.id': 'keyword', + tags: 'keyword', + 'threat.enrichments': 'nested', + 'threat.enrichments.indicator': 'object', + 'threat.enrichments.indicator.as.number': 'long', + 'threat.enrichments.indicator.as.organization.name': 'keyword', + 'threat.enrichments.indicator.confidence': 'keyword', + 'threat.enrichments.indicator.description': 'keyword', + 'threat.enrichments.indicator.email.address': 'keyword', + 'threat.enrichments.indicator.file.accessed': 'date', + 'threat.enrichments.indicator.file.attributes': 'keyword', + 'threat.enrichments.indicator.file.code_signature.digest_algorithm': 'keyword', + 'threat.enrichments.indicator.file.code_signature.exists': 'boolean', + 'threat.enrichments.indicator.file.code_signature.signing_id': 'keyword', + 'threat.enrichments.indicator.file.code_signature.status': 'keyword', + 'threat.enrichments.indicator.file.code_signature.subject_name': 'keyword', + 'threat.enrichments.indicator.file.code_signature.team_id': 'keyword', + 'threat.enrichments.indicator.file.code_signature.timestamp': 'date', + 'threat.enrichments.indicator.file.code_signature.trusted': 'boolean', + 'threat.enrichments.indicator.file.code_signature.valid': 'boolean', + 'threat.enrichments.indicator.file.created': 'date', + 'threat.enrichments.indicator.file.ctime': 'date', + 'threat.enrichments.indicator.file.device': 'keyword', + 'threat.enrichments.indicator.file.directory': 'keyword', + 'threat.enrichments.indicator.file.drive_letter': 'keyword', + 'threat.enrichments.indicator.file.elf.architecture': 'keyword', + 'threat.enrichments.indicator.file.elf.byte_order': 'keyword', + 'threat.enrichments.indicator.file.elf.cpu_type': 'keyword', + 'threat.enrichments.indicator.file.elf.creation_date': 'date', + 'threat.enrichments.indicator.file.elf.exports': 'flattened', + 'threat.enrichments.indicator.file.elf.go_import_hash': 'keyword', + 'threat.enrichments.indicator.file.elf.go_imports': 'flattened', + 'threat.enrichments.indicator.file.elf.go_imports_names_entropy': 'long', + 'threat.enrichments.indicator.file.elf.go_imports_names_var_entropy': 'long', + 'threat.enrichments.indicator.file.elf.go_stripped': 'boolean', + 'threat.enrichments.indicator.file.elf.header.abi_version': 'keyword', + 'threat.enrichments.indicator.file.elf.header.class': 'keyword', + 'threat.enrichments.indicator.file.elf.header.data': 'keyword', + 'threat.enrichments.indicator.file.elf.header.entrypoint': 'long', + 'threat.enrichments.indicator.file.elf.header.object_version': 'keyword', + 'threat.enrichments.indicator.file.elf.header.os_abi': 'keyword', + 'threat.enrichments.indicator.file.elf.header.type': 'keyword', + 'threat.enrichments.indicator.file.elf.header.version': 'keyword', + 'threat.enrichments.indicator.file.elf.import_hash': 'keyword', + 'threat.enrichments.indicator.file.elf.imports': 'flattened', + 'threat.enrichments.indicator.file.elf.imports_names_entropy': 'long', + 'threat.enrichments.indicator.file.elf.imports_names_var_entropy': 'long', + 'threat.enrichments.indicator.file.elf.sections': 'nested', + 'threat.enrichments.indicator.file.elf.sections.chi2': 'long', + 'threat.enrichments.indicator.file.elf.sections.entropy': 'long', + 'threat.enrichments.indicator.file.elf.sections.flags': 'keyword', + 'threat.enrichments.indicator.file.elf.sections.name': 'keyword', + 'threat.enrichments.indicator.file.elf.sections.physical_offset': 'keyword', + 'threat.enrichments.indicator.file.elf.sections.physical_size': 'long', + 'threat.enrichments.indicator.file.elf.sections.type': 'keyword', + 'threat.enrichments.indicator.file.elf.sections.var_entropy': 'long', + 'threat.enrichments.indicator.file.elf.sections.virtual_address': 'long', + 'threat.enrichments.indicator.file.elf.sections.virtual_size': 'long', + 'threat.enrichments.indicator.file.elf.segments': 'nested', + 'threat.enrichments.indicator.file.elf.segments.sections': 'keyword', + 'threat.enrichments.indicator.file.elf.segments.type': 'keyword', + 'threat.enrichments.indicator.file.elf.shared_libraries': 'keyword', + 'threat.enrichments.indicator.file.elf.telfhash': 'keyword', + 'threat.enrichments.indicator.file.extension': 'keyword', + 'threat.enrichments.indicator.file.fork_name': 'keyword', + 'threat.enrichments.indicator.file.gid': 'keyword', + 'threat.enrichments.indicator.file.group': 'keyword', + 'threat.enrichments.indicator.file.hash.md5': 'keyword', + 'threat.enrichments.indicator.file.hash.sha1': 'keyword', + 'threat.enrichments.indicator.file.hash.sha256': 'keyword', + 'threat.enrichments.indicator.file.hash.sha384': 'keyword', + 'threat.enrichments.indicator.file.hash.sha512': 'keyword', + 'threat.enrichments.indicator.file.hash.ssdeep': 'keyword', + 'threat.enrichments.indicator.file.hash.tlsh': 'keyword', + 'threat.enrichments.indicator.file.inode': 'keyword', + 'threat.enrichments.indicator.file.mime_type': 'keyword', + 'threat.enrichments.indicator.file.mode': 'keyword', + 'threat.enrichments.indicator.file.mtime': 'date', + 'threat.enrichments.indicator.file.name': 'keyword', + 'threat.enrichments.indicator.file.owner': 'keyword', + 'threat.enrichments.indicator.file.path': 'keyword', + 'threat.enrichments.indicator.file.pe.architecture': 'keyword', + 'threat.enrichments.indicator.file.pe.company': 'keyword', + 'threat.enrichments.indicator.file.pe.description': 'keyword', + 'threat.enrichments.indicator.file.pe.file_version': 'keyword', + 'threat.enrichments.indicator.file.pe.go_import_hash': 'keyword', + 'threat.enrichments.indicator.file.pe.go_imports': 'flattened', + 'threat.enrichments.indicator.file.pe.go_imports_names_entropy': 'long', + 'threat.enrichments.indicator.file.pe.go_imports_names_var_entropy': 'long', + 'threat.enrichments.indicator.file.pe.go_stripped': 'boolean', + 'threat.enrichments.indicator.file.pe.imphash': 'keyword', + 'threat.enrichments.indicator.file.pe.import_hash': 'keyword', + 'threat.enrichments.indicator.file.pe.imports': 'flattened', + 'threat.enrichments.indicator.file.pe.imports_names_entropy': 'long', + 'threat.enrichments.indicator.file.pe.imports_names_var_entropy': 'long', + 'threat.enrichments.indicator.file.pe.original_file_name': 'keyword', + 'threat.enrichments.indicator.file.pe.pehash': 'keyword', + 'threat.enrichments.indicator.file.pe.product': 'keyword', + 'threat.enrichments.indicator.file.pe.sections': 'nested', + 'threat.enrichments.indicator.file.pe.sections.entropy': 'long', + 'threat.enrichments.indicator.file.pe.sections.name': 'keyword', + 'threat.enrichments.indicator.file.pe.sections.physical_size': 'long', + 'threat.enrichments.indicator.file.pe.sections.var_entropy': 'long', + 'threat.enrichments.indicator.file.pe.sections.virtual_size': 'long', + 'threat.enrichments.indicator.file.size': 'long', + 'threat.enrichments.indicator.file.target_path': 'keyword', + 'threat.enrichments.indicator.file.type': 'keyword', + 'threat.enrichments.indicator.file.uid': 'keyword', + 'threat.enrichments.indicator.file.x509.alternative_names': 'keyword', + 'threat.enrichments.indicator.file.x509.issuer.common_name': 'keyword', + 'threat.enrichments.indicator.file.x509.issuer.country': 'keyword', + 'threat.enrichments.indicator.file.x509.issuer.distinguished_name': 'keyword', + 'threat.enrichments.indicator.file.x509.issuer.locality': 'keyword', + 'threat.enrichments.indicator.file.x509.issuer.organization': 'keyword', + 'threat.enrichments.indicator.file.x509.issuer.organizational_unit': 'keyword', + 'threat.enrichments.indicator.file.x509.issuer.state_or_province': 'keyword', + 'threat.enrichments.indicator.file.x509.not_after': 'date', + 'threat.enrichments.indicator.file.x509.not_before': 'date', + 'threat.enrichments.indicator.file.x509.public_key_algorithm': 'keyword', + 'threat.enrichments.indicator.file.x509.public_key_curve': 'keyword', + 'threat.enrichments.indicator.file.x509.public_key_exponent': 'long', + 'threat.enrichments.indicator.file.x509.public_key_size': 'long', + 'threat.enrichments.indicator.file.x509.serial_number': 'keyword', + 'threat.enrichments.indicator.file.x509.signature_algorithm': 'keyword', + 'threat.enrichments.indicator.file.x509.subject.common_name': 'keyword', + 'threat.enrichments.indicator.file.x509.subject.country': 'keyword', + 'threat.enrichments.indicator.file.x509.subject.distinguished_name': 'keyword', + 'threat.enrichments.indicator.file.x509.subject.locality': 'keyword', + 'threat.enrichments.indicator.file.x509.subject.organization': 'keyword', + 'threat.enrichments.indicator.file.x509.subject.organizational_unit': 'keyword', + 'threat.enrichments.indicator.file.x509.subject.state_or_province': 'keyword', + 'threat.enrichments.indicator.file.x509.version_number': 'keyword', + 'threat.enrichments.indicator.first_seen': 'date', + 'threat.enrichments.indicator.geo.city_name': 'keyword', + 'threat.enrichments.indicator.geo.continent_code': 'keyword', + 'threat.enrichments.indicator.geo.continent_name': 'keyword', + 'threat.enrichments.indicator.geo.country_iso_code': 'keyword', + 'threat.enrichments.indicator.geo.country_name': 'keyword', + 'threat.enrichments.indicator.geo.location': 'geo_point', + 'threat.enrichments.indicator.geo.name': 'keyword', + 'threat.enrichments.indicator.geo.postal_code': 'keyword', + 'threat.enrichments.indicator.geo.region_iso_code': 'keyword', + 'threat.enrichments.indicator.geo.region_name': 'keyword', + 'threat.enrichments.indicator.geo.timezone': 'keyword', + 'threat.enrichments.indicator.ip': 'ip', + 'threat.enrichments.indicator.last_seen': 'date', + 'threat.enrichments.indicator.marking.tlp': 'keyword', + 'threat.enrichments.indicator.marking.tlp_version': 'keyword', + 'threat.enrichments.indicator.modified_at': 'date', + 'threat.enrichments.indicator.name': 'keyword', + 'threat.enrichments.indicator.port': 'long', + 'threat.enrichments.indicator.provider': 'keyword', + 'threat.enrichments.indicator.reference': 'keyword', + 'threat.enrichments.indicator.registry.data.bytes': 'keyword', + 'threat.enrichments.indicator.registry.data.strings': 'wildcard', + 'threat.enrichments.indicator.registry.data.type': 'keyword', + 'threat.enrichments.indicator.registry.hive': 'keyword', + 'threat.enrichments.indicator.registry.key': 'keyword', + 'threat.enrichments.indicator.registry.path': 'keyword', + 'threat.enrichments.indicator.registry.value': 'keyword', + 'threat.enrichments.indicator.scanner_stats': 'long', + 'threat.enrichments.indicator.sightings': 'long', + 'threat.enrichments.indicator.type': 'keyword', + 'threat.enrichments.indicator.url.domain': 'keyword', + 'threat.enrichments.indicator.url.extension': 'keyword', + 'threat.enrichments.indicator.url.fragment': 'keyword', + 'threat.enrichments.indicator.url.full': 'wildcard', + 'threat.enrichments.indicator.url.original': 'wildcard', + 'threat.enrichments.indicator.url.password': 'keyword', + 'threat.enrichments.indicator.url.path': 'wildcard', + 'threat.enrichments.indicator.url.port': 'long', + 'threat.enrichments.indicator.url.query': 'keyword', + 'threat.enrichments.indicator.url.registered_domain': 'keyword', + 'threat.enrichments.indicator.url.scheme': 'keyword', + 'threat.enrichments.indicator.url.subdomain': 'keyword', + 'threat.enrichments.indicator.url.top_level_domain': 'keyword', + 'threat.enrichments.indicator.url.username': 'keyword', + 'threat.enrichments.indicator.x509.alternative_names': 'keyword', + 'threat.enrichments.indicator.x509.issuer.common_name': 'keyword', + 'threat.enrichments.indicator.x509.issuer.country': 'keyword', + 'threat.enrichments.indicator.x509.issuer.distinguished_name': 'keyword', + 'threat.enrichments.indicator.x509.issuer.locality': 'keyword', + 'threat.enrichments.indicator.x509.issuer.organization': 'keyword', + 'threat.enrichments.indicator.x509.issuer.organizational_unit': 'keyword', + 'threat.enrichments.indicator.x509.issuer.state_or_province': 'keyword', + 'threat.enrichments.indicator.x509.not_after': 'date', + 'threat.enrichments.indicator.x509.not_before': 'date', + 'threat.enrichments.indicator.x509.public_key_algorithm': 'keyword', + 'threat.enrichments.indicator.x509.public_key_curve': 'keyword', + 'threat.enrichments.indicator.x509.public_key_exponent': 'long', + 'threat.enrichments.indicator.x509.public_key_size': 'long', + 'threat.enrichments.indicator.x509.serial_number': 'keyword', + 'threat.enrichments.indicator.x509.signature_algorithm': 'keyword', + 'threat.enrichments.indicator.x509.subject.common_name': 'keyword', + 'threat.enrichments.indicator.x509.subject.country': 'keyword', + 'threat.enrichments.indicator.x509.subject.distinguished_name': 'keyword', + 'threat.enrichments.indicator.x509.subject.locality': 'keyword', + 'threat.enrichments.indicator.x509.subject.organization': 'keyword', + 'threat.enrichments.indicator.x509.subject.organizational_unit': 'keyword', + 'threat.enrichments.indicator.x509.subject.state_or_province': 'keyword', + 'threat.enrichments.indicator.x509.version_number': 'keyword', + 'threat.enrichments.matched.atomic': 'keyword', + 'threat.enrichments.matched.field': 'keyword', + 'threat.enrichments.matched.id': 'keyword', + 'threat.enrichments.matched.index': 'keyword', + 'threat.enrichments.matched.occurred': 'date', + 'threat.enrichments.matched.type': 'keyword', + 'threat.feed.dashboard_id': 'keyword', + 'threat.feed.description': 'keyword', + 'threat.feed.name': 'keyword', + 'threat.feed.reference': 'keyword', + 'threat.framework': 'keyword', + 'threat.group.alias': 'keyword', + 'threat.group.id': 'keyword', + 'threat.group.name': 'keyword', + 'threat.group.reference': 'keyword', + 'threat.indicator.as.number': 'long', + 'threat.indicator.as.organization.name': 'keyword', + 'threat.indicator.confidence': 'keyword', + 'threat.indicator.description': 'keyword', + 'threat.indicator.email.address': 'keyword', + 'threat.indicator.file.accessed': 'date', + 'threat.indicator.file.attributes': 'keyword', + 'threat.indicator.file.code_signature.digest_algorithm': 'keyword', + 'threat.indicator.file.code_signature.exists': 'boolean', + 'threat.indicator.file.code_signature.signing_id': 'keyword', + 'threat.indicator.file.code_signature.status': 'keyword', + 'threat.indicator.file.code_signature.subject_name': 'keyword', + 'threat.indicator.file.code_signature.team_id': 'keyword', + 'threat.indicator.file.code_signature.timestamp': 'date', + 'threat.indicator.file.code_signature.trusted': 'boolean', + 'threat.indicator.file.code_signature.valid': 'boolean', + 'threat.indicator.file.created': 'date', + 'threat.indicator.file.ctime': 'date', + 'threat.indicator.file.device': 'keyword', + 'threat.indicator.file.directory': 'keyword', + 'threat.indicator.file.drive_letter': 'keyword', + 'threat.indicator.file.elf.architecture': 'keyword', + 'threat.indicator.file.elf.byte_order': 'keyword', + 'threat.indicator.file.elf.cpu_type': 'keyword', + 'threat.indicator.file.elf.creation_date': 'date', + 'threat.indicator.file.elf.exports': 'flattened', + 'threat.indicator.file.elf.go_import_hash': 'keyword', + 'threat.indicator.file.elf.go_imports': 'flattened', + 'threat.indicator.file.elf.go_imports_names_entropy': 'long', + 'threat.indicator.file.elf.go_imports_names_var_entropy': 'long', + 'threat.indicator.file.elf.go_stripped': 'boolean', + 'threat.indicator.file.elf.header.abi_version': 'keyword', + 'threat.indicator.file.elf.header.class': 'keyword', + 'threat.indicator.file.elf.header.data': 'keyword', + 'threat.indicator.file.elf.header.entrypoint': 'long', + 'threat.indicator.file.elf.header.object_version': 'keyword', + 'threat.indicator.file.elf.header.os_abi': 'keyword', + 'threat.indicator.file.elf.header.type': 'keyword', + 'threat.indicator.file.elf.header.version': 'keyword', + 'threat.indicator.file.elf.import_hash': 'keyword', + 'threat.indicator.file.elf.imports': 'flattened', + 'threat.indicator.file.elf.imports_names_entropy': 'long', + 'threat.indicator.file.elf.imports_names_var_entropy': 'long', + 'threat.indicator.file.elf.sections': 'nested', + 'threat.indicator.file.elf.sections.chi2': 'long', + 'threat.indicator.file.elf.sections.entropy': 'long', + 'threat.indicator.file.elf.sections.flags': 'keyword', + 'threat.indicator.file.elf.sections.name': 'keyword', + 'threat.indicator.file.elf.sections.physical_offset': 'keyword', + 'threat.indicator.file.elf.sections.physical_size': 'long', + 'threat.indicator.file.elf.sections.type': 'keyword', + 'threat.indicator.file.elf.sections.var_entropy': 'long', + 'threat.indicator.file.elf.sections.virtual_address': 'long', + 'threat.indicator.file.elf.sections.virtual_size': 'long', + 'threat.indicator.file.elf.segments': 'nested', + 'threat.indicator.file.elf.segments.sections': 'keyword', + 'threat.indicator.file.elf.segments.type': 'keyword', + 'threat.indicator.file.elf.shared_libraries': 'keyword', + 'threat.indicator.file.elf.telfhash': 'keyword', + 'threat.indicator.file.extension': 'keyword', + 'threat.indicator.file.fork_name': 'keyword', + 'threat.indicator.file.gid': 'keyword', + 'threat.indicator.file.group': 'keyword', + 'threat.indicator.file.hash.md5': 'keyword', + 'threat.indicator.file.hash.sha1': 'keyword', + 'threat.indicator.file.hash.sha256': 'keyword', + 'threat.indicator.file.hash.sha384': 'keyword', + 'threat.indicator.file.hash.sha512': 'keyword', + 'threat.indicator.file.hash.ssdeep': 'keyword', + 'threat.indicator.file.hash.tlsh': 'keyword', + 'threat.indicator.file.inode': 'keyword', + 'threat.indicator.file.mime_type': 'keyword', + 'threat.indicator.file.mode': 'keyword', + 'threat.indicator.file.mtime': 'date', + 'threat.indicator.file.name': 'keyword', + 'threat.indicator.file.owner': 'keyword', + 'threat.indicator.file.path': 'keyword', + 'threat.indicator.file.pe.architecture': 'keyword', + 'threat.indicator.file.pe.company': 'keyword', + 'threat.indicator.file.pe.description': 'keyword', + 'threat.indicator.file.pe.file_version': 'keyword', + 'threat.indicator.file.pe.go_import_hash': 'keyword', + 'threat.indicator.file.pe.go_imports': 'flattened', + 'threat.indicator.file.pe.go_imports_names_entropy': 'long', + 'threat.indicator.file.pe.go_imports_names_var_entropy': 'long', + 'threat.indicator.file.pe.go_stripped': 'boolean', + 'threat.indicator.file.pe.imphash': 'keyword', + 'threat.indicator.file.pe.import_hash': 'keyword', + 'threat.indicator.file.pe.imports': 'flattened', + 'threat.indicator.file.pe.imports_names_entropy': 'long', + 'threat.indicator.file.pe.imports_names_var_entropy': 'long', + 'threat.indicator.file.pe.original_file_name': 'keyword', + 'threat.indicator.file.pe.pehash': 'keyword', + 'threat.indicator.file.pe.product': 'keyword', + 'threat.indicator.file.pe.sections': 'nested', + 'threat.indicator.file.pe.sections.entropy': 'long', + 'threat.indicator.file.pe.sections.name': 'keyword', + 'threat.indicator.file.pe.sections.physical_size': 'long', + 'threat.indicator.file.pe.sections.var_entropy': 'long', + 'threat.indicator.file.pe.sections.virtual_size': 'long', + 'threat.indicator.file.size': 'long', + 'threat.indicator.file.target_path': 'keyword', + 'threat.indicator.file.type': 'keyword', + 'threat.indicator.file.uid': 'keyword', + 'threat.indicator.file.x509.alternative_names': 'keyword', + 'threat.indicator.file.x509.issuer.common_name': 'keyword', + 'threat.indicator.file.x509.issuer.country': 'keyword', + 'threat.indicator.file.x509.issuer.distinguished_name': 'keyword', + 'threat.indicator.file.x509.issuer.locality': 'keyword', + 'threat.indicator.file.x509.issuer.organization': 'keyword', + 'threat.indicator.file.x509.issuer.organizational_unit': 'keyword', + 'threat.indicator.file.x509.issuer.state_or_province': 'keyword', + 'threat.indicator.file.x509.not_after': 'date', + 'threat.indicator.file.x509.not_before': 'date', + 'threat.indicator.file.x509.public_key_algorithm': 'keyword', + 'threat.indicator.file.x509.public_key_curve': 'keyword', + 'threat.indicator.file.x509.public_key_exponent': 'long', + 'threat.indicator.file.x509.public_key_size': 'long', + 'threat.indicator.file.x509.serial_number': 'keyword', + 'threat.indicator.file.x509.signature_algorithm': 'keyword', + 'threat.indicator.file.x509.subject.common_name': 'keyword', + 'threat.indicator.file.x509.subject.country': 'keyword', + 'threat.indicator.file.x509.subject.distinguished_name': 'keyword', + 'threat.indicator.file.x509.subject.locality': 'keyword', + 'threat.indicator.file.x509.subject.organization': 'keyword', + 'threat.indicator.file.x509.subject.organizational_unit': 'keyword', + 'threat.indicator.file.x509.subject.state_or_province': 'keyword', + 'threat.indicator.file.x509.version_number': 'keyword', + 'threat.indicator.first_seen': 'date', + 'threat.indicator.geo.city_name': 'keyword', + 'threat.indicator.geo.continent_code': 'keyword', + 'threat.indicator.geo.continent_name': 'keyword', + 'threat.indicator.geo.country_iso_code': 'keyword', + 'threat.indicator.geo.country_name': 'keyword', + 'threat.indicator.geo.location': 'geo_point', + 'threat.indicator.geo.name': 'keyword', + 'threat.indicator.geo.postal_code': 'keyword', + 'threat.indicator.geo.region_iso_code': 'keyword', + 'threat.indicator.geo.region_name': 'keyword', + 'threat.indicator.geo.timezone': 'keyword', + 'threat.indicator.ip': 'ip', + 'threat.indicator.last_seen': 'date', + 'threat.indicator.marking.tlp': 'keyword', + 'threat.indicator.marking.tlp_version': 'keyword', + 'threat.indicator.modified_at': 'date', + 'threat.indicator.name': 'keyword', + 'threat.indicator.port': 'long', + 'threat.indicator.provider': 'keyword', + 'threat.indicator.reference': 'keyword', + 'threat.indicator.registry.data.bytes': 'keyword', + 'threat.indicator.registry.data.strings': 'wildcard', + 'threat.indicator.registry.data.type': 'keyword', + 'threat.indicator.registry.hive': 'keyword', + 'threat.indicator.registry.key': 'keyword', + 'threat.indicator.registry.path': 'keyword', + 'threat.indicator.registry.value': 'keyword', + 'threat.indicator.scanner_stats': 'long', + 'threat.indicator.sightings': 'long', + 'threat.indicator.type': 'keyword', + 'threat.indicator.url.domain': 'keyword', + 'threat.indicator.url.extension': 'keyword', + 'threat.indicator.url.fragment': 'keyword', + 'threat.indicator.url.full': 'wildcard', + 'threat.indicator.url.original': 'wildcard', + 'threat.indicator.url.password': 'keyword', + 'threat.indicator.url.path': 'wildcard', + 'threat.indicator.url.port': 'long', + 'threat.indicator.url.query': 'keyword', + 'threat.indicator.url.registered_domain': 'keyword', + 'threat.indicator.url.scheme': 'keyword', + 'threat.indicator.url.subdomain': 'keyword', + 'threat.indicator.url.top_level_domain': 'keyword', + 'threat.indicator.url.username': 'keyword', + 'threat.indicator.x509.alternative_names': 'keyword', + 'threat.indicator.x509.issuer.common_name': 'keyword', + 'threat.indicator.x509.issuer.country': 'keyword', + 'threat.indicator.x509.issuer.distinguished_name': 'keyword', + 'threat.indicator.x509.issuer.locality': 'keyword', + 'threat.indicator.x509.issuer.organization': 'keyword', + 'threat.indicator.x509.issuer.organizational_unit': 'keyword', + 'threat.indicator.x509.issuer.state_or_province': 'keyword', + 'threat.indicator.x509.not_after': 'date', + 'threat.indicator.x509.not_before': 'date', + 'threat.indicator.x509.public_key_algorithm': 'keyword', + 'threat.indicator.x509.public_key_curve': 'keyword', + 'threat.indicator.x509.public_key_exponent': 'long', + 'threat.indicator.x509.public_key_size': 'long', + 'threat.indicator.x509.serial_number': 'keyword', + 'threat.indicator.x509.signature_algorithm': 'keyword', + 'threat.indicator.x509.subject.common_name': 'keyword', + 'threat.indicator.x509.subject.country': 'keyword', + 'threat.indicator.x509.subject.distinguished_name': 'keyword', + 'threat.indicator.x509.subject.locality': 'keyword', + 'threat.indicator.x509.subject.organization': 'keyword', + 'threat.indicator.x509.subject.organizational_unit': 'keyword', + 'threat.indicator.x509.subject.state_or_province': 'keyword', + 'threat.indicator.x509.version_number': 'keyword', + 'threat.software.alias': 'keyword', + 'threat.software.id': 'keyword', + 'threat.software.name': 'keyword', + 'threat.software.platforms': 'keyword', + 'threat.software.reference': 'keyword', + 'threat.software.type': 'keyword', + 'threat.tactic.id': 'keyword', + 'threat.tactic.name': 'keyword', + 'threat.tactic.reference': 'keyword', + 'threat.technique.id': 'keyword', + 'threat.technique.name': 'keyword', + 'threat.technique.reference': 'keyword', + 'threat.technique.subtechnique.id': 'keyword', + 'threat.technique.subtechnique.name': 'keyword', + 'threat.technique.subtechnique.reference': 'keyword', + 'tls.cipher': 'keyword', + 'tls.client.certificate': 'keyword', + 'tls.client.certificate_chain': 'keyword', + 'tls.client.hash.md5': 'keyword', + 'tls.client.hash.sha1': 'keyword', + 'tls.client.hash.sha256': 'keyword', + 'tls.client.issuer': 'keyword', + 'tls.client.ja3': 'keyword', + 'tls.client.not_after': 'date', + 'tls.client.not_before': 'date', + 'tls.client.server_name': 'keyword', + 'tls.client.subject': 'keyword', + 'tls.client.supported_ciphers': 'keyword', + 'tls.client.x509.alternative_names': 'keyword', + 'tls.client.x509.issuer.common_name': 'keyword', + 'tls.client.x509.issuer.country': 'keyword', + 'tls.client.x509.issuer.distinguished_name': 'keyword', + 'tls.client.x509.issuer.locality': 'keyword', + 'tls.client.x509.issuer.organization': 'keyword', + 'tls.client.x509.issuer.organizational_unit': 'keyword', + 'tls.client.x509.issuer.state_or_province': 'keyword', + 'tls.client.x509.not_after': 'date', + 'tls.client.x509.not_before': 'date', + 'tls.client.x509.public_key_algorithm': 'keyword', + 'tls.client.x509.public_key_curve': 'keyword', + 'tls.client.x509.public_key_exponent': 'long', + 'tls.client.x509.public_key_size': 'long', + 'tls.client.x509.serial_number': 'keyword', + 'tls.client.x509.signature_algorithm': 'keyword', + 'tls.client.x509.subject.common_name': 'keyword', + 'tls.client.x509.subject.country': 'keyword', + 'tls.client.x509.subject.distinguished_name': 'keyword', + 'tls.client.x509.subject.locality': 'keyword', + 'tls.client.x509.subject.organization': 'keyword', + 'tls.client.x509.subject.organizational_unit': 'keyword', + 'tls.client.x509.subject.state_or_province': 'keyword', + 'tls.client.x509.version_number': 'keyword', + 'tls.curve': 'keyword', + 'tls.established': 'boolean', + 'tls.next_protocol': 'keyword', + 'tls.resumed': 'boolean', + 'tls.server.certificate': 'keyword', + 'tls.server.certificate_chain': 'keyword', + 'tls.server.hash.md5': 'keyword', + 'tls.server.hash.sha1': 'keyword', + 'tls.server.hash.sha256': 'keyword', + 'tls.server.issuer': 'keyword', + 'tls.server.ja3s': 'keyword', + 'tls.server.not_after': 'date', + 'tls.server.not_before': 'date', + 'tls.server.subject': 'keyword', + 'tls.server.x509.alternative_names': 'keyword', + 'tls.server.x509.issuer.common_name': 'keyword', + 'tls.server.x509.issuer.country': 'keyword', + 'tls.server.x509.issuer.distinguished_name': 'keyword', + 'tls.server.x509.issuer.locality': 'keyword', + 'tls.server.x509.issuer.organization': 'keyword', + 'tls.server.x509.issuer.organizational_unit': 'keyword', + 'tls.server.x509.issuer.state_or_province': 'keyword', + 'tls.server.x509.not_after': 'date', + 'tls.server.x509.not_before': 'date', + 'tls.server.x509.public_key_algorithm': 'keyword', + 'tls.server.x509.public_key_curve': 'keyword', + 'tls.server.x509.public_key_exponent': 'long', + 'tls.server.x509.public_key_size': 'long', + 'tls.server.x509.serial_number': 'keyword', + 'tls.server.x509.signature_algorithm': 'keyword', + 'tls.server.x509.subject.common_name': 'keyword', + 'tls.server.x509.subject.country': 'keyword', + 'tls.server.x509.subject.distinguished_name': 'keyword', + 'tls.server.x509.subject.locality': 'keyword', + 'tls.server.x509.subject.organization': 'keyword', + 'tls.server.x509.subject.organizational_unit': 'keyword', + 'tls.server.x509.subject.state_or_province': 'keyword', + 'tls.server.x509.version_number': 'keyword', + 'tls.version': 'keyword', + 'tls.version_protocol': 'keyword', + 'trace.id': 'keyword', + 'transaction.id': 'keyword', + 'url.domain': 'keyword', + 'url.extension': 'keyword', + 'url.fragment': 'keyword', + 'url.full': 'wildcard', + 'url.original': 'wildcard', + 'url.password': 'keyword', + 'url.path': 'wildcard', + 'url.port': 'long', + 'url.query': 'keyword', + 'url.registered_domain': 'keyword', + 'url.scheme': 'keyword', + 'url.subdomain': 'keyword', + 'url.top_level_domain': 'keyword', + 'url.username': 'keyword', + 'user.changes.domain': 'keyword', + 'user.changes.email': 'keyword', + 'user.changes.full_name': 'keyword', + 'user.changes.group.domain': 'keyword', + 'user.changes.group.id': 'keyword', + 'user.changes.group.name': 'keyword', + 'user.changes.hash': 'keyword', + 'user.changes.id': 'keyword', + 'user.changes.name': 'keyword', + 'user.changes.roles': 'keyword', + 'user.domain': 'keyword', + 'user.effective.domain': 'keyword', + 'user.effective.email': 'keyword', + 'user.effective.full_name': 'keyword', + 'user.effective.group.domain': 'keyword', + 'user.effective.group.id': 'keyword', + 'user.effective.group.name': 'keyword', + 'user.effective.hash': 'keyword', + 'user.effective.id': 'keyword', + 'user.effective.name': 'keyword', + 'user.effective.roles': 'keyword', + 'user.email': 'keyword', + 'user.full_name': 'keyword', + 'user.group.domain': 'keyword', + 'user.group.id': 'keyword', + 'user.group.name': 'keyword', + 'user.hash': 'keyword', + 'user.id': 'keyword', + 'user.name': 'keyword', + 'user.risk.calculated_level': 'keyword', + 'user.risk.calculated_score': 'float', + 'user.risk.calculated_score_norm': 'float', + 'user.risk.static_level': 'keyword', + 'user.risk.static_score': 'float', + 'user.risk.static_score_norm': 'float', + 'user.roles': 'keyword', + 'user.target.domain': 'keyword', + 'user.target.email': 'keyword', + 'user.target.full_name': 'keyword', + 'user.target.group.domain': 'keyword', + 'user.target.group.id': 'keyword', + 'user.target.group.name': 'keyword', + 'user.target.hash': 'keyword', + 'user.target.id': 'keyword', + 'user.target.name': 'keyword', + 'user.target.roles': 'keyword', + 'user_agent.device.name': 'keyword', + 'user_agent.name': 'keyword', + 'user_agent.original': 'keyword', + 'user_agent.os.family': 'keyword', + 'user_agent.os.full': 'keyword', + 'user_agent.os.kernel': 'keyword', + 'user_agent.os.name': 'keyword', + 'user_agent.os.platform': 'keyword', + 'user_agent.os.type': 'keyword', + 'user_agent.os.version': 'keyword', + 'user_agent.version': 'keyword', + 'volume.bus_type': 'keyword', + 'volume.default_access': 'keyword', + 'volume.device_name': 'keyword', + 'volume.device_type': 'keyword', + 'volume.dos_name': 'keyword', + 'volume.file_system_type': 'keyword', + 'volume.mount_name': 'keyword', + 'volume.nt_name': 'keyword', + 'volume.product_id': 'keyword', + 'volume.product_name': 'keyword', + 'volume.removable': 'boolean', + 'volume.serial_number': 'keyword', + 'volume.size': 'long', + 'volume.vendor_id': 'keyword', + 'volume.vendor_name': 'keyword', + 'volume.writable': 'boolean', + 'vulnerability.category': 'keyword', + 'vulnerability.classification': 'keyword', + 'vulnerability.description': 'keyword', + 'vulnerability.enumeration': 'keyword', + 'vulnerability.id': 'keyword', + 'vulnerability.reference': 'keyword', + 'vulnerability.report_id': 'keyword', + 'vulnerability.scanner.vendor': 'keyword', + 'vulnerability.score.base': 'float', + 'vulnerability.score.environmental': 'float', + 'vulnerability.score.temporal': 'float', + 'vulnerability.score.version': 'keyword', + 'vulnerability.severity': 'keyword', +}; + +export const ECS_FIELDS: EcsFields = { + 'destination.address': 'Destination network address.', + 'destination.bytes': 'Bytes sent from the destination to the source.', + 'destination.domain': 'The domain name of the destination.', + 'destination.ip': 'IP address of the destination.', + 'destination.mac': 'MAC address of the destination.', + 'destination.packets': 'Packets sent from the destination to the source.', + 'destination.port': 'Port of the destination.', + 'destination.user.domain': 'Name of the directory the user is a member of.', + 'destination.user.email': 'User email address.', + 'destination.user.full_name': 'Users full name, if available.', + 'destination.user.group.domain': 'Name of the directory the group is a member of.', + 'destination.user.group.id': 'Unique identifier for the group on the system/platform.', + 'destination.user.group.name': 'Name of the group.', + 'destination.user.id': 'Unique identifier of the user.', + 'destination.user.name': 'Short name or login of the user.', + 'event.action': 'The action captured by the event.', + 'event.created': 'Time when the event was first read by an agent or by your pipeline.', + 'event.code': 'Identification code for this event.', + 'event.duration': 'Duration of the event in nanoseconds.', + 'event.end': + 'event.end contains the date when the event ended or when the activity was last observed.', + 'event.id': 'Unique ID to describe the event.', + 'event.severity': 'Numeric severity of the event.', + 'file.directory': 'Directory where the file is located.', + 'file.extension': 'File extension, excluding the leading dot.', + 'file.gid': 'Primary group ID (GID) of the file.', + 'file.group': 'Primary group name of the file.', + 'file.hash.md5': 'MD5 hash.', + 'file.hash.sha1': 'SHA1 hash.', + 'file.hash.sha256': 'SHA256 hash.', + 'file.inode': 'Inode representing the file in the filesystem.', + 'file.name': 'Name of the file including the extension, without the directory.', + 'file.path': 'Full path to the file, including the file name.', + 'file.size': 'File size in bytes.', + 'file.uid': 'The user ID (UID) or security identifier (SID) of the file owner.', + 'group.domain': 'Name of the directory the group is a member of.', + 'group.id': 'Unique identifier for the group on the system/platform.', + 'group.name': 'Name of the group.', + 'http.request.method': 'HTTP request method.', + 'http.response.status_code': 'HTTP response status code.', + 'http.version': 'HTTP version.', + 'network.application': 'Application level protocol name.', + 'network.bytes': 'Total bytes transferred in both directions.', + 'network.direction': 'Direction of the network traffic.', + 'network.packets': 'Total packets transferred in both directions.', + 'network.protocol': 'Application protocol name.', + 'network.transport': 'Protocol Name corresponding to the field `iana_number`.', + 'network.type': 'In the OSI Model this would be the Network Layer. ipv4, ipv6, ipsec, pim, etc', + 'organization.id': 'Unique identifier for the organization.', + 'organization.name': 'Organization name.', + 'process.args': 'Array of process arguments.', + 'process.args_count': 'Length of the process.args array.', + 'process.command_line': 'Full command line that started the process.', + 'process.end': 'The time the process ended.', + 'process.executable': 'Absolute path to the process executable.', + 'process.hash.md5': 'MD5 hash.', + 'process.hash.sha1': 'SHA1 hash.', + 'process.hash.sha256': 'SHA256 hash.', + 'process.name': 'Process name.', + 'process.parent.args': 'Array of process arguments.', + 'process.parent.args_count': 'Length of the process.args array.', + 'process.parent.command_line': 'Full command line that started the process.', + 'process.parent.end': 'The time the process ended.', + 'process.parent.executable': 'Absolute path to the process executable.', + 'process.parent.group.id': 'Unique identifier for the group on the system/platform.', + 'process.parent.group.name': 'Name of the group.', + 'process.parent.hash.md5': 'MD5 hash.', + 'process.parent.hash.sha1': 'SHA1 hash.', + 'process.parent.hash.sha256': 'SHA256 hash.', + 'process.parent.name': 'Process name.', + 'process.parent.pgid': 'Deprecated identifier of the group of processes the process belongs to.', + 'process.parent.pid': 'Process id.', + 'process.parent.start': 'The time the process started.', + 'process.parent.thread.id': 'Thread ID.', + 'process.parent.thread.name': 'Thread name.', + 'process.parent.user.id': 'Unique identifier of the user.', + 'process.parent.user.name': 'Short name or login of the user.', + 'process.pgid': 'Deprecated identifier of the group of processes the process belongs to.', + 'process.pid': 'Process id.', + 'process.start': 'The time the process started.', + 'process.thread.id': 'Thread ID.', + 'process.thread.name': 'Thread name.', + 'process.user.id': 'Unique identifier of the user.', + 'process.user.name': 'Short name or login of the user.', + 'rule.author': 'Rule author', + 'rule.category': 'Rule category', + 'rule.description': 'Rule description', + 'rule.id': 'Rule ID', + 'rule.license': 'Rule license', + 'rule.name': 'Rule name', + 'rule.reference': 'Rule reference URL', + 'rule.ruleset': 'Rule ruleset', + 'rule.uuid': 'Rule UUID', + 'rule.version': 'Rule version', + 'source.address': 'Source network address.', + 'source.bytes': 'Bytes sent from the source to the destination.', + 'source.domain': 'The domain name of the source.', + 'source.ip': 'IP address of the source.', + 'source.mac': 'MAC address of the source.', + 'source.packets': 'Packets sent from the source to the destination.', + 'source.port': 'Port of the source.', + 'source.user.domain': 'Name of the directory the user is a member of.', + 'source.user.email': 'User email address.', + 'source.user.full_name': 'Users full name, if available.', + 'source.user.group.domain': 'Name of the directory the group is a member of.', + 'source.user.group.id': 'Unique identifier for the group on the system/platform.', + 'source.user.group.name': 'Name of the group.', + 'source.user.id': 'Unique identifier of the user.', + 'source.user.name': 'Short name or login of the user.', + 'source.user.roles': 'Array of user roles at the time of the event.', + 'tls.server.x509.alternative_names': 'List of subject alternative names (SAN).', + 'tls.server.x509.issuer.common_name': + 'List of common name (CN) of issuing certificate authority.', + 'threat.framework': 'Threat classification framework.', + 'threat.tactic.id': 'Threat tactic id.', + 'threat.tactic.name': 'Threat tactic.', + 'threat.technique.id': 'Threat technique id.', + 'threat.technique.name': 'Threat technique name.', + 'url.domain': 'Domain of the url.', + 'url.extension': 'File extension from the request url, excluding the leading dot.', + 'url.fragment': 'Portion of the url after the `#`.', + 'url.full': 'Full unparsed URL.', + 'url.original': 'Unmodified original url as seen in the event source.', + 'url.path': 'Path of the request, such as "/search".', + 'url.port': 'Port of the request, such as 443.', + 'url.query': 'Query string of the request.', + 'url.scheme': 'Scheme of the url.', + 'user.domain': 'Name of the directory the user is a member of.', + 'user.email': 'User email address.', + 'user.full_name': 'Users full name, if available.', + 'user.group.domain': 'Name of the directory the group is a member of.', + 'user.group.id': 'Unique identifier for the group on the system/platform.', + 'user.group.name': 'Name of the group.', + 'user.id': 'Unique identifier of the user.', + 'user.name': 'Short name or login of the user.', + 'user.roles': 'Array of user roles at the time of the event.', + 'user_agent.original': 'Unparsed user_agent string.', +}; + +export const ECS_EXAMPLE_ANSWER = { + crowdstrike: { + falcon: { + metadata: { + customerIDString: null, + offset: null, + eventType: { + target: 'event.code', + confidence: 0.94, + type: 'string', + date_formats: [], + }, + eventCreationTime: { + target: 'event.created', + confidence: 0.85, + type: 'date', + date_formats: ['UNIX'], + }, + version: null, + event: { + DeviceId: null, + CustomerId: null, + Ipv: { + target: 'network.type', + confidence: 0.99, + type: 'string', + date_formats: [], + }, + CommandLine: { + target: 'process.command_line', + confidence: 0.9, + type: 'string', + date_formats: [], + }, + ConnectionDirection: { + target: 'network.direction', + confidence: 0.9, + type: 'string', + date_formats: [], + }, + EventType: { + target: 'event.action', + confidence: 0.82, + type: 'string', + date_formats: [], + }, + Flags: { Audit: null, Log: null, Monitor: null }, + HostName: { + target: 'host.name', + confidence: 0.82, + type: 'string', + date_formats: [], + }, + LocalAddress: { + target: 'source.address', + confidence: 0.83, + type: 'string', + date_formats: [], + }, + LocalPort: { + target: 'source.port', + confidence: 0.83, + type: 'number', + date_formats: [], + }, + PolicyName: null, + RemoteAddress: { + target: 'destination.address', + confidence: 0.83, + type: 'string', + date_formats: [], + }, + RemotePort: { + target: 'destination.port', + confidence: 0.83, + type: 'number', + date_formats: [], + }, + RuleAction: { + target: 'event.type', + confidence: 0.86, + type: 'string', + date_formats: [], + }, + RuleDescription: { + target: 'rule.description', + confidence: 0.99, + type: 'string', + date_formats: [], + }, + UTCTimestamp: { + target: '@timestamp', + confidence: 0.99, + type: 'string', + date_formats: ['UNIX_MS'], + }, + }, + }, + }, + }, +}; diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/duplicates.test.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/duplicates.test.ts new file mode 100644 index 0000000000000..9270b2453e261 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/duplicates.test.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FakeLLM } from '@langchain/core/utils/testing'; +import { handleDuplicates } from './duplicates'; +import type { EcsMappingState } from '../../types'; +import { ecsTestState } from '../../../__jest__/fixtures/ecs_mapping'; +import { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; + +const mockLlm = new FakeLLM({ + response: '{ "message": "ll callback later."}', +}) as unknown as ActionsClientChatOpenAI | ActionsClientSimpleChatModel; + +const testState: EcsMappingState = ecsTestState; + +describe('Testing ecs handler', () => { + it('handleDuplicates()', async () => { + const response = await handleDuplicates(testState, mockLlm); + expect(response.currentMapping).toStrictEqual({ message: 'll callback later.' }); + expect(response.lastExecutedChain).toBe('duplicateFields'); + }); +}); diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/duplicates.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/duplicates.ts new file mode 100644 index 0000000000000..fd11a660e75ab --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/duplicates.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 type { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; +import { JsonOutputParser } from '@langchain/core/output_parsers'; +import type { EcsMappingState } from '../../types'; +import { ECS_DUPLICATES_PROMPT } from './prompts'; + +export async function handleDuplicates( + state: EcsMappingState, + model: ActionsClientChatOpenAI | ActionsClientSimpleChatModel +) { + const ecsDuplicatesPrompt = ECS_DUPLICATES_PROMPT; + const outputParser = new JsonOutputParser(); + const ecsDuplicatesGraph = ecsDuplicatesPrompt.pipe(model).pipe(outputParser); + + const currentMapping = await ecsDuplicatesGraph.invoke({ + ecs: state.ecs, + current_mapping: JSON.stringify(state.currentMapping, null, 2), + ex_answer: state.exAnswer, + duplicate_fields: state.duplicateFields, + }); + + return { currentMapping, lastExecutedChain: 'duplicateFields' }; +} diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/graph.test.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/graph.test.ts new file mode 100644 index 0000000000000..0ae626924c349 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/graph.test.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FakeLLM } from '@langchain/core/utils/testing'; +import { getEcsGraph } from './graph'; +import { + ecsInitialMappingMockedResponse, + ecsDuplicateMockedResponse, + ecsInvalidMappingMockedResponse, + ecsMissingKeysMockedResponse, + ecsMappingExpectedResults, +} from '../../../__jest__/fixtures/ecs_mapping'; +import { mockedRequest } from '../../../__jest__/fixtures'; +import { handleEcsMapping } from './mapping'; +import { handleDuplicates } from './duplicates'; +import { handleMissingKeys } from './missing'; +import { handleInvalidEcs } from './invalid'; +import { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; + +const mockLlm = new FakeLLM({ + response: "I'll callback later.", +}) as unknown as ActionsClientChatOpenAI | ActionsClientSimpleChatModel; + +jest.mock('./mapping'); +jest.mock('./duplicates'); +jest.mock('./missing'); +jest.mock('./invalid'); + +describe('EcsGraph', () => { + describe('Compiling and Running', () => { + beforeEach(() => { + // Mocked responses for each node that requires an LLM API call/response. + const mockInvokeMapping = jest.fn().mockResolvedValue(ecsInitialMappingMockedResponse); + const mockInvokeDuplicates = jest.fn().mockResolvedValue(ecsDuplicateMockedResponse); + const mockInvokeMissingKeys = jest.fn().mockResolvedValue(ecsMissingKeysMockedResponse); + const mockInvokeInvalidEcs = jest.fn().mockResolvedValue(ecsInvalidMappingMockedResponse); + + // Returns the initial response, with one duplicate field, to trigger the next step. + (handleEcsMapping as jest.Mock).mockImplementation(async () => ({ + currentMapping: await mockInvokeMapping(), + lastExecutedChain: 'ecsMapping', + })); + // Returns the response with the duplicate field removed, but missing one to trigger the next step. + (handleDuplicates as jest.Mock).mockImplementation(async () => ({ + currentMapping: await mockInvokeDuplicates(), + lastExecutedChain: 'duplicateFields', + })); + + // Returns the response with the missing field added, but invalid ECS field to trigger the next step. + (handleMissingKeys as jest.Mock).mockImplementation(async () => ({ + currentMapping: await mockInvokeMissingKeys(), + lastExecutedChain: 'missingKeys', + })); + + // Returns the response with the invalid ECS field fixed, which finishes the chain. + (handleInvalidEcs as jest.Mock).mockImplementation(async () => ({ + currentMapping: await mockInvokeInvalidEcs(), + lastExecutedChain: 'invalidEcs', + })); + }); + it('Ensures that the graph compiles', async () => { + // When getEcsGraph runs, langgraph compiles the graph it will error if the graph has any issues. + // Common issues for example detecting a node has no next step, or there is a infinite loop between them. + try { + await getEcsGraph(mockLlm); + } catch (error) { + fail(`getEcsGraph threw an error: ${error}`); + } + }); + it('Runs the whole graph, with mocked outputs from the LLM.', async () => { + // The mocked outputs are specifically crafted to trigger ALL different conditions, allowing us to test the whole graph. + // This is why we have all the expects ensuring each function was called. + + const ecsGraph = await getEcsGraph(mockLlm); + const response = await ecsGraph.invoke(mockedRequest); + expect(response.results).toStrictEqual(ecsMappingExpectedResults); + + // Check if the functions were called + expect(handleEcsMapping).toHaveBeenCalled(); + expect(handleDuplicates).toHaveBeenCalled(); + expect(handleMissingKeys).toHaveBeenCalled(); + expect(handleInvalidEcs).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/graph.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/graph.ts new file mode 100644 index 0000000000000..2c8e7283d4728 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/graph.ts @@ -0,0 +1,174 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; +import type { StateGraphArgs } from '@langchain/langgraph'; +import { END, START, StateGraph } from '@langchain/langgraph'; +import type { EcsMappingState } from '../../types'; +import { mergeSamples, modifySamples } from '../../util/samples'; +import { ECS_EXAMPLE_ANSWER, ECS_FIELDS } from './constants'; +import { handleDuplicates } from './duplicates'; +import { handleInvalidEcs } from './invalid'; +import { handleEcsMapping } from './mapping'; +import { handleMissingKeys } from './missing'; +import { createPipeline } from './pipeline'; +import { handleValidateMappings } from './validate'; + +const graphState: StateGraphArgs['channels'] = { + ecs: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + lastExecutedChain: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + rawSamples: { + value: (x: string[], y?: string[]) => y ?? x, + default: () => [], + }, + samples: { + value: (x: string[], y?: string[]) => y ?? x, + default: () => [], + }, + formattedSamples: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + exAnswer: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + packageName: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + dataStreamName: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + finalized: { + value: (x: boolean, y?: boolean) => y ?? x, + default: () => false, + }, + currentMapping: { + value: (x: object, y?: object) => y ?? x, + default: () => ({}), + }, + currentPipeline: { + value: (x: object, y?: object) => y ?? x, + default: () => ({}), + }, + duplicateFields: { + value: (x: string[], y?: string[]) => y ?? x, + default: () => [], + }, + missingKeys: { + value: (x: string[], y?: string[]) => y ?? x, + default: () => [], + }, + invalidEcsFields: { + value: (x: string[], y?: string[]) => y ?? x, + default: () => [], + }, + results: { + value: (x: object, y?: object) => y ?? x, + default: () => ({}), + }, + logFormat: { + value: (x: string, y?: string) => y ?? x, + default: () => 'json', + }, + ecsVersion: { + value: (x: string, y?: string) => y ?? x, + default: () => '8.11.0', + }, +}; + +function modelInput(state: EcsMappingState): Partial { + const samples = modifySamples(state); + const formattedSamples = mergeSamples(samples); + return { + exAnswer: JSON.stringify(ECS_EXAMPLE_ANSWER, null, 2), + ecs: JSON.stringify(ECS_FIELDS, null, 2), + samples, + finalized: false, + formattedSamples, + lastExecutedChain: 'modelInput', + }; +} + +function modelOutput(state: EcsMappingState): Partial { + const currentPipeline = createPipeline(state); + return { + finalized: true, + lastExecutedChain: 'modelOutput', + results: { + mapping: state.currentMapping, + pipeline: currentPipeline, + }, + }; +} + +function inputRouter(state: EcsMappingState): string { + if (Object.keys(state.currentMapping).length === 0) { + return 'ecsMapping'; + } + return 'modelOutput'; +} + +function chainRouter(state: EcsMappingState): string { + if (Object.keys(state.duplicateFields).length > 0) { + return 'duplicateFields'; + } + if (Object.keys(state.missingKeys).length > 0) { + return 'missingKeys'; + } + if (Object.keys(state.invalidEcsFields).length > 0) { + return 'invalidEcsFields'; + } + if (!state.finalized) { + return 'modelOutput'; + } + return END; +} + +export async function getEcsGraph(model: ActionsClientChatOpenAI | ActionsClientSimpleChatModel) { + const workflow = new StateGraph({ + channels: graphState, + }) + .addNode('modelInput', modelInput) + .addNode('modelOutput', modelOutput) + .addNode('handleEcsMapping', (state: EcsMappingState) => handleEcsMapping(state, model)) + .addNode('handleValidation', handleValidateMappings) + .addNode('handleDuplicates', (state: EcsMappingState) => handleDuplicates(state, model)) + .addNode('handleMissingKeys', (state: EcsMappingState) => handleMissingKeys(state, model)) + .addNode('handleInvalidEcs', (state: EcsMappingState) => handleInvalidEcs(state, model)) + .addEdge(START, 'modelInput') + .addEdge('modelOutput', END) + .addEdge('handleEcsMapping', 'handleValidation') + .addEdge('handleDuplicates', 'handleValidation') + .addEdge('handleMissingKeys', 'handleValidation') + .addEdge('handleInvalidEcs', 'handleValidation') + .addConditionalEdges('modelInput', inputRouter, { + ecsMapping: 'handleEcsMapping', + modelOutput: 'modelOutput', + }) + .addConditionalEdges('handleValidation', chainRouter, { + duplicateFields: 'handleDuplicates', + missingKeys: 'handleMissingKeys', + invalidEcsFields: 'handleInvalidEcs', + modelOutput: 'modelOutput', + }); + + const compiledEcsGraph = workflow.compile(); + + return compiledEcsGraph; +} diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/index.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/index.ts new file mode 100644 index 0000000000000..91ea9fed3b3d3 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export { getEcsGraph } from './graph'; diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/invalid.test.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/invalid.test.ts new file mode 100644 index 0000000000000..ce1f76ce7a721 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/invalid.test.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FakeLLM } from '@langchain/core/utils/testing'; +import { handleInvalidEcs } from './invalid'; +import type { EcsMappingState } from '../../types'; +import { ecsTestState } from '../../../__jest__/fixtures/ecs_mapping'; +import { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; + +const mockLlm = new FakeLLM({ + response: '{ "message": "ll callback later."}', +}) as unknown as ActionsClientChatOpenAI | ActionsClientSimpleChatModel; + +const testState: EcsMappingState = ecsTestState; + +describe('Testing ecs handlers', () => { + it('handleInvalidEcs()', async () => { + const response = await handleInvalidEcs(testState, mockLlm); + expect(response.currentMapping).toStrictEqual({ message: 'll callback later.' }); + expect(response.lastExecutedChain).toBe('invalidEcs'); + }); +}); diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/invalid.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/invalid.ts new file mode 100644 index 0000000000000..dcbba0ebe9d13 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/invalid.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 { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; +import { JsonOutputParser } from '@langchain/core/output_parsers'; +import type { EcsMappingState } from '../../types'; +import { ECS_INVALID_PROMPT } from './prompts'; + +export async function handleInvalidEcs( + state: EcsMappingState, + model: ActionsClientChatOpenAI | ActionsClientSimpleChatModel +) { + const ecsInvalidEcsPrompt = ECS_INVALID_PROMPT; + const outputParser = new JsonOutputParser(); + const ecsInvalidEcsGraph = ecsInvalidEcsPrompt.pipe(model).pipe(outputParser); + + const currentMapping = await ecsInvalidEcsGraph.invoke({ + ecs: state.ecs, + current_mapping: JSON.stringify(state.currentMapping, null, 2), + ex_answer: state.exAnswer, + formatted_samples: state.formattedSamples, + invalid_ecs_fields: state.invalidEcsFields, + }); + + return { currentMapping, lastExecutedChain: 'invalidEcs' }; +} diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/mapping.test.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/mapping.test.ts new file mode 100644 index 0000000000000..dbbfc0608d010 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/mapping.test.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FakeLLM } from '@langchain/core/utils/testing'; +import { handleEcsMapping } from './mapping'; +import type { EcsMappingState } from '../../types'; +import { ecsTestState } from '../../../__jest__/fixtures/ecs_mapping'; +import { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; + +const mockLlm = new FakeLLM({ + response: '{ "message": "ll callback later."}', +}) as unknown as ActionsClientChatOpenAI | ActionsClientSimpleChatModel; + +const testState: EcsMappingState = ecsTestState; + +describe('Testing ecs handler', () => { + it('handleEcsMapping()', async () => { + const response = await handleEcsMapping(testState, mockLlm); + expect(response.currentMapping).toStrictEqual({ message: 'll callback later.' }); + expect(response.lastExecutedChain).toBe('ecsMapping'); + }); +}); diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/mapping.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/mapping.ts new file mode 100644 index 0000000000000..7ecb108659f45 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/mapping.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 { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; +import { JsonOutputParser } from '@langchain/core/output_parsers'; +import type { EcsMappingState } from '../../types'; +import { ECS_MAIN_PROMPT } from './prompts'; + +export async function handleEcsMapping( + state: EcsMappingState, + model: ActionsClientChatOpenAI | ActionsClientSimpleChatModel +) { + const ecsMainPrompt = ECS_MAIN_PROMPT; + const outputParser = new JsonOutputParser(); + const ecsMainGraph = ecsMainPrompt.pipe(model).pipe(outputParser); + + const currentMapping = await ecsMainGraph.invoke({ + ecs: state.ecs, + formatted_samples: state.formattedSamples, + package_name: state.packageName, + data_stream_name: state.dataStreamName, + ex_answer: state.exAnswer, + }); + + return { currentMapping, lastExecutedChain: 'ecsMapping' }; +} diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/missing.test.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/missing.test.ts new file mode 100644 index 0000000000000..b369d28b1e177 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/missing.test.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FakeLLM } from '@langchain/core/utils/testing'; +import { handleMissingKeys } from './missing'; +import type { EcsMappingState } from '../../types'; +import { ecsTestState } from '../../../__jest__/fixtures/ecs_mapping'; +import { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; + +const mockLlm = new FakeLLM({ + response: '{ "message": "ll callback later."}', +}) as unknown as ActionsClientChatOpenAI | ActionsClientSimpleChatModel; + +const testState: EcsMappingState = ecsTestState; + +describe('Testing ecs handler', () => { + it('handleMissingKeys()', async () => { + const response = await handleMissingKeys(testState, mockLlm); + expect(response.currentMapping).toStrictEqual({ message: 'll callback later.' }); + expect(response.lastExecutedChain).toBe('missingKeys'); + }); +}); diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/missing.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/missing.ts new file mode 100644 index 0000000000000..d7f1f65b2b4ea --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/missing.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 { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; +import { JsonOutputParser } from '@langchain/core/output_parsers'; +import type { EcsMappingState } from '../../types'; +import { ECS_MISSING_KEYS_PROMPT } from './prompts'; + +export async function handleMissingKeys( + state: EcsMappingState, + model: ActionsClientChatOpenAI | ActionsClientSimpleChatModel +) { + const ecsMissingPrompt = ECS_MISSING_KEYS_PROMPT; + const outputParser = new JsonOutputParser(); + const ecsMissingGraph = ecsMissingPrompt.pipe(model).pipe(outputParser); + + const currentMapping = await ecsMissingGraph.invoke({ + ecs: state.ecs, + current_mapping: JSON.stringify(state.currentMapping, null, 2), + ex_answer: state.exAnswer, + formatted_samples: state.formattedSamples, + missing_keys: state?.missingKeys, + }); + + return { currentMapping, lastExecutedChain: 'missingKeys' }; +} diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/pipeline.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/pipeline.ts new file mode 100644 index 0000000000000..c4ad10f47a9f2 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/pipeline.ts @@ -0,0 +1,177 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { load } from 'js-yaml'; +import { Environment, FileSystemLoader } from 'nunjucks'; +import { join as joinPath } from 'path'; +import type { EcsMappingState } from '../../types'; +import { ECS_TYPES } from './constants'; + +interface IngestPipeline { + [key: string]: unknown; +} + +interface ECSField { + target: string; + confidence: number; + date_formats: string[]; + type: string; +} + +function generateProcessor( + currentPath: string, + ecsField: ECSField, + expectedEcsType: string, + sampleValue: unknown +): object { + if (needsTypeConversion(sampleValue, expectedEcsType)) { + return { + convert: { + field: currentPath, + target_field: ecsField.target, + type: getConvertProcessorType(expectedEcsType), + ignore_missing: true, + }, + }; + } + + if (ecsField.type === 'date') { + return { + date: { + field: currentPath, + target_field: ecsField.target, + formats: ecsField.date_formats, + if: currentPath.replace(/\./g, '?.'), + }, + }; + } + + return { + rename: { + field: currentPath, + target_field: ecsField.target, + ignore_missing: true, + }, + }; +} + +function getSampleValue(key: string, samples: Record): unknown { + const keyList = key.split('.'); + let value: any = samples; + for (const k of keyList) { + if (value === undefined || value === null) { + return null; + } + value = value[k]; + } + return value; +} + +function getEcsType(ecsField: ECSField, ecsTypes: Record): string { + const ecsTarget = ecsField.target; + return ecsTypes[ecsTarget]; +} + +function getConvertProcessorType(expectedEcsType: string): string { + if (expectedEcsType === 'long') { + return 'long'; + } + if (['scaled_float', 'float'].includes(expectedEcsType)) { + return 'float'; + } + if (expectedEcsType === 'ip') { + return 'ip'; + } + if (expectedEcsType === 'boolean') { + return 'boolean'; + } + return 'string'; +} + +function needsTypeConversion(sample: unknown, expected: string): boolean { + if (sample === null || sample === undefined) { + return false; + } + + if (expected === 'ip') { + return true; + } + + if (expected === 'boolean' && typeof sample !== 'boolean') { + return true; + } + + if (['long', 'float', 'scaled_float'].includes(expected) && typeof sample !== 'number') { + return true; + } + + if ( + ['keyword', 'wildcard', 'match_only_text', 'constant_keyword'].includes(expected) && + !(typeof sample === 'string' || Array.isArray(sample)) + ) { + return true; + } + + // If types are anything but the above, we return false. Example types: + // "nested", "flattened", "object", "geopoint", "date" + return false; +} + +function generateProcessors(ecsMapping: object, samples: object, basePath: string = ''): object[] { + const ecsTypes = ECS_TYPES; + const valueFieldKeys = new Set(['target', 'confidence', 'date_formats', 'type']); + const results: object[] = []; + + for (const [key, value] of Object.entries(ecsMapping)) { + const currentPath = basePath ? `${basePath}.${key}` : key; + + if (value !== null && typeof value === 'object' && value?.target !== null) { + const valueKeys = new Set(Object.keys(value)); + if ([...valueFieldKeys].every((k) => valueKeys.has(k))) { + const processor = generateProcessor( + currentPath, + value as ECSField, + getEcsType(value as ECSField, ecsTypes), + getSampleValue(currentPath, samples) + ); + results.push(processor); + } else { + results.push(...generateProcessors(value, samples, currentPath)); + } + } + } + return results; +} + +export function createPipeline(state: EcsMappingState): IngestPipeline { + const samples = JSON.parse(state.formattedSamples); + + const processors = generateProcessors(state.currentMapping, samples); + // Retrieve all source field names from convert processors to populate single remove processor: + const fieldsToRemove = processors + .map((p: any) => p.convert?.field) + .filter((f: unknown) => f != null); + const mappedValues = { + processors, + ecs_version: state.ecsVersion, + package_name: state.packageName, + data_stream_name: state.dataStreamName, + log_format: state.logFormat, + fields_to_remove: fieldsToRemove, + }; + const templatesPath = joinPath(__dirname, '../../templates'); + const env = new Environment(new FileSystemLoader(templatesPath), { + autoescape: false, + }); + env.addFilter('startswith', function (str, prefix) { + return str.startsWith(prefix); + }); + const template = env.getTemplate('pipeline.yml.njk'); + const renderedTemplate = template.render(mappedValues); + const ingestPipeline = load(renderedTemplate) as IngestPipeline; + return ingestPipeline; +} diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/prompts.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/prompts.ts new file mode 100644 index 0000000000000..f336b2cde4b48 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/prompts.ts @@ -0,0 +1,183 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ChatPromptTemplate } from '@langchain/core/prompts'; +export const ECS_MAIN_PROMPT = ChatPromptTemplate.fromMessages([ + [ + 'system', + `You are a helpful, expert assistant in Elastic Common Schema (ECS), focusing only on helping users with translating their provided combined samples to Elastic Common Schema (ECS). + +Here is some context for you to reference for your task, read it carefully as you will get questions about it later: + + +{ecs} + + +{formatted_samples} + +`, + ], + [ + 'human', + `Looking at the combined sample from {package_name} {data_stream_name} provided above. The combined sample is a JSON object that includes all unique fields from the log samples sent by {package_name} {data_stream_name}. + +Go through each value step by step and modify it with the following process: +1. Check if the name of each key and its current value matches the description and usecase of any of the above ECS fields. +2. If one or more relevant ECS field is found, pick the one you are most confident about. +3. If no relevant ECS field is found, the value should just be replaced with "null" rather than a new object. +4. Only if a relevant ECS field is found replace the value with a new object that has the keys "target", "confidence", "date_format" and "type". +5. The object key "target" should be set to be the full path of the ECS field name you think it matches. Set the object key "type" to be either "string", "boolean", "number" or "date" depending on what was detected as the example value. +6. If the type "date" is used, then set date_format to be an array of one or more of the equivilant JAVA date formats that fits the example value, including those with nanosecond precision. If the type is not date then date_format should be set to an empty array []. +7. Use a custom date pattern if the built-in date format patterns do not match the example value , including those with nanosecond precision. +8. For each key that you set a target ECS field, also score the confidence you have in that the target field is correct, use a float between 0.0 and 1.0 and set the value in the nested "confidence" key. +9. When you want to use an ECS field as a value for a target, but another field already has the same ECS field as its target, try to find another fitting ECS field. If none is found then the one you are least confident about should have the object replaced with null. +10. If you are not confident for a specific field, you should always set the value to null. +11. These {package_name} log samples are based on source and destination type data, prioritize these compared to other related ECS fields like host.* and observer.*. + +You ALWAYS follow these guidelines when writing your response: + +- Never use \`event.category\` or \`event.type\` as target ECS fields. +- The target key should never have a null value, if no matching target ECS field is found, the whole key value should be set to null. +- Never use the same ECS target multiple times. If no other field is found that you are confident in, it should always be null. +- All keys should be under the {package_name} {data_stream_name} parent fields, same as the original combined sample above. +- All target key values should be ECS field names only from the above ECS fields provided as context. +- All original keys from the combined sample object needs to be in your response. +- Only when a target value is set should type, date_format and confidence be filled out. If no target value then the value should simply be null. +- Do not respond with anything except the ecs maping JSON object enclosed with 3 backticks (\`), see example response below. + + +Example response format: + +A: Please find the JSON object below: +\`\`\`json +{ex_answer} +\`\`\` +"`, + ], + ['ai', 'Please find the JSON object below:'], +]); + +export const ECS_INVALID_PROMPT = ChatPromptTemplate.fromMessages([ + [ + 'system', + `You are a helpful, expert assistant in Elastic Common Schema (ECS), you help review and try to resolve incorrect field mappings. + +Here is some context for you to reference your task, read it carefully as you will get questions about it later: + + +{ecs} + + +{formatted_samples} + + +{current_mapping} + +`, + ], + [ + 'human', + `The following fields are mapped incorrectly in the current mapping, please help me resolve this: + +{invalid_ecs_fields} + +To resolve the invalid ecs fields, go through each key and value defined in the invalid fields, and modify the current mapping step by step, and ensure they follow these guidelines: + +- Update the provided current mapping object, the value should be the corresponding Elastic Common Schema field name. If no good or valid match is found the value should always be null. +- Do not respond with anything except the updated current mapping JSON object enclosed with 3 backticks (\`). See example response below. + + +Example response format: + +A: Please find the JSON object below: +\`\`\`json +{ex_answer} +\`\`\` +`, + ], + ['ai', 'Please find the JSON object below:'], +]); + +export const ECS_MISSING_KEYS_PROMPT = ChatPromptTemplate.fromMessages([ + [ + 'system', + `You are a helpful, expert assistant in Elastic Common Schema (ECS), you help review and try to resolve missing fields in the current mapping. + +Here is some context for you to reference for your task, read it carefully as you will get questions about it later: + + +{ecs} + + +{formatted_samples} + + +{current_mapping} + +`, + ], + [ + 'human', + `The following keys are missing from the current mapping: + +{missing_keys} + + +Help resolve the issue by adding the missing keys, look up example values from the formatted samples, and go through each missing key step by step, resolve it by following these guidelines: + +- Update the provided current mapping object with all the missing keys, the value should be the corresponding Elastic Common Schema field name. If no good match is found the value should always be null. +- Do not respond with anything except the updated current mapping JSON object enclosed with 3 backticks (\`). See example response below. + + +Example response format: + +A: Please find the JSON object below: +\`\`\`json +{ex_answer} +\`\`\` +`, + ], + ['ai', 'Please find the JSON object below:'], +]); + +export const ECS_DUPLICATES_PROMPT = ChatPromptTemplate.fromMessages([ + [ + 'system', + `You are a helpful, expert assistant in Elastic Common Schema (ECS), you help review and try to resolve incorrect duplicate fields in the current mapping. + +Here is some context for you to reference for your task, read it carefully as you will get questions about it later: + + +{ecs} + + +{current_mapping} + +`, + ], + [ + 'human', + `The following duplicate fields are mapped to the same ECS fields in the current mapping, please help me resolve this: + +{duplicate_fields} + + +To resolve the duplicate mappings, go through each key and value defined in the duplicate fields, and modify the current mapping step by step, and ensure they follow these guidelines: + +- Multiple keys should not have the same value (ECS field it will be mapped to). If multiple keys do have the same value then always choose the best match for the ECS field, while the other duplicates should have their value changed to null. +- Do not respond with anything except the updated current mapping JSON object enclosed with 3 backticks (\`). See example response below. + + +Example response format: + +A: Please find the JSON object below: +\`\`\`json +{ex_answer} +\`\`\` +`, + ], + ['ai', 'Please find the JSON object below:'], +]); diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/validate.test.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/validate.test.ts new file mode 100644 index 0000000000000..1fac2a93ce53c --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/validate.test.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { processMapping } from './validate'; + +describe('Testing ecs handler', () => { + it('processMapping()', async () => { + const path: string[] = []; + const value = { + checkpoint: { + firewall: { + product: null, + sequencenum: null, + subject: null, + ifdir: null, + origin: { + target: 'source.address', + confidence: 0.9, + type: 'string', + date_formats: [], + }, + flags: null, + sendtotrackerasadvancedauditlog: null, + originsicname: null, + version: null, + administrator: { + target: 'user.name', + confidence: 0.8, + type: 'string', + date_formats: [], + }, + foo: { + target: null, // Invalid value , to be skipped + confidence: 0.8, + type: 'string', + date_formats: [], + }, + }, + }, + }; + const output: Record = {}; + await processMapping(path, value, output); + expect(output).toEqual({ + 'source.address': [['checkpoint', 'firewall', 'origin']], + 'user.name': [['checkpoint', 'firewall', 'administrator']], + }); + }); +}); diff --git a/x-pack/plugins/integration_assistant/server/graphs/ecs/validate.ts b/x-pack/plugins/integration_assistant/server/graphs/ecs/validate.ts new file mode 100644 index 0000000000000..15d3db7c95551 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/ecs/validate.ts @@ -0,0 +1,160 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { ECS_FULL } from '../../../common/ecs'; +import type { EcsMappingState } from '../../types'; + +const valueFieldKeys = new Set(['target', 'confidence', 'date_formats', 'type']); +type AnyObject = Record; + +function extractKeys(data: AnyObject, prefix: string = ''): Set { + const keys = new Set(); + + for (const [key, value] of Object.entries(data)) { + const fullKey = prefix ? `${prefix}.${key}` : key; + + if (Array.isArray(value)) { + // Directly add the key for arrays without iterating over elements + keys.add(fullKey); + } else if (typeof value === 'object' && value !== null) { + const valueKeys = new Set(Object.keys(value)); + + if ([...valueFieldKeys].every((k) => valueKeys.has(k))) { + keys.add(fullKey); + } else { + // Recursively extract keys if the current value is a nested object + for (const nestedKey of extractKeys(value, fullKey)) { + keys.add(nestedKey); + } + } + } else { + // Add the key if the value is not an object or is null + keys.add(fullKey); + } + } + + return keys; +} + +function findMissingFields(formattedSamples: string, ecsMapping: AnyObject): string[] { + const combinedSamples = JSON.parse(formattedSamples); + const uniqueKeysFromSamples = extractKeys(combinedSamples); + const ecsResponseKeys = extractKeys(ecsMapping); + + const missingKeys = [...uniqueKeysFromSamples].filter((key) => !ecsResponseKeys.has(key)); + return missingKeys; +} + +export function processMapping( + path: string[], + value: any, + output: Record +): void { + if (typeof value === 'object' && value !== null) { + if (!Array.isArray(value)) { + // If the value is a dict with all the keys returned for each source field, this is the full path of the field. + const valueKeys = new Set(Object.keys(value)); + + if ([...valueFieldKeys].every((k) => valueKeys.has(k))) { + if (value?.target !== null) { + if (!output[value?.target]) { + output[value.target] = []; + } + output[value.target].push(path); + } + } else { + // Regular dictionary, continue traversing + for (const [k, v] of Object.entries(value)) { + processMapping([...path, k], v, output); + } + } + } else { + // If the value is an array, iterate through items and process them + for (const item of value) { + if (typeof item === 'object' && item !== null) { + processMapping(path, item, output); + } + } + } + } else if (value !== null) { + // Direct value, accumulate path + if (!output[value]) { + output[value] = []; + } + output[value].push(path); + } +} + +function getValueFromPath(obj: AnyObject, path: string[]): unknown { + return path.reduce((acc, key) => (acc && acc[key] !== undefined ? acc[key] : null), obj); +} + +function findDuplicateFields(samples: string[], ecsMapping: AnyObject): string[] { + const parsedSamples = samples.map((sample) => JSON.parse(sample)); + const results: string[] = []; + const output: Record = {}; + + // Get all keys for each target ECS mapping field + processMapping([], ecsMapping, output); + + // Filter out any ECS field that does not have multiple source fields mapped to it + const filteredOutput = Object.fromEntries( + Object.entries(output).filter(([_, paths]) => paths.length > 1 && _ !== null) + ); + + // For each ECS field where value is the ECS field and paths is the array of source field names + for (const [value, paths] of Object.entries(filteredOutput)) { + // For each log sample, checking if more than 1 source field exists in the same sample + for (const sample of parsedSamples) { + const foundPaths = paths.filter((path) => getValueFromPath(sample, path) !== null); + if (foundPaths.length > 1) { + const matchingFields = foundPaths.map((p) => p.join('.')); + results.push( + `One or more samples have matching fields for ECS field '${value}': ${matchingFields.join( + ', ' + )}` + ); + break; + } + } + } + + return results; +} + +// Function to find invalid ECS fields +function findInvalidEcsFields(ecsMapping: AnyObject): string[] { + const results: string[] = []; + const output: Record = {}; + const ecsDict = ECS_FULL; + + processMapping([], ecsMapping, output); + const filteredOutput = Object.fromEntries( + Object.entries(output).filter(([key, _]) => key !== null) + ); + + for (const [ecsValue, paths] of Object.entries(filteredOutput)) { + if (!Object.prototype.hasOwnProperty.call(ecsDict, ecsValue)) { + const field = paths.map((p) => p.join('.')); + results.push(`Invalid ECS field mapping identified for ${ecsValue} : ${field.join(', ')}`); + } + } + + return results; +} + +export function handleValidateMappings(state: EcsMappingState): AnyObject { + const missingKeys = findMissingFields(state?.formattedSamples, state?.currentMapping); + const duplicateFields = findDuplicateFields(state?.samples, state?.currentMapping); + const invalidEcsFields = findInvalidEcsFields(state?.currentMapping); + return { + missingKeys, + duplicateFields, + invalidEcsFields, + lastExecutedChain: 'validateMappings', + }; +} diff --git a/x-pack/plugins/integration_assistant/server/graphs/related/constants.ts b/x-pack/plugins/integration_assistant/server/graphs/related/constants.ts new file mode 100644 index 0000000000000..61cc877667659 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/related/constants.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export const RELATED_ECS_FIELDS = { + 'related.hash': { + type: 'keyword', + description: 'All the hashes seen in the docs', + note: 'this field should contain an array of values', + }, + 'related.hosts': { + type: 'keyword', + description: 'All hostnames or other host identifiers seen in the docs', + note: 'this field should contain an array of values', + }, + 'related.ip': { + type: 'keyword', + description: 'All of the IPs seen in the docs', + note: 'this field should contain an array of values', + }, + 'related.user': { + type: 'keyword', + description: 'All the user names or other user identifiers seen in the docs', + note: 'this field should contain an array of values', + }, +}; + +export const RELATED_EXAMPLE_ANSWER = [ + { + append: { + field: 'related.ip', + value: ['{{{source.ip}}}'], + allow_duplicates: 'false', + }, + }, + { + append: { + field: 'related.user', + value: ['{{{server.user.name}}}'], + allow_duplicates: 'false', + }, + }, + { + append: { + field: 'related.hosts', + value: ['{{{client.domain}}}'], + allow_duplicates: 'false', + }, + }, + { + append: { + field: 'related.hash', + value: ['{{{file.hash.sha1}}}'], + allow_duplicates: 'false', + }, + }, +]; diff --git a/x-pack/plugins/integration_assistant/server/graphs/related/errors.test.ts b/x-pack/plugins/integration_assistant/server/graphs/related/errors.test.ts new file mode 100644 index 0000000000000..24dc4365dcbff --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/related/errors.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FakeLLM } from '@langchain/core/utils/testing'; +import { handleErrors } from './errors'; +import type { RelatedState } from '../../types'; +import { + relatedTestState, + relatedMockProcessors, + relatedExpectedHandlerResponse, +} from '../../../__jest__/fixtures/related'; +import { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; + +const mockLlm = new FakeLLM({ + response: JSON.stringify(relatedMockProcessors, null, 2), +}) as unknown as ActionsClientChatOpenAI | ActionsClientSimpleChatModel; + +const testState: RelatedState = relatedTestState; + +describe('Testing related handler', () => { + it('handleErrors()', async () => { + const response = await handleErrors(testState, mockLlm); + expect(response.currentPipeline).toStrictEqual(relatedExpectedHandlerResponse.currentPipeline); + expect(response.lastExecutedChain).toBe('error'); + }); +}); diff --git a/x-pack/plugins/integration_assistant/server/graphs/related/errors.ts b/x-pack/plugins/integration_assistant/server/graphs/related/errors.ts new file mode 100644 index 0000000000000..025422008c4dc --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/related/errors.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; +import { JsonOutputParser } from '@langchain/core/output_parsers'; +import type { ESProcessorItem, Pipeline } from '../../../common'; +import type { RelatedState } from '../../types'; +import { combineProcessors } from '../../util/processors'; +import { RELATED_ERROR_PROMPT } from './prompts'; + +export async function handleErrors( + state: RelatedState, + model: ActionsClientChatOpenAI | ActionsClientSimpleChatModel +) { + const relatedErrorPrompt = RELATED_ERROR_PROMPT; + const outputParser = new JsonOutputParser(); + const relatedErrorGraph = relatedErrorPrompt.pipe(model).pipe(outputParser); + + const currentProcessors = (await relatedErrorGraph.invoke({ + current_processors: JSON.stringify(state.currentProcessors, null, 2), + ex_answer: state.exAnswer, + errors: JSON.stringify(state.errors, null, 2), + package_name: state.packageName, + data_stream_name: state.dataStreamName, + })) as ESProcessorItem[]; + + const currentPipeline = combineProcessors(state.initialPipeline as Pipeline, currentProcessors); + return { + currentPipeline, + currentProcessors, + reviewed: false, + lastExecutedChain: 'error', + }; +} diff --git a/x-pack/plugins/integration_assistant/server/graphs/related/graph.test.ts b/x-pack/plugins/integration_assistant/server/graphs/related/graph.test.ts new file mode 100644 index 0000000000000..40989e9733800 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/related/graph.test.ts @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IScopedClusterClient } from '@kbn/core/server'; +import { FakeLLM } from '@langchain/core/utils/testing'; +import { getRelatedGraph } from './graph'; +import { + relatedExpectedResults, + relatedErrorMockedResponse, + relatedInitialMockedResponse, + relatedReviewMockedResponse, + relatedInitialPipeline, + testPipelineError, + testPipelineValidResult, +} from '../../../__jest__/fixtures/related'; +import { mockedRequestWithPipeline } from '../../../__jest__/fixtures'; +import { handleReview } from './review'; +import { handleRelated } from './related'; +import { handleErrors } from './errors'; +import { testPipeline, combineProcessors } from '../../util'; +import { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; + +const mockLlm = new FakeLLM({ + response: "I'll callback later.", +}) as unknown as ActionsClientChatOpenAI | ActionsClientSimpleChatModel; + +jest.mock('./errors'); +jest.mock('./review'); +jest.mock('./related'); + +jest.mock('../../util/pipeline', () => ({ + testPipeline: jest.fn(), +})); + +describe('runRelatedGraph', () => { + const mockClient = { + asCurrentUser: { + indices: { + getMapping: jest.fn(), + }, + }, + } as unknown as IScopedClusterClient; + beforeEach(() => { + // Mocked responses for each node that requires an LLM API call/response. + const mockInvokeRelated = jest.fn().mockResolvedValue(relatedInitialMockedResponse); + const mockInvokeError = jest.fn().mockResolvedValue(relatedErrorMockedResponse); + const mockInvokeReview = jest.fn().mockResolvedValue(relatedReviewMockedResponse); + + // After this is triggered, the mock of TestPipeline will trigger the expected error, to route to error handler + (handleRelated as jest.Mock).mockImplementation(async () => ({ + currentPipeline: relatedInitialPipeline, + currentProcessors: await mockInvokeRelated(), + reviewed: false, + finalized: false, + lastExecutedChain: 'related', + })); + // Error pipeline returns the correct response to trigger a review. + (handleErrors as jest.Mock).mockImplementation(async () => ({ + currentPipeline: relatedInitialPipeline, + currentProcessors: await mockInvokeError(), + reviewed: false, + finalized: false, + lastExecutedChain: 'error', + })); + // After the review it should route to modelOutput and finish. + (handleReview as jest.Mock).mockImplementation(async () => { + const currentProcessors = await mockInvokeReview(); + const currentPipeline = combineProcessors(relatedInitialPipeline, currentProcessors); + return { + currentProcessors, + currentPipeline, + reviewed: true, + finalized: false, + lastExecutedChain: 'review', + }; + }); + }); + + it('Ensures that the graph compiles', async () => { + try { + await getRelatedGraph(mockClient, mockLlm); + } catch (error) { + // noop + } + }); + + it('Runs the whole graph, with mocked outputs from the LLM.', async () => { + const relatedGraph = await getRelatedGraph(mockClient, mockLlm); + + (testPipeline as jest.Mock) + .mockResolvedValueOnce(testPipelineValidResult) + .mockResolvedValueOnce(testPipelineError) + .mockResolvedValueOnce(testPipelineValidResult) + .mockResolvedValueOnce(testPipelineValidResult) + .mockResolvedValueOnce(testPipelineValidResult); + + let response; + try { + response = await relatedGraph.invoke(mockedRequestWithPipeline); + } catch (e) { + // noop + } + + expect(response.results).toStrictEqual(relatedExpectedResults); + + // Check if the functions were called + expect(handleRelated).toHaveBeenCalled(); + expect(handleErrors).toHaveBeenCalled(); + expect(handleReview).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/integration_assistant/server/graphs/related/graph.ts b/x-pack/plugins/integration_assistant/server/graphs/related/graph.ts new file mode 100644 index 0000000000000..9b50c05889402 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/related/graph.ts @@ -0,0 +1,171 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; +import type { StateGraphArgs } from '@langchain/langgraph'; +import { StateGraph, END, START } from '@langchain/langgraph'; +import type { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; +import type { RelatedState } from '../../types'; +import { modifySamples, formatSamples } from '../../util/samples'; +import { handleValidatePipeline } from '../../util/graph'; +import { handleRelated } from './related'; +import { handleErrors } from './errors'; +import { handleReview } from './review'; +import { RELATED_ECS_FIELDS, RELATED_EXAMPLE_ANSWER } from './constants'; + +const graphState: StateGraphArgs['channels'] = { + lastExecutedChain: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + rawSamples: { + value: (x: string[], y?: string[]) => y ?? x, + default: () => [], + }, + samples: { + value: (x: string[], y?: string[]) => y ?? x, + default: () => [], + }, + formattedSamples: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + ecs: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + exAnswer: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + packageName: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + dataStreamName: { + value: (x: string, y?: string) => y ?? x, + default: () => '', + }, + finalized: { + value: (x: boolean, y?: boolean) => y ?? x, + default: () => false, + }, + reviewed: { + value: (x: boolean, y?: boolean) => y ?? x, + default: () => false, + }, + errors: { + value: (x: object, y?: object) => y ?? x, + default: () => ({}), + }, + pipelineResults: { + value: (x: object[], y?: object[]) => y ?? x, + default: () => [], + }, + currentPipeline: { + value: (x: object, y?: object) => y ?? x, + default: () => ({}), + }, + currentProcessors: { + value: (x: object[], y?: object[]) => y ?? x, + default: () => [], + }, + initialPipeline: { + value: (x: object, y?: object) => y ?? x, + default: () => ({}), + }, + results: { + value: (x: object, y?: object) => y ?? x, + default: () => ({}), + }, +}; + +function modelInput(state: RelatedState): Partial { + const samples = modifySamples(state); + const formattedSamples = formatSamples(samples); + const initialPipeline = JSON.parse(JSON.stringify(state.currentPipeline)); + return { + exAnswer: JSON.stringify(RELATED_EXAMPLE_ANSWER, null, 2), + ecs: JSON.stringify(RELATED_ECS_FIELDS, null, 2), + samples, + formattedSamples, + initialPipeline, + finalized: false, + reviewed: false, + lastExecutedChain: 'modelInput', + }; +} + +function modelOutput(state: RelatedState): Partial { + return { + finalized: true, + lastExecutedChain: 'modelOutput', + results: { + docs: state.pipelineResults, + pipeline: state.currentPipeline, + }, + }; +} + +function inputRouter(state: RelatedState): string { + if (Object.keys(state.pipelineResults).length === 0) { + return 'validatePipeline'; + } + return 'related'; +} + +function chainRouter(state: RelatedState): string { + if (Object.keys(state.currentProcessors).length === 0) { + return 'related'; + } + if (Object.keys(state.errors).length > 0) { + return 'errors'; + } + if (!state.reviewed) { + return 'review'; + } + if (!state.finalized) { + return 'modelOutput'; + } + return END; +} + +export async function getRelatedGraph( + client: IScopedClusterClient, + model: ActionsClientChatOpenAI | ActionsClientSimpleChatModel +) { + const workflow = new StateGraph({ channels: graphState }) + .addNode('modelInput', modelInput) + .addNode('modelOutput', modelOutput) + .addNode('handleRelated', (state: RelatedState) => handleRelated(state, model)) + .addNode('handleValidatePipeline', (state: RelatedState) => + handleValidatePipeline(state, client) + ) + .addNode('handleErrors', (state: RelatedState) => handleErrors(state, model)) + .addNode('handleReview', (state: RelatedState) => handleReview(state, model)) + .addEdge(START, 'modelInput') + .addEdge('modelOutput', END) + .addEdge('handleRelated', 'handleValidatePipeline') + .addEdge('handleErrors', 'handleValidatePipeline') + .addEdge('handleReview', 'handleValidatePipeline') + .addConditionalEdges('modelInput', inputRouter, { + related: 'handleRelated', + validatePipeline: 'handleValidatePipeline', + }) + .addConditionalEdges('handleValidatePipeline', chainRouter, { + related: 'handleRelated', + errors: 'handleErrors', + review: 'handleReview', + modelOutput: 'modelOutput', + }); + + const compiledRelatedGraph = workflow.compile(); + return compiledRelatedGraph; +} diff --git a/x-pack/plugins/integration_assistant/server/graphs/related/index.ts b/x-pack/plugins/integration_assistant/server/graphs/related/index.ts new file mode 100644 index 0000000000000..4294a4dd34ccd --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/related/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +export { getRelatedGraph } from './graph'; diff --git a/x-pack/plugins/integration_assistant/server/graphs/related/prompts.ts b/x-pack/plugins/integration_assistant/server/graphs/related/prompts.ts new file mode 100644 index 0000000000000..2a14b52907103 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/related/prompts.ts @@ -0,0 +1,142 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ChatPromptTemplate } from '@langchain/core/prompts'; + +export const RELATED_MAIN_PROMPT = ChatPromptTemplate.fromMessages([ + [ + 'system', + `You are a helpful, expert assistant on Elasticsearch Ingest Pipelines, focusing on providing append processors that can be used to enrich samples with all relevant related.ip, related.hash, related.user and related.host fields. + Here are some context for you to reference for your task, read it carefully as you will get questions about it later: + + + {ecs} + + `, + ], + [ + 'human', + `Please help me by providing all relevant append processors for any detected related.ip, related.hash, related.user and related.host fields that would fit the below pipeline results as an array of JSON objects. + + + {pipeline_results} + + + Go through each of the pipeline results above step by step and do the following to add all relevant related.ip, related.hash, related.user and related.host fields. + 1. Try to understand what is unique about each pipeline result, and what sort of related.ip, related.hash, related.user and related.host fields that fit best, and if there is any unique values for each result. + 2. For each of related.ip, related.hash, related.user and related.host fields that you find, add a new append processor to your array of JSON objects. + 3. If only certain results are relevant to the related.ip, related.hash, related.user and related.host fields, add an if condition similar to the above example processors, that describes what value or field needs to be available for this categorization to take place. The if condition should be inside the processor object. + 4. Always check if the related.ip, related.hash, related.user and related.host fields are common in the ecs context above. + 5. The value argument for the append processor shall consist of one field. + + You ALWAYS follow these guidelines when writing your response: + + - You can add as many append processors you need to cover all the fields that you detected. + - If conditions should always use a ? character when accessing nested fields, in case the field might not always be available, see example processors above. + - When an if condition is not needed the argument should not be used for the processor object. + - Do not respond with anything except the array of processors as a valid JSON objects enclosed with 3 backticks (\`), see example response below. + + + Example response format: + + A: Please find the Related processors below: + \`\`\`json + {ex_answer} + \`\`\` + `, + ], + ['ai', 'Please find the Related processors below:'], +]); + +export const RELATED_ERROR_PROMPT = ChatPromptTemplate.fromMessages([ + [ + 'system', + `You are a helpful, expert assistant on Elasticsearch Ingest Pipelines, focusing on resolving errors and issues with append processors used for related field categorization. + Here is some context that you can reference for your task, read it carefully as you will get questions about it later: + + + {current_processors} + + + {errors} + + `, + ], + [ + 'human', + `Please go through each error above, carefully review the provided current processors, and resolve the most likely cause to the supplied error by returning an updated version of the current_processors. + + Follow these steps to help resolve the current ingest pipeline issues: + 1. Try to fix all related errors before responding. + 2. Apply all fixes to the provided array of current append processors. + 3. If you do not know how to fix an error, then continue to the next and return the complete updated array of current append processors. + + You ALWAYS follow these guidelines when writing your response: + + - When checking for the existance of multiple values in a single variable, use this format: "if": "['value1', 'value2'].contains(ctx.{package_name}?.{data_stream_name}?.field)" + - If conditions should never be in a format like "if": "true". If it exist in the current array of append processors, remove only the redundant if condition. + - If the error complains that it is a null point exception, always ensure the if conditions uses a ? when accessing nested fields. For example ctx.field1?.nestedfield1?.nestedfield2. + - Never use "split" in template values, only use the field name inside the triple brackets. If the error mentions "Improperly closed variable in query-template" then check each "value" field for any special characters and remove them. + - Do not respond with anything except the complete updated array of processors as a valid JSON object enclosed with 3 backticks (\`), see example response below. + + + Example response format: + + A: Please find the updated ECS related append processors below: + \`\`\`json + {ex_answer} + \`\`\` + `, + ], + ['ai', 'Please find the updated ECS related append processors below:'], +]); + +export const RELATED_REVIEW_PROMPT = ChatPromptTemplate.fromMessages([ + [ + 'system', + `You are a helpful, expert assistant on Elasticsearch Ingest Pipelines, focusing on adding improvements to the provided array of processors and reviewing the current results. + + Here is some context that you can reference for your task, read it carefully as you will get questions about it later: + + + {current_processors} + + `, + ], + [ + 'human', + `Testing my current pipeline returned me with the below pipeline results: + + {pipeline_results} + + + Please review the pipeline results and the array of current processors, ensuring to identify all the related.ip , related.user , related.hash and related.host fields that would match each pipeline result document. If any related.ip , related.user , related.hash or related.host fields is missing from any of the pipeline results, add them by updating the array of current processors and return the whole updated array of processors. + + For each pipeline result you review step by step, remember the below steps: + 1. Check each of the pipeline results to see if the field/value matches related.ip , related.user , related.hash or related.host. If not then try to correlate the results with the current processors and see if either a new append processor should be added to the list with a matching if condition, or if any of the if conditions should be modified as they are not matching that is in the results. + 2. If the results have related.ip , related.user , related.hash or related.host value, see if more of them could match, if so it could be added to the relevant append processor which added the initial values. + 3. Ensure that all append processors has allow_duplicates: false, as seen in the example response. + + You ALWAYS follow these guidelines when writing your response: + + - You can use as many append processors as you need to add all relevant ECS categories and types combinations. + - If conditions should always use a ? character when accessing nested fields, in case the field might not always be available, see example processors above. + - When an if condition is not needed the argument should not be used for the processor object. + - If not updates are needed you respond with the initially provided current processors. + - Each append processor needs to have the allow_duplicates: false argument, as shown in the below example response. + - Do not respond with anything except updated array of processors as a valid JSON object enclosed with 3 backticks (\`), see example response below. + + + Example response format: + + A: Please find the updated ECS related append processors below: + \`\`\` + {ex_answer} + \`\`\` + `, + ], + ['ai', 'Please find the updated ECS related append processors below:'], +]); diff --git a/x-pack/plugins/integration_assistant/server/graphs/related/related.test.ts b/x-pack/plugins/integration_assistant/server/graphs/related/related.test.ts new file mode 100644 index 0000000000000..3a741020fb530 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/related/related.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FakeLLM } from '@langchain/core/utils/testing'; +import { handleRelated } from './related'; +import type { RelatedState } from '../../types'; +import { + relatedTestState, + relatedMockProcessors, + relatedExpectedHandlerResponse, +} from '../../../__jest__/fixtures/related'; +import { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; + +const mockLlm = new FakeLLM({ + response: JSON.stringify(relatedMockProcessors, null, 2), +}) as unknown as ActionsClientChatOpenAI | ActionsClientSimpleChatModel; + +const testState: RelatedState = relatedTestState; + +describe('Testing related handler', () => { + it('handleRelated()', async () => { + const response = await handleRelated(testState, mockLlm); + expect(response.currentPipeline).toStrictEqual(relatedExpectedHandlerResponse.currentPipeline); + expect(response.lastExecutedChain).toBe('related'); + }); +}); diff --git a/x-pack/plugins/integration_assistant/server/graphs/related/related.ts b/x-pack/plugins/integration_assistant/server/graphs/related/related.ts new file mode 100644 index 0000000000000..2c98381510d9b --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/related/related.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 { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; +import { JsonOutputParser } from '@langchain/core/output_parsers'; +import type { ESProcessorItem, Pipeline } from '../../../common'; +import type { RelatedState } from '../../types'; +import { combineProcessors } from '../../util/processors'; +import { RELATED_MAIN_PROMPT } from './prompts'; + +export async function handleRelated( + state: RelatedState, + model: ActionsClientChatOpenAI | ActionsClientSimpleChatModel +) { + const relatedMainPrompt = RELATED_MAIN_PROMPT; + const outputParser = new JsonOutputParser(); + const relatedMainGraph = relatedMainPrompt.pipe(model).pipe(outputParser); + + const currentProcessors = (await relatedMainGraph.invoke({ + pipeline_results: JSON.stringify(state.pipelineResults, null, 2), + ex_answer: state.exAnswer, + ecs: state.ecs, + })) as ESProcessorItem[]; + + const currentPipeline = combineProcessors(state.initialPipeline as Pipeline, currentProcessors); + + return { + currentPipeline, + currentProcessors, + reviewed: false, + lastExecutedChain: 'related', + }; +} diff --git a/x-pack/plugins/integration_assistant/server/graphs/related/review.test.ts b/x-pack/plugins/integration_assistant/server/graphs/related/review.test.ts new file mode 100644 index 0000000000000..475f0d72b988d --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/related/review.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FakeLLM } from '@langchain/core/utils/testing'; +import { handleReview } from './review'; +import type { RelatedState } from '../../types'; +import { + relatedTestState, + relatedMockProcessors, + relatedExpectedHandlerResponse, +} from '../../../__jest__/fixtures/related'; +import { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; + +const mockLlm = new FakeLLM({ + response: JSON.stringify(relatedMockProcessors, null, 2), +}) as unknown as ActionsClientChatOpenAI | ActionsClientSimpleChatModel; + +const testState: RelatedState = relatedTestState; + +describe('Testing related handler', () => { + it('handleReview()', async () => { + const response = await handleReview(testState, mockLlm); + expect(response.currentPipeline).toStrictEqual(relatedExpectedHandlerResponse.currentPipeline); + expect(response.lastExecutedChain).toBe('review'); + }); +}); diff --git a/x-pack/plugins/integration_assistant/server/graphs/related/review.ts b/x-pack/plugins/integration_assistant/server/graphs/related/review.ts new file mode 100644 index 0000000000000..6c07079e18f48 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/graphs/related/review.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 { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; +import { JsonOutputParser } from '@langchain/core/output_parsers'; +import type { ESProcessorItem, Pipeline } from '../../../common'; +import type { RelatedState } from '../../types'; +import { combineProcessors } from '../../util/processors'; +import { RELATED_REVIEW_PROMPT } from './prompts'; + +export async function handleReview( + state: RelatedState, + model: ActionsClientChatOpenAI | ActionsClientSimpleChatModel +) { + const relatedReviewPrompt = RELATED_REVIEW_PROMPT; + const outputParser = new JsonOutputParser(); + const relatedReviewGraph = relatedReviewPrompt.pipe(model).pipe(outputParser); + + const currentProcessors = (await relatedReviewGraph.invoke({ + current_processors: JSON.stringify(state.currentProcessors, null, 2), + ex_answer: state.exAnswer, + pipeline_results: JSON.stringify(state.pipelineResults, null, 2), + })) as ESProcessorItem[]; + + const currentPipeline = combineProcessors(state.initialPipeline as Pipeline, currentProcessors); + + return { + currentPipeline, + currentProcessors, + reviewed: true, + lastExecutedChain: 'review', + }; +} diff --git a/x-pack/plugins/integration_assistant/server/index.ts b/x-pack/plugins/integration_assistant/server/index.ts new file mode 100644 index 0000000000000..d259ae80c53de --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PluginInitializerContext } from '@kbn/core/server'; + +export { config } from './config'; + +export async function plugin(initializerContext: PluginInitializerContext) { + const { IntegrationAssistantPlugin } = await import('./plugin'); + return new IntegrationAssistantPlugin(initializerContext); +} + +export type { IntegrationAssistantPluginSetup, IntegrationAssistantPluginStart } from './types'; diff --git a/x-pack/plugins/integration_assistant/server/integration_builder/agent.ts b/x-pack/plugins/integration_assistant/server/integration_builder/agent.ts new file mode 100644 index 0000000000000..2f622eec103e5 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/integration_builder/agent.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { join as joinPath } from 'path'; +import type { InputType } from '../../common'; +import { createSync, ensureDirSync, readSync } from '../util'; + +export function createAgentInput(specificDataStreamDir: string, inputTypes: InputType[]): void { + const agentDir = joinPath(specificDataStreamDir, 'agent', 'stream'); + const agentTemplatesDir = joinPath(__dirname, '../templates/agent'); + ensureDirSync(agentDir); + + // Load common options that exists for all .yml.hbs files, to be merged with each specific input file + const commonFilePath = joinPath(agentTemplatesDir, 'common.yml.hbs'); + const commonFile = readSync(commonFilePath); + + for (const inputType of inputTypes) { + const inputTypeFilePath = joinPath( + agentTemplatesDir, + `${inputType.replaceAll('-', '_')}.yml.hbs` + ); + const inputTypeFile = readSync(inputTypeFilePath); + + const combinedContents = `${inputTypeFile}\n${commonFile}`; + + const destinationFilePath = joinPath(agentDir, `${inputType}.yml.hbs`); + createSync(destinationFilePath, combinedContents); + } +} diff --git a/x-pack/plugins/integration_assistant/server/integration_builder/build_integration.ts b/x-pack/plugins/integration_assistant/server/integration_builder/build_integration.ts new file mode 100644 index 0000000000000..05018d056b498 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/integration_builder/build_integration.ts @@ -0,0 +1,142 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import AdmZip from 'adm-zip'; +import nunjucks from 'nunjucks'; +import { tmpdir } from 'os'; +import { join as joinPath } from 'path'; +import type { Datastream, Integration } from '../../common'; +import { copySync, createSync, ensureDirSync, generateUniqueId } from '../util'; +import { createAgentInput } from './agent'; +import { createDatastream } from './data_stream'; +import { createFieldMapping } from './fields'; +import { createPipeline } from './pipeline'; + +export async function buildPackage(integration: Integration): Promise { + const templateDir = joinPath(__dirname, '../templates'); + const agentTemplates = joinPath(templateDir, 'agent'); + const manifestTemplates = joinPath(templateDir, 'manifest'); + const systemTestTemplates = joinPath(templateDir, 'system_tests'); + nunjucks.configure([templateDir, agentTemplates, manifestTemplates, systemTestTemplates], { + autoescape: false, + }); + + const tmpDir = joinPath(tmpdir(), `integration-assistant-${generateUniqueId()}`); + const packageDir = createDirectories(tmpDir, integration); + const dataStreamsDir = joinPath(packageDir, 'data_stream'); + + for (const dataStream of integration.dataStreams) { + const dataStreamName = dataStream.name; + const specificDataStreamDir = joinPath(dataStreamsDir, dataStreamName); + + createDatastream(integration.name, specificDataStreamDir, dataStream); + createAgentInput(specificDataStreamDir, dataStream.inputTypes); + createPipeline(specificDataStreamDir, dataStream.pipeline); + createFieldMapping(integration.name, dataStreamName, specificDataStreamDir, dataStream.docs); + } + + const tmpPackageDir = joinPath(tmpDir, `${integration.name}-0.1.0`); + + const zipBuffer = await createZipArchive(tmpPackageDir); + return zipBuffer; +} + +function createDirectories(tmpDir: string, integration: Integration): string { + const packageDir = joinPath(tmpDir, `${integration.name}-0.1.0`); + ensureDirSync(tmpDir); + ensureDirSync(packageDir); + createPackage(packageDir, integration); + return packageDir; +} + +function createPackage(packageDir: string, integration: Integration): void { + createReadme(packageDir, integration); + createChangelog(packageDir); + createBuildFile(packageDir); + createPackageManifest(packageDir, integration); + // Skipping creation of system tests temporarily for custom package generation + // createPackageSystemTests(packageDir, integration); + createLogo(packageDir, integration); +} + +function createLogo(packageDir: string, integration: Integration): void { + const logoDir = joinPath(packageDir, 'img'); + ensureDirSync(logoDir); + + if (integration?.logo !== undefined) { + const buffer = Buffer.from(integration.logo, 'base64'); + createSync(joinPath(logoDir, 'logo.svg'), buffer); + } else { + const imgTemplateDir = joinPath(__dirname, '../templates/img'); + copySync(joinPath(imgTemplateDir, 'logo.svg'), joinPath(logoDir, 'logo.svg')); + } +} + +function createBuildFile(packageDir: string): void { + const buildFile = nunjucks.render('build.yml.njk', { ecs_version: '8.11.0' }); + const buildDir = joinPath(packageDir, '_dev/build'); + + ensureDirSync(buildDir); + createSync(joinPath(buildDir, 'build.yml'), buildFile); +} + +function createChangelog(packageDir: string): void { + const changelogTemplate = nunjucks.render('changelog.yml.njk', { + initial_version: '0.1.0', + }); + + createSync(joinPath(packageDir, 'changelog.yml'), changelogTemplate); +} + +function createReadme(packageDir: string, integration: Integration) { + const readmeDirPath = joinPath(packageDir, '_dev/build/docs/'); + ensureDirSync(readmeDirPath); + const readmeTemplate = nunjucks.render('readme.md.njk', { + package_name: integration.name, + data_streams: integration.dataStreams, + }); + + createSync(joinPath(readmeDirPath, 'README.md'), readmeTemplate); +} + +async function createZipArchive(tmpPackageDir: string): Promise { + const zip = new AdmZip(); + zip.addLocalFolder(tmpPackageDir); + const buffer = zip.toBuffer(); + return buffer; +} + +function createPackageManifest(packageDir: string, integration: Integration): void { + const uniqueInputs: { [key: string]: { type: string; title: string; description: string } } = {}; + + integration.dataStreams.forEach((dataStream: Datastream) => { + dataStream.inputTypes.forEach((inputType: string) => { + if (!uniqueInputs[inputType]) { + uniqueInputs[inputType] = { + type: inputType, + title: dataStream.title, + description: dataStream.description, + }; + } + }); + }); + + const uniqueInputsList = Object.values(uniqueInputs); + + const packageManifest = nunjucks.render('package_manifest.yml.njk', { + format_version: '3.1.4', + package_title: integration.title, + package_name: integration.name, + package_version: '0.1.0', + package_description: integration.description, + package_owner: '@elastic/custom-integrations', + min_version: '^8.13.0', + inputs: uniqueInputsList, + }); + + createSync(joinPath(packageDir, 'manifest.yml'), packageManifest); +} diff --git a/x-pack/plugins/integration_assistant/server/integration_builder/data_stream.ts b/x-pack/plugins/integration_assistant/server/integration_builder/data_stream.ts new file mode 100644 index 0000000000000..8236666aec321 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/integration_builder/data_stream.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import nunjucks from 'nunjucks'; +import { join as joinPath } from 'path'; +import type { Datastream } from '../../common'; +import { copySync, createSync, ensureDirSync, listDirSync } from '../util'; + +export function createDatastream( + packageName: string, + specificDataStreamDir: string, + dataStream: Datastream +): void { + const dataStreamName = dataStream.name; + const pipelineDir = joinPath(specificDataStreamDir, 'elasticsearch', 'ingest_pipeline'); + const title = dataStream.title; + const description = dataStream.description; + + ensureDirSync(specificDataStreamDir); + createDataStreamFolders(specificDataStreamDir, pipelineDir); + createPipelineTests(specificDataStreamDir, dataStream.rawSamples, packageName, dataStreamName); + + const dataStreams: string[] = []; + for (const inputType of dataStream.inputTypes) { + const mappedValues = { + data_stream_title: title, + data_stream_description: description, + package_name: packageName, + data_stream_name: dataStreamName, + }; + const dataStreamManifest = nunjucks.render( + `${inputType.replaceAll('-', '_')}_manifest.yml.njk`, + mappedValues + ); + const commonManifest = nunjucks.render('common_manifest.yml.njk', mappedValues); + + const combinedManifest = `${dataStreamManifest}\n${commonManifest}`; + dataStreams.push(combinedManifest); + + // We comment this out for now, as its not really needed for custom integrations + /* createDataStreamSystemTests( + specificDataStreamDir, + inputType, + mappedValues, + packageName, + dataStreamName + ); + */ + } + + const finalManifest = nunjucks.render('data_stream.yml.njk', { + title, + data_streams: dataStreams, + }); + + createSync(joinPath(specificDataStreamDir, 'manifest.yml'), finalManifest); +} + +function createDataStreamFolders(specificDataStreamDir: string, pipelineDir: string): void { + const dataStreamTemplatesDir = joinPath(__dirname, '../templates/data_stream'); + const items = listDirSync(dataStreamTemplatesDir); + + for (const item of items) { + const s = joinPath(dataStreamTemplatesDir, item); + const d = joinPath(specificDataStreamDir, item); + copySync(s, d); + } + + ensureDirSync(pipelineDir); +} + +function createPipelineTests( + specificDataStreamDir: string, + rawSamples: string[], + packageName: string, + dataStreamName: string +): void { + const pipelineTestTemplatesDir = joinPath(__dirname, '../templates/pipeline_tests'); + const pipelineTestsDir = joinPath(specificDataStreamDir, '_dev/test/pipeline'); + ensureDirSync(pipelineTestsDir); + const items = listDirSync(pipelineTestTemplatesDir); + for (const item of items) { + const s = joinPath(pipelineTestTemplatesDir, item); + const d = joinPath(pipelineTestsDir, item.replaceAll('_', '-')); + copySync(s, d); + } + const formattedPackageName = packageName.replace(/_/g, '-'); + const formattedDataStreamName = dataStreamName.replace(/_/g, '-'); + const testFileName = joinPath( + pipelineTestsDir, + `test-${formattedPackageName}-${formattedDataStreamName}.log` + ); + createSync(testFileName, rawSamples.join('\n')); +} + +// We are skipping this one for now, as its not really needed for custom integrations +/* function createDataStreamSystemTests( + specificDataStreamDir: string, + inputType: string, + mappedValues: Record, + packageName: string, + dataStreamName: string +): void { + const systemTestTemplatesDir = joinPath(__dirname, '../templates/system_tests'); + nunjucks.configure({ autoescape: true }); + const env = new nunjucks.Environment(new nunjucks.FileSystemLoader(systemTestTemplatesDir)); + mappedValues.package_name = packageName.replace(/_/g, '-'); + mappedValues.data_stream_name = dataStreamName.replace(/_/g, '-'); + const systemTestFolder = joinPath(specificDataStreamDir, '_dev/test/system'); + + fs.mkdirSync(systemTestFolder, { recursive: true }); + + const systemTestTemplate = env.getTemplate(`test_${inputType.replaceAll('-', '_')}_config.yml.njk`); + const systemTestRendered = systemTestTemplate.render(mappedValues); + + const systemTestFileName = joinPath(systemTestFolder, `test-${inputType}-config.yml`); + fs.writeFileSync(systemTestFileName, systemTestRendered, 'utf-8'); +}*/ diff --git a/x-pack/plugins/integration_assistant/server/integration_builder/dev_folders.ts b/x-pack/plugins/integration_assistant/server/integration_builder/dev_folders.ts new file mode 100644 index 0000000000000..bac22efe2d3b3 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/integration_builder/dev_folders.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 { join as joinPath } from 'path'; +import nunjucks from 'nunjucks'; +import type { Integration } from '../../common'; +import { ensureDirSync, createSync } from '../util'; + +export function createPackageSystemTests(integrationDir: string, integration: Integration) { + const systemTestsDockerDir = joinPath(integrationDir, '_dev/deploy/docker/'); + const systemTestsSamplesDir = joinPath(systemTestsDockerDir, 'sample_logs'); + ensureDirSync(systemTestsSamplesDir); + + const streamVersion = '0.13.0'; + const dockerComposeVersion = '2.3'; + const dockerServices: string[] = []; + for (const stream of integration.dataStreams) { + const packageName = integration.name.replace(/_/g, '-'); + const dataStreamName = stream.name.replace(/_/g, '-'); + + const systemTestFileName = joinPath( + systemTestsSamplesDir, + `test-${packageName}-${dataStreamName}.log` + ); + const rawSamplesContent = stream.rawSamples.join('\n'); + createSync(systemTestFileName, rawSamplesContent); + + for (const inputType of stream.inputTypes) { + const mappedValues = { + package_name: packageName, + data_stream_name: dataStreamName, + stream_version: streamVersion, + }; + const renderedService = nunjucks.render( + `service_${inputType.replaceAll('_', '-')}.njk`, + mappedValues + ); + dockerServices.push(renderedService); + } + } + + const renderedDockerCompose = nunjucks.render('docker_compose.yml.njk', { + services: dockerServices.join('\n'), + docker_compose_version: dockerComposeVersion, + }); + + const dockerComposeFileName = joinPath(systemTestsDockerDir, 'docker-compose.yml'); + createSync(dockerComposeFileName, renderedDockerCompose); +} diff --git a/x-pack/plugins/integration_assistant/server/integration_builder/fields.ts b/x-pack/plugins/integration_assistant/server/integration_builder/fields.ts new file mode 100644 index 0000000000000..c95a15cbe871d --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/integration_builder/fields.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 nunjucks from 'nunjucks'; + +import { createSync, generateFields, mergeSamples } from '../util'; + +export function createFieldMapping( + packageName: string, + dataStreamName: string, + specificDataStreamDir: string, + docs: object[] +): void { + createBaseFields(specificDataStreamDir, packageName, dataStreamName); + createCustomFields(specificDataStreamDir, docs); +} + +function createBaseFields( + specificDataStreamDir: string, + packageName: string, + dataStreamName: string +): void { + const datasetName = `${packageName}.${dataStreamName}`; + const baseFields = nunjucks.render('base_fields.yml.njk', { + module: packageName, + dataset: datasetName, + }); + + createSync(`${specificDataStreamDir}/base-fields.yml`, baseFields); +} + +function createCustomFields(specificDataStreamDir: string, pipelineResults: object[]): void { + const mergedResults = mergeSamples(pipelineResults); + const fieldKeys = generateFields(mergedResults); + createSync(`${specificDataStreamDir}/fields/fields.yml`, fieldKeys); +} diff --git a/x-pack/plugins/integration_assistant/server/integration_builder/index.ts b/x-pack/plugins/integration_assistant/server/integration_builder/index.ts new file mode 100644 index 0000000000000..8de03051d75c5 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/integration_builder/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 { buildPackage } from './build_integration'; diff --git a/x-pack/plugins/integration_assistant/server/integration_builder/pipeline.ts b/x-pack/plugins/integration_assistant/server/integration_builder/pipeline.ts new file mode 100644 index 0000000000000..d733fd001be02 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/integration_builder/pipeline.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 { join as joinPath } from 'path'; +import yaml from 'js-yaml'; +import { createSync } from '../util'; + +export function createPipeline(specificDataStreamDir: string, pipeline: object): void { + const filePath = joinPath(specificDataStreamDir, 'elasticsearch/ingest_pipeline/default.yml'); + const yamlContent = `---\n${yaml.dump(pipeline, { sortKeys: false })}`; + createSync(filePath, yamlContent); +} diff --git a/x-pack/plugins/integration_assistant/server/plugin.ts b/x-pack/plugins/integration_assistant/server/plugin.ts new file mode 100644 index 0000000000000..4f7dfa87291cc --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/plugin.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + Plugin, + PluginInitializerContext, + CoreSetup, + CoreStart, + Logger, + CustomRequestHandlerContext, +} from '@kbn/core/server'; +import type { PluginStartContract as ActionsPluginsStart } from '@kbn/actions-plugin/server/plugin'; +import { registerRoutes } from './routes'; +import type { IntegrationAssistantPluginSetup, IntegrationAssistantPluginStart } from './types'; + +export type IntegrationAssistantRouteHandlerContext = CustomRequestHandlerContext<{ + integrationAssistant: { + getStartServices: CoreSetup<{ + actions: ActionsPluginsStart; + }>['getStartServices']; + logger: Logger; + }; +}>; + +export class IntegrationAssistantPlugin + implements Plugin +{ + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + public setup( + core: CoreSetup<{ + actions: ActionsPluginsStart; + }> + ) { + core.http.registerRouteHandlerContext< + IntegrationAssistantRouteHandlerContext, + 'integrationAssistant' + >('integrationAssistant', () => ({ + getStartServices: core.getStartServices, + logger: this.logger, + })); + const router = core.http.createRouter(); + this.logger.debug('integrationAssistant api: Setup'); + + registerRoutes(router); + + return {}; + } + + public start(core: CoreStart) { + this.logger.debug('integrationAssistant api: Started'); + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/integration_assistant/server/routes/build_integration_routes.ts b/x-pack/plugins/integration_assistant/server/routes/build_integration_routes.ts new file mode 100644 index 0000000000000..c3943c7d6398d --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/routes/build_integration_routes.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 { IRouter } from '@kbn/core/server'; +import { BuildIntegrationRequestBody, INTEGRATION_BUILDER_PATH } from '../../common'; +import { buildPackage } from '../integration_builder'; +import type { IntegrationAssistantRouteHandlerContext } from '../plugin'; +import { buildRouteValidationWithZod } from '../util/route_validation'; + +export function registerIntegrationBuilderRoutes( + router: IRouter +) { + router.versioned + .post({ + path: INTEGRATION_BUILDER_PATH, + access: 'internal', + }) + .addVersion( + { + version: '1', + validate: { + request: { + body: buildRouteValidationWithZod(BuildIntegrationRequestBody), + }, + }, + }, + async (_, request, response) => { + const { integration } = request.body; + try { + const zippedIntegration = await buildPackage(integration); + return response.custom({ statusCode: 200, body: zippedIntegration }); + } catch (e) { + return response.customError({ statusCode: 500, body: e }); + } + } + ); +} diff --git a/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts b/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts new file mode 100644 index 0000000000000..5ec9dc1b758ef --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/routes/categorization_routes.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IKibanaResponse, IRouter } from '@kbn/core/server'; +import { getRequestAbortedSignal } from '@kbn/data-plugin/server'; +import { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; +import { + CATEGORIZATION_GRAPH_PATH, + CategorizationRequestBody, + CategorizationResponse, +} from '../../common'; +import { ROUTE_HANDLER_TIMEOUT } from '../constants'; +import { getCategorizationGraph } from '../graphs/categorization'; +import type { IntegrationAssistantRouteHandlerContext } from '../plugin'; +import { buildRouteValidationWithZod } from '../util/route_validation'; + +export function registerCategorizationRoutes( + router: IRouter +) { + router.versioned + .post({ + path: CATEGORIZATION_GRAPH_PATH, + access: 'internal', + options: { + timeout: { + idleSocket: ROUTE_HANDLER_TIMEOUT, + }, + }, + }) + .addVersion( + { + version: '1', + validate: { + request: { + body: buildRouteValidationWithZod(CategorizationRequestBody), + }, + }, + }, + async (context, req, res): Promise> => { + const { packageName, datastreamName, rawSamples, currentPipeline } = req.body; + const services = await context.resolve(['core']); + const { client } = services.core.elasticsearch; + const { getStartServices, logger } = await context.integrationAssistant; + const [, { actions: actionsPlugin }] = await getStartServices(); + + try { + const actionsClient = await actionsPlugin.getActionsClientWithRequest(req); + const connector = req.body.connectorId + ? await actionsClient.get({ id: req.body.connectorId }) + : (await actionsClient.getAll()).filter( + (connectorItem) => connectorItem.actionTypeId === '.bedrock' + )[0]; + + const abortSignal = getRequestAbortedSignal(req.events.aborted$); + const isOpenAI = connector.actionTypeId === '.gen-ai'; + const llmClass = isOpenAI ? ActionsClientChatOpenAI : ActionsClientSimpleChatModel; + + const model = new llmClass({ + actions: actionsPlugin, + connectorId: connector.id, + request: req, + logger, + llmType: isOpenAI ? 'openai' : 'bedrock', + model: connector.config?.defaultModel, + temperature: 0.05, + maxTokens: 4096, + signal: abortSignal, + streaming: false, + }); + + const graph = await getCategorizationGraph(client, model); + const results = await graph.invoke({ + packageName, + datastreamName, + rawSamples, + currentPipeline, + }); + return res.ok({ body: CategorizationResponse.parse(results) }); + } catch (e) { + return res.badRequest({ body: e }); + } + } + ); +} diff --git a/x-pack/plugins/integration_assistant/server/routes/ecs_routes.ts b/x-pack/plugins/integration_assistant/server/routes/ecs_routes.ts new file mode 100644 index 0000000000000..1ee7659b3598b --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/routes/ecs_routes.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IKibanaResponse, IRouter } from '@kbn/core/server'; +import { getRequestAbortedSignal } from '@kbn/data-plugin/server'; +import { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; +import { ECS_GRAPH_PATH, EcsMappingRequestBody, EcsMappingResponse } from '../../common'; +import { ROUTE_HANDLER_TIMEOUT } from '../constants'; +import { getEcsGraph } from '../graphs/ecs'; +import type { IntegrationAssistantRouteHandlerContext } from '../plugin'; +import { buildRouteValidationWithZod } from '../util/route_validation'; + +export function registerEcsRoutes(router: IRouter) { + router.versioned + .post({ + path: ECS_GRAPH_PATH, + access: 'internal', + options: { + timeout: { + idleSocket: ROUTE_HANDLER_TIMEOUT, + }, + }, + }) + .addVersion( + { + version: '1', + validate: { + request: { + body: buildRouteValidationWithZod(EcsMappingRequestBody), + }, + }, + }, + async (context, req, res): Promise> => { + const { packageName, datastreamName, rawSamples, mapping } = req.body; + const { getStartServices, logger } = await context.integrationAssistant; + const [, { actions: actionsPlugin }] = await getStartServices(); + + try { + const actionsClient = await actionsPlugin.getActionsClientWithRequest(req); + const connector = req.body.connectorId + ? await actionsClient.get({ id: req.body.connectorId }) + : (await actionsClient.getAll()).filter( + (connectorItem) => connectorItem.actionTypeId === '.bedrock' + )[0]; + + const abortSignal = getRequestAbortedSignal(req.events.aborted$); + const isOpenAI = connector.actionTypeId === '.gen-ai'; + const llmClass = isOpenAI ? ActionsClientChatOpenAI : ActionsClientSimpleChatModel; + + const model = new llmClass({ + actions: actionsPlugin, + connectorId: connector.id, + request: req, + logger, + llmType: isOpenAI ? 'openai' : 'bedrock', + model: connector.config?.defaultModel, + temperature: 0.05, + maxTokens: 4096, + signal: abortSignal, + streaming: false, + }); + + const graph = await getEcsGraph(model); + + let results; + if (req.body?.mapping) { + results = await graph.invoke({ + packageName, + datastreamName, + rawSamples, + mapping, + }); + } else + results = await graph.invoke({ + packageName, + datastreamName, + rawSamples, + }); + return res.ok({ body: EcsMappingResponse.parse(results) }); + } catch (e) { + return res.badRequest({ body: e }); + } + } + ); +} diff --git a/x-pack/plugins/integration_assistant/server/routes/index.ts b/x-pack/plugins/integration_assistant/server/routes/index.ts new file mode 100644 index 0000000000000..ebc597350969e --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/routes/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 { registerRoutes } from './register_routes'; diff --git a/x-pack/plugins/integration_assistant/server/routes/pipeline_routes.ts b/x-pack/plugins/integration_assistant/server/routes/pipeline_routes.ts new file mode 100644 index 0000000000000..4b4ccc0a859a5 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/routes/pipeline_routes.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IKibanaResponse, IRouter } from '@kbn/core/server'; +import { CheckPipelineRequestBody, CheckPipelineResponse, TEST_PIPELINE_PATH } from '../../common'; +import { ROUTE_HANDLER_TIMEOUT } from '../constants'; +import type { IntegrationAssistantRouteHandlerContext } from '../plugin'; +import { testPipeline } from '../util/pipeline'; +import { buildRouteValidationWithZod } from '../util/route_validation'; + +export function registerPipelineRoutes(router: IRouter) { + router.versioned + .post({ + path: TEST_PIPELINE_PATH, + access: 'internal', + options: { + timeout: { + idleSocket: ROUTE_HANDLER_TIMEOUT, + }, + }, + }) + .addVersion( + { + version: '1', + validate: { + request: { + body: buildRouteValidationWithZod(CheckPipelineRequestBody), + }, + }, + }, + async (context, req, res): Promise> => { + const { rawSamples, pipeline } = req.body; + const services = await context.resolve(['core']); + const { client } = services.core.elasticsearch; + try { + const results = await testPipeline(rawSamples, pipeline, client); + if (results?.errors && results.errors.length > 0) { + return res.badRequest({ body: JSON.stringify(results.errors) }); + } + return res.ok({ body: CheckPipelineResponse.parse(results) }); + } catch (e) { + return res.badRequest({ body: e }); + } + } + ); +} diff --git a/x-pack/plugins/integration_assistant/server/routes/register_routes.ts b/x-pack/plugins/integration_assistant/server/routes/register_routes.ts new file mode 100644 index 0000000000000..a8ccc39ff2a0f --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/routes/register_routes.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IRouter } from '@kbn/core/server'; +import { registerEcsRoutes } from './ecs_routes'; +import { registerIntegrationBuilderRoutes } from './build_integration_routes'; +import { registerCategorizationRoutes } from './categorization_routes'; +import { registerRelatedRoutes } from './related_routes'; +import { registerPipelineRoutes } from './pipeline_routes'; +import type { IntegrationAssistantRouteHandlerContext } from '../plugin'; + +export function registerRoutes(router: IRouter) { + registerEcsRoutes(router); + registerIntegrationBuilderRoutes(router); + registerCategorizationRoutes(router); + registerRelatedRoutes(router); + registerPipelineRoutes(router); +} diff --git a/x-pack/plugins/integration_assistant/server/routes/related_routes.ts b/x-pack/plugins/integration_assistant/server/routes/related_routes.ts new file mode 100644 index 0000000000000..90533742e8ff7 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/routes/related_routes.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IKibanaResponse, IRouter } from '@kbn/core/server'; +import { getRequestAbortedSignal } from '@kbn/data-plugin/server'; +import { + ActionsClientChatOpenAI, + ActionsClientSimpleChatModel, +} from '@kbn/langchain/server/language_models'; +import { RELATED_GRAPH_PATH, RelatedRequestBody, RelatedResponse } from '../../common'; +import { ROUTE_HANDLER_TIMEOUT } from '../constants'; +import { getRelatedGraph } from '../graphs/related'; +import type { IntegrationAssistantRouteHandlerContext } from '../plugin'; +import { buildRouteValidationWithZod } from '../util/route_validation'; + +export function registerRelatedRoutes(router: IRouter) { + router.versioned + .post({ + path: RELATED_GRAPH_PATH, + access: 'internal', + options: { + timeout: { + idleSocket: ROUTE_HANDLER_TIMEOUT, + }, + }, + }) + .addVersion( + { + version: '1', + validate: { + request: { + body: buildRouteValidationWithZod(RelatedRequestBody), + }, + }, + }, + async (context, req, res): Promise> => { + const { packageName, datastreamName, rawSamples, currentPipeline } = req.body; + const services = await context.resolve(['core']); + const { client } = services.core.elasticsearch; + const { getStartServices, logger } = await context.integrationAssistant; + const [, { actions: actionsPlugin }] = await getStartServices(); + try { + const actionsClient = await actionsPlugin.getActionsClientWithRequest(req); + const connector = req.body.connectorId + ? await actionsClient.get({ id: req.body.connectorId }) + : (await actionsClient.getAll()).filter( + (connectorItem) => connectorItem.actionTypeId === '.bedrock' + )[0]; + + const isOpenAI = connector.actionTypeId === '.gen-ai'; + const llmClass = isOpenAI ? ActionsClientChatOpenAI : ActionsClientSimpleChatModel; + const abortSignal = getRequestAbortedSignal(req.events.aborted$); + + const model = new llmClass({ + actions: actionsPlugin, + connectorId: connector.id, + request: req, + logger, + llmType: isOpenAI ? 'openai' : 'bedrock', + model: connector.config?.defaultModel, + temperature: 0.05, + maxTokens: 4096, + signal: abortSignal, + streaming: false, + }); + + const graph = await getRelatedGraph(client, model); + const results = await graph.invoke({ + packageName, + datastreamName, + rawSamples, + currentPipeline, + }); + return res.ok({ body: RelatedResponse.parse(results) }); + } catch (e) { + return res.badRequest({ body: e }); + } + } + ); +} diff --git a/x-pack/plugins/integration_assistant/server/templates/agent/aws_cloudwatch.yml.hbs b/x-pack/plugins/integration_assistant/server/templates/agent/aws_cloudwatch.yml.hbs new file mode 100644 index 0000000000000..c90e8a267890c --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/agent/aws_cloudwatch.yml.hbs @@ -0,0 +1,76 @@ +{{#unless log_group_name}} +{{#unless log_group_name_prefix}} +{{#if log_group_arn }} +log_group_arn: {{ log_group_arn }} +{{/if}} +{{/unless}} +{{/unless}} +{{#unless log_group_arn}} +{{#unless log_group_name}} +{{#if log_group_name_prefix }} +log_group_name_prefix: {{ log_group_name_prefix }} +{{/if}} +{{/unless}} +{{/unless}} +{{#unless log_group_arn}} +{{#unless log_group_name_prefix}} +{{#if log_group_name }} +log_group_name: {{ log_group_name }} +{{/if}} +{{/unless}} +{{/unless}} +{{#unless log_group_arn}} +region_name: {{ region_name }} +{{/unless}} +{{#unless log_stream_prefix}} +{{#if log_streams }} +log_streams: {{ log_streams }} +{{/if}} +{{/unless}} +{{#unless log_streams}} +{{#if log_stream_prefix }} +log_stream_prefix: {{ log_stream_prefix }} +{{/if}} +{{/unless}} +{{#if start_position }} +start_position: {{ start_position }} +{{/if}} +{{#if scan_frequency }} +scan_frequency: {{ scan_frequency }} +{{/if}} +{{#if api_sleep }} +api_sleep: {{ api_sleep }} +{{/if}} +{{#if api_timeout}} +api_timeout: {{api_timeout}} +{{/if}} +{{#if latency }} +latency: {{ latency }} +{{/if}} +{{#if number_of_workers }} +number_of_workers: {{ number_of_workers }} +{{/if}} +{{#if credential_profile_name}} +credential_profile_name: {{credential_profile_name}} +{{/if}} +{{#if shared_credential_file}} +shared_credential_file: {{shared_credential_file}} +{{/if}} +{{#if default_region}} +default_region: {{default_region}} +{{/if}} +{{#if access_key_id}} +access_key_id: {{access_key_id}} +{{/if}} +{{#if secret_access_key}} +secret_access_key: {{secret_access_key}} +{{/if}} +{{#if session_token}} +session_token: {{session_token}} +{{/if}} +{{#if role_arn}} +role_arn: {{role_arn}} +{{/if}} +{{#if proxy_url }} +proxy_url: {{proxy_url}} +{{/if}} \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/agent/aws_s3.yml.hbs b/x-pack/plugins/integration_assistant/server/templates/agent/aws_s3.yml.hbs new file mode 100644 index 0000000000000..5951396423391 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/agent/aws_s3.yml.hbs @@ -0,0 +1,130 @@ +{{! start SQS queue }} +{{#unless bucket_arn}} +{{#unless non_aws_bucket_name}} +{{#if queue_url }} +queue_url: {{ queue_url }} +{{/if}} +{{/unless}} +{{/unless}} +{{! end SQS queue }} + +{{#unless queue_url}}{{! start S3 bucket polling }} + +{{! +When using an S3 bucket, you can specify only one of the following options: +- An AWS bucket ARN +- A non-AWS bucket name +}} + +{{! shared S3 bucket polling options }} +{{#if number_of_workers }} +number_of_workers: {{ number_of_workers }} +{{/if}} +{{#if bucket_list_prefix }} +bucket_list_prefix: {{ bucket_list_prefix }} +{{/if}} +{{#if bucket_list_interval }} +bucket_list_interval: {{ bucket_list_interval }} +{{/if}} + +{{! AWS S3 bucket ARN options }} +{{#unless non_aws_bucket_name}} +{{#if bucket_arn }} +bucket_arn: {{ bucket_arn }} +{{/if}} +{{/unless}}{{! end AWS S3 bucket ARN options }} + +{{! non-AWS S3 bucket ARN options }} +{{#unless bucket_arn}} +{{#if non_aws_bucket_name }} +non_aws_bucket_name: {{ non_aws_bucket_name }} +{{/if}} +{{/unless}}{{! end non-AWS S3 bucket ARN options }} + +{{/unless}}{{! end S3 bucket polling }} + +{{#if buffer_size }} +buffer_size: {{ buffer_size }} +{{/if}} +{{#if content_type }} +content_type: {{ content_type }} +{{/if}} +{{#if encoding }} +encoding: {{ encoding }} +{{/if}} +{{#if expand_event_list_from_field }} +expand_event_list_from_field: {{ expand_event_list_from_field }} +{{/if}} +{{#if buffer_size }} +buffer_size: {{ buffer_size }} +{{/if}} +{{#if fips_enabled }} +fips_enabled: {{ fips_enabled }} +{{/if}} +{{#if include_s3_metadata }} +include_s3_metadata: {{ include_s3_metadata }} +{{/if}} +{{#if max_bytes }} +max_bytes: {{ max_bytes }} +{{/if}} +{{#if max_number_of_messages }} +max_number_of_messages: {{ max_number_of_messages }} +{{/if}} +{{#if path_style }} +path_style: {{ path_style }} +{{/if}} +{{#if provider }} +provider: {{ provider }} +{{/if}} +{{#if sqs.max_receive_count }} +sqs.max_receive_count: {{ sqs.max_receive_count }} +{{/if}} +{{#if sqs.wait_time }} +sqs.wait_time: {{ sqs.wait_time }} +{{/if}} + +{{#if file_selectors}} +file_selectors: +{{file_selectors}} +{{/if}} + +{{#if credential_profile_name}} +credential_profile_name: {{credential_profile_name}} +{{/if}} +{{#if shared_credential_file}} +shared_credential_file: {{shared_credential_file}} +{{/if}} +{{#if visibility_timeout}} +visibility_timeout: {{visibility_timeout}} +{{/if}} +{{#if api_timeout}} +api_timeout: {{api_timeout}} +{{/if}} +{{#if endpoint}} +endpoint: {{endpoint}} +{{/if}} +{{#if default_region}} +default_region: {{default_region}} +{{/if}} +{{#if access_key_id}} +access_key_id: {{access_key_id}} +{{/if}} +{{#if secret_access_key}} +secret_access_key: {{secret_access_key}} +{{/if}} +{{#if session_token}} +session_token: {{session_token}} +{{/if}} +{{#if role_arn}} +role_arn: {{role_arn}} +{{/if}} +{{#if fips_enabled}} +fips_enabled: {{fips_enabled}} +{{/if}} +{{#if proxy_url }} +proxy_url: {{proxy_url}} +{{/if}} +{{#if parsers}} +parsers: +{{parsers}} +{{/if}} \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/agent/azure_blob_storage.yml.hbs b/x-pack/plugins/integration_assistant/server/templates/agent/azure_blob_storage.yml.hbs new file mode 100644 index 0000000000000..6e319399e7b3d --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/agent/azure_blob_storage.yml.hbs @@ -0,0 +1,35 @@ +{{#if account_name}} +account_name: {{account_name}} +{{/if}} +{{#if service_account_key}} +auth.shared_credentials.account_key: {{service_account_key}} +{{/if}} +{{#if service_account_uri}} +auth.connection_string.uri: {{service_account_uri}} +{{/if}} +{{#if storage_url}} +storage_url: {{storage_url}} +{{/if}} +{{#if number_of_workers}} +max_workers: {{number_of_workers}} +{{/if}} +{{#if poll}} +poll: {{poll}} +{{/if}} +{{#if poll_interval}} +poll_interval: {{poll_interval}} +{{/if}} +{{#if containers}} +containers: +{{containers}} +{{/if}} +{{#if file_selectors}} +file_selectors: +{{file_selectors}} +{{/if}} +{{#if timestamp_epoch}} +timestamp_epoch: {{timestamp_epoch}} +{{/if}} +{{#if expand_event_list_from_field}} +expand_event_list_from_field: {{expand_event_list_from_field}} +{{/if}} \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/agent/azure_eventhub.yml.hbs b/x-pack/plugins/integration_assistant/server/templates/agent/azure_eventhub.yml.hbs new file mode 100644 index 0000000000000..ed13f215ac169 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/agent/azure_eventhub.yml.hbs @@ -0,0 +1,28 @@ +{{#if eventhub}} +eventhub: {{eventhub}} +{{/if}} +{{#if consumer_group}} +consumer_group: {{consumer_group}} +{{/if}} +{{#if connection_string}} +connection_string: {{connection_string}} +{{/if}} +{{#if storage_account}} +storage_account: {{storage_account}} +{{/if}} +{{#if storage_account_key}} +storage_account_key: {{storage_account_key}} +{{/if}} +{{#if storage_account_container}} +storage_account_container: {{storage_account_container}} +{{/if}} +{{#if resource_manager_endpoint}} +resource_manager_endpoint: {{resource_manager_endpoint}} +{{/if}} +sanitize_options: +{{#if sanitize_newlines}} + - NEW_LINES +{{/if}} +{{#if sanitize_singlequotes}} + - SINGLE_QUOTES +{{/if}} \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/agent/cloudfoundry.yml.hbs b/x-pack/plugins/integration_assistant/server/templates/agent/cloudfoundry.yml.hbs new file mode 100644 index 0000000000000..38e88ea691699 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/agent/cloudfoundry.yml.hbs @@ -0,0 +1,24 @@ +{{#if api_address}} +api_address: {{api_address}} +{{/if}} +{{#if doppler_address}} +doppler_address: {{doppler_address}} +{{/if}} +{{#if uaa_address}} +uaa_address: {{uaa_address}} +{{/if}} +{{#if rlp_address}} +rlp_address: {{rlp_address}} +{{/if}} +{{#if client_id}} +client_id: {{client_id}} +{{/if}} +{{#if client_secret}} +client_secret: {{client_secret}} +{{/if}} +{{#if version}} +version: {{version}} +{{/if}} +{{#if shard_id}} +shard_id: {{shard_id}} +{{/if}} \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/agent/common.yml.hbs b/x-pack/plugins/integration_assistant/server/templates/agent/common.yml.hbs new file mode 100644 index 0000000000000..a11c556b5099b --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/agent/common.yml.hbs @@ -0,0 +1,14 @@ +tags: +{{#if preserve_original_event}} + - preserve_original_event +{{/if}} +{{#each tags as |tag|}} + - {{tag}} +{{/each}} +{{#contains "forwarded" tags}} +publisher_pipeline.disable_host: true +{{/contains}} +{{#if processors}} +processors: +{{processors}} +{{/if}} \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/agent/filestream.yml.hbs b/x-pack/plugins/integration_assistant/server/templates/agent/filestream.yml.hbs new file mode 100644 index 0000000000000..437accfc32650 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/agent/filestream.yml.hbs @@ -0,0 +1,13 @@ +paths: +{{#each paths as |path|}} + - {{path}} +{{/each}} +{{#if exclude_files}} +prospector.scanner.exclude_files: +{{#each exclude_files as |pattern f|}} + - {{pattern}} +{{/each}} +{{/if}} +{{#if custom}} +{{custom}} +{{/if}} \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/agent/gcp_pubsub.yml.hbs b/x-pack/plugins/integration_assistant/server/templates/agent/gcp_pubsub.yml.hbs new file mode 100644 index 0000000000000..6cee3bb8e1ae4 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/agent/gcp_pubsub.yml.hbs @@ -0,0 +1,27 @@ +{{#if project_id}} +project_id: {{project_id}} +{{/if}} +{{#if topic}} +topic: {{topic}} +{{/if}} +{{#if subscription_name}} +subscription.name: {{subscription_name}} +{{/if}} +{{#if subscription_create}} +subscription.create: {{subscription_create}} +{{/if}} +{{#if subscription_num_goroutines}} +subscription.num_goroutines: {{subscription_num_goroutines}} +{{/if}} +{{#if subscription_max_outstanding_messages}} +subscription.max_outstanding_messages: {{subscription_max_outstanding_messages}} +{{/if}} +{{#if credentials_file}} +credentials_file: {{credentials_file}} +{{/if}} +{{#if credentials_json}} +credentials_json: '{{credentials_json}}' +{{/if}} +{{#if alternative_host}} +alternative_host: {{alternative_host}} +{{/if}} \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/agent/gcs.yml.hbs b/x-pack/plugins/integration_assistant/server/templates/agent/gcs.yml.hbs new file mode 100644 index 0000000000000..37ee85e48db93 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/agent/gcs.yml.hbs @@ -0,0 +1,35 @@ +{{#if project_id}} +project_id: {{project_id}} +{{/if}} +{{#if alternative_host}} +alternative_host: {{alternative_host}} +{{/if}} +{{#if service_account_key}} +auth.credentials_json.account_key: {{service_account_key}} +{{/if}} +{{#if service_account_file}} +auth.credentials_file.path: {{service_account_file}} +{{/if}} +{{#if number_of_workers}} +max_workers: {{number_of_workers}} +{{/if}} +{{#if poll}} +poll: {{poll}} +{{/if}} +{{#if poll_interval}} +poll_interval: {{poll_interval}} +{{/if}} +{{#if bucket_timeout}} +bucket_timeout: {{bucket_timeout}} +{{/if}} +{{#if buckets}} +buckets: +{{buckets}} +{{/if}} +{{#if file_selectors}} +file_selectors: +{{file_selectors}} +{{/if}} +{{#if timestamp_epoch}} +timestamp_epoch: {{timestamp_epoch}} +{{/if}} \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/agent/http_endpoint.yml.hbs b/x-pack/plugins/integration_assistant/server/templates/agent/http_endpoint.yml.hbs new file mode 100644 index 0000000000000..1accdcbaa22e6 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/agent/http_endpoint.yml.hbs @@ -0,0 +1,57 @@ +{{#if listen_address}} +listen_address: {{listen_address}} +{{/if}} +{{#if listen_port}} +listen_port: {{listen_port}} +{{/if}} +{{#if prefix}} +prefix: {{prefix}} +{{/if}} +{{#if preserve_original_event}} +preserve_original_event: {{preserve_original_event}} +{{/if}} +{{#if basic_auth}} +basic_auth: {{basic_auth}} +{{/if}} +{{#if username}} +username: {{username}} +{{/if}} +{{#if password}} +password: {{password}} +{{/if}} +{{#if secret_header}} +secret.header: {{secret_header}} +{{/if}} +{{#if secret_value}} +secret.value: {{secret_value}} +{{/if}} +{{#if hmac_header}} +hmac.header: {{hmac_header}} +{{/if}} +{{#if hmac_key}} +hmac.key: {{hmac_key}} +{{/if}} +{{#if hmac_type}} +hmac.type: {{hmac_type}} +{{/if}} +{{#if hmac_prefix}} +hmac.prefix: {{hmac_prefix}} +{{/if}} +{{#if content_type}} +content_type: {{content_type}} +{{/if}} +{{#if response_code}} +response_code: {{response_code}} +{{/if}} +{{#if response_body}} +response_body: '{{response_body}}' +{{/if}} +{{#if url}} +url: {{url}} +{{/if}} +{{#if include_headers}} +include_headers: +{{#each include_headers as |header|}} + - {{header}} +{{/each}} +{{/if}} \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/agent/journald.yml.hbs b/x-pack/plugins/integration_assistant/server/templates/agent/journald.yml.hbs new file mode 100644 index 0000000000000..4bcad79a53ddc --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/agent/journald.yml.hbs @@ -0,0 +1,44 @@ +condition: ${host.platform} == 'linux' + +{{#if paths}} +paths: +{{#each paths as |path i|}} + - {{path}} +{{/each}} +{{/if}} +{{#if backoff}} +backoff: {{backoff}} +{{/if}} +{{#if max_backoff}} +max_backoff: {{max_backoff}} +{{/if}} +{{#if seek}} +seek: {{seek}} +{{/if}} +{{#if cursor_seek_fallback}} +cursor_seek_fallback: {{cursor_seek_fallback}} +{{/if}} +{{#if since}} +since: {{since}} +{{/if}} +{{#if units}} +units: {{units}} +{{/if}} +{{#if syslog_identifiers}} +syslog_identifiers: +{{#each syslog_identifiers as |identifier i|}} + - {{identifier}} +{{/each}} +{{/if}} +{{#if transports}} +transports: +{{#each transports as |transport i|}} + - {{transport}} +{{/each}} +{{/if}} +{{#if include_matches}} +include_matches: +{{#each include_matches as |match i|}} + - {{match}} +{{/each}} +{{/if}} \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/agent/kafka.yml.hbs b/x-pack/plugins/integration_assistant/server/templates/agent/kafka.yml.hbs new file mode 100644 index 0000000000000..79b74ed4cdcb8 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/agent/kafka.yml.hbs @@ -0,0 +1,100 @@ +{{#if hosts}} +hosts: +{{#each hosts as |host i|}} + - {{host}} +{{/each}} +{{/if}} +{{#if topics}} +topics: +{{#each topics as |topic i|}} + - {{topic}} +{{/each}} +{{/if}} +{{#if group_id}} +group_id: {{group_id}} +{{/if}} +{{#if client_id}} +client_id: {{client_id}} +{{/if}} +{{#if username}} +username: {{username}} +{{/if}} +{{#if password}} +password: {{password}} +{{/if}} +{{#if version}} +version: {{version}} +{{/if}} +{{#if initial_offset}} +initial_offset: {{initial_offset}} +{{/if}} +{{#if connect_backoff}} +connect_backoff: {{connect_backoff}} +{{/if}} +{{#if consume_backoff}} +consume_backoff: {{consume_backoff}} +{{/if}} +{{#if max_wait_time}} +max_wait_time: {{max_wait_time}} +{{/if}} +{{#if wait_close}} +wait_close: {{wait_close}} +{{/if}} +{{#if isolation_level}} +isolation_level: {{isolation_level}} +{{/if}} +{{#if expand_event_list_from_field}} +expand_event_list_from_field: {{expand_event_list_from_field}} +{{/if}} +{{#if fetch_min}} +fetch.min: {{fetch_min}} +{{/if}} +{{#if fetch_default}} +fetch.default: {{fetch_default}} +{{/if}} +{{#if fetch_max}} +fetch.max: {{fetch_max}} +{{/if}} +{{#if rebalance_strategy}} +rebalance.strategy: {{rebalance_strategy}} +{{/if}} +{{#if rebalance_timeout}} +rebalance.timeout: {{rebalance_timeout}} +{{/if}} +{{#if rebalance_max_retries}} +rebalance.max_retries: {{rebalance_max_retries}} +{{/if}} +{{#if rebalance_retry_backoff}} +rebalance.retry_backoff: {{rebalance_retry_backoff}} +{{/if}} +{{#if parsers}} +parsers: +{{parsers}} +{{/if}} +{{#if kerberos_enabled}} +kerberos.enabled: {{kerberos_enabled}} +{{/if}} +{{#if kerberos_auth_type}} +kerberos.auth_type: {{kerberos_auth_type}} +{{/if}} +{{#if kerberos_config_path}} +kerberos.config_path: {{kerberos_config_path}} +{{/if}} +{{#if kerberos_username}} +kerberos.username: {{kerberos_username}} +{{/if}} +{{#if kerberos_password}} +kerberos.password: {{kerberos_password}} +{{/if}} +{{#if kerberos_keytab}} +kerberos.keytab: {{kerberos_keytab}} +{{/if}} +{{#if kerberos_service_name}} +kerberos.service_name: {{kerberos_service_name}} +{{/if}} +{{#if kerberos_realm}} +kerberos.realm: {{kerberos_realm}} +{{/if}} +{{#if kerberos_enable_krb5_fast}} +kerberos.enable_krb5_fast: {{kerberos_enable_krb5_fast}} +{{/if}} \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/agent/logfile.yml.hbs b/x-pack/plugins/integration_assistant/server/templates/agent/logfile.yml.hbs new file mode 100644 index 0000000000000..181b2466dff7f --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/agent/logfile.yml.hbs @@ -0,0 +1,13 @@ +paths: +{{#each paths as |path i|}} + - {{path}} +{{/each}} +{{#if exclude_files}} +exclude_files: +{{#each exclude_files as |file f|}} + - {{file}} +{{/each}} +{{/if}} +{{#if custom}} +{{custom}} +{{/if}} \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/agent/tcp.yml.hbs b/x-pack/plugins/integration_assistant/server/templates/agent/tcp.yml.hbs new file mode 100644 index 0000000000000..037d4fc8a4590 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/agent/tcp.yml.hbs @@ -0,0 +1,19 @@ +host: {{listen_address}}:{{listen_port}} +{{#if max_message_size}} +max_message_size: {{max_message_size}} +{{/if}} +{{#if framing}} +framing: {{framing}} +{{/if}} +{{#if line_delimiter}} +line_delimiter: {{line_delimiter}} +{{/if}} +{{#if max_connections}} +max_connections: {{max_connections}} +{{/if}} +{{#if timeout}} +timeout: {{timeout}} +{{/if}} +{{#if keep_null}} +keep_null: {{keep_null}} +{{/if}} \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/agent/udp.yml.hbs b/x-pack/plugins/integration_assistant/server/templates/agent/udp.yml.hbs new file mode 100644 index 0000000000000..22f842ae31af6 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/agent/udp.yml.hbs @@ -0,0 +1,10 @@ +host: {{listen_address}}:{{listen_port}} +{{#if max_message_size}} +max_message_size: {{max_message_size}} +{{/if}} +{{#if timeout}} +timeout: {{timeout}} +{{/if}} +{{#if keep_null}} +keep_null: {{keep_null}} +{{/if}} \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/base_fields.yml.njk b/x-pack/plugins/integration_assistant/server/templates/base_fields.yml.njk new file mode 100644 index 0000000000000..336d4c30e0dd5 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/base_fields.yml.njk @@ -0,0 +1,20 @@ +- name: data_stream.type + type: constant_keyword + description: Data stream type. +- name: data_stream.dataset + type: constant_keyword + description: Data stream dataset name. +- name: data_stream.namespace + type: constant_keyword + description: Data stream namespace. +- name: event.module + type: constant_keyword + description: Event module + value: {{ module }} +- name: event.dataset + type: constant_keyword + description: Event dataset + value: {{ dataset }} +- name: "@timestamp" + type: date + description: Event timestamp. diff --git a/x-pack/plugins/integration_assistant/server/templates/build.yml.njk b/x-pack/plugins/integration_assistant/server/templates/build.yml.njk new file mode 100644 index 0000000000000..8eb17a43a735e --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/build.yml.njk @@ -0,0 +1,3 @@ +dependencies: + ecs: + reference: "git@{{ ecs_version }}" \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/changelog.yml.njk b/x-pack/plugins/integration_assistant/server/templates/changelog.yml.njk new file mode 100644 index 0000000000000..eaf3d00631fa9 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/changelog.yml.njk @@ -0,0 +1,6 @@ +# newer versions go on top +- version: {{ initial_version }} + changes: + - description: Initial Version + type: enhancement + link: https://github.com/elastic/integrations/pull/xxxx diff --git a/x-pack/plugins/integration_assistant/server/templates/data_stream/fields/agent.yml b/x-pack/plugins/integration_assistant/server/templates/data_stream/fields/agent.yml new file mode 100644 index 0000000000000..d815b78d95fcf --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/data_stream/fields/agent.yml @@ -0,0 +1,44 @@ +- name: cloud + title: Cloud + group: 2 + description: Fields related to the cloud or infrastructure the events are coming from. + footnote: 'Examples: If Metricbeat is running on an EC2 host and fetches data from its host, the cloud info contains the data about this machine. If Metricbeat runs on a remote machine outside the cloud and fetches data from a service running in the cloud, the field contains cloud data from the machine the service is running on.' + type: group + fields: + - name: image.id + type: keyword + description: Image ID for the cloud instance. +- name: container + title: Container + group: 2 + description: 'Container fields are used for meta information about the specific container that is the source of information. + These fields help correlate data based containers from any runtime.' + type: group + fields: + - name: labels + level: extended + type: object + object_type: keyword + description: Image labels. +- name: host + title: Host + group: 2 + description: 'A host is defined as a general computing instance. + ECS host.* fields should be populated with details about the host on which the event happened, or from which the measurement was taken. Host types include hardware, virtual machines, Docker containers, and Kubernetes nodes.' + type: group + fields: + - name: containerized + type: boolean + description: > + If the host is a container. + - name: os.build + type: keyword + example: "18D109" + description: > + OS build information. + - name: os.codename + type: keyword + example: "stretch" + description: > + OS codename, if any. + diff --git a/x-pack/plugins/integration_assistant/server/templates/data_stream/fields/beats.yml b/x-pack/plugins/integration_assistant/server/templates/data_stream/fields/beats.yml new file mode 100644 index 0000000000000..9bcba659d84c0 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/data_stream/fields/beats.yml @@ -0,0 +1,30 @@ +- name: input.type + type: keyword + description: Type of Filebeat input. +- name: log.flags + type: keyword + description: Flags for the log file. +- name: log.offset + type: long + description: Offset of the entry in the log file. +- name: log.file + type: group + fields: + - name: device_id + type: keyword + description: ID of the device containing the filesystem where the file resides. + - name: fingerprint + type: keyword + description: The sha256 fingerprint identity of the file when fingerprinting is enabled. + - name: inode + type: keyword + description: Inode number of the log file. + - name: idxhi + type: keyword + description: The high-order part of a unique identifier that is associated with a file. (Windows-only) + - name: idxlo + type: keyword + description: The low-order part of a unique identifier that is associated with a file. (Windows-only) + - name: vol + type: keyword + description: The serial number of the volume that contains a file. (Windows-only) \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/img/logo.svg b/x-pack/plugins/integration_assistant/server/templates/img/logo.svg new file mode 100644 index 0000000000000..173fdec5072e9 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/img/logo.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/aws_cloudwatch_manifest.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/aws_cloudwatch_manifest.yml.njk new file mode 100644 index 0000000000000..c2334c76052a0 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/aws_cloudwatch_manifest.yml.njk @@ -0,0 +1,92 @@ +- input: aws-cloudwatch + template_path: aws-cloudwatch.yml.hbs + title: {{ data_stream_title }} + description: {{ data_stream_description }} + vars: + - name: log_group_arn + type: text + title: Log Group ARN + multi: false + required: false + show_user: true + description: ARN of the log group to collect logs from. + - name: start_position + type: text + title: Start Position + multi: false + required: false + default: beginning + show_user: true + description: Allows user to specify if this input should read log files from the beginning or from the end. + - name: log_group_name + type: text + title: Log Group Name + multi: false + required: false + show_user: false + description: Name of the log group to collect logs from. `region_name` is required when `log_group_name` is given. + - name: log_group_name_prefix + type: text + title: Log Group Name Prefix + multi: false + required: false + show_user: false + description: The prefix for a group of log group names. `region_name` is required when `log_group_name_prefix` is given. `log_group_name` and `log_group_name_prefix` cannot be given at the same time. + - name: region_name + type: text + title: Region Name + multi: false + required: false + show_user: false + description: Region that the specified log group or log group prefix belongs to. + - name: log_streams + type: text + title: Log Streams + multi: true + required: false + show_user: false + description: A list of strings of log streams names that Filebeat collect log events from. + - name: log_stream_prefix + type: text + title: Log Stream Prefix + multi: false + required: false + show_user: false + description: A string to filter the results to include only log events from log streams that have names starting with this prefix. + - name: scan_frequency + type: text + title: Scan Frequency + multi: false + required: false + show_user: false + default: 1m + description: This config parameter sets how often Filebeat checks for new log events from the specified log group. + - name: api_timeput + type: text + title: API Timeout + multi: false + required: false + show_user: false + default: 120s + description: The maximum duration of AWS API can take. If it exceeds the timeout, AWS API will be interrupted. + - name: api_sleep + type: text + title: API Sleep + multi: false + required: false + show_user: false + default: 200ms + description: This is used to sleep between AWS FilterLogEvents API calls inside the same collection period. `FilterLogEvents` API has a quota of 5 transactions per second (TPS)/account/Region. This value should only be adjusted when there are multiple Filebeats or multiple Filebeat inputs collecting logs from the same region and AWS account. + - name: latency + type: text + title: Latency + multi: false + required: false + show_user: false + description: "The amount of time required for the logs to be available to CloudWatch Logs. Sample values, `1m` or `5m` — see Golang [time.ParseDuration](https://pkg.go.dev/time#ParseDuration) for more details. Latency translates the query's time range to consider the CloudWatch Logs latency. Example: `5m` means that the integration will query CloudWatch to search for logs available 5 minutes ago." + - name: number_of_workers + type: integer + title: Number of workers + required: false + show_user: false + description: The number of workers assigned to reading from log groups. Each worker will read log events from one of the log groups matching `log_group_name_prefix`. For example, if `log_group_name_prefix` matches five log groups, then `number_of_workers` should be set to `5`. The default value is `1`. \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/aws_s3.yml_manifest.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/aws_s3.yml_manifest.yml.njk new file mode 100644 index 0000000000000..6265b57e6ed35 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/aws_s3.yml_manifest.yml.njk @@ -0,0 +1,177 @@ +- input: aws-s3 + template_path: aws-s3.yml.hbs + title: {{ data_stream_title }} + description: {{ data_stream_description }} + vars: + - name: bucket_arn + type: text + title: Bucket ARN + multi: false + required: false + show_user: true + description: ARN of the AWS S3 bucket that will be polled for list operation. (Required when `queue_url` and `non_aws_bucket_name` are not set). + - name: queue_url + type: text + title: Queue URL + multi: false + required: false + show_user: true + description: URL of the AWS SQS queue that messages will be received from. + - name: number_of_workers + type: integer + title: Number of Workers + multi: false + required: false + default: 1 + show_user: true + description: Number of workers that will process the S3 objects listed. (Required when `bucket_arn` is set). + - name: parsers + type: yaml + title: Parsers + description: >- + This option expects a list of parsers that the payload has to go through. For more information see [Parsers](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-aws-s3.html#input-aws-s3-parsers) + required: false + show_user: true + multi: false + default: | + #- multiline: + # pattern: "^- + A standard MIME type describing the format of the object data. This can be set to override the MIME type that was given to the object when it was uploaded. For example application/json. + - name: encoding + type: text + title: Encoding + multi: false + required: false + show_user: false + description: The file encoding to use for reading data that contains international characters. This only applies to non-JSON logs. + - name: expand_event_list_from_field + type: text + title: Expand Event List from Field + multi: false + required: false + show_user: false + description: >- + If the fileset using this input expects to receive multiple messages bundled under a specific field then the config option expand_event_list_from_field value can be assigned the name of the field. This setting will be able to split the messages under the group value into separate events. For example, CloudTrail logs are in JSON format and events are found under the JSON object "Records". + - name: file_selectors + type: yaml + title: File Selectors + multi: true + required: false + show_user: false + description: >- + If the SQS queue will have events that correspond to files that this integration shouldn’t process file_selectors can be used to limit the files that are downloaded. This is a list of selectors which are made up of regex and expand_event_list_from_field options. The regex should match the S3 object key in the SQS message, and the optional expand_event_list_from_field is the same as the global setting. If file_selectors is given, then any global expand_event_list_from_field value is ignored in favor of the ones specified in the file_selectors. Regex syntax is the same as the Go language. Files that don’t match one of the regexes won’t be processed. content_type, parsers, include_s3_metadata,max_bytes, buffer_size, and encoding may also be set for each file selector. + - name: fips_enabled + type: bool + title: Enable S3 FIPS + default: false + multi: false + required: false + show_user: false + description: Enabling this option changes the service name from `s3` to `s3-fips` for connecting to the correct service endpoint. + - name: include_s3_metadata + type: text + title: Include S3 Metadata + multi: true + required: false + show_user: false + description: >- + This input can include S3 object metadata in the generated events for use in follow-on processing. You must specify the list of keys to include. By default none are included. If the key exists in the S3 response then it will be included in the event as aws.s3.metadata. where the key name as been normalized to all lowercase. + - name: max_bytes + type: text + title: Max Bytes + default: 10MiB + multi: false + required: false + show_user: false + description: The maximum number of bytes that a single log message can have. All bytes after max_bytes are discarded and not sent. This setting is especially useful for multiline log messages, which can get large. This only applies to non-JSON logs. + - name: max_number_of_messages + type: integer + title: Maximum Concurrent SQS Messages + description: The maximum number of SQS messages that can be inflight at any time. + default: 5 + required: false + show_user: false + - name: non_aws_bucket_name + type: text + title: Non AWS Bucket Name + multi: false + required: false + show_user: false + description: Name of the S3 bucket that will be polled for list operation. Required for 3rd party S3 compatible services. (Required when queue_url and bucket_arn are not set). + - name: path_style + type: text + title: Path Style + multi: false + required: false + show_user: false + description: >- + Enabling this option sets the bucket name as a path in the API call instead of a subdomain. When enabled https://.s3...com becomes https://s3...com/. This is only supported with 3rd party S3 providers. AWS does not support path style. + - name: provider + type: text + title: Provider Name + multi: false + required: false + show_user: false + description: Name of the 3rd party S3 bucket provider like backblaze or GCP. + - name: sqs.max_receive_count + type: integer + title: SQS Message Maximum Receive Count + multi: false + required: false + show_user: false + default: 5 + description: The maximum number of times a SQS message should be received (retried) before deleting it. This feature prevents poison-pill messages (messages that can be received but can’t be processed) from consuming resources. + - name: sqs.wait_time + type: text + title: SQS Maximum Wait Time + multi: false + required: false + show_user: false + default: 20s + description: >- + The maximum duration that an SQS `ReceiveMessage` call should wait for a message to arrive in the queue before returning. The maximum value is `20s`. + - name: visibility_timeout + type: text + title: Visibility Timeout + multi: false + required: false + show_user: false + description: The duration that the received messages are hidden from subsequent retrieve requests after being retrieved by a ReceiveMessage request. The maximum is 12 hours. \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/azure_blob_storage_manifest.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/azure_blob_storage_manifest.yml.njk new file mode 100644 index 0000000000000..897a6a043f86f --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/azure_blob_storage_manifest.yml.njk @@ -0,0 +1,74 @@ +- input: azure-blob-storage + template_path: azure-blob-storage.yml.hbs + title: {{ data_stream_title }} + description: {{ data_stream_description }} + vars: + - name: storage_url + type: text + title: Storage URL + description: >- + Use this attribute to specify a custom storage URL if required. By default it points to azure cloud storage. Only use this if there is a specific need to connect to a different environment where blob storage is available. + URL format : {{protocol}}://{{account_name}}.{{storage_uri}}. + required: false + show_user: false + - name: number_of_workers + type: integer + title: Maximum number of workers + multi: false + required: false + show_user: true + default: 3 + description: Determines how many workers are spawned per container. + - name: poll + type: bool + title: Polling + multi: false + required: false + show_user: true + default: true + description: Determines if the container will be continuously polled for new documents. + - name: poll_interval + type: text + title: Polling interval + multi: false + required: false + show_user: true + default: 15s + description: Determines the time interval between polling operations. + - name: containers + type: yaml + title: Containers + description: "This attribute contains the details about a specific container like, name, number_of_workers, poll, poll_interval etc. \nThe attribute 'name' is specific to a container as it describes the container name, while the fields number_of_workers, poll, poll_interval can exist both at the container level and at the global level. \nIf you have already defined the attributes globally, then you can only specify the container name in this yaml config. \nIf you want to override any specific attribute for a container, then, you can define it here. \nAny attribute defined in the yaml will override the global definitions. Please see the relevant [documentation](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-azure-blob-storage.html#attrib-containers) for further information.\n" + required: true + show_user: true + default: | + - name: azure-container1 + max_workers: 3 + poll: true + poll_interval: 15s + #- name: azure-container2 + # max_workers: 3 + # poll: true + # poll_interval: 10s + - name: file_selectors + type: yaml + title: File Selectors + multi: false + required: false + show_user: false + default: | + # - regex: "event/" + description: "If the container will have events that correspond to files that this integration shouldn’t process, file_selectors can be used to limit the files that are downloaded. This is a list of selectors which is made up of regex patters. \nThe regex should match the container filepath. Regexes use [RE2 syntax](https://pkg.go.dev/regexp/syntax). Files that don’t match one of the regexes will not be processed.\n" + - name: timestamp_epoch + type: integer + title: Timestamp Epoch + multi: false + required: false + show_user: false + - name: expand_event_list_from_field + type: text + title: Expand Event List From Field + multi: false + required: false + show_user: false + description: "If the file-set using this input expects to receive multiple messages bundled under a specific field or an array of objects then the config option for 'expand_event_list_from_field' can be specified. \nThis setting will be able to split the messages under the group value into separate events. This can be specified at the global level or at the container level.\nFor more info please refer to the [documentation](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-azure-blob-storage.html#attrib-expand_event_list_from_field).\n" \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/azure_eventhub_manifest.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/azure_eventhub_manifest.yml.njk new file mode 100644 index 0000000000000..feac0ec87759f --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/azure_eventhub_manifest.yml.njk @@ -0,0 +1,74 @@ +- input: azure-eventhub + template_path: azure-eventhub.yml.hbs + title: {{ data_stream_title }} + description: {{ data_stream_description }} + vars: + - name: eventhub + type: text + title: Event Hub + multi: false + required: true + show_user: true + description: >- + Elastic recommends using one event hub for each integration. Visit [Create an event hub](https://docs.elastic.co/integrations/azure#create-an-event-hub) to learn more. Use event hub names up to 30 characters long to avoid compatibility issues. + - name: connection_string + type: text + title: Connection String + multi: false + required: true + show_user: true + description: >- + The connection string required to communicate with Event Hubs. See [Get an Event Hubs connection string](https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-get-connection-string) to learn more. + - name: storage_account + type: text + title: Storage Account + multi: false + required: true + show_user: true + description: >- + The name of the storage account where the consumer group's state/offsets will be stored and updated. + - name: storage_account_key + type: text + title: Storage Account Key + multi: false + required: true + show_user: true + description: >- + The storage account key, this key will be used to authorize access to data in your storage account. + - name: consumer_group + type: text + title: Consumer Group + multi: false + required: true + show_user: true + default: $Default + - name: resource_manager_endpoint + type: text + title: Resource Manager Endpoint + multi: false + required: false + show_user: false + - name: storage_account_container + type: text + title: Storage Account Container + multi: false + required: false + show_user: false + description: >- + The storage account container where the integration stores the checkpoint data for the consumer group. It is an advanced option to use with extreme care. You MUST use a dedicated storage account container for each Azure log type (activity, sign-in, audit logs, and others). DO NOT REUSE the same container name for more than one Azure log type. See [Container Names](https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names) for details on naming rules from Microsoft. The integration generates a default container name if not specified. + - name: sanitize_newlines + type: bool + title: Sanitizes New Lines + description: Removes new lines in logs to ensure proper formatting of JSON data and avoid parsing issues during processing. + multi: false + required: false + show_user: false + default: false + - name: sanitize_singlequotes + required: true + show_user: false + title: Sanitizes Single Quotes + description: Replaces single quotes with double quotes (single quotes inside double quotes are omitted) in logs to ensure proper formatting of JSON data and avoid parsing issues during processing. + type: bool + multi: false + default: false \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/cloudfoundry_manifest.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/cloudfoundry_manifest.yml.njk new file mode 100644 index 0000000000000..b8e85c57c52d1 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/cloudfoundry_manifest.yml.njk @@ -0,0 +1,101 @@ +- input: cloudfoundry + template_path: cloudfoundry.yml.hbs + title: {{ data_stream_title }} + description: {{ data_stream_description }} + vars: + - name: api_address + type: text + title: API Address + multi: false + required: true + show_user: true + default: "http://api.bosh-lite.com" + description: The URL of the Cloud Foundry API. + - name: client_id + type: text + title: Client ID + multi: false + required: true + show_user: true + description: Client ID to authenticate with Cloud Foundry. + - name: client_secret + type: text + title: Client Secret + multi: false + required: true + show_user: true + description: Client Secret to authenticate with Cloud Foundry. + - name: shard_id + type: text + title: Shard ID + required: false + show_user: false + description: Shard ID for the connection with Cloud Foundry. Use the same ID across multiple filebeat to shard the load of events. Default: "(generated UUID)". + - name: version + type: text + title: Cloud Foundry API Version + required: false + show_user: false + description: Consumer API version to connect with Cloud Foundry to collect events. Use v1 to collect events using Doppler/Traffic Control. Use v2 to collect events from the RLP Gateway. Default: "v1". + - name: doppler_address + type: text + title: Doppler Address + required: false + show_user: false + description: The URL of the Cloud Foundry Doppler Websocket. Optional. Default: "(value from ${api_address}/v2/info)". + - name: uaa_address + type: text + title: UAA Address + required: false + show_user: false + description: The URL of the Cloud Foundry UAA API. Optional. Default: "(value from ${api_address}/v2/info)". + - name: rlp_address + type: text + title: RLP Address + required: false + show_user: false + description: The URL of the Cloud Foundry RLP Gateway. Optional. Default: "(log-stream subdomain under the same domain as api_server)". + - name: custom + type: yaml + title: Additional Log Configuration Options + description: > + Configuration options that can be used to further change input configuration. Check the [Filebeat documentation](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-log.html) for more information. + required: false + show_user: false + default: | + #recursive_glob.enabled: true + #encoding: plain + harvester_buffer_size: 16384 + max_bytes: 10485760 + # json.keys_under_root: false + # json.add_error_key: false + # json.message_key: message + # json.overwrite_keys: false + # json.expand_keys: false + # json.document_id: "" + # json.ignore_decoding_error: false + # multiline.type: pattern + # multiline.pattern: "^{" + # multiline.negate: true + # multiline.match: after + # multiline.max_lines: 500 + # multiline.timeout: 5s + # multiline.flush_pattern: "" + # exclude_lines: ['^DBG'] + # include_lines: ['^ERR', '^WARN'] + # scan_frequency: 10s + # harvester_limit: 0 + # tail_files: false + # backoff: 1s + # max_backoff: 10s + # backoff_factor: 2 + # close_inactive: 5m + # close_renamed: false + # close_removed: true + # close_eof: false + # close_timeout: 0 + # clean_removed: true + # clean_inactive: 0 + # ignore_older: 0 + # max_bytes: 10485760 + # symlinks: false \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/common_manifest.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/common_manifest.yml.njk new file mode 100644 index 0000000000000..95b0dbc37b3cf --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/common_manifest.yml.njk @@ -0,0 +1,25 @@ + - name: preserve_original_event + required: true + show_user: true + title: Preserve original event + description: Preserves a raw copy of the original event, added to the field `event.original` + type: bool + multi: false + default: false + - name: tags + type: text + title: Tags + multi: true + required: true + show_user: false + default: + - forwarded + - {{ package_name }}-{{ data_stream_name }} + - name: processors + type: yaml + title: Processors + multi: false + required: false + show_user: false + description: > + Processors are used to reduce the number of fields in the exported event or to enhance the event with metadata. This executes in the agent before the logs are parsed. See [Processors](https://www.elastic.co/guide/en/beats/filebeat/current/filtering-and-enhancing-data.html) for details. \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/data_stream.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/data_stream.yml.njk new file mode 100644 index 0000000000000..e90bdd91f69e4 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/data_stream.yml.njk @@ -0,0 +1,4 @@ +title: {{ title }} +type: logs +streams:{% for data_stream in data_streams %} +{{ data_stream | indent(2, true) }}{% endfor %} \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/filestream_manifest.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/filestream_manifest.yml.njk new file mode 100644 index 0000000000000..0884b55123850 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/filestream_manifest.yml.njk @@ -0,0 +1,60 @@ +- input: filestream + template_path: filestream.yml.hbs + title: {{ data_stream_title }} + description: {{ data_stream_description }} + vars: + - name: paths + type: text + title: Paths + multi: true + required: true + show_user: true + default: + - '/test/path' + description: The full path to the related log file. + - name: exclude_files + type: text + title: Exclude Files + description: A list of regular expressions to match the files that you want Filebeat to ignore. + required: false + show_user: true + default: + - '\.gz$' + - name: custom + type: yaml + title: Additional Filestream Configuration Options + required: false + show_user: false + description: >- + Configuration options that can be used to further change input configuration. Check the [Filebeat documentation](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-filestream.html) for more information. + default: |- + #encoding: plain + prospector.scanner.recursive_glob: true + #prospector.scanner.symlinks: true + #prospector.scanner.include_files: [''] + #prospector.scanner.resend_on_touch: false + #prospector.scanner.check_interval: 10s + #prospector.scanner.fingerprint.enabled: false + #prospector.scanner.fingerprint.offset: 0 + #prospector.scanner.fingerprint.length: 1024 + #ignore_older: 0 + #ignore_inactive: '' + #close.on_state_change.inactive: 5m + #close.on_state_change.renamed: false + #close.on_state_change.removed: false + #close.reader.on_eof: false + #close.reader.after_interval: 0 + #clean_inactive: 0 + #clean_removed: true + #backoff.init: 2s + #backoff.max: 10s + #file_identity.native: ~ + #file_identity.path: '' + #file_identity.inode_marker.path: '' + #file_identity.fingerprint: ~ + #rotation.external.strategy.copytruncate.suffix_regex: '\.\d$' + #rotation.external.strategy.copytruncate.dateformat: '-20060102' + #include_lines: ['sometext'] + #exclude_lines: ['^DBG'] + #buffer_size: 16384 + #message_max_bytes: 1048576 \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/gcp_pubsub_manifest.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/gcp_pubsub_manifest.yml.njk new file mode 100644 index 0000000000000..920450fd7b911 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/gcp_pubsub_manifest.yml.njk @@ -0,0 +1,64 @@ +- input: gcp-pubsub + template_path: gcp-pubsub.yml.hbs + title: {{ data_stream_title }} + description: {{ data_stream_description }} + vars: + - name: project_id + type: text + title: Project ID + multi: false + required: true + show_user: true + - name: topic + type: text + title: Topic + multi: false + required: true + show_user: true + - name: subscription_name + type: text + title: Subscription Name + multi: false + required: true + show_user: true + - name: credentials_file + type: text + title: Credentials File + multi: false + required: false + show_user: true + description: Path to a JSON file containing the credentials and key used to subscribe. + - name: credentials_json + type: text + title: Credentials JSON + multi: false + required: false + show_user: true + description: JSON blob containing the credentials and key used to subscribe. + - name: subscription_create + type: bool + title: Subscription Create + description: If true, the integration will create the subscription on start. + multi: false + required: false + show_user: true + - name: subscription_num_goroutines + type: text + title: Subscription Num Goroutines + description: Number of goroutines created to read from the subscription. This does not limit the number of messages that can be processed concurrently or the maximum number of goroutines the input will create. + multi: false + required: false + show_user: false + - name: subscription_max_outstanding_messages + type: text + title: Subscription Max Outstanding Messages + description: The maximum number of unprocessed messages (unacknowledged but not yet expired). If the value is negative, then there will be no limit on the number of unprocessed messages. Default is 1000. + multi: false + required: false + show_user: false + - name: alternative_host + type: text + title: Alternative host + multi: false + required: false + show_user: false \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/gcs_manifest.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/gcs_manifest.yml.njk new file mode 100644 index 0000000000000..4beef9fc5f439 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/gcs_manifest.yml.njk @@ -0,0 +1,106 @@ +- input: gcs + template_path: gcs.yml.hbs + title: {{ data_stream_title }} + description: {{ data_stream_description }} + vars: + - name: project_id + type: text + title: Project ID + description: >- + This attribute is required for various internal operations with respect to authentication, creating storage clients and logging which are used internally for various processing purposes. + multi: false + required: true + show_user: true + default: my-project-id + - name: service_account_key + type: password + title: Service Account Key + description: >- + This attribute contains the json service account credentials string, which can be generated from the google cloud console, ref[Service Account Keys](https://cloud.google.com/iam/docs/creating-managing-service-account-keys). + Required if a Service Account File is not provided. + multi: false + required: false + show_user: true + - name: service_account_file + type: text + title: Service Account File + description: >- + This attribute contains the service account credentials file, which can be generated from the google cloud console, ref [Service Account Keys](https://cloud.google.com/iam/docs/creating-managing-service-account-keys). + Required if a Service Account Key is not provided. + multi: false + required: false + show_user: true + - name: number_of_workers + type: integer + title: Maximum number of workers + multi: false + required: false + show_user: true + default: 3 + description: Determines how many workers are spawned per bucket. + - name: poll + type: bool + title: Polling + multi: false + required: false + show_user: true + default: true + description: Determines if the bucket will be continuously polled for new documents. + - name: poll_interval + type: text + title: Polling interval + multi: false + required: false + show_user: true + default: 15s + description: Determines the time interval between polling operations. + - name: bucket_timeout + type: text + title: Bucket Timeout + multi: false + required: false + show_user: true + default: 120s + description: Defines the maximum time that the sdk will wait for a bucket api response before timing out. Valid time units are ns, us, ms, s, m, h. + - name: buckets + type: yaml + title: Buckets + description: "This attribute contains the details about a specific bucket like, name, number_of_workers, poll, poll_interval and bucket_timeout. The attribute 'name' is specific to a bucket as it describes the bucket name, while the fields number_of_workers, poll, poll_interval and bucket_timeout can exist both at the bucket level and at the global level. If you have already defined the attributes globally, then you can only specify the name in this yaml config. If you want to override any specific attribute for a specific bucket, then, you can define it here. Any attribute defined in the yaml will override the global definitions. Please see the relevant [Documentation](https://www.elastic.co/guide/en/beats/filebeat/8.5/filebeat-input-gcs.html#attrib-buckets) for further information.\n" + required: true + show_user: true + default: >- + # You can define as many buckets as you want here. + + - name: logs + - name: logs_2 + + # The config below is an example of how to override the global config. + + #- name: event_logs_3 + # number_of_workers: 3 + # poll: true + # poll_interval: 10s + # bucket_timeout: 30s + - name: file_selectors + type: yaml + title: File Selectors + multi: false + required: false + show_user: false + default: >- + # - regex: "event/" + description: "If the GCS bucket will have events that correspond to files that this integration shouldn’t process, file_selectors can be used to limit the files that are downloaded. This is a list of selectors which is made up of regex patters. \nThe regex should match the GCS bucket filepath. Regexes use [RE2 syntax](https://pkg.go.dev/regexp/syntax). Files that don’t match one of the regexes will not be processed.\n" + - name: timestamp_epoch + type: integer + title: Timestamp Epoch + multi: false + required: false + show_user: false + description: Defines the epoch time in seconds, which is used to filter out objects/files that are older than the specified timestamp. + - name: alternative_host + type: text + title: Alternative Host + description: Used to override the default host for the storage client (default is storage.googleapis.com) + required: false + multi: false + show_user: false \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/http_endpoint_manifest.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/http_endpoint_manifest.yml.njk new file mode 100644 index 0000000000000..1c75f6f337806 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/http_endpoint_manifest.yml.njk @@ -0,0 +1,133 @@ +- input: http_endpoint + template_path: http_endpoint.yml.hbs + title: {{ data_stream_title }} + description: {{ data_stream_description }} + vars: + - name: listen_address + type: text + title: Listen Address + description: | + Bind address for the HTTP listener. Use 0.0.0.0 to listen on all interfaces. + required: true + show_user: true + default: localhost + - name: listen_port + type: text + title: Listen port + description: | + Bind port for the listener. + required: true + show_user: true + default: 8080 + - name: url + type: text + title: URL + description: This options specific which URL path to accept requests on. Defaults to /. + required: false + show_user: true + - name: data_stream.dataset + type: text + title: Dataset name + description: | + Dataset to write data to. Changing the dataset will send the data to a different index. You can't use `-` in the name of a dataset and only valid characters for [Elasticsearch index names](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html). + default: http_endpoint.generic + required: true + show_user: true + - name: pipeline + type: text + title: Ingest Pipeline + description: | + The Ingest Node pipeline ID to be used by the integration. + required: false + show_user: true + - name: preserve_original_event + type: bool + title: Preserve Original Event + description: This option copies the raw unmodified body of the incoming request to the event.original field as a string before sending the event to Elasticsearch. + required: false + show_user: true + - name: prefix + type: text + title: Prefix + description: This option specifies which prefix field the incoming request will be mapped to. + required: false + show_user: false + - name: basic_auth + type: bool + title: Basic Auth + description: Enables or disables HTTP basic auth for each incoming request. If enabled then username and password will also need to be configured. + required: false + show_user: false + - name: username + type: text + title: Username + description: If basic_auth is enabled, this is the username used for authentication against the HTTP listener. Requires password to also be set. + required: false + show_user: false + - name: password + type: password + title: Password + description: If basic_auth is enabled, this is the password used for authentication against the HTTP listener. Requires username to also be set. + required: false + show_user: false + - name: secret_header + type: text + title: Secret Header + description: The header to check for a specific value specified by secret.value. Certain webhooks provide the possibility to include a special header and secret to identify the source. + required: false + show_user: false + - name: secret_value + type: password + title: Secret Value + description: The secret stored in the header name specified by secret.header. Certain webhooks provide the possibility to include a special header and secret to identify the source. + required: false + show_user: false + - name: hmac_header + type: text + title: HMAC Header + description: The name of the header that contains the HMAC signature, for example X-Dropbox-Signature, X-Hub-Signature-256, etc. + required: false + show_user: false + - name: hmac_key + type: password + title: HMAC Key + description: The secret key used to calculate the HMAC signature. Typically, the webhook sender provides this value. + required: false + show_user: false + - name: hmac_type + type: text + title: HMAC Type + description: The hash algorithm to use for the HMAC comparison. At this time the only valid values are sha256 or sha1. + required: false + show_user: false + - name: hmac_prefix + type: text + title: HMAC Prefix + description: The prefix for the signature. Certain webhooks prefix the HMAC signature with a value, for example sha256=. + required: false + show_user: false + - name: content_type + type: text + title: Content Type + description: By default the input expects the incoming POST to include a Content-Type of application/json to try to enforce the incoming data to be valid JSON. In certain scenarios when the source of the request is not able to do that, it can be overwritten with another value or set to null. + required: false + show_user: false + - name: response_code + type: text + title: Response Code + description: The HTTP response code returned upon success. Should be in the 2XX range. + required: false + show_user: false + - name: response_body + type: text + title: Response Body + description: The response body returned upon success. Should be a single line JSON string. + required: false + show_user: false + - name: include_headers + type: text + title: Include Headers + description: This options specifies a list of HTTP headers that should be copied from the incoming request and included in the document. All configured headers will always be canonicalized to match the headers of the incoming request. For example, ["content-type"] will become ["Content-Type"] when the filebeat is running. + multi: true + required: false + show_user: false \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/journald_manifest.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/journald_manifest.yml.njk new file mode 100644 index 0000000000000..eef7588f18def --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/journald_manifest.yml.njk @@ -0,0 +1,77 @@ +- input: journald + template_path: journald.yml.hbs + title: {{ data_stream_title }} + description: {{ data_stream_description }} + vars: + - name: include_matches + type: text + title: Include Matches + multi: true + show_user: true + description: >- + A list of filter expressions used to select the logs to read (e.g. `_SYSTEMD_UNIT=vault.service`). Defaults to all logs. See [include_matches](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-journald.html#filebeat-input-journald-include-matches) for details. + - name: backoff + type: text + title: Backoff Duration + multi: false + show_user: false + default: "1s" + description: >- + The number of seconds to wait before trying to read again from journals. + - name: max_backoff + type: text + title: Max Backoff Duration + multi: false + show_user: false + default: "60s" + description: >- + The maximum number of seconds to wait before attempting to read again from journals. + - name: seek + type: text + title: Start Position + multi: false + show_user: false + description: >- + The position to start reading the journal from. See [seek](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-journald.html#filebeat-input-journald-seek) for details. + - name: cursor_seek_fallback + type: text + title: Start Position Fallback + multi: false + show_user: false + description: >- + The position to start reading the journal from if no cursor information is available. See [cursor_seek_fallback](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-journald.html#filebeat-input-journald-cursor_seek_fallback) for details. + - name: since + type: text + title: Start Since + multi: false + show_user: false + description: >- + A time offset from the current time to start reading from. Example: "-24h" + - name: units + type: text + title: Units + multi: true + show_user: false + description: >- + Iterate only the entries of the units specified in this option. The iterated entries include messages from the units, messages about the units by authorized daemons and coredumps. However, it does not match systemd user units. + - name: syslog_identifiers + type: text + title: Syslog Identifiers + multi: true + show_user: false + description: >- + Read only the entries with the selected syslog identifiers. + - name: transports + type: text + title: Included Transport Types + multi: true + show_user: false + description: >- + Collect the messages using the specified transports. See [transports](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-journald.html#filebeat-input-journald-transports) for more details. + - name: paths + type: text + title: Journal paths + multi: true + show_user: false + description: >- + List of journals to read from. Defaults to the system journal. diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/kafka_manifest.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/kafka_manifest.yml.njk new file mode 100644 index 0000000000000..7374b34906c0c --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/kafka_manifest.yml.njk @@ -0,0 +1,221 @@ +- input: kafka + template_path: kafka.yml.hbs + title: {{ data_stream_title }} + description: {{ data_stream_description }} + vars: + - name: hosts + type: text + title: Hosts + description: | + A list of Kafka bootstrapping hosts (brokers) for this cluster. + required: true + show_user: true + multi: true + - name: topics + type: text + title: Topics + description: | + A list of topics to read from. + required: true + show_user: true + multi: true + - name: data_stream.dataset + type: text + title: Dataset name + description: | + Dataset to write data to. Changing the dataset will send the data to a different index. You can't use `-` in the name of a dataset and only valid characters for [Elasticsearch index names](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html). + default: kafka_log.generic + required: true + show_user: true + - name: pipeline + type: text + title: Ingest Pipeline + description: | + The Ingest Node pipeline ID to be used by the integration. + required: false + show_user: true + - name: group_id + type: text + title: Group ID + description: The Kafka consumer group id. + required: true + show_user: true + - name: client_id + type: text + title: Client ID + description: The Kafka client id (optional). + required: false + show_user: true + - name: version + type: text + title: Version + description: The version of the Kafka protocol to use (defaults to "1.0.0"). + required: false + show_user: true + - name: expand_event_list_from_field + type: text + title: Expand Event List from Field + description: Split a field that contains an array of JSON objects, the value would be the name of this field. + required: false + show_user: true + - name: parsers + type: yaml + title: Parsers + description: | + This option expects a list of parsers that the payload has to go through. For more information see [Parsers](https://www.elastic.co/guide/en/beats/filebeat/8.0/filebeat-input-kafka.html#_parsers_2) + required: false + show_user: true + multi: false + default: | + #- ndjson: + # keys_under_root: true + # message_key: msg + #- multiline: + # type: counter + # lines_count: 3 + - name: username + type: text + title: Username + description: Username used for SASL authentication. + required: false + show_user: true + - name: password + type: password + title: Password + description: Password used for SASL authentication. + required: false + show_user: true + - name: kerberos_enabled + type: bool + title: Kerberos Enabled + description: The enabled setting can be used to enable the Kerberos configuration by setting it to true. The default value is false. + required: false + show_user: false + - name: kerberos_auth_type + type: text + title: Kerberos Auth Type + description: | + There are two options to authenticate with Kerberos KDC: password and keytab. + Password expects the principal name and its password. When choosing keytab, you have to specify a principal name and a path to a keytab. The keytab must contain the keys of the selected principal. Otherwise, authentication will fail. + required: false + show_user: false + - name: kerberos_config_path + type: text + title: Kerberos Config Path + description: You need to set the path to the krb5.conf, so Elastic Agent can find the Kerberos KDC to retrieve a ticket. + required: false + show_user: false + - name: kerberos_username + type: text + title: Kerberos Username + description: Name of the principal used to connect to the output. + required: false + show_user: false + - name: kerberos_password + type: password + title: Kerberos Password + description: If you configured password for Auth Type, you have to provide a password for the selected principal. + required: false + show_user: false + - name: kerberos_keytab + type: text + title: Kerberos Keytab + description: If you configured keytab for Auth Type, you have to provide the path to the keytab of the selected principal. + required: false + show_user: false + - name: kerberos_service_name + type: text + title: Kerberos Service Name + description: This option can only be configured for Kafka. It is the name of the Kafka service, usually "kafka". + required: false + show_user: false + - name: kerberos_realm + type: text + title: Kerberos Realm + description: Name of the realm where the output resides. + required: false + show_user: false + - name: kerberos_enable_krb5_fast + type: bool + title: Kerberos KRB5 Fast + description: Enable Kerberos FAST authentication. This may conflict with some Active Directory installations. The default is false. + required: false + show_user: false + - name: initial_offset + type: text + title: Initial Offset + description: The initial offset to start reading, either "oldest" or "newest". Defaults to "oldest". + required: false + show_user: false + - name: connect_backoff + type: text + title: Connect Backoff + description: How long to wait before trying to reconnect to the kafka cluster after a fatal error. Default is 30s. + required: false + show_user: false + - name: consume_backoff + type: text + title: Consume Backoff + description: How long to wait before retrying a failed read. Default is 2s. + required: false + show_user: false + - name: max_wait_time + type: text + title: Max Wait Time + description: How long to wait for the minimum number of input bytes while reading. Default is 250ms. + required: false + show_user: false + - name: wait_close + type: text + title: Wait Close + description: When shutting down, how long to wait for in-flight messages to be delivered and acknowledged. + required: false + show_user: false + - name: isolation_level + type: text + title: Wait Close + description: This configures the Kafka group isolation level, supports the values "read_uncommitted" which returns all messages in the message channel and "read_committed" which hides messages that are part of an aborted transaction. The default is "read_uncommitted". + required: false + show_user: false + - name: fetch_min + type: text + title: Fetch Min + description: The minimum number of bytes to wait for. Defaults to 1. + required: false + show_user: false + - name: fetch_default + type: text + title: Fetch Default + description: The default number of bytes to read per request. Defaults to 1MB. + required: false + show_user: false + - name: fetch_max + type: text + title: Fetch Max + description: The maximum number of bytes to read per request. Defaults to 0 (no limit). + required: false + show_user: false + - name: rebalance_strategy + type: text + title: Rebalance Strategy + description: Either "range" or "roundrobin". Defaults to "range". + required: false + show_user: false + - name: rebalance_timeout + type: text + title: Rebalance Timeout + description: How long to wait for an attempted rebalance. Defaults to 60s. + required: false + show_user: false + - name: rebalance_max_retries + type: text + title: Rebalance Max Retries + description: How many times to retry if rebalancing fails. Defaults to 4. + required: false + show_user: false + - name: rebalance_retry_backoff + type: text + title: Rebalance Retry Backoff + description: How long to wait after an unsuccessful rebalance attempt. Defaults to 2s. + required: false + show_user: false \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/logfile_manifest.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/logfile_manifest.yml.njk new file mode 100644 index 0000000000000..24de5572cf73d --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/logfile_manifest.yml.njk @@ -0,0 +1,66 @@ +- input: logfile + template_path: logfile.yml.hbs + title: {{ data_stream_title }} + description: {{ data_stream_description }} + vars: + - name: paths + type: text + title: Paths + multi: true + required: true + show_user: true + default: + - '/test/path' + description: The full path to the related log file. + - name: exclude_files + type: text + title: Exclude Files + description: A list of regular expressions to match the files that you want Filebeat to ignore. + required: false + show_user: true + default: + - '\.gz$' + - name: custom + type: yaml + title: Additional Log Configuration Options + description: > + Configuration options that can be used to further change input configuration. Check the [Filebeat documentation](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-log.html) for more information. + required: false + show_user: false + default: | + #recursive_glob.enabled: true + #encoding: plain + harvester_buffer_size: 16384 + max_bytes: 10485760 + # json.keys_under_root: false + # json.add_error_key: false + # json.message_key: message + # json.overwrite_keys: false + # json.expand_keys: false + # json.document_id: "" + # json.ignore_decoding_error: false + # multiline.type: pattern + # multiline.pattern: "^{" + # multiline.negate: true + # multiline.match: after + # multiline.max_lines: 500 + # multiline.timeout: 5s + # multiline.flush_pattern: "" + # exclude_lines: ['^DBG'] + # include_lines: ['^ERR', '^WARN'] + # scan_frequency: 10s + # harvester_limit: 0 + # tail_files: false + # backoff: 1s + # max_backoff: 10s + # backoff_factor: 2 + # close_inactive: 5m + # close_renamed: false + # close_removed: true + # close_eof: false + # close_timeout: 0 + # clean_removed: true + # clean_inactive: 0 + # ignore_older: 0 + # max_bytes: 10485760 + # symlinks: false \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/package_manifest.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/package_manifest.yml.njk new file mode 100644 index 0000000000000..e4beecdd132ea --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/package_manifest.yml.njk @@ -0,0 +1,28 @@ +format_version: {{ format_version }} +name: {{ package_name }} +title: {{ package_title }} +version: {{ package_version }} +description: {{ package_description }} +type: integration +categories: + - security + - iam +conditions: + kibana: + version: {{ min_version }} +icons: + - src: /img/logo.svg + title: {{ package_name }} Logo + size: 32x32 + type: image/svg+xml +policy_templates: + - name: {{ package_name }} + title: {{ package_title }} + description: {{ package_description}} + inputs: {% for input in inputs %} + - type: {{ input.type }} + title: {{ input.title }} + description: {{ input.description }} {% endfor %} +owner: + github: {{ package_owner }} + type: elastic \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/ssl_manifest.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/ssl_manifest.yml.njk new file mode 100644 index 0000000000000..0eb62ad2f5924 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/ssl_manifest.yml.njk @@ -0,0 +1,75 @@ + - name: ssl_supported_protocols + type: text + title: SSL Supported Protocols + multi: true + required: false + show_user: false + default: + - 'TLSv1.1' + - 'TLSv1.2' + - 'TLSv1.3' + description: List of allowed SSL/TLS versions. If SSL/TLS server decides for protocol versions not configure. + - name: ssl_cipher_suites + type: text + title: SSL Cipher Suites + multi: true + required: false + show_user: false + description: The list of cipher suites to use. If not specified, the default cipher suites will be used. + - name: ssl_curve_types + type: text + title: SSL Curve Types + multi: true + required: false + show_user: false + description: The list of curve types for ECDHE (Elliptic Curve Diffie-Hellman ephemeral key exchange). + - name: ssl_ca_sha256 + type: text + title: SSL SHA256 Pin + multi: true + required: false + show_user: false + description: Configure a pin that can be used to do extra validation of the verified certificate chain. + - name: ssl_certificate_authorities + type: text + title: SSL Certificate Authorities + multi: true + required: false + show_user: false + description: The list of root certificates for verifications is required. If certificate_authorities is empty or not set, the system keystore is used. Example: /etc/pki/root/ca.pem + - name: ssl_certificate + type: text + title: SSL Certificate + multi: false + required: false + show_user: false + description: Path to the SSL certificate file to be used. Example: /etc/pki/client/cert.pem + - name: ssl_certificate_key + type: text + title: SSL Certificate Key + multi: false + required: false + show_user: false + description: Path to the SSL certificate key file to be used. Example: /etc/pki/client/cert.key + - name: ssl_certificate_key_passphrase + type: text + title: SSL Certificate Key Passphrase + multi: false + required: false + show_user: false + description: The passphrase used to decrypt an encrypted key stored in the configured key file. + - name: ssl_verification_mode + type: text + title: SSL Certificate Key Passphrase + multi: false + required: false + show_user: false + default: 'full' + description: The passphrase used to decrypt an encrypted key stored in the configured key file. + - name: ssl_certificate_authority_trusted_fingerprint + type: text + title: SSL Certificate Authority Trusted Fingerprint + multi: false + required: false + show_user: false + description: A HEX encoded SHA-256 of a CA certificate. If this certificate is present in the chain during the handshake, it will be added to the certificate_authorities list and the handshake will continue normaly. \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/tcp_manifest.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/tcp_manifest.yml.njk new file mode 100644 index 0000000000000..eb2b5d27d55c9 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/tcp_manifest.yml.njk @@ -0,0 +1,57 @@ +- input: tcp + template_path: tcp.yml.hbs + title: {{ data_stream_title }} + description: {{ data_stream_description }} + vars: + - name: listen_address + type: text + title: Listen Address + description: | + Bind address for the listener. Use 0.0.0.0 to listen on all interfaces. + required: true + show_user: true + default: localhost + - name: listen_port + type: text + title: Listen port + description: | + Bind port for the listener. + required: true + show_user: true + default: 8080 + - name: max_message_size + type: text + title: Max Message Size + description: The maximum size of the message received over TCP. The default is 20MiB + required: false + show_user: false + - name: framing + type: text + title: Framing + description: Specify the framing used to split incoming events. Can be one of delimiter or rfc6587. The default is delimiter + required: false + show_user: false + - name: line_delimiter + type: text + title: Line Delimiter + description: Specify the characters used to split the incoming events. The default is \n. + required: false + show_user: false + - name: max_connections + type: text + title: Max Connections + description: The at most number of connections to accept at any given point in time. + required: false + show_user: false + - name: timeout + type: text + title: Timeout + description: The duration of inactivity before a remote connection is closed. The default is 300s. Valid time units are ns, us, ms, s, m, h. + required: false + show_user: false + - name: keep_null + type: bool + title: Keep Null Values + description: If this option is set to true, fields with null values will be published in the output document. By default, keep_null is set to false. + required: false + show_user: false \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/manifest/udp_manifest.yml.njk b/x-pack/plugins/integration_assistant/server/templates/manifest/udp_manifest.yml.njk new file mode 100644 index 0000000000000..9955d2222b4ba --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/manifest/udp_manifest.yml.njk @@ -0,0 +1,39 @@ +- input: udp + template_path: udp.yml.hbs + title: {{ data_stream_title }} + description: {{ data_stream_description }} + vars: + - name: listen_address + type: text + title: Listen Address + description: | + Bind address for the listener. Use 0.0.0.0 to listen on all interfaces. + required: true + show_user: true + default: localhost + - name: listen_port + type: text + title: Listen port + description: | + Bind port for the listener. + required: true + show_user: true + default: 8080 + - name: max_message_size + type: text + title: Max Message Size + description: The maximum size of the message received over TCP. The default is 20MiB + required: false + show_user: false + - name: timeout + type: text + title: Timeout + description: The duration of inactivity before a remote connection is closed. The default is 300s. Valid time units are ns, us, ms, s, m, h. + required: false + show_user: false + - name: keep_null + type: bool + title: Keep Null Values + description: If this option is set to true, fields with null values will be published in the output document. By default, keep_null is set to false. + required: false + show_user: false \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/pipeline.yml.njk b/x-pack/plugins/integration_assistant/server/templates/pipeline.yml.njk new file mode 100644 index 0000000000000..ba4bee23cf633 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/pipeline.yml.njk @@ -0,0 +1,131 @@ +--- +description: Pipeline to process {{ package_name }} {{ data_stream_name }} logs +processors: + - set: + field: ecs.version + tag: set_ecs_version + value: '{{ ecs_version }}' + - rename: + field: message + target_field: event.original + tag: rename_message + ignore_missing: true + if: ctx.event?.original == null + - remove: + field: message + ignore_missing: true + tag: remove_message + if: 'ctx.event?.original != null'{% if log_format == 'json' %} + - json: + field: event.original + tag: json_original + target_field: {{ package_name }}.{{ data_stream_name }}{% endif %} +{% for processor in processors %}{% for key, value in processor %} + {% if key == 'rename' %} + - {{ key }}: + field: {{ value.field }} + target_field: {% if value.target_field | startswith('@') %}"{{ value.target_field }}"{% else %}{{ value.target_field }}{% endif %} + ignore_missing: true{% endif %} + {% if key == 'date' %} + - {{ key }}: + field: {{ value.field }} + target_field: {% if value.target_field | startswith('@') %}"{{ value.target_field }}"{% else %}{{ value.target_field }}{% endif %} + formats: + {% for format in value.formats %} + - {{ format }} + {% endfor %} + if: "ctx.{{ value.if }} != null"{% endif %} + {% if key == 'convert' %} + - {{ key }}: + field: {{ value.field }} + target_field: {% if value.target_field | startswith('@') %}"{{ value.target_field }}"{% else %}{{ value.target_field }}{% endif %} + ignore_missing: true + ignore_failure: true + type: {{value.type}}{% endif %}{% endfor %}{% endfor %} + - script: + description: Drops null/empty values recursively. + tag: script_drop_null_empty_values + lang: painless + {% raw %}source: | + boolean dropEmptyFields(Object object) { + if (object == null || object == "") { + return true; + } else if (object instanceof Map) { + ((Map) object).values().removeIf(value -> dropEmptyFields(value)); + return (((Map) object).size() == 0); + } else if (object instanceof List) { + ((List) object).removeIf(value -> dropEmptyFields(value)); + return (((List) object).length == 0); + } + return false; + } + dropEmptyFields(ctx);{% endraw %} + - geoip: + field: source.ip + tag: geoip_source_ip + target_field: source.geo + ignore_missing: true + - geoip: + ignore_missing: true + database_file: GeoLite2-ASN.mmdb + field: source.ip + tag: geoip_source_asn + target_field: source.as + properties: + - asn + - organization_name + - rename: + field: source.as.asn + tag: rename_source_as_asn + target_field: source.as.number + ignore_missing: true + - rename: + field: source.as.organization_name + tag: rename_source_as_organization_name + target_field: source.as.organization.name + ignore_missing: true + - geoip: + field: destination.ip + tag: geoip_destination_ip + target_field: destination.geo + ignore_missing: true + - geoip: + database_file: GeoLite2-ASN.mmdb + field: destination.ip + tag: geoip_destination_asn + target_field: destination.as + properties: + - asn + - organization_name + ignore_missing: true + - rename: + field: destination.as.asn + tag: rename_destination_as_asn + target_field: destination.as.number + ignore_missing: true + - rename: + field: destination.as.organization_name + tag: rename_destination_as_organization_name + target_field: destination.as.organization.name + ignore_missing: true +{% if fields_to_remove.length > 0 %} + - remove: + field: + {% for field in fields_to_remove %} + - {{ field }} + {% endfor %} + ignore_missing: true + tag: remove_fields{% endif %} + - remove: + field: event.original + tag: remove_original_event + if: ctx?.tags == null || !(ctx.tags.contains("preserve_original_event")) + ignore_failure: true + ignore_missing: true +on_failure: + - append: + field: error.message + value: '{% raw %}Processor {{{_ingest.on_failure_processor_type}}} with tag {{{_ingest.on_failure_processor_tag}}} in pipeline {{{_ingest.on_failure_pipeline}}} failed with message: {{{_ingest.on_failure_message}}}{% endraw %}' + - set: + field: event.kind + value: pipeline_error diff --git a/x-pack/plugins/integration_assistant/server/templates/pipeline_tests/test_common_config.yml b/x-pack/plugins/integration_assistant/server/templates/pipeline_tests/test_common_config.yml new file mode 100644 index 0000000000000..772cb40587804 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/pipeline_tests/test_common_config.yml @@ -0,0 +1,3 @@ +fields: + tags: + - preserve_original_event \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/readme.md.njk b/x-pack/plugins/integration_assistant/server/templates/readme.md.njk new file mode 100644 index 0000000000000..74520da051b8b --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/readme.md.njk @@ -0,0 +1,24 @@ +# {{ package_name }} Integration + +This integration is for ingesting data from [{{ package_name }}](https://example.com/). +{% for data_stream in data_streams %} +- `{{ data_stream.name }}`: {{ data_stream.description }} +{% endfor %} +See [Link to docs](https://example.com/docs) for more information. + +## Compatibility + +Insert compatibility information here. This could for example be which versions of the product it was tested with. + +## Setup + +Insert how to configure the vendor side of the integration here, for example how to configure the API, create a syslog remote destination etc. + +## Logs +{% for data_stream in data_streams %} +### {{ data_stream.name }} + +Insert a description of the data stream here. + +{% raw %}{{fields {% endraw %}"{{ data_stream.name }}"{% raw %}}}{% endraw %} +{% endfor %} diff --git a/x-pack/plugins/integration_assistant/server/templates/system_tests/docker_compose.yml.njk b/x-pack/plugins/integration_assistant/server/templates/system_tests/docker_compose.yml.njk new file mode 100644 index 0000000000000..74ebed9dd0a75 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/system_tests/docker_compose.yml.njk @@ -0,0 +1,3 @@ +version: "{{ docker_compose_version }}" +services: {% for service in services %} + {{ service }}{% endfor %} \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/system_tests/service_filestream.njk b/x-pack/plugins/integration_assistant/server/templates/system_tests/service_filestream.njk new file mode 100644 index 0000000000000..642897a0fbfea --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/system_tests/service_filestream.njk @@ -0,0 +1,6 @@ +{{package_name}}-{{data_stream_name}}-filestream: + image: alpine + volumes: + - ./sample_logs:/sample_logs:ro + - ${SERVICE_LOGS_DIR}:/var/log + command: /bin/sh -c "cp /sample_logs/* /var/log/" \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/system_tests/service_gcs.njk b/x-pack/plugins/integration_assistant/server/templates/system_tests/service_gcs.njk new file mode 100644 index 0000000000000..3a1010d0b0bfb --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/system_tests/service_gcs.njk @@ -0,0 +1,7 @@ +{{package_name}}-{{data_stream_name}}-gcs: + image: fsouza/fake-gcs-server:latest + command: -host=0.0.0.0 -public-host=elastic-package-service_{{package_name}}-{{data_stream_name}}-gcs_1 -port=4443 -scheme=http + volumes: + - ./sample_logs:/data + ports: + - 4443/tcp \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/system_tests/service_logfile.njk b/x-pack/plugins/integration_assistant/server/templates/system_tests/service_logfile.njk new file mode 100644 index 0000000000000..1393ef7cc1098 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/system_tests/service_logfile.njk @@ -0,0 +1,6 @@ +{{package_name}}-{{data_stream_name}}-logfile: + image: alpine + volumes: + - ./sample_logs:/sample_logs:ro + - ${SERVICE_LOGS_DIR}:/var/log + command: /bin/sh -c "cp /sample_logs/* /var/log/" \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/system_tests/service_tcp.njk b/x-pack/plugins/integration_assistant/server/templates/system_tests/service_tcp.njk new file mode 100644 index 0000000000000..0267c60d00d4d --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/system_tests/service_tcp.njk @@ -0,0 +1,6 @@ +{{package_name}}-{{data_stream_name}}-tcp: + image: docker.elastic.co/observability/stream:{{stream_version}} + volumes: + - ./sample_logs:/sample_logs:ro + entrypoint: /bin/bash + command: -c "/stream log --start-signal=SIGHUP --delay=5s --addr elastic-agent:9025 -p=tcp /sample_logs/{{package_name}}.log" \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/system_tests/service_udp.njk b/x-pack/plugins/integration_assistant/server/templates/system_tests/service_udp.njk new file mode 100644 index 0000000000000..bdb8b5b91b8ff --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/system_tests/service_udp.njk @@ -0,0 +1,6 @@ +{{package_name}}-{{data_stream_name}}-udp: + image: docker.elastic.co/observability/stream:{{stream_version}} + volumes: + - ./sample_logs:/sample_logs:ro + entrypoint: /bin/bash + command: -c "/stream log --start-signal=SIGHUP --delay=5s --addr elastic-agent:9025 -p=udp /sample_logs/{{package_name}}.log" \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/system_tests/test_filestream_config.yml.njk b/x-pack/plugins/integration_assistant/server/templates/system_tests/test_filestream_config.yml.njk new file mode 100644 index 0000000000000..3a861dfe3b7d1 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/system_tests/test_filestream_config.yml.njk @@ -0,0 +1,13 @@ +service: {{package_name}}-{{data_stream_name}}-filestream +input: filestream +data_stream: + vars: + preserve_original_event: true + paths: + - '{% raw %}{{SERVICE_LOGS_DIR}}{% endraw %}/test-{{package_name}}-{{data_stream_name}}.log' +numeric_keyword_fields: + - log.file.device_id + - log.file.inode + - log.file.idxhi + - log.file.idxlo + - log.file.vol \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/system_tests/test_gcs_config.yml.njk b/x-pack/plugins/integration_assistant/server/templates/system_tests/test_gcs_config.yml.njk new file mode 100644 index 0000000000000..3bdf39c42fac7 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/system_tests/test_gcs_config.yml.njk @@ -0,0 +1,10 @@ +service: {{package_name}}-{{data_stream_name}}-gcs +input: gcs +data_stream: + vars: + project_id: testproject + alternative_host: "http://{% raw %}{{Hostname}}:{{Port}}{% endraw %}" + buckets: | + - name: testbucket + poll: true + poll_interval: 15s diff --git a/x-pack/plugins/integration_assistant/server/templates/system_tests/test_logfile_config.yml.njk b/x-pack/plugins/integration_assistant/server/templates/system_tests/test_logfile_config.yml.njk new file mode 100644 index 0000000000000..d6d891cd7038b --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/system_tests/test_logfile_config.yml.njk @@ -0,0 +1,13 @@ +service: {{package_name}}-{{data_stream_name}}-logfile +input: logfile +data_stream: + vars: + preserve_original_event: true + paths: + - '{% raw %}{{SERVICE_LOGS_DIR}}{% endraw %}/{{package_name}}-{{data_stream_name}}.log' +numeric_keyword_fields: + - log.file.device_id + - log.file.inode + - log.file.idxhi + - log.file.idxlo + - log.file.vol \ No newline at end of file diff --git a/x-pack/plugins/integration_assistant/server/templates/system_tests/test_tcp_config.yml.njk b/x-pack/plugins/integration_assistant/server/templates/system_tests/test_tcp_config.yml.njk new file mode 100644 index 0000000000000..1c5377a87e213 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/system_tests/test_tcp_config.yml.njk @@ -0,0 +1,7 @@ +service: {{package_name}}-{{data_stream_name}}-tcp +input: tcp +data_stream: + vars: + preserve_original_event: true + listen_address: 0.0.0.0 + listen_port: 9025 diff --git a/x-pack/plugins/integration_assistant/server/templates/system_tests/test_udp_config.yml.njk b/x-pack/plugins/integration_assistant/server/templates/system_tests/test_udp_config.yml.njk new file mode 100644 index 0000000000000..634f151b97198 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/templates/system_tests/test_udp_config.yml.njk @@ -0,0 +1,7 @@ +service: {{package_name}}-{{data_stream_name}}-udp +input: udp +data_stream: + vars: + preserve_original_event: true + listen_address: 0.0.0.0 + listen_port: 9025 diff --git a/x-pack/plugins/integration_assistant/server/types.ts b/x-pack/plugins/integration_assistant/server/types.ts new file mode 100644 index 0000000000000..54713df18b0d8 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/types.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. + */ + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface IntegrationAssistantPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface IntegrationAssistantPluginStart {} + +export interface CategorizationState { + rawSamples: string[]; + samples: string[]; + formattedSamples: string; + ecsTypes: string; + ecsCategories: string; + exAnswer: string; + lastExecutedChain: string; + packageName: string; + dataStreamName: string; + errors: object; + pipelineResults: object[]; + finalized: boolean; + reviewed: boolean; + currentPipeline: object; + currentProcessors: object[]; + invalidCategorization: object; + initialPipeline: object; + results: object; +} + +export interface EcsMappingState { + ecs: string; + lastExecutedChain: string; + rawSamples: string[]; + samples: string[]; + formattedSamples: string; + exAnswer: string; + packageName: string; + dataStreamName: string; + finalized: boolean; + currentMapping: object; + currentPipeline: object; + duplicateFields: string[]; + missingKeys: string[]; + invalidEcsFields: string[]; + results: object; + logFormat: string; + ecsVersion: string; +} + +export interface RelatedState { + rawSamples: string[]; + samples: string[]; + formattedSamples: string; + ecs: string; + exAnswer: string; + packageName: string; + dataStreamName: string; + errors: object; + pipelineResults: object[]; + finalized: boolean; + reviewed: boolean; + currentPipeline: object; + currentProcessors: object[]; + initialPipeline: object; + results: object; + lastExecutedChain: string; +} diff --git a/x-pack/plugins/integration_assistant/server/util/files.ts b/x-pack/plugins/integration_assistant/server/util/files.ts new file mode 100644 index 0000000000000..77c508f81e4b0 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/util/files.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { cpSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'fs'; +import { dirname } from 'path'; + +export function existsSync(path: string): boolean { + try { + statSync(path); + return true; + } catch (error) { + if (error.code === 'ENOENT') { + return false; + } else { + throw error; + } + } +} + +export function ensureDirSync(dirPath: string): void { + const exists = existsSync(dirPath); + if (!exists) { + mkdirSync(dirPath, { recursive: true }); + } +} + +export function createSync(path: string, content: string | Buffer): void { + writeFileSync(path, content, { encoding: 'utf-8' }); +} + +export function copySync(source: string, destination: string): void { + // Ensure the destination directory exists + mkdirSync(dirname(destination), { recursive: true }); + cpSync(source, destination, { recursive: true }); +} + +export function listDirSync(path: string): string[] { + return readdirSync(path); +} + +export function readSync(path: string): string { + return readFileSync(path, { encoding: 'utf-8' }); +} diff --git a/x-pack/plugins/integration_assistant/server/util/graph.ts b/x-pack/plugins/integration_assistant/server/util/graph.ts new file mode 100644 index 0000000000000..6149be42ce446 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/util/graph.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; +import type { CategorizationState, RelatedState } from '../types'; +import { testPipeline } from './pipeline'; + +export async function handleValidatePipeline( + state: CategorizationState | RelatedState, + client: IScopedClusterClient +): Promise | Partial> { + const results = await testPipeline(state.rawSamples, state.currentPipeline, client); + return { + errors: results.errors, + pipelineResults: results.pipelineResults, + lastExecutedChain: 'validate_pipeline', + }; +} diff --git a/x-pack/plugins/integration_assistant/server/util/index.ts b/x-pack/plugins/integration_assistant/server/util/index.ts new file mode 100644 index 0000000000000..b84db3eee4ee7 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/util/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { existsSync, ensureDirSync, createSync, copySync, listDirSync, readSync } from './files'; + +export { generateFields, mergeSamples } from './samples'; +export { deepCopy, generateUniqueId } from './util'; +export { testPipeline } from './pipeline'; +export { combineProcessors } from './processors'; diff --git a/x-pack/plugins/integration_assistant/server/util/pipeline.ts b/x-pack/plugins/integration_assistant/server/util/pipeline.ts new file mode 100644 index 0000000000000..c9c58c78b6c9a --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/util/pipeline.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; + +interface DocTemplate { + _index: string; + _id: string; + _source: { + message: string; + }; +} + +function formatSample(sample: string): DocTemplate { + const docsTemplate: DocTemplate = { + _index: 'index', + _id: 'id', + _source: { message: '' }, + }; + const formatted: DocTemplate = { ...docsTemplate }; + formatted._source.message = sample; + return formatted; +} + +export async function testPipeline( + samples: string[], + pipeline: object, + client: IScopedClusterClient +): Promise<{ pipelineResults: object[]; errors: object[] }> { + const docs = samples.map((sample) => formatSample(sample)); + const pipelineResults: object[] = []; + const errors: object[] = []; + + try { + const output = await client.asCurrentUser.ingest.simulate({ docs, pipeline }); + for (const doc of output.docs) { + if (doc.doc?._source?.error) { + errors.push(doc.doc._source.error); + } else if (doc.doc?._source) { + pipelineResults.push(doc.doc._source); + } + } + } catch (e) { + errors.push({ error: (e as Error).message }); + } + + return { pipelineResults, errors }; +} diff --git a/x-pack/plugins/integration_assistant/server/util/processors.ts b/x-pack/plugins/integration_assistant/server/util/processors.ts new file mode 100644 index 0000000000000..8c28a21b16b3c --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/util/processors.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ESProcessorItem, Pipeline } from '../../common'; +import { deepCopy } from './util'; + +export function combineProcessors( + initialPipeline: Pipeline, + processors: ESProcessorItem[] +): Pipeline { + // Create a deep copy of the initialPipeline to avoid modifying the original input + const currentPipeline = deepCopy(initialPipeline); + + // Add the new processors right before the last 2 removeprocessor in the initial pipeline. + // This is so all the processors if conditions are not accessing possibly removed fields. + const currentProcessors = currentPipeline.processors; + const combinedProcessors = [ + ...currentProcessors.slice(0, -2), + ...processors, + ...currentProcessors.slice(-2), + ]; + currentPipeline.processors = combinedProcessors; + return currentPipeline; +} diff --git a/x-pack/plugins/integration_assistant/server/util/route_validation.ts b/x-pack/plugins/integration_assistant/server/util/route_validation.ts new file mode 100644 index 0000000000000..e9f9c08b74c81 --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/util/route_validation.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RouteValidationFunction, RouteValidationResultFactory } from '@kbn/core/server'; +import { stringifyZodError } from '@kbn/zod-helpers'; +import type { TypeOf, ZodType } from 'zod'; + +export const buildRouteValidationWithZod = + >(schema: T): RouteValidationFunction => + (inputValue: unknown, validationResult: RouteValidationResultFactory) => { + const decoded = schema.safeParse(inputValue); + if (decoded.success) { + return validationResult.ok(decoded.data); + } else { + return validationResult.badRequest(stringifyZodError(decoded.error)); + } + }; diff --git a/x-pack/plugins/integration_assistant/server/util/samples.ts b/x-pack/plugins/integration_assistant/server/util/samples.ts new file mode 100644 index 0000000000000..8b306213fd3fd --- /dev/null +++ b/x-pack/plugins/integration_assistant/server/util/samples.ts @@ -0,0 +1,207 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import * as yaml from 'js-yaml'; +import type { CategorizationState, EcsMappingState, RelatedState } from '../types'; + +interface SampleObj { + [key: string]: unknown; +} + +interface NewObj { + [key: string]: { + [key: string]: SampleObj; + }; +} + +interface Field { + name: string; + type: string; + fields?: Field[]; +} + +export function modifySamples(state: EcsMappingState | CategorizationState | RelatedState) { + const modifiedSamples: string[] = []; + const rawSamples = state.rawSamples; + const packageName = state.packageName; + const dataStreamName = state.dataStreamName; + + for (const sample of rawSamples) { + const sampleObj: SampleObj = JSON.parse(sample); + const newObj: NewObj = { + [packageName]: { + [dataStreamName]: sampleObj, + }, + }; + const modifiedSample = JSON.stringify(newObj); + modifiedSamples.push(modifiedSample); + } + + return modifiedSamples; +} + +function isEmptyValue(value: unknown): boolean { + return ( + value === null || + value === undefined || + (typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0) || + (Array.isArray(value) && value.length === 0) + ); +} + +function merge(target: Record, source: Record): Record { + for (const [key, sourceValue] of Object.entries(source)) { + const targetValue = target[key]; + if (Array.isArray(sourceValue)) { + // Directly assign arrays + target[key] = sourceValue; + } else if ( + typeof sourceValue === 'object' && + sourceValue !== null && + !Array.isArray(targetValue) + ) { + if (typeof targetValue !== 'object' || isEmptyValue(targetValue)) { + target[key] = merge({}, sourceValue); + } else { + target[key] = merge(targetValue, sourceValue); + } + } else if (!(key in target) || (isEmptyValue(targetValue) && !isEmptyValue(sourceValue))) { + target[key] = sourceValue; + } + } + return target; +} + +export function mergeSamples(objects: any[]): string { + let result: Record = {}; + + for (const obj of objects) { + let sample: Record = obj; + if (typeof obj === 'string') { + sample = JSON.parse(obj); + } + result = merge(result, sample); + } + + return JSON.stringify(result, null, 2); +} + +export function formatSamples(samples: string[]): string { + const formattedSamples: unknown[] = []; + + for (const sample of samples) { + const sampleObj = JSON.parse(sample); + formattedSamples.push(sampleObj); + } + + return JSON.stringify(formattedSamples, null, 2); +} + +function determineType(value: unknown): string { + if (typeof value === 'object' && value !== null) { + if (Array.isArray(value)) { + return 'group'; + } + return 'group'; + } + if (typeof value === 'string') { + return 'keyword'; + } + if (typeof value === 'boolean') { + return 'boolean'; + } + if (typeof value === 'number') { + return 'long'; + } + return 'keyword'; // Default type for null or other undetermined types +} + +function recursiveParse(obj: unknown, path: string[]): Field { + if (typeof obj === 'object' && obj !== null) { + if (Array.isArray(obj)) { + // Assume list elements are uniform and use the first element as representative + if (obj.length > 0) { + return recursiveParse(obj[0], path); + } + return { name: path[path.length - 1], type: 'group', fields: [] }; + } + const fields: Field[] = []; + for (const [key, value] of Object.entries(obj)) { + fields.push(recursiveParse(value, path.concat(key))); + } + return { name: path[path.length - 1], type: 'group', fields }; + } + return { name: path[path.length - 1], type: determineType(obj) }; +} + +export function generateFields(mergedDocs: string): string { + const ecsTopKeysSet: Set = new Set([ + '@timestamp', + 'agent', + 'as', + 'base', + 'client', + 'cloud', + 'code_signature', + 'container', + 'data_stream', + 'destination', + 'device', + 'dll', + 'dns', + 'ecs', + 'elf', + 'email', + 'error', + 'event', + 'faas', + 'file', + 'geo', + 'group', + 'hash', + 'host', + 'http', + 'interface', + 'labels', + 'log', + 'macho', + 'message', + 'network', + 'observer', + 'orchestrator', + 'organization', + 'os', + 'package', + 'pe', + 'process', + 'registry', + 'related', + 'risk', + 'rule', + 'server', + 'service', + 'source', + 'tags', + 'threat', + 'tls', + 'tracing', + 'url', + 'user', + 'user_agent', + 'vlan', + 'volume', + 'vulnerability', + 'x509', + ]); + + const doc: SampleObj = JSON.parse(mergedDocs); + const fieldsStructure: Field[] = Object.keys(doc) + .filter((key) => !ecsTopKeysSet.has(key)) + .map((key) => recursiveParse(doc[key], [key])); + + return yaml.dump(fieldsStructure, { sortKeys: false }); +} diff --git a/x-pack/plugins/lists/common/api/values/find_list/find_list_route.ts b/x-pack/plugins/integration_assistant/server/util/util.ts similarity index 57% rename from x-pack/plugins/lists/common/api/values/find_list/find_list_route.ts rename to x-pack/plugins/integration_assistant/server/util/util.ts index 8c3301daed3c9..e01063540a750 100644 --- a/x-pack/plugins/lists/common/api/values/find_list/find_list_route.ts +++ b/x-pack/plugins/integration_assistant/server/util/util.ts @@ -5,6 +5,10 @@ * 2.0. */ -import { findListSchema, foundListSchema } from '@kbn/securitysolution-io-ts-list-types'; +export function deepCopy(obj: T): T { + return JSON.parse(JSON.stringify(obj)); +} -export { findListSchema as findListRequestQuery, foundListSchema as findListResponse }; +export function generateUniqueId() { + return `${Date.now() + Math.floor(Math.random() * 1e13)}`; +} diff --git a/x-pack/plugins/integration_assistant/tsconfig.json b/x-pack/plugins/integration_assistant/tsconfig.json new file mode 100644 index 0000000000000..515611f12f166 --- /dev/null +++ b/x-pack/plugins/integration_assistant/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": [ + "index.ts", + "server/**/*.ts", + "common/**/*.ts", + "__jest__/**/*", + "../../typings/**/*" + ], + "exclude": ["target/**/*"], + "kbn_references": [ + "@kbn/core", + "@kbn/config-schema", + "@kbn/langchain", + "@kbn/core-elasticsearch-server", + "@kbn/actions-plugin", + "@kbn/data-plugin", + "@kbn/zod-helpers" + ] +} diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index 8ebd12cd00c63..da083413ea92f 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -625,7 +625,9 @@ export const LensTopNavMenu = ({ allowEmbed: false, allowShortUrl: false, delegatedShareUrlHandler: () => { - return isCurrentStateDirty ? shareableUrl! : savedObjectURL.href; + return isCurrentStateDirty || !currentDoc?.savedObjectId + ? shareableUrl! + : savedObjectURL.href; }, objectId: currentDoc?.savedObjectId, objectType: 'lens', @@ -636,7 +638,7 @@ export const LensTopNavMenu = ({ }, sharingData, // only want to know about changes when savedObjectURL.href - isDirty: isCurrentStateDirty, + isDirty: isCurrentStateDirty || !currentDoc?.savedObjectId, // disable the menu if both shortURL permission and the visualization has not been saved // TODO: improve here the disabling state with more specific checks disabledShareUrl: Boolean(!shareUrlEnabled && !currentDoc?.savedObjectId), diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/helpers.ts b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/helpers.ts index d1dd5febca59e..8da4c87607987 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/helpers.ts +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/helpers.ts @@ -36,10 +36,6 @@ export const getSuggestions = async ( ? await deps.dataViews.create(dataViewSpec) : await getESQLAdHocDataview(indexPattern, deps.dataViews); - if (dataView.fields.getByName('@timestamp')?.type === 'date' && !dataViewSpec) { - dataView.timeFieldName = '@timestamp'; - } - const columns = await getESQLQueryColumns({ esqlQuery: 'esql' in query ? query.esql : '', search: deps.data.search.search, diff --git a/x-pack/plugins/lens/public/datasources/text_based/utils.ts b/x-pack/plugins/lens/public/datasources/text_based/utils.ts index 4ca9324df6fc4..9621ab8103e4a 100644 --- a/x-pack/plugins/lens/public/datasources/text_based/utils.ts +++ b/x-pack/plugins/lens/public/datasources/text_based/utils.ts @@ -88,9 +88,6 @@ export async function getStateFromAggregateQuery( const dataView = await getESQLAdHocDataview(indexPattern, dataViews); if (dataView && dataView.id) { - if (dataView?.fields?.getByName('@timestamp')?.type === 'date') { - dataView.timeFieldName = '@timestamp'; - } dataViewId = dataView?.id; indexPatternRefs = [ ...indexPatternRefs, diff --git a/x-pack/plugins/lens/public/shared_components/axis/title/axis_title_settings.tsx b/x-pack/plugins/lens/public/shared_components/axis/title/axis_title_settings.tsx deleted file mode 100644 index a65425d07d7a0..0000000000000 --- a/x-pack/plugins/lens/public/shared_components/axis/title/axis_title_settings.tsx +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useCallback, useMemo } from 'react'; -import { EuiSpacer, EuiFormRow } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { useDebouncedValue } from '@kbn/visualization-ui-components'; -import type { AxesSettingsConfig } from '../../../visualizations/xy/types'; -import { type LabelMode, VisLabel } from '../..'; - -type AxesSettingsConfigKeys = keyof AxesSettingsConfig; - -export interface AxisTitleSettingsProps { - /** - * Determines the axis - */ - axis: AxesSettingsConfigKeys; - /** - * Determines the axis title - */ - axisTitle: string | undefined; - /** - * Callback to axis title change for both title and visibility - */ - updateTitleState: ( - state: { title?: string; visible: boolean }, - axis: AxesSettingsConfigKeys - ) => void; - /** - * Determines if the title visibility switch is on and the input text is disabled - */ - isAxisTitleVisible: boolean; -} - -export const AxisTitleSettings: React.FunctionComponent = ({ - axis, - axisTitle, - updateTitleState, - isAxisTitleVisible, -}) => { - const axisState = useMemo( - () => ({ - title: axisTitle, - visibility: - !axisTitle && isAxisTitleVisible - ? 'auto' - : isAxisTitleVisible - ? 'custom' - : ('none' as LabelMode), - }), - [axisTitle, isAxisTitleVisible] - ); - const onTitleChange = useCallback( - ({ title, visibility }: { title?: string; visibility: LabelMode }) => - updateTitleState({ title, visible: visibility !== 'none' }, axis), - [axis, updateTitleState] - ); - const { inputValue: localAxisState, handleInputChange: onLocalTitleChange } = useDebouncedValue<{ - title?: string; - visibility: LabelMode; - }>( - { - value: axisState, - onChange: onTitleChange, - }, - { allowFalsyValue: true } - ); - - return ( - <> - - { - onLocalTitleChange({ title: label, visibility: mode }); - }} - /> - - - - ); -}; diff --git a/x-pack/plugins/lens/public/shared_components/axis/title/axis_title_settings.test.tsx b/x-pack/plugins/lens/public/shared_components/axis/title/toolbar_title_settings.test.tsx similarity index 80% rename from x-pack/plugins/lens/public/shared_components/axis/title/axis_title_settings.test.tsx rename to x-pack/plugins/lens/public/shared_components/axis/title/toolbar_title_settings.test.tsx index 7783ce163830a..807e02ff05641 100644 --- a/x-pack/plugins/lens/public/shared_components/axis/title/axis_title_settings.test.tsx +++ b/x-pack/plugins/lens/public/shared_components/axis/title/toolbar_title_settings.test.tsx @@ -6,16 +6,16 @@ */ import React from 'react'; -import { AxisTitleSettings, AxisTitleSettingsProps } from './axis_title_settings'; +import { ToolbarTitleSettings, TitleSettingsProps } from './toolbar_title_settings'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -const renderAxisTicksSettings = (propsOverrides?: Partial) => { +const renderAxisTicksSettings = (propsOverrides?: Partial) => { const rtlRender = render( - @@ -35,33 +35,33 @@ describe('Axes Title settings', () => { }); it('should set the mode to Auto if no title is passed over', () => { - const { getAxisTitleInput } = renderAxisTicksSettings({ axisTitle: undefined }); + const { getAxisTitleInput } = renderAxisTicksSettings({ title: undefined }); expect(getAxisTitleInput()).toHaveValue(''); }); it('should set the mode to Auto if empty title is passed over', () => { - const { getAxisTitleInput } = renderAxisTicksSettings({ axisTitle: '' }); + const { getAxisTitleInput } = renderAxisTicksSettings({ title: '' }); expect(getAxisTitleInput()).toHaveValue(''); }); it('should set the mode to None if empty title is passed over and the visibility is set to false', () => { const { getAxisTitleSelect } = renderAxisTicksSettings({ - axisTitle: '', - isAxisTitleVisible: false, + title: '', + isTitleVisible: false, }); expect(getAxisTitleSelect()).toHaveValue('none'); }); it('should disable the input text if the switch is off', () => { const { getAxisTitleInput } = renderAxisTicksSettings({ - isAxisTitleVisible: false, + isTitleVisible: false, }); expect(getAxisTitleInput()).toBeDisabled(); }); it('should allow custom mode on user input even with empty string', () => { const { getAxisTitleSelect, getAxisTitleInput } = renderAxisTicksSettings({ - axisTitle: '', + title: '', }); userEvent.selectOptions(getAxisTitleSelect(), 'custom'); expect(getAxisTitleSelect()).toHaveValue('custom'); @@ -71,8 +71,8 @@ describe('Axes Title settings', () => { it('should reset the label when moving from custom to auto', async () => { const updateTitleStateSpy = jest.fn(); const { getAxisTitleSelect, getAxisTitleInput } = renderAxisTicksSettings({ - isAxisTitleVisible: true, - axisTitle: 'Custom title', + isTitleVisible: true, + title: 'Custom title', updateTitleState: updateTitleStateSpy, }); userEvent.selectOptions(getAxisTitleSelect(), 'auto'); diff --git a/x-pack/plugins/lens/public/shared_components/axis/title/toolbar_title_settings.tsx b/x-pack/plugins/lens/public/shared_components/axis/title/toolbar_title_settings.tsx new file mode 100644 index 0000000000000..258e41624402d --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/axis/title/toolbar_title_settings.tsx @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useMemo } from 'react'; +import { EuiSpacer, EuiFormRow } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useDebouncedValue } from '@kbn/visualization-ui-components'; +import type { AxesSettingsConfig } from '../../../visualizations/xy/types'; +import { type LabelMode, VisLabel } from '../..'; + +type SettingsConfigKeys = keyof AxesSettingsConfig | 'legend'; + +export interface TitleSettingsProps { + /** + * Determines the settingId - either axis or legend + */ + settingId: SettingsConfigKeys; + /** + * Determines the title + */ + title: string | undefined; + /** + * Callback to title change for both title and visibility + */ + updateTitleState: ( + state: { title?: string; visible: boolean }, + settingId: SettingsConfigKeys + ) => void; + /** + * Determines if the title visibility switch is on and the input text is disabled + */ + isTitleVisible: boolean; + strings?: { + header: string; + label: string; + getDataTestSubj: (axis: SettingsConfigKeys) => string; + }; + placeholder?: string; +} + +const axisStrings = { + header: i18n.translate('xpack.lens.label.shared.axisHeader', { + defaultMessage: 'Axis title', + }), + label: i18n.translate('xpack.lens.shared.axisNameLabel', { + defaultMessage: 'Axis title', + }), + getDataTestSubj: (axis: SettingsConfigKeys) => `lns${axis}AxisTitle`, +}; + +export const ToolbarTitleSettings: React.FunctionComponent = ({ + settingId, + title, + updateTitleState, + isTitleVisible, + strings = axisStrings, + placeholder = i18n.translate('xpack.lens.shared.overwriteAxisTitle', { + defaultMessage: 'Overwrite axis title', + }), +}) => { + const axisState = useMemo( + () => ({ + title, + visibility: + !title && isTitleVisible ? 'auto' : isTitleVisible ? 'custom' : ('none' as LabelMode), + }), + [title, isTitleVisible] + ); + const onTitleChange = useCallback( + ({ title: t, visibility }: { title?: string; visibility: LabelMode }) => + updateTitleState({ title: t, visible: visibility !== 'none' }, settingId), + [settingId, updateTitleState] + ); + const { inputValue: localState, handleInputChange: onLocalTitleChange } = useDebouncedValue<{ + title?: string; + visibility: LabelMode; + }>( + { + value: axisState, + onChange: onTitleChange, + }, + { allowFalsyValue: true } + ); + + return ( + <> + + { + onLocalTitleChange({ title: label, visibility: mode }); + }} + /> + + + + ); +}; diff --git a/x-pack/plugins/lens/public/shared_components/index.ts b/x-pack/plugins/lens/public/shared_components/index.ts index cd663ca08601e..1e3f84dd2accb 100644 --- a/x-pack/plugins/lens/public/shared_components/index.ts +++ b/x-pack/plugins/lens/public/shared_components/index.ts @@ -22,7 +22,7 @@ export { export * from './coloring'; export * from './helpers'; export { ValueLabelsSettings } from './value_labels_settings'; -export { AxisTitleSettings } from './axis/title/axis_title_settings'; +export { ToolbarTitleSettings } from './axis/title/toolbar_title_settings'; export { AxisTicksSettings } from './axis/ticks/axis_ticks_settings'; export * from './static_header'; export * from './vis_label'; diff --git a/x-pack/plugins/lens/public/shared_components/legend/layout/columns_number_setting.tsx b/x-pack/plugins/lens/public/shared_components/legend/layout/columns_number_setting.tsx index 1f21c9004043e..81d1acd84e0fe 100644 --- a/x-pack/plugins/lens/public/shared_components/legend/layout/columns_number_setting.tsx +++ b/x-pack/plugins/lens/public/shared_components/legend/layout/columns_number_setting.tsx @@ -22,22 +22,22 @@ interface ColumnsNumberSettingProps { */ onFloatingColumnsChange?: (value: number) => void; /** - * Indicates if legend is located outside + * Indicates if the component should be hidden */ - isLegendOutside: boolean; + isHidden: boolean; } export const ColumnsNumberSetting = ({ floatingColumns, onFloatingColumnsChange = () => {}, - isLegendOutside, + isHidden, }: ColumnsNumberSettingProps) => { const { inputValue, handleInputChange } = useDebouncedValue({ value: floatingColumns ?? DEFAULT_FLOATING_COLUMNS, onChange: onFloatingColumnsChange, }); - if (isLegendOutside) return null; + if (isHidden) return null; return ( { let defaultProps: LegendSettingsPopoverProps; @@ -49,7 +50,6 @@ describe('Legend Settings', () => { renderOptions ); const openLegendPopover = () => userEvent.click(screen.getByRole('button', { name: 'Legend' })); - openLegendPopover(); return { @@ -125,4 +125,28 @@ describe('Legend Settings', () => { renderLegendSettingsPopover({ mode: 'hide', renderNestedLegendSwitch: true }); expect(screen.queryByRole('switch', { name: 'Nested' })).toBeNull(); }); + + it('should display allowed legend stats', () => { + const onLegendStatsChange = jest.fn(); + renderLegendSettingsPopover({ + allowedLegendStats: [ + { + label: 'Current and last value', + value: LegendValue.CurrentAndLastValue, + toolTipContent: 'Shows the current and last value', + }, + { + label: 'Average', + value: LegendValue.Average, + toolTipContent: 'Shows the average value', + }, + ], + onLegendStatsChange, + }); + expect(screen.queryByRole('button', { name: 'Layout' })).toBeNull(); + fireEvent.click(screen.getByRole('combobox', { name: 'Statistics' })); + fireEvent.click(screen.getByRole('option', { name: 'Current and last value' })); + // expect(screen.getByRole('group', { name: 'Layout' })).toBeInTheDocument(); + expect(onLegendStatsChange).toBeCalledWith([LegendValue.CurrentAndLastValue], false); + }); }); diff --git a/x-pack/plugins/lens/public/shared_components/legend/legend_settings_popover.tsx b/x-pack/plugins/lens/public/shared_components/legend/legend_settings_popover.tsx index ccfdaa29089b1..80389e041763f 100644 --- a/x-pack/plugins/lens/public/shared_components/legend/legend_settings_popover.tsx +++ b/x-pack/plugins/lens/public/shared_components/legend/legend_settings_popover.tsx @@ -15,8 +15,10 @@ import { EuiFieldNumber, EuiFlexItem, EuiFlexGroup, + EuiComboBox, + EuiHorizontalRule, } from '@elastic/eui'; -import { Position, VerticalAlignment, HorizontalAlignment } from '@elastic/charts'; +import { Position, VerticalAlignment, HorizontalAlignment, LegendValue } from '@elastic/charts'; import { LegendSize } from '@kbn/visualizations-plugin/public'; import { useDebouncedValue } from '@kbn/visualization-ui-components'; import { XYLegendValue } from '@kbn/visualizations-plugin/common/constants'; @@ -24,8 +26,10 @@ import { ToolbarPopover, type ToolbarPopoverProps } from '../toolbar_popover'; import { LegendLocationSettings } from './location/legend_location_settings'; import { ColumnsNumberSetting } from './layout/columns_number_setting'; import { LegendSizeSettings } from './size/legend_size_settings'; +import { nonNullable } from '../../utils'; +import { ToolbarTitleSettings } from '../axis/title/toolbar_title_settings'; -export interface LegendSettingsPopoverProps { +export interface LegendSettingsPopoverProps { /** * Determines the legend display options */ @@ -107,17 +111,18 @@ export interface LegendSettingsPopoverProps { */ onNestedLegendChange?: (event: EuiSwitchEvent) => void; /** - * value in legend status + * current value in legend stats */ - legendStats?: S[]; + legendStats?: LegendStats[]; /** - * Callback on value in legend status change + * legend statistics that are allowed */ - onLegendStatsChange?: (checked?: boolean) => void; + allowedLegendStats?: Array<{ label: string; value: LegendStats; toolTipContent?: string }>; /** - * If true, value in legend switch is rendered + * Callback on value in legend stats change */ - allowLegendStats?: boolean; + onLegendStatsChange?: (legendStats?: LegendStats[], hasConvertedToTable?: boolean) => void; + /** * Button group position */ @@ -135,6 +140,10 @@ export interface LegendSettingsPopoverProps { * (We're trying to get people to stop using it so it can eventually be removed.) */ showAutoLegendSizeOption: boolean; + titlePlaceholder?: string; + legendTitle?: string; + isTitleVisible?: boolean; + onLegendTitleChange?: (state: { title?: string; visible: boolean }) => void; } const DEFAULT_TRUNCATE_LINES = 1; @@ -182,9 +191,30 @@ const PANEL_STYLE = { width: '500px', }; -export function LegendSettingsPopover({ +const legendTitleStrings = { + header: i18n.translate('xpack.lens.label.shared.legendHeader', { + defaultMessage: 'Series header', + }), + label: i18n.translate('xpack.lens.shared.Lagend ', { + defaultMessage: 'Series header', + }), + placeholder: i18n.translate('xpack.lens.shared.overwriteLegendTitle', { + defaultMessage: 'Overwrite series header', + }), + getDataTestSubj: () => `lnsLegendTableSeriesHeader`, +}; + +export function shouldDisplayTable(legendValues: LegendValue[]) { + return legendValues.some((v) => v !== LegendValue.CurrentAndLastValue); +} + +export function LegendSettingsPopover({ + allowedLegendStats = [], legendOptions, mode, + legendTitle, + isTitleVisible, + onLegendTitleChange, onDisplayChange, position, location, @@ -198,9 +228,8 @@ export function LegendSettingsPopover({ renderNestedLegendSwitch, nestedLegend, onNestedLegendChange = noop, - legendStats, + legendStats = [], onLegendStatsChange = noop, - allowLegendStats, groupPosition = 'right', maxLines, onMaxLinesChange = noop, @@ -209,7 +238,20 @@ export function LegendSettingsPopover({ legendSize, onLegendSizeChange, showAutoLegendSizeOption, -}: LegendSettingsPopoverProps) { + titlePlaceholder, +}: LegendSettingsPopoverProps) { + const isLegendNotHidden = mode !== 'hide'; + + const showsStatisticsSetting = isLegendNotHidden && allowedLegendStats.length > 1; + + const showsShowValueSetting = + isLegendNotHidden && + allowedLegendStats.length === 1 && + (allowedLegendStats[0].value === LegendValue.CurrentAndLastValue || + allowedLegendStats[0].value === LegendValue.Value); + + const showsLegendTitleSetting = shouldDisplayTable(legendStats) && !!onLegendTitleChange; + return ( ({ ({ onChange={onDisplayChange} /> - {mode !== 'hide' && ( + + {isLegendNotHidden && ( <> ({ )} + + )} + {showsStatisticsSetting && ( + <> + - - - - - - - - + allowedLegendStats.find((option) => option.value === value)) + .filter(nonNullable)} + onChange={(options) => { + const newLegendStats = options.map(({ value }) => value).filter(nonNullable); + const hasConvertedToTable = + !shouldDisplayTable(legendStats) && shouldDisplayTable(newLegendStats); + onLegendStatsChange(newLegendStats, hasConvertedToTable); + }} + isClearable={true} + compressed + /> + + )} - {renderNestedLegendSwitch && ( - + {showsLegendTitleSetting && ( + + )} + {showsShowValueSetting && ( + + { + onLegendStatsChange(ev.target.checked ? [allowedLegendStats[0].value] : []); + }} + /> + + )} + + {isLegendNotHidden && ( + + + - - )} - {allowLegendStats && ( - - { - onLegendStatsChange(ev.target.checked); - }} + + + - - )} - + + + + )} + + {isLegendNotHidden && renderNestedLegendSwitch && ( + + + )} ); diff --git a/x-pack/plugins/lens/public/shared_components/legend/location/legend_location_settings.tsx b/x-pack/plugins/lens/public/shared_components/legend/location/legend_location_settings.tsx index 039f778d7439e..5c28b2e2a0888 100644 --- a/x-pack/plugins/lens/public/shared_components/legend/location/legend_location_settings.tsx +++ b/x-pack/plugins/lens/public/shared_components/legend/location/legend_location_settings.tsx @@ -158,7 +158,7 @@ export const LegendLocationSettings: React.FunctionComponent @@ -167,7 +167,7 @@ export const LegendLocationSettings: React.FunctionComponent diff --git a/x-pack/plugins/lens/public/shared_components/vis_label.tsx b/x-pack/plugins/lens/public/shared_components/vis_label.tsx index 6d54d1633d439..10c8f3ea42ed6 100644 --- a/x-pack/plugins/lens/public/shared_components/vis_label.tsx +++ b/x-pack/plugins/lens/public/shared_components/vis_label.tsx @@ -76,10 +76,9 @@ export function VisLabel({ data-test-subj={`${dataTestSubj}-select`} aria-label="Label" onChange={({ target }) => { - const title = - target.value === 'custom' ? '' : target.value === 'auto' ? undefined : label; handleChange({ - label: title, + // reset title to undefined when switching mode + label: undefined, mode: target.value as LabelMode, }); }} diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/toolbar_component.tsx b/x-pack/plugins/lens/public/visualizations/heatmap/toolbar_component.tsx index c621a59e9ea9b..327bdef6c5bc4 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/toolbar_component.tsx +++ b/x-pack/plugins/lens/public/visualizations/heatmap/toolbar_component.tsx @@ -17,7 +17,7 @@ import { LegendSettingsPopover, ToolbarPopover, ValueLabelsSettings, - AxisTitleSettings, + ToolbarTitleSettings, AxisTicksSettings, } from '../../shared_components'; import type { HeatmapVisualizationState } from './types'; @@ -151,9 +151,9 @@ export const HeatmapToolbar = memo( buttonDataTestSubj="lnsHeatmapVerticalAxisButton" panelClassName="lnsVisToolbarAxis__popover" > - { setState({ ...state, @@ -164,7 +164,7 @@ export const HeatmapToolbar = memo( }, }); }} - isAxisTitleVisible={state?.gridConfig.isYAxisTitleVisible} + isTitleVisible={state?.gridConfig.isYAxisTitleVisible} /> - setState({ ...state, @@ -210,7 +210,7 @@ export const HeatmapToolbar = memo( }, }) } - isAxisTitleVisible={state?.gridConfig.isXAxisTitleVisible} + isTitleVisible={state?.gridConfig.isXAxisTitleVisible} /> = { }, legend: { flat: true, - defaultLegendStats: [PartitionLegendValue.Value], + defaultLegendStats: [LegendValue.Value], hideNestedLegendSwitch: true, getShowLegendDefault: () => true, }, diff --git a/x-pack/plugins/lens/public/visualizations/partition/persistence.tsx b/x-pack/plugins/lens/public/visualizations/partition/persistence.tsx index 9fc04b3702296..ba8632659836a 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/persistence.tsx +++ b/x-pack/plugins/lens/public/visualizations/partition/persistence.tsx @@ -5,8 +5,8 @@ * 2.0. */ +import { LegendValue } from '@elastic/charts'; import { cloneDeep } from 'lodash'; -import { PartitionLegendValue } from '@kbn/visualizations-plugin/common/constants'; import { PieLayerState, PieVisualizationState } from '../../../common/types'; type PersistedPieLayerState = PieLayerState & { @@ -28,7 +28,7 @@ function convertToLegendStats(state: PieVisualizationState) { if ('showValuesInLegend' in l) { l.legendStats = [ ...new Set([ - ...(l.showValuesInLegend ? [PartitionLegendValue.Value] : []), + ...(l.showValuesInLegend ? [LegendValue.Value] : []), ...(l.legendStats || []), ]), ]; diff --git a/x-pack/plugins/lens/public/visualizations/partition/render_helpers.test.ts b/x-pack/plugins/lens/public/visualizations/partition/render_helpers.test.ts index e273fde23f4db..bef4db9d0a2f6 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/render_helpers.test.ts +++ b/x-pack/plugins/lens/public/visualizations/partition/render_helpers.test.ts @@ -10,7 +10,7 @@ import type { Datatable } from '@kbn/expressions-plugin/public'; import { checkTableForContainsSmallValues, getLegendStats } from './render_helpers'; import { PieLayerState } from '../../../common/types'; import { PieChartTypes } from '../../../common/constants'; -import { PartitionLegendValue } from '@kbn/visualizations-plugin/common/constants'; +import { LegendValue } from '@elastic/charts'; describe('render helpers', () => { describe('#checkTableForContainsSmallValues', () => { @@ -72,28 +72,22 @@ describe('render helpers', () => { describe('#getLegendStats', () => { it('should firstly read the state value', () => { expect( - getLegendStats( - { legendStats: [PartitionLegendValue.Value] } as PieLayerState, - PieChartTypes.WAFFLE - ) - ).toEqual([PartitionLegendValue.Value]); + getLegendStats({ legendStats: [LegendValue.Value] } as PieLayerState, PieChartTypes.WAFFLE) + ).toEqual([LegendValue.Value]); expect( - getLegendStats( - { legendStats: [] as PartitionLegendValue[] } as PieLayerState, - PieChartTypes.WAFFLE - ) + getLegendStats({ legendStats: [] as LegendValue[] } as PieLayerState, PieChartTypes.WAFFLE) ).toEqual([]); }); it('should read value from meta in case of value in state is undefined', () => { expect(getLegendStats({} as PieLayerState, PieChartTypes.WAFFLE)).toEqual([ - PartitionLegendValue.Value, + LegendValue.Value, ]); expect( getLegendStats({ legendStats: undefined } as PieLayerState, PieChartTypes.WAFFLE) - ).toEqual([PartitionLegendValue.Value]); + ).toEqual([LegendValue.Value]); expect(getLegendStats({} as PieLayerState, PieChartTypes.PIE)).toEqual(undefined); }); diff --git a/x-pack/plugins/lens/public/visualizations/partition/render_helpers.ts b/x-pack/plugins/lens/public/visualizations/partition/render_helpers.ts index 869597f92dcb1..b2bd2cdd9a058 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/render_helpers.ts +++ b/x-pack/plugins/lens/public/visualizations/partition/render_helpers.ts @@ -5,8 +5,8 @@ * 2.0. */ +import { LegendValue } from '@elastic/charts'; import type { Datatable } from '@kbn/expressions-plugin/public'; -import { PartitionLegendValue } from '@kbn/visualizations-plugin/common/constants'; import type { PieChartType, PieLayerState } from '../../../common/types'; import { PartitionChartsMeta } from './partition_charts_meta'; @@ -14,7 +14,7 @@ export const getLegendStats = (layer: PieLayerState, shape: PieChartType) => { if ('defaultLegendStats' in PartitionChartsMeta[shape]?.legend) { return ( layer.legendStats ?? - PartitionChartsMeta[shape].legend.defaultLegendStats ?? [PartitionLegendValue.Value] + PartitionChartsMeta[shape].legend.defaultLegendStats ?? [LegendValue.Value] ); } }; diff --git a/x-pack/plugins/lens/public/visualizations/partition/toolbar.tsx b/x-pack/plugins/lens/public/visualizations/partition/toolbar.tsx index 792e45ba99b2b..dfccdccc9f85f 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/toolbar.tsx +++ b/x-pack/plugins/lens/public/visualizations/partition/toolbar.tsx @@ -16,10 +16,10 @@ import { EuiHorizontalRule, EuiButtonGroup, } from '@elastic/eui'; -import type { Position } from '@elastic/charts'; +import { LegendValue, Position } from '@elastic/charts'; import { LegendSize } from '@kbn/visualizations-plugin/public'; import { useDebouncedValue } from '@kbn/visualization-ui-components'; -import { PartitionLegendValue } from '@kbn/visualizations-plugin/common/constants'; +import { type PartitionLegendValue } from '@kbn/visualizations-plugin/common/constants'; import { DEFAULT_PERCENT_DECIMALS } from './constants'; import { PartitionChartsMeta } from './partition_charts_meta'; import { PieVisualizationState, SharedPieLayerState } from '../../../common/types'; @@ -29,6 +29,15 @@ import { ToolbarPopover, LegendSettingsPopover } from '../../shared_components'; import { getDefaultVisualValuesForLayer } from '../../shared_components/datasource_default_values'; import { getLegendStats } from './render_helpers'; +const partitionLegendValues = [ + { + value: LegendValue.Value, + label: i18n.translate('xpack.lens.shared.legendValues.value', { + defaultMessage: 'Value', + }), + }, +]; + const legendOptions: Array<{ value: SharedPieLayerState['legendDisplay']; label: string; @@ -135,10 +144,8 @@ export function PieToolbar(props: VisualizationToolbarProps { - onStateChange({ - legendStats: checked ? [PartitionLegendValue.Value] : [], - }); + (legendStats) => { + onStateChange({ legendStats }); }, [onStateChange] ); @@ -250,7 +257,11 @@ export function PieToolbar(props: VisualizationToolbarProps { describe('converting to legendStats', () => { it('loads a chart with `legendStats` property', () => { const persistedState = getExampleState(); - persistedState.layers[0].legendStats = [PartitionLegendValue.Value]; + persistedState.layers[0].legendStats = [LegendValue.Value]; const runtimeState = pieVisualization.initialize(() => 'first', persistedState); diff --git a/x-pack/plugins/lens/public/visualizations/xy/legend_stats_telemetry_helpers.test.ts b/x-pack/plugins/lens/public/visualizations/xy/legend_stats_telemetry_helpers.test.ts new file mode 100644 index 0000000000000..16bc56cf983a9 --- /dev/null +++ b/x-pack/plugins/lens/public/visualizations/xy/legend_stats_telemetry_helpers.test.ts @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LegendValue } from '@elastic/charts'; +import { getLegendStatsTelemetryEvents } from './legend_stats_telemetry_helpers'; + +describe('legend_stats_telemetry_helpers', () => { + it('no events if legend stats are not defined', () => { + expect(getLegendStatsTelemetryEvents(undefined)).toEqual([]); + }); + it('ignores single CurrentAndLastValue stat as it does not trigger table view', () => { + expect(getLegendStatsTelemetryEvents([LegendValue.CurrentAndLastValue])).toEqual([]); + expect( + getLegendStatsTelemetryEvents([LegendValue.CurrentAndLastValue, LegendValue.Average]) + ).toEqual([ + 'lens_legend_stats', + 'lens_legend_stats_currentAndLastValue', + 'lens_legend_stats_average', + 'lens_legend_stats_amount_2', + ]); + }); + + it('no events if no changes made in color mapping', () => { + expect(getLegendStatsTelemetryEvents([LegendValue.Average], [LegendValue.Average])).toEqual([]); + expect( + getLegendStatsTelemetryEvents( + [LegendValue.CurrentAndLastValue, LegendValue.Average], + [LegendValue.CurrentAndLastValue, LegendValue.Average] + ) + ).toEqual([]); + }); + describe('calculates counter events properly', () => { + it('returns single count event', () => { + expect(getLegendStatsTelemetryEvents([LegendValue.Average])).toEqual([ + 'lens_legend_stats', + 'lens_legend_stats_average', + 'lens_legend_stats_amount_1', + ]); + }); + it('returns 2 count event', () => { + expect(getLegendStatsTelemetryEvents([LegendValue.Average, LegendValue.Count])).toEqual([ + 'lens_legend_stats', + 'lens_legend_stats_average', + 'lens_legend_stats_count', + 'lens_legend_stats_amount_2', + ]); + }); + it('returns 3 count event', () => { + expect( + getLegendStatsTelemetryEvents([ + LegendValue.Average, + LegendValue.Count, + LegendValue.CurrentAndLastValue, + ]) + ).toEqual([ + 'lens_legend_stats', + 'lens_legend_stats_average', + 'lens_legend_stats_count', + 'lens_legend_stats_currentAndLastValue', + 'lens_legend_stats_amount_3', + ]); + }); + it('returns 4 count event', () => { + expect( + getLegendStatsTelemetryEvents([ + LegendValue.CurrentAndLastValue, + LegendValue.Max, + LegendValue.Min, + LegendValue.Average, + ]) + ).toEqual([ + 'lens_legend_stats', + 'lens_legend_stats_currentAndLastValue', + 'lens_legend_stats_max', + 'lens_legend_stats_min', + 'lens_legend_stats_average', + 'lens_legend_stats_amount_4_to_7', + ]); + }); + + it('returns >8 count event', () => { + expect( + getLegendStatsTelemetryEvents([ + LegendValue.CurrentAndLastValue, + LegendValue.Max, + LegendValue.Min, + LegendValue.Average, + LegendValue.Count, + LegendValue.Total, + LegendValue.LastValue, + LegendValue.FirstValue, + LegendValue.Median, + ]) + ).toEqual([ + 'lens_legend_stats', + 'lens_legend_stats_currentAndLastValue', + 'lens_legend_stats_max', + 'lens_legend_stats_min', + 'lens_legend_stats_average', + 'lens_legend_stats_count', + 'lens_legend_stats_total', + 'lens_legend_stats_lastValue', + 'lens_legend_stats_firstValue', + 'lens_legend_stats_median', + 'lens_legend_stats_amount_above_8', + ]); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/visualizations/xy/legend_stats_telemetry_helpers.ts b/x-pack/plugins/lens/public/visualizations/xy/legend_stats_telemetry_helpers.ts new file mode 100644 index 0000000000000..87143688c206d --- /dev/null +++ b/x-pack/plugins/lens/public/visualizations/xy/legend_stats_telemetry_helpers.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isEqual } from 'lodash'; +import { XYLegendValue } from '@kbn/visualizations-plugin/common'; +import { nonNullable } from '../../utils'; +import { shouldDisplayTable } from '../../shared_components/legend/legend_settings_popover'; + +const LEGEND_STATS_PREFIX = 'lens_legend_stats'; +const constructName = (eventName: string) => `${LEGEND_STATS_PREFIX}${eventName}`; + +export const getLegendStatsTelemetryEvents = ( + legendStats: XYLegendValue[] | undefined, + prevLegendStats?: XYLegendValue[] | undefined +) => { + if (!legendStats || !legendStats.length || isEqual(legendStats, prevLegendStats)) { + return []; + } + if (!shouldDisplayTable(legendStats)) { + return []; + } + const mainEvent = LEGEND_STATS_PREFIX; + const typesEvents = legendStats.map((legendStat) => { + return constructName(`_${legendStat}`); + }); + const counterText = getRangeText(legendStats.length); + const counterEvent = counterText ? constructName(counterText) : undefined; + + return [mainEvent, ...typesEvents, counterEvent].filter(nonNullable); +}; + +function getRangeText(n: number) { + if (n < 1) { + return; + } + if (n < 4) { + return `_amount_${String(n)}`; + } + if (n >= 4 && n <= 7) { + return '_amount_4_to_7'; + } + if (n >= 8) { + return '_amount_above_8'; + } +} diff --git a/x-pack/plugins/lens/public/visualizations/xy/persistence.ts b/x-pack/plugins/lens/public/visualizations/xy/persistence.ts index 5191cc96bac3d..8ad5d82d364e2 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/persistence.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/persistence.ts @@ -9,8 +9,8 @@ import { v4 as uuidv4 } from 'uuid'; import type { SavedObjectReference } from '@kbn/core/public'; import { EVENT_ANNOTATION_GROUP_TYPE } from '@kbn/event-annotation-common'; import { cloneDeep } from 'lodash'; -import { XYLegendValue } from '@kbn/visualizations-plugin/common/constants'; +import { LegendValue } from '@elastic/charts'; import { layerTypes } from '../../../common/layer_types'; import { AnnotationGroups } from '../../types'; import { @@ -278,7 +278,7 @@ function convertToLegendStats(state: XYState & { valuesInLegend?: unknown }) { ...state.legend, legendStats: [ ...new Set([ - ...(valuesInLegend ? [XYLegendValue.CurrentAndLastValue] : []), + ...(valuesInLegend ? [LegendValue.CurrentAndLastValue] : []), ...(state.legend.legendStats || []), ]), ], diff --git a/x-pack/plugins/lens/public/visualizations/xy/to_expression.ts b/x-pack/plugins/lens/public/visualizations/xy/to_expression.ts index 76e00c892d243..c756c4eb137a9 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/to_expression.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/to_expression.ts @@ -293,6 +293,7 @@ export const buildXYExpression = ( : state.legend.legendSize ? state.legend.legendSize : undefined, + layout: state.legend.layout, horizontalAlignment: state.legend.horizontalAlignment && state.legend.isInside ? state.legend.horizontalAlignment @@ -309,6 +310,8 @@ export const buildXYExpression = ( : [], maxLines: state.legend.maxLines, legendStats: state.legend.legendStats, + title: state.legend.title, + isTitleVisible: state.legend.isTitleVisible, shouldTruncate: state.legend.shouldTruncate ?? getDefaultVisualValuesForLayer(state, datasourceLayers).truncateText, diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx index 73802ac2b4c47..03a391d4e29d6 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.tsx @@ -6,7 +6,7 @@ */ import { type ExtraAppendLayerArg, getXyVisualization } from './visualization'; -import { Position } from '@elastic/charts'; +import { LegendValue, Position } from '@elastic/charts'; import { Operation, OperationDescriptor, @@ -53,7 +53,6 @@ import { } from './visualization_helpers'; import { cloneDeep } from 'lodash'; import { DataViewsServicePublic } from '@kbn/data-views-plugin/public'; -import { XYLegendValue } from '@kbn/visualizations-plugin/common/constants'; import { XYPersistedByReferenceAnnotationLayerConfig, XYPersistedByValueAnnotationLayerConfig, @@ -612,7 +611,7 @@ describe('xy_visualization', () => { ...exampleState(), legend: { ...exampleState().legend, - legendStats: [XYLegendValue.CurrentAndLastValue], + legendStats: [LegendValue.CurrentAndLastValue], }, }; diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx index 487e23e1224b7..ca99a3a7fd853 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx @@ -118,7 +118,9 @@ import { AddLayerButton } from './add_layer'; import { LayerSettings } from './layer_settings'; import { IgnoredGlobalFiltersEntries } from '../../shared_components/ignore_global_filter'; import { getColorMappingTelemetryEvents } from '../../lens_ui_telemetry/color_telemetry_helpers'; +import { getLegendStatsTelemetryEvents } from './legend_stats_telemetry_helpers'; import { XYPersistedState, convertToPersistable, convertToRuntime } from './persistence'; +import { shouldDisplayTable } from '../../shared_components/legend/legend_settings_popover'; import { ANNOTATION_MISSING_DATE_HISTOGRAM, LAYER_SETTINGS_IGNORE_GLOBAL_FILTERS, @@ -1062,10 +1064,22 @@ export const getXyVisualization = ({ getTelemetryEventsOnSave(state, prevState) { const dataLayers = getDataLayers(state.layers); const prevLayers = prevState ? getDataLayers(prevState.layers) : undefined; - return dataLayers.flatMap((l) => { + const colorMappingEvents = dataLayers.flatMap((l) => { const prevLayer = prevLayers?.find((prevL) => prevL.layerId === l.layerId); return getColorMappingTelemetryEvents(l.colorMapping, prevLayer?.colorMapping); }); + const legendStatsEvents = getLegendStatsTelemetryEvents( + state.legend.legendStats, + prevState ? prevState.legend.legendStats : undefined + ); + return colorMappingEvents.concat(legendStatsEvents); + }, + + getRenderEventCounters(state) { + if (shouldDisplayTable(state.legend.legendStats ?? [])) { + return [`legend_stats`]; + } + return []; }, }); diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/axis_settings_popover.test.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/axis_settings_popover.test.tsx index 1976b8084d7f9..fbf14216ad7a2 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/axis_settings_popover.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/axis_settings_popover.test.tsx @@ -39,7 +39,7 @@ describe('AxesSettingsPopover', () => { axis: 'x', areTickLabelsVisible: true, areGridlinesVisible: true, - isAxisTitleVisible: true, + isTitleVisible: true, toggleTickLabelsVisibility: jest.fn(), toggleGridlinesVisibility: jest.fn(), hasBarOrAreaOnAxis: false, diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/axis_settings_popover.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/axis_settings_popover.tsx index 1714fc4a4e406..20390f322fd90 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/axis_settings_popover.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/axis_settings_popover.tsx @@ -21,7 +21,7 @@ import { useDebouncedValue } from '@kbn/visualization-ui-components'; import { isHorizontalChart } from '../state_helpers'; import { ToolbarPopover, - AxisTitleSettings, + ToolbarTitleSettings, AxisBoundsControl, AxisTicksSettings, } from '../../../shared_components'; @@ -51,7 +51,7 @@ export interface AxisSettingsPopoverProps { */ updateTitleState: ( title: { title?: string; visible: boolean }, - axis: AxesSettingsConfigKeys + settingId: AxesSettingsConfigKeys ) => void; /** * Determines if the popover is Disabled @@ -84,7 +84,7 @@ export interface AxisSettingsPopoverProps { /** * Determines if the title visibility switch is on and the input text is disabled */ - isAxisTitleVisible: boolean; + isTitleVisible: boolean; /** * Set endzone visibility */ @@ -224,7 +224,7 @@ export const AxisSettingsPopover: React.FunctionComponent - updateTitleState(title, axis)} + isTitleVisible={isTitleVisible} /> = [ + { + value: LegendValue.Average, + label: i18n.translate('xpack.lens.shared.legendValues.average', { + defaultMessage: 'Average', + }), + toolTipContent: i18n.translate('xpack.lens.shared.legendValues.averageDesc', { + defaultMessage: 'Average of all values in the series.', + }), + }, + { + value: LegendValue.Median, + label: i18n.translate('xpack.lens.shared.legendValues.median', { + defaultMessage: 'Median', + }), + toolTipContent: i18n.translate('xpack.lens.shared.legendValues.medianDesc', { + defaultMessage: 'Median value in the series.', + }), + }, + { + value: LegendValue.Min, + label: i18n.translate('xpack.lens.shared.legendValues.min', { + defaultMessage: 'Minimum', + }), + toolTipContent: i18n.translate('xpack.lens.shared.legendValues.minDesc', { + defaultMessage: 'Minimum value in the series.', + }), + }, + { + value: LegendValue.Max, + label: i18n.translate('xpack.lens.shared.legendValues.max', { + defaultMessage: 'Maximum', + }), + toolTipContent: i18n.translate('xpack.lens.shared.legendValues.maxDesc', { + defaultMessage: 'Maximum value in the series.', + }), + }, + { + value: LegendValue.Range, + label: i18n.translate('xpack.lens.shared.legendValues.range', { + defaultMessage: 'Range', + }), + toolTipContent: i18n.translate('xpack.lens.shared.legendValues.rangeDesc', { + defaultMessage: 'Difference between the min and the max in the series.', + }), + }, + { + value: LegendValue.LastValue, + label: i18n.translate('xpack.lens.shared.legendValues.lastValue', { + defaultMessage: 'Last value', + }), + toolTipContent: i18n.translate('xpack.lens.shared.legendValues.lastValueDesc', { + defaultMessage: 'Last value in the series.', + }), + }, + { + value: LegendValue.LastNonNullValue, + label: i18n.translate('xpack.lens.shared.legendValues.lastNonNullValue', { + defaultMessage: 'Last non-null value', + }), + toolTipContent: i18n.translate('xpack.lens.shared.legendValues.lastNonNullValueDesc', { + defaultMessage: 'Last non-null value in the series.', + }), + }, + { + value: LegendValue.FirstValue, + label: i18n.translate('xpack.lens.shared.legendValues.firstValue', { + defaultMessage: 'First value', + }), + toolTipContent: i18n.translate('xpack.lens.shared.legendValues.firstValueDesc', { + defaultMessage: 'First value in the series.', + }), + }, + { + value: LegendValue.FirstNonNullValue, + label: i18n.translate('xpack.lens.shared.legendValues.firstNonNullValue', { + defaultMessage: 'First non-null value', + }), + toolTipContent: i18n.translate('xpack.lens.shared.legendValues.firstNonNullValueDesc', { + defaultMessage: 'First non-null value in the series.', + }), + }, + { + value: LegendValue.Difference, + label: i18n.translate('xpack.lens.shared.legendValues.diff', { + defaultMessage: 'Difference', + }), + toolTipContent: i18n.translate('xpack.lens.shared.legendValues.diffDesc', { + defaultMessage: 'Difference between first and last value in the series.', + }), + }, + { + value: LegendValue.DifferencePercent, + label: i18n.translate('xpack.lens.shared.legendValues.diffPercent', { + defaultMessage: 'Difference %', + }), + toolTipContent: i18n.translate('xpack.lens.shared.legendValues.diffPercentDesc', { + defaultMessage: 'Difference in percent between first and last value in the series.', + }), + }, + { + value: LegendValue.Total, + label: i18n.translate('xpack.lens.shared.legendValues.total', { + defaultMessage: 'Sum', + }), + toolTipContent: i18n.translate('xpack.lens.shared.legendValues.totalDesc', { + defaultMessage: 'The sum of all values in the series.', + }), + }, + { + value: LegendValue.Count, + label: i18n.translate('xpack.lens.shared.legendValues.count', { + defaultMessage: 'Count', + }), + toolTipContent: i18n.translate('xpack.lens.shared.legendValues.countDesc', { + defaultMessage: 'Count of all the values in the series.', + }), + }, + { + value: LegendValue.DistinctCount, + label: i18n.translate('xpack.lens.shared.legendValues.distinctCount', { + defaultMessage: 'Distinct count', + }), + toolTipContent: i18n.translate('xpack.lens.shared.legendValues.distinctCountDesc', { + defaultMessage: 'Count of distinct values in the series.', + }), + }, + { + value: LegendValue.Variance, + label: i18n.translate('xpack.lens.shared.legendValues.variance', { + defaultMessage: 'Variance', + }), + toolTipContent: i18n.translate('xpack.lens.shared.legendValues.varianceDesc', { + defaultMessage: 'Variance of all the values in the series.', + }), + }, + { + value: LegendValue.StdDeviation, + label: i18n.translate('xpack.lens.shared.legendValues.stdDev', { + defaultMessage: 'Std deviation', + }), + toolTipContent: i18n.translate('xpack.lens.shared.legendValues.stdDevDesc', { + defaultMessage: 'Standard deviation of all the values in the series.', + }), + }, + // Moved to the bottom to limit its usage. It could cause some UX issues due to the dynamic nature + // of the data displayed + { + value: LegendValue.CurrentAndLastValue, + label: i18n.translate('xpack.lens.shared.legendValues.currentValue', { + defaultMessage: 'Current or last value', + }), + toolTipContent: i18n.translate('xpack.lens.shared.legendValues.currentValueDesc', { + defaultMessage: + 'Value of the bucket being hovered or the last bucket value when not hovering.', + }), + }, +]; + +const defaultLegendTitle = i18n.translate('xpack.lens.xyChart.legendTitle', { + defaultMessage: 'Legend', +}); + export const XyToolbar = memo(function XyToolbar( props: VisualizationToolbarProps & { useLegacyTimeAxis?: boolean } ) { @@ -209,7 +375,7 @@ export const XyToolbar = memo(function XyToolbar( ); const nonOrdinalXAxis = dataLayers.every( (layer) => - !layer.xAccessor || + layer.xAccessor && getScaleType( props.frame.datasourceLayers[layer.layerId]?.getOperationForColumnId(layer.xAccessor) ?? null, @@ -336,9 +502,6 @@ export const XyToolbar = memo(function XyToolbar( ).truncateText; const legendSize = state.legend.legendSize; - - const [hadAutoLegendSize] = useState(() => legendSize === LegendSize.AUTO); - return ( @@ -362,6 +525,23 @@ export const XyToolbar = memo(function XyToolbar( }, }); }} + titlePlaceholder={ + frame.activeData?.[dataLayers[0].layerId]?.columns.find( + (col) => col.id === dataLayers[0].splitAccessor + )?.name ?? defaultLegendTitle + } + legendTitle={state?.legend.title} + onLegendTitleChange={({ title, visible }) => { + setState({ + ...state, + legend: { + ...state.legend, + title, + isTitleVisible: visible, + }, + }); + }} + isTitleVisible={state?.legend.isTitleVisible} onDisplayChange={(optionId) => { const newMode = legendOptions.find(({ id }) => id === optionId)!.value; if (newMode === 'auto') { @@ -435,14 +615,29 @@ export const XyToolbar = memo(function XyToolbar( legend: { ...state.legend, verticalAlignment, horizontalAlignment }, }); }} - allowLegendStats={nonOrdinalXAxis} + allowedLegendStats={nonOrdinalXAxis ? xyLegendValues : undefined} legendStats={state?.legend.legendStats} - onLegendStatsChange={(checked) => { + onLegendStatsChange={(legendStats, hasConvertedToTable) => { + if (hasConvertedToTable) { + setState({ + ...state, + legend: { + ...state.legend, + legendStats, + legendSize: LegendSize.AUTO, + isVisible: true, + showSingleSeries: true, + }, + }); + return; + } setState({ ...state, legend: { ...state.legend, - legendStats: checked ? [XYLegendValue.CurrentAndLastValue] : [], + legendStats, + isVisible: true, + showSingleSeries: true, }, }); }} @@ -456,7 +651,7 @@ export const XyToolbar = memo(function XyToolbar( }, }); }} - showAutoLegendSizeOption={hadAutoLegendSize} + showAutoLegendSizeOption={true} /> @@ -491,7 +686,7 @@ export const XyToolbar = memo(function XyToolbar( } orientation={labelsOrientation.yLeft} setOrientation={onLabelsOrientationChange} - isAxisTitleVisible={axisTitlesVisibilitySettings.yLeft} + isTitleVisible={axisTitlesVisibilitySettings.yLeft} extent={state?.yLeftExtent || { mode: 'full' }} setExtent={setExtentFn('yLeftExtent')} hasBarOrAreaOnAxis={hasBarOrAreaOnLeftAxis} @@ -514,7 +709,7 @@ export const XyToolbar = memo(function XyToolbar( toggleGridlinesVisibility={onGridlinesVisibilitySettingsChange} orientation={labelsOrientation.x} setOrientation={onLabelsOrientationChange} - isAxisTitleVisible={axisTitlesVisibilitySettings.x} + isTitleVisible={axisTitlesVisibilitySettings.x} endzonesVisible={!state?.hideEndzones} setEndzoneVisibility={onChangeEndzoneVisiblity} currentTimeMarkerVisible={state?.showCurrentTimeMarker} @@ -559,7 +754,7 @@ export const XyToolbar = memo(function XyToolbar( orientation={labelsOrientation.yRight} setOrientation={onLabelsOrientationChange} hasPercentageAxis={hasPercentageAxis(axisGroups, 'right', state)} - isAxisTitleVisible={axisTitlesVisibilitySettings.yRight} + isTitleVisible={axisTitlesVisibilitySettings.yRight} extent={state?.yRightExtent || { mode: 'full' }} setExtent={setExtentFn('yRightExtent')} hasBarOrAreaOnAxis={hasBarOrAreaOnRightAxis} diff --git a/x-pack/plugins/licensing/common/register_analytics_context_provider.ts b/x-pack/plugins/licensing/common/register_analytics_context_provider.ts index 60f3fbbb3e603..5defe217901b2 100644 --- a/x-pack/plugins/licensing/common/register_analytics_context_provider.ts +++ b/x-pack/plugins/licensing/common/register_analytics_context_provider.ts @@ -7,7 +7,7 @@ import type { Observable } from 'rxjs'; import { map } from 'rxjs'; -import type { AnalyticsClient } from '@kbn/analytics-client'; +import type { AnalyticsClient } from '@kbn/ebt/client'; import type { ILicense } from './types'; export function registerAnalyticsContextProvider( diff --git a/x-pack/plugins/licensing/tsconfig.json b/x-pack/plugins/licensing/tsconfig.json index f726ed7c09123..8eed250935b33 100644 --- a/x-pack/plugins/licensing/tsconfig.json +++ b/x-pack/plugins/licensing/tsconfig.json @@ -12,9 +12,9 @@ "@kbn/config-schema", "@kbn/std", "@kbn/i18n", - "@kbn/analytics-client", "@kbn/logging-mocks", - "@kbn/react-kibana-mount" + "@kbn/react-kibana-mount", + "@kbn/ebt" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/lists/common/api/index.ts b/x-pack/plugins/lists/common/api/index.ts index 1d76a041d2824..1cbbeba219078 100644 --- a/x-pack/plugins/lists/common/api/index.ts +++ b/x-pack/plugins/lists/common/api/index.ts @@ -27,20 +27,4 @@ export * from './exceptions/summary_exception_list/summary_exception_list_route' export * from './exceptions/update_endpoint_list_item/update_endpoint_list_item_route'; export * from './exceptions/update_exception_list_item/update_exception_list_item_route'; export * from './exceptions/update_exception_list/update_exception_list_route'; -export * from './values/create_list_index/create_list_index_route'; -export * from './values/create_list_item/create_list_item_route'; -export * from './values/create_list/create_list_route'; -export * from './values/delete_list_index/delete_list_index_route'; -export * from './values/delete_list_item/delete_list_item_route'; -export * from './values/delete_list/delete_list_route'; -export * from './values/find_list_item/find_list_item_route'; -export * from './values/find_list/find_list_route'; export * from './values/find_lists_by_size/find_lists_by_size_route'; -export * from './values/import_list_item/import_list_item_route'; -export * from './values/patch_list_item/patch_list_item_route'; -export * from './values/patch_list/patch_list_route'; -export * from './values/read_list_index/read_list_index_route'; -export * from './values/read_list_item/read_list_item_route'; -export * from './values/read_list/read_list_route'; -export * from './values/update_list_item/update_list_item_route'; -export * from './values/update_list/update_list_route'; diff --git a/x-pack/plugins/lists/common/api/values/create_list/create_list_route.ts b/x-pack/plugins/lists/common/api/values/create_list/create_list_route.ts deleted file mode 100644 index 71dc2eecde8ea..0000000000000 --- a/x-pack/plugins/lists/common/api/values/create_list/create_list_route.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - CreateListSchemaDecoded, - createListSchema, - listSchema, -} from '@kbn/securitysolution-io-ts-list-types'; - -export { createListSchema as createListRequest, listSchema as createListResponse }; -export type { CreateListSchemaDecoded as CreateListRequestDecoded }; diff --git a/x-pack/plugins/lists/common/api/values/delete_list_item/delete_list_item_route.ts b/x-pack/plugins/lists/common/api/values/delete_list_item/delete_list_item_route.ts deleted file mode 100644 index 418401d2b6c17..0000000000000 --- a/x-pack/plugins/lists/common/api/values/delete_list_item/delete_list_item_route.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - deleteListItemSchema, - listItemArraySchema, - listItemSchema, -} from '@kbn/securitysolution-io-ts-list-types'; - -export { - deleteListItemSchema as deleteListItemRequestQuery, - listItemSchema as deleteListItemResponse, - listItemArraySchema as deleteListItemArrayResponse, -}; diff --git a/x-pack/plugins/lists/common/api/values/find_list_item/find_list_item_route.ts b/x-pack/plugins/lists/common/api/values/find_list_item/find_list_item_route.ts deleted file mode 100644 index b9d4d93ccb340..0000000000000 --- a/x-pack/plugins/lists/common/api/values/find_list_item/find_list_item_route.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - FindListItemSchemaDecoded, - findListItemSchema, - foundListItemSchema, -} from '@kbn/securitysolution-io-ts-list-types'; - -export { - findListItemSchema as findListItemRequestQuery, - foundListItemSchema as findListItemResponse, -}; -export type { FindListItemSchemaDecoded as FindListItemRequestQueryDecoded }; diff --git a/x-pack/plugins/lists/common/api/values/patch_list_item/patch_list_item_route.ts b/x-pack/plugins/lists/common/api/values/patch_list_item/patch_list_item_route.ts deleted file mode 100644 index 12b4bd36e968e..0000000000000 --- a/x-pack/plugins/lists/common/api/values/patch_list_item/patch_list_item_route.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { listItemSchema, patchListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; - -export { patchListItemSchema as patchListItemRequest, listItemSchema as patchListItemResponse }; diff --git a/x-pack/plugins/lists/common/api/values/read_list/read_list_route.ts b/x-pack/plugins/lists/common/api/values/read_list/read_list_route.ts deleted file mode 100644 index be13d604f57c5..0000000000000 --- a/x-pack/plugins/lists/common/api/values/read_list/read_list_route.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { listSchema, readListSchema } from '@kbn/securitysolution-io-ts-list-types'; - -export { readListSchema as readListRequestQuery, listSchema as readListResponse }; diff --git a/x-pack/plugins/lists/common/api/values/read_list_item/read_list_item_route.ts b/x-pack/plugins/lists/common/api/values/read_list_item/read_list_item_route.ts deleted file mode 100644 index 0b67159ceaf03..0000000000000 --- a/x-pack/plugins/lists/common/api/values/read_list_item/read_list_item_route.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - listItemArraySchema, - listItemSchema, - readListItemSchema, -} from '@kbn/securitysolution-io-ts-list-types'; - -export { - readListItemSchema as readListItemRequestQuery, - listItemSchema as readListItemResponse, - listItemArraySchema as readListItemArrayResponse, -}; diff --git a/x-pack/plugins/lists/common/api/values/update_list/update_list_route.ts b/x-pack/plugins/lists/common/api/values/update_list/update_list_route.ts deleted file mode 100644 index 801e9784ac80d..0000000000000 --- a/x-pack/plugins/lists/common/api/values/update_list/update_list_route.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { listSchema, updateListSchema } from '@kbn/securitysolution-io-ts-list-types'; - -export { updateListSchema as updateListRequest, listSchema as updateListResponse }; diff --git a/x-pack/plugins/lists/common/api/values/update_list_item/update_list_item_route.ts b/x-pack/plugins/lists/common/api/values/update_list_item/update_list_item_route.ts deleted file mode 100644 index 9b512e61f464e..0000000000000 --- a/x-pack/plugins/lists/common/api/values/update_list_item/update_list_item_route.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { listItemSchema, updateListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; - -export { updateListItemSchema as updateListItemRequest, listItemSchema as updateListItemResponse }; diff --git a/x-pack/plugins/lists/common/schemas/request/create_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/create_list_schema.mock.ts index 117659423103d..57dae64930dc5 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_list_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_list_schema.mock.ts @@ -5,11 +5,11 @@ * 2.0. */ -import type { CreateListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import type { CreateListRequestBodyInput } from '@kbn/securitysolution-lists-common/api'; import { DESCRIPTION, LIST_ID, META, NAME, TYPE, VERSION } from '../../constants.mock'; -export const getCreateListSchemaMock = (): CreateListSchema => ({ +export const getCreateListSchemaMock = (): CreateListRequestBodyInput => ({ description: DESCRIPTION, deserializer: undefined, id: LIST_ID, @@ -23,7 +23,7 @@ export const getCreateListSchemaMock = (): CreateListSchema => ({ /** * Useful for end to end tests and other mechanisms which want to fill in the values */ -export const getCreateMinimalListSchemaMock = (): CreateListSchema => ({ +export const getCreateMinimalListSchemaMock = (): CreateListRequestBodyInput => ({ description: DESCRIPTION, id: LIST_ID, name: NAME, @@ -33,7 +33,7 @@ export const getCreateMinimalListSchemaMock = (): CreateListSchema => ({ /** * Useful for end to end tests and other mechanisms which want to fill in the values */ -export const getCreateMinimalListSchemaMockWithoutId = (): CreateListSchema => ({ +export const getCreateMinimalListSchemaMockWithoutId = (): CreateListRequestBodyInput => ({ description: DESCRIPTION, name: NAME, type: TYPE, diff --git a/x-pack/plugins/lists/common/schemas/request/update_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/update_list_schema.mock.ts index d38744a35a6cb..564c79e67a2bb 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_list_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_list_schema.mock.ts @@ -5,11 +5,11 @@ * 2.0. */ -import type { UpdateListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import type { UpdateListRequestBody } from '@kbn/securitysolution-lists-common/api'; import { DESCRIPTION, LIST_ID, META, NAME, _VERSION } from '../../constants.mock'; -export const getUpdateListSchemaMock = (): UpdateListSchema => ({ +export const getUpdateListSchemaMock = (): UpdateListRequestBody => ({ _version: _VERSION, description: DESCRIPTION, id: LIST_ID, @@ -21,7 +21,7 @@ export const getUpdateListSchemaMock = (): UpdateListSchema => ({ * Useful for end to end tests and other mechanisms which want to fill in the values * after doing a get of the structure. */ -export const getUpdateMinimalListSchemaMock = (): UpdateListSchema => ({ +export const getUpdateMinimalListSchemaMock = (): UpdateListRequestBody => ({ description: DESCRIPTION, id: LIST_ID, name: NAME, diff --git a/x-pack/plugins/lists/server/routes/list/create_list_route.ts b/x-pack/plugins/lists/server/routes/list/create_list_route.ts index 063056461c20c..3b0e80aa7ee7e 100644 --- a/x-pack/plugins/lists/server/routes/list/create_list_route.ts +++ b/x-pack/plugins/lists/server/routes/list/create_list_route.ts @@ -5,17 +5,13 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { LIST_URL } from '@kbn/securitysolution-list-constants'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { CreateListRequestBody, CreateListResponse } from '@kbn/securitysolution-lists-common/api'; import type { ListsPluginRouter } from '../../types'; -import { - CreateListRequestDecoded, - createListRequest, - createListResponse, -} from '../../../common/api'; -import { buildRouteValidation, buildSiemResponse } from '../utils'; +import { buildSiemResponse } from '../utils'; import { getListClient } from '..'; export const createListRoute = (router: ListsPluginRouter): void => { @@ -31,9 +27,7 @@ export const createListRoute = (router: ListsPluginRouter): void => { { validate: { request: { - body: buildRouteValidation( - createListRequest - ), + body: buildRouteValidationWithZod(CreateListRequestBody), }, }, version: '2023-10-31', @@ -77,12 +71,8 @@ export const createListRoute = (router: ListsPluginRouter): void => { type, version, }); - const [validated, errors] = validate(list, createListResponse); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ body: validated ?? {} }); - } + + return response.ok({ body: CreateListResponse.parse(list) }); } } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/lists/server/routes/list/delete_list_route.ts b/x-pack/plugins/lists/server/routes/list/delete_list_route.ts index f2571cd44acf9..d28b777a2ba8e 100644 --- a/x-pack/plugins/lists/server/routes/list/delete_list_route.ts +++ b/x-pack/plugins/lists/server/routes/list/delete_list_route.ts @@ -17,12 +17,13 @@ import { } from '@kbn/securitysolution-io-ts-list-types'; import { getSavedObjectType } from '@kbn/securitysolution-list-utils'; import { LIST_URL } from '@kbn/securitysolution-list-constants'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { DeleteListRequestQuery, DeleteListResponse } from '@kbn/securitysolution-lists-common/api'; import type { ListsPluginRouter } from '../../types'; import type { ExceptionListClient } from '../../services/exception_lists/exception_list_client'; import { escapeQuotes } from '../../services/utils/escape_query'; -import { deleteListRequestQuery, deleteListResponse } from '../../../common/api'; -import { buildRouteValidation, buildSiemResponse } from '../utils'; +import { buildSiemResponse } from '../utils'; import { getExceptionListClient, getListClient } from '..'; export const deleteListRoute = (router: ListsPluginRouter): void => { @@ -38,7 +39,7 @@ export const deleteListRoute = (router: ListsPluginRouter): void => { { validate: { request: { - query: buildRouteValidation(deleteListRequestQuery), + query: buildRouteValidationWithZod(DeleteListRequestQuery), }, }, version: '2023-10-31', @@ -49,7 +50,6 @@ export const deleteListRoute = (router: ListsPluginRouter): void => { const lists = await getListClient(context); const exceptionLists = await getExceptionListClient(context); const { id, deleteReferences, ignoreReferences } = request.query; - let deleteExceptionItemResponses; // ignoreReferences=true maintains pre-7.11 behavior of deleting value list without performing any additional checks if (!ignoreReferences) { @@ -75,7 +75,7 @@ export const deleteListRoute = (router: ListsPluginRouter): void => { if (deleteReferences) { // Delete referenced exception list items // TODO: Create deleteListItems to delete in batch - deleteExceptionItemResponses = await Promise.all( + await Promise.all( referencedExceptionListItems.map(async (listItem) => { // Ensure only the single entry is deleted as there could be a separate value list referenced that is okay to keep // TODO: Add API to delete single entry const remainingEntries = listItem.entries.filter( @@ -122,16 +122,9 @@ export const deleteListRoute = (router: ListsPluginRouter): void => { statusCode: 404, }); } else { - const [validated, errors] = validate(deleted, deleteListResponse); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ - body: validated ?? { - deleteItemResponses: deleteExceptionItemResponses, - }, - }); - } + return response.ok({ + body: DeleteListResponse.parse(deleted), + }); } } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/lists/server/routes/list/import_list_item_route.ts b/x-pack/plugins/lists/server/routes/list/import_list_item_route.ts index d9d1bc5af1e6e..f3f52828f7872 100644 --- a/x-pack/plugins/lists/server/routes/list/import_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/list/import_list_item_route.ts @@ -8,14 +8,17 @@ import { extname } from 'path'; import { schema } from '@kbn/config-schema'; -import { validate } from '@kbn/securitysolution-io-ts-utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { + ImportListItemsRequestQuery, + ImportListItemsResponse, +} from '@kbn/securitysolution-lists-common/api'; import type { ListsPluginRouter } from '../../types'; import { ConfigType } from '../../config'; -import { importListItemRequestQuery, importListItemResponse } from '../../../common/api'; -import { buildRouteValidation, buildSiemResponse } from '../utils'; +import { buildSiemResponse } from '../utils'; import { createStreamFromBuffer } from '../utils/create_stream_from_buffer'; import { getListClient } from '..'; @@ -43,7 +46,7 @@ export const importListItemRoute = (router: ListsPluginRouter, config: ConfigTyp validate: { request: { body: schema.buffer(), - query: buildRouteValidation(importListItemRequestQuery), + query: buildRouteValidationWithZod(ImportListItemsRequestQuery), }, }, version: '2023-10-31', @@ -119,12 +122,7 @@ export const importListItemRoute = (router: ListsPluginRouter, config: ConfigTyp version: 1, }); - const [validated, errors] = validate(list, importListItemResponse); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ body: validated ?? {} }); - } + return response.ok({ body: ImportListItemsResponse.parse(list) }); } else if (type != null) { const importedList = await lists.importListItemsToStream({ deserializer, @@ -142,12 +140,8 @@ export const importListItemRoute = (router: ListsPluginRouter, config: ConfigTyp statusCode: 400, }); } - const [validated, errors] = validate(importedList, importListItemResponse); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ body: validated ?? {} }); - } + + return response.ok({ body: ImportListItemsResponse.parse(importedList) }); } else { return siemResponse.error({ body: 'Either type or list_id need to be defined in the query', diff --git a/x-pack/plugins/lists/server/routes/list/patch_list_route.ts b/x-pack/plugins/lists/server/routes/list/patch_list_route.ts index 336239854f70b..fb14166d74f9c 100644 --- a/x-pack/plugins/lists/server/routes/list/patch_list_route.ts +++ b/x-pack/plugins/lists/server/routes/list/patch_list_route.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { LIST_URL } from '@kbn/securitysolution-list-constants'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { PatchListRequestBody, PatchListResponse } from '@kbn/securitysolution-lists-common/api'; import type { ListsPluginRouter } from '../../types'; -import { patchListRequest, patchListResponse } from '../../../common/api'; -import { buildRouteValidation, buildSiemResponse } from '../utils'; +import { buildSiemResponse } from '../utils'; import { getListClient } from '..'; export const patchListRoute = (router: ListsPluginRouter): void => { @@ -27,7 +27,7 @@ export const patchListRoute = (router: ListsPluginRouter): void => { { validate: { request: { - body: buildRouteValidation(patchListRequest), + body: buildRouteValidationWithZod(PatchListRequestBody), }, }, version: '2023-10-31', @@ -54,12 +54,7 @@ export const patchListRoute = (router: ListsPluginRouter): void => { statusCode: 404, }); } else { - const [validated, errors] = validate(list, patchListResponse); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ body: validated ?? {} }); - } + return response.ok({ body: PatchListResponse.parse(list) }); } } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/lists/server/routes/list/read_list_route.ts b/x-pack/plugins/lists/server/routes/list/read_list_route.ts index ab403e933e53f..e9af8cfc8f4eb 100644 --- a/x-pack/plugins/lists/server/routes/list/read_list_route.ts +++ b/x-pack/plugins/lists/server/routes/list/read_list_route.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { LIST_URL } from '@kbn/securitysolution-list-constants'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { GetListRequestQuery, GetListResponse } from '@kbn/securitysolution-lists-common/api'; import type { ListsPluginRouter } from '../../types'; -import { readListRequestQuery, readListResponse } from '../../../common/api'; -import { buildRouteValidation, buildSiemResponse } from '../utils'; +import { buildSiemResponse } from '../utils'; import { getListClient } from '..'; export const readListRoute = (router: ListsPluginRouter): void => { @@ -27,7 +27,7 @@ export const readListRoute = (router: ListsPluginRouter): void => { { validate: { request: { - query: buildRouteValidation(readListRequestQuery), + query: buildRouteValidationWithZod(GetListRequestQuery), }, }, version: '2023-10-31', @@ -44,12 +44,7 @@ export const readListRoute = (router: ListsPluginRouter): void => { statusCode: 404, }); } else { - const [validated, errors] = validate(list, readListResponse); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ body: validated ?? {} }); - } + return response.ok({ body: GetListResponse.parse(list) }); } } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/lists/server/routes/list/update_list_route.ts b/x-pack/plugins/lists/server/routes/list/update_list_route.ts index 46b2d8e058857..c20714b75e090 100644 --- a/x-pack/plugins/lists/server/routes/list/update_list_route.ts +++ b/x-pack/plugins/lists/server/routes/list/update_list_route.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { LIST_URL } from '@kbn/securitysolution-list-constants'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { UpdateListRequestBody, UpdateListResponse } from '@kbn/securitysolution-lists-common/api'; import type { ListsPluginRouter } from '../../types'; -import { updateListRequest, updateListResponse } from '../../../common/api'; -import { buildRouteValidation, buildSiemResponse } from '../utils'; +import { buildSiemResponse } from '../utils'; import { getListClient } from '..'; export const updateListRoute = (router: ListsPluginRouter): void => { @@ -27,7 +27,7 @@ export const updateListRoute = (router: ListsPluginRouter): void => { { validate: { request: { - body: buildRouteValidation(updateListRequest), + body: buildRouteValidationWithZod(UpdateListRequestBody), }, }, version: '2023-10-31', @@ -54,12 +54,7 @@ export const updateListRoute = (router: ListsPluginRouter): void => { statusCode: 404, }); } else { - const [validated, errors] = validate(list, updateListResponse); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ body: validated ?? {} }); - } + return response.ok({ body: UpdateListResponse.parse(list) }); } } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/lists/server/routes/list_index/create_list_index_route.ts b/x-pack/plugins/lists/server/routes/list_index/create_list_index_route.ts index e3cab179c2f40..2f74871f23fc2 100644 --- a/x-pack/plugins/lists/server/routes/list_index/create_list_index_route.ts +++ b/x-pack/plugins/lists/server/routes/list_index/create_list_index_route.ts @@ -5,11 +5,10 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { LIST_INDEX } from '@kbn/securitysolution-list-constants'; +import { CreateListIndexResponse } from '@kbn/securitysolution-lists-common/api'; -import { createListIndexResponse } from '../../../common/api'; import type { ListsPluginRouter } from '../../types'; import { buildSiemResponse } from '../utils'; import { getListClient } from '..'; @@ -64,12 +63,7 @@ export const createListIndexRoute = (router: ListsPluginRouter): void => { : lists.createListItemDataStream()); } - const [validated, errors] = validate({ acknowledged: true }, createListIndexResponse); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ body: validated ?? {} }); - } + return response.ok({ body: CreateListIndexResponse.parse({ acknowledged: true }) }); } catch (err) { const error = transformError(err); return siemResponse.error({ diff --git a/x-pack/plugins/lists/server/routes/list_index/delete_list_index_route.ts b/x-pack/plugins/lists/server/routes/list_index/delete_list_index_route.ts index a9683f4636151..0814739ab11e7 100644 --- a/x-pack/plugins/lists/server/routes/list_index/delete_list_index_route.ts +++ b/x-pack/plugins/lists/server/routes/list_index/delete_list_index_route.ts @@ -5,13 +5,12 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { LIST_INDEX } from '@kbn/securitysolution-list-constants'; +import { DeleteListIndexResponse } from '@kbn/securitysolution-lists-common/api'; import { ListClient } from '../../services/lists/list_client'; import type { ListsPluginRouter } from '../../types'; -import { deleteListIndexResponse } from '../../../common/api'; import { buildSiemResponse } from '../utils'; import { getListClient } from '..'; @@ -83,12 +82,7 @@ export const deleteListIndexRoute = (router: ListsPluginRouter): void => { await deleteIndexTemplates(lists); - const [validated, errors] = validate({ acknowledged: true }, deleteListIndexResponse); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ body: validated ?? {} }); - } + return response.ok({ body: DeleteListIndexResponse.parse({ acknowledged: true }) }); } catch (err) { const error = transformError(err); return siemResponse.error({ diff --git a/x-pack/plugins/lists/server/routes/list_index/export_list_item_route.ts b/x-pack/plugins/lists/server/routes/list_index/export_list_item_route.ts index 1d43d4fd8c181..af9c40117f9c6 100644 --- a/x-pack/plugins/lists/server/routes/list_index/export_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/list_index/export_list_item_route.ts @@ -9,10 +9,11 @@ import { Stream } from 'stream'; import { transformError } from '@kbn/securitysolution-es-utils'; import { LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { ExportListItemsRequestQuery } from '@kbn/securitysolution-lists-common/api'; import type { ListsPluginRouter } from '../../types'; -import { exportListItemRequestQuery } from '../../../common/api'; -import { buildRouteValidation, buildSiemResponse } from '../utils'; +import { buildSiemResponse } from '../utils'; import { getListClient } from '..'; export const exportListItemRoute = (router: ListsPluginRouter): void => { @@ -28,7 +29,7 @@ export const exportListItemRoute = (router: ListsPluginRouter): void => { { validate: { request: { - query: buildRouteValidation(exportListItemRequestQuery), + query: buildRouteValidationWithZod(ExportListItemsRequestQuery), }, }, version: '2023-10-31', diff --git a/x-pack/plugins/lists/server/routes/list_index/find_list_route.ts b/x-pack/plugins/lists/server/routes/list_index/find_list_route.ts index 9b492006e0605..157b11e3832eb 100644 --- a/x-pack/plugins/lists/server/routes/list_index/find_list_route.ts +++ b/x-pack/plugins/lists/server/routes/list_index/find_list_route.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { LIST_URL } from '@kbn/securitysolution-list-constants'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { FindListsRequestQuery, FindListsResponse } from '@kbn/securitysolution-lists-common/api'; import type { ListsPluginRouter } from '../../types'; import { decodeCursor } from '../../services/utils'; -import { findListRequestQuery, findListResponse } from '../../../common/api'; -import { buildRouteValidation, buildSiemResponse, getListClient } from '../utils'; +import { buildSiemResponse, getListClient } from '../utils'; export const findListRoute = (router: ListsPluginRouter): void => { router.versioned @@ -27,7 +27,7 @@ export const findListRoute = (router: ListsPluginRouter): void => { { validate: { request: { - query: buildRouteValidation(findListRequestQuery), + query: buildRouteValidationWithZod(FindListsRequestQuery), }, }, version: '2023-10-31', @@ -74,12 +74,8 @@ export const findListRoute = (router: ListsPluginRouter): void => { sortField, sortOrder, }); - const [validated, errors] = validate(exceptionList, findListResponse); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ body: validated ?? {} }); - } + + return response.ok({ body: FindListsResponse.parse(exceptionList) }); } } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/lists/server/routes/list_index/read_list_index_route.ts b/x-pack/plugins/lists/server/routes/list_index/read_list_index_route.ts index 6e9fdb29e55b8..80637788e00db 100644 --- a/x-pack/plugins/lists/server/routes/list_index/read_list_index_route.ts +++ b/x-pack/plugins/lists/server/routes/list_index/read_list_index_route.ts @@ -5,12 +5,11 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { LIST_INDEX } from '@kbn/securitysolution-list-constants'; +import { GetListIndexResponse } from '@kbn/securitysolution-lists-common/api'; import type { ListsPluginRouter } from '../../types'; -import { readListIndexResponse } from '../../../common/api'; import { buildSiemResponse } from '../utils'; import { getListClient } from '..'; @@ -37,15 +36,12 @@ export const readListIndexRoute = (router: ListsPluginRouter): void => { const listItemDataStreamExists = await lists.getListItemDataStreamExists(); if (listDataStreamExists && listItemDataStreamExists) { - const [validated, errors] = validate( - { list_index: listDataStreamExists, list_item_index: listItemDataStreamExists }, - readListIndexResponse - ); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ body: validated ?? {} }); - } + return response.ok({ + body: GetListIndexResponse.parse({ + list_index: listDataStreamExists, + list_item_index: listItemDataStreamExists, + }), + }); } else if (!listDataStreamExists && listItemDataStreamExists) { return siemResponse.error({ body: `data stream ${lists.getListName()} does not exist`, diff --git a/x-pack/plugins/lists/server/routes/list_item/create_list_item_route.ts b/x-pack/plugins/lists/server/routes/list_item/create_list_item_route.ts index b3ab6f4e09eb7..2f8bd2738ae0f 100644 --- a/x-pack/plugins/lists/server/routes/list_item/create_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/list_item/create_list_item_route.ts @@ -5,13 +5,16 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { + CreateListItemRequestBody, + CreateListItemResponse, +} from '@kbn/securitysolution-lists-common/api'; -import { createListItemRequest, createListItemResponse } from '../../../common/api'; import type { ListsPluginRouter } from '../../types'; -import { buildRouteValidation, buildSiemResponse } from '../utils'; +import { buildSiemResponse } from '../utils'; import { getListClient } from '..'; export const createListItemRoute = (router: ListsPluginRouter): void => { @@ -27,7 +30,7 @@ export const createListItemRoute = (router: ListsPluginRouter): void => { { validate: { request: { - body: buildRouteValidation(createListItemRequest), + body: buildRouteValidationWithZod(CreateListItemRequestBody), }, }, version: '2023-10-31', @@ -63,13 +66,9 @@ export const createListItemRoute = (router: ListsPluginRouter): void => { type: list.type, value, }); + if (createdListItem != null) { - const [validated, errors] = validate(createdListItem, createListItemResponse); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ body: validated ?? {} }); - } + return response.ok({ body: CreateListItemResponse.parse(createdListItem) }); } else { return siemResponse.error({ body: 'list item invalid', diff --git a/x-pack/plugins/lists/server/routes/list_item/delete_list_item_route.ts b/x-pack/plugins/lists/server/routes/list_item/delete_list_item_route.ts index 644c8235bae06..2cf30a61988b0 100644 --- a/x-pack/plugins/lists/server/routes/list_item/delete_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/list_item/delete_list_item_route.ts @@ -5,17 +5,16 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { + DeleteListItemRequestQuery, + DeleteListItemResponse, +} from '@kbn/securitysolution-lists-common/api'; import type { ListsPluginRouter } from '../../types'; -import { - deleteListItemArrayResponse, - deleteListItemRequestQuery, - deleteListItemResponse, -} from '../../../common/api'; -import { buildRouteValidation, buildSiemResponse } from '../utils'; +import { buildSiemResponse } from '../utils'; import { getListClient } from '..'; export const deleteListItemRoute = (router: ListsPluginRouter): void => { @@ -31,7 +30,7 @@ export const deleteListItemRoute = (router: ListsPluginRouter): void => { { validate: { request: { - query: buildRouteValidation(deleteListItemRequestQuery), + query: buildRouteValidationWithZod(DeleteListItemRequestQuery), }, }, version: '2023-10-31', @@ -50,12 +49,7 @@ export const deleteListItemRoute = (router: ListsPluginRouter): void => { statusCode: 404, }); } else { - const [validated, errors] = validate(deleted, deleteListItemResponse); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ body: validated ?? {} }); - } + return response.ok({ body: DeleteListItemResponse.parse(deleted) }); } } else if (listId != null && value != null) { const list = await lists.getList({ id: listId }); @@ -77,12 +71,7 @@ export const deleteListItemRoute = (router: ListsPluginRouter): void => { statusCode: 404, }); } else { - const [validated, errors] = validate(deleted, deleteListItemArrayResponse); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ body: validated ?? {} }); - } + return response.ok({ body: DeleteListItemResponse.parse(deleted) }); } } } else { diff --git a/x-pack/plugins/lists/server/routes/list_item/find_list_item_route.ts b/x-pack/plugins/lists/server/routes/list_item/find_list_item_route.ts index 0fd4300ea51dc..f65b678e36242 100644 --- a/x-pack/plugins/lists/server/routes/list_item/find_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/list_item/find_list_item_route.ts @@ -5,18 +5,17 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { + FindListItemsRequestQuery, + FindListItemsResponse, +} from '@kbn/securitysolution-lists-common/api'; import type { ListsPluginRouter } from '../../types'; import { decodeCursor } from '../../services/utils'; -import { - FindListItemRequestQueryDecoded, - findListItemRequestQuery, - findListItemResponse, -} from '../../../common/api'; -import { buildRouteValidation, buildSiemResponse, getListClient } from '../utils'; +import { buildSiemResponse, getListClient } from '../utils'; export const findListItemRoute = (router: ListsPluginRouter): void => { router.versioned @@ -31,10 +30,7 @@ export const findListItemRoute = (router: ListsPluginRouter): void => { { validate: { request: { - query: buildRouteValidation< - typeof findListItemRequestQuery, - FindListItemRequestQueryDecoded - >(findListItemRequestQuery), + query: buildRouteValidationWithZod(FindListItemsRequestQuery), }, }, version: '2023-10-31', @@ -90,12 +86,7 @@ export const findListItemRoute = (router: ListsPluginRouter): void => { statusCode: 404, }); } else { - const [validated, errors] = validate(exceptionList, findListItemResponse); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ body: validated ?? {} }); - } + return response.ok({ body: FindListItemsResponse.parse(exceptionList) }); } } } catch (err) { diff --git a/x-pack/plugins/lists/server/routes/list_item/patch_list_item_route.ts b/x-pack/plugins/lists/server/routes/list_item/patch_list_item_route.ts index ff369aa9403e8..68c82e93fcc38 100644 --- a/x-pack/plugins/lists/server/routes/list_item/patch_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/list_item/patch_list_item_route.ts @@ -5,13 +5,16 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { + PatchListItemRequestBody, + PatchListItemResponse, +} from '@kbn/securitysolution-lists-common/api'; import type { ListsPluginRouter } from '../../types'; -import { patchListItemRequest, patchListItemResponse } from '../../../common/api'; -import { buildRouteValidation, buildSiemResponse } from '../utils'; +import { buildSiemResponse } from '../utils'; import { getListClient } from '..'; export const patchListItemRoute = (router: ListsPluginRouter): void => { @@ -27,7 +30,7 @@ export const patchListItemRoute = (router: ListsPluginRouter): void => { { validate: { request: { - body: buildRouteValidation(patchListItemRequest), + body: buildRouteValidationWithZod(PatchListItemRequestBody), }, }, version: '2023-10-31', @@ -61,12 +64,7 @@ export const patchListItemRoute = (router: ListsPluginRouter): void => { statusCode: 404, }); } else { - const [validated, errors] = validate(listItem, patchListItemResponse); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ body: validated ?? {} }); - } + return response.ok({ body: PatchListItemResponse.parse(listItem) }); } } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/lists/server/routes/list_item/read_list_item_route.ts b/x-pack/plugins/lists/server/routes/list_item/read_list_item_route.ts index 67c4793736483..0e6a9dbe50155 100644 --- a/x-pack/plugins/lists/server/routes/list_item/read_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/list_item/read_list_item_route.ts @@ -5,17 +5,16 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { + GetListItemRequestQuery, + GetListItemResponse, +} from '@kbn/securitysolution-lists-common/api'; import type { ListsPluginRouter } from '../../types'; -import { - readListItemArrayResponse, - readListItemRequestQuery, - readListItemResponse, -} from '../../../common/api'; -import { buildRouteValidation, buildSiemResponse } from '../utils'; +import { buildSiemResponse } from '../utils'; import { getListClient } from '..'; export const readListItemRoute = (router: ListsPluginRouter): void => { @@ -31,7 +30,7 @@ export const readListItemRoute = (router: ListsPluginRouter): void => { { validate: { request: { - query: buildRouteValidation(readListItemRequestQuery), + query: buildRouteValidationWithZod(GetListItemRequestQuery), }, }, version: '2023-10-31', @@ -49,12 +48,7 @@ export const readListItemRoute = (router: ListsPluginRouter): void => { statusCode: 404, }); } else { - const [validated, errors] = validate(listItem, readListItemResponse); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ body: validated ?? {} }); - } + return response.ok({ body: GetListItemResponse.parse(listItem) }); } } else if (listId != null && value != null) { const list = await lists.getList({ id: listId }); @@ -75,12 +69,7 @@ export const readListItemRoute = (router: ListsPluginRouter): void => { statusCode: 404, }); } else { - const [validated, errors] = validate(listItem, readListItemArrayResponse); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ body: validated ?? {} }); - } + return response.ok({ body: GetListItemResponse.parse(listItem) }); } } } else { diff --git a/x-pack/plugins/lists/server/routes/list_item/update_list_item_route.ts b/x-pack/plugins/lists/server/routes/list_item/update_list_item_route.ts index c627f9e9e95ea..8b2c4f65e54eb 100644 --- a/x-pack/plugins/lists/server/routes/list_item/update_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/list_item/update_list_item_route.ts @@ -5,13 +5,16 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { + UpdateListItemRequestBody, + UpdateListItemResponse, +} from '@kbn/securitysolution-lists-common/api'; import type { ListsPluginRouter } from '../../types'; -import { updateListItemRequest, updateListItemResponse } from '../../../common/api'; -import { buildRouteValidation, buildSiemResponse } from '../utils'; +import { buildSiemResponse } from '../utils'; import { getListClient } from '..'; export const updateListItemRoute = (router: ListsPluginRouter): void => { @@ -27,7 +30,7 @@ export const updateListItemRoute = (router: ListsPluginRouter): void => { { validate: { request: { - body: buildRouteValidation(updateListItemRequest), + body: buildRouteValidationWithZod(UpdateListItemRequestBody), }, }, version: '2023-10-31', @@ -59,12 +62,7 @@ export const updateListItemRoute = (router: ListsPluginRouter): void => { statusCode: 404, }); } else { - const [validated, errors] = validate(listItem, updateListItemResponse); - if (errors != null) { - return siemResponse.error({ body: errors, statusCode: 500 }); - } else { - return response.ok({ body: validated ?? {} }); - } + return response.ok({ body: UpdateListItemResponse.parse(listItem) }); } } catch (err) { const error = transformError(err); diff --git a/x-pack/plugins/lists/tsconfig.json b/x-pack/plugins/lists/tsconfig.json index b18887c254336..47dc15c00ec8b 100644 --- a/x-pack/plugins/lists/tsconfig.json +++ b/x-pack/plugins/lists/tsconfig.json @@ -1,15 +1,14 @@ - { "extends": "../../../tsconfig.base.json", "compilerOptions": { - "outDir": "target/types", + "outDir": "target/types" }, "include": [ "common/**/*", "public/**/*", "server/**/*", // have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636 - "server/**/*.json", + "server/**/*.json" ], "kbn_references": [ "@kbn/core", @@ -20,6 +19,7 @@ "@kbn/securitysolution-list-constants", "@kbn/securitysolution-list-hooks", "@kbn/securitysolution-list-api", + "@kbn/securitysolution-lists-common", "@kbn/kibana-react-plugin", "@kbn/i18n", "@kbn/data-plugin", @@ -39,8 +39,7 @@ "@kbn/utility-types", "@kbn/core-elasticsearch-client-server-mocks", "@kbn/core-saved-objects-server", + "@kbn/zod-helpers" ], - "exclude": [ - "target/**/*", - ] + "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/maps/common/embeddable/extract.test.ts b/x-pack/plugins/maps/common/embeddable/extract.test.ts index b7440d5f4a098..38e011b58a02b 100644 --- a/x-pack/plugins/maps/common/embeddable/extract.test.ts +++ b/x-pack/plugins/maps/common/embeddable/extract.test.ts @@ -13,7 +13,7 @@ test('Should return original state and empty references with by-reference embedd type: 'map', }; - expect(extract!(mapByReferenceInput)).toEqual({ + expect(extract(mapByReferenceInput)).toEqual({ state: mapByReferenceInput, references: [], }); @@ -29,7 +29,7 @@ test('Should update state with refNames with by-value embeddable state', () => { type: 'map', }; - expect(extract!(mapByValueInput)).toEqual({ + expect(extract(mapByValueInput)).toEqual({ references: [ { id: '90943e30-9a47-11e8-b64d-95841ca0b247', diff --git a/x-pack/plugins/maps/common/embeddable/extract.ts b/x-pack/plugins/maps/common/embeddable/extract.ts index 46ff3def8f0bc..8a6e26ecbcd1b 100644 --- a/x-pack/plugins/maps/common/embeddable/extract.ts +++ b/x-pack/plugins/maps/common/embeddable/extract.ts @@ -10,7 +10,7 @@ import { MapEmbeddablePersistableState } from './types'; import type { MapAttributes } from '../content_management'; import { extractReferences } from '../migrations/references'; -export const extract: EmbeddableRegistryDefinition['extract'] = (state) => { +export const extract: NonNullable = (state) => { const typedState = state as MapEmbeddablePersistableState; // by-reference embeddable diff --git a/x-pack/plugins/maps/common/embeddable/index.ts b/x-pack/plugins/maps/common/embeddable/index.ts index 16577df879fbc..f6cc8ea27f122 100644 --- a/x-pack/plugins/maps/common/embeddable/index.ts +++ b/x-pack/plugins/maps/common/embeddable/index.ts @@ -5,5 +5,6 @@ * 2.0. */ +export type { MapEmbeddablePersistableState } from './types'; export { extract } from './extract'; export { inject } from './inject'; diff --git a/x-pack/plugins/maps/common/embeddable/inject.test.ts b/x-pack/plugins/maps/common/embeddable/inject.test.ts index 2a9a162c948dd..0ee6e7bc0aac9 100644 --- a/x-pack/plugins/maps/common/embeddable/inject.test.ts +++ b/x-pack/plugins/maps/common/embeddable/inject.test.ts @@ -21,7 +21,7 @@ test('Should return original state with by-reference embeddable state', () => { }, ]; - expect(inject!(mapByReferenceInput, refernces)).toEqual(mapByReferenceInput); + expect(inject(mapByReferenceInput, refernces)).toEqual(mapByReferenceInput); }); test('Should inject refNames with by-value embeddable state', () => { @@ -41,7 +41,7 @@ test('Should inject refNames with by-value embeddable state', () => { }, ]; - expect(inject!(mapByValueInput, refernces)).toEqual({ + expect(inject(mapByValueInput, refernces)).toEqual({ id: '8d62c3f0-c61f-4c09-ac24-9b8ee4320e20', attributes: { layerListJSON: '[{"sourceDescriptor":{"indexPatternId":"changed_index_pattern_id"}}]', diff --git a/x-pack/plugins/maps/common/embeddable/inject.ts b/x-pack/plugins/maps/common/embeddable/inject.ts index 0fc0963e4be74..04fac94dca096 100644 --- a/x-pack/plugins/maps/common/embeddable/inject.ts +++ b/x-pack/plugins/maps/common/embeddable/inject.ts @@ -10,7 +10,7 @@ import type { MapEmbeddablePersistableState } from './types'; import type { MapAttributes } from '../content_management'; import { extractReferences, injectReferences } from '../migrations/references'; -export const inject: EmbeddableRegistryDefinition['inject'] = (state, references) => { +export const inject: NonNullable = (state, references) => { const typedState = state as MapEmbeddablePersistableState; // by-reference embeddable diff --git a/x-pack/plugins/maps/kibana.jsonc b/x-pack/plugins/maps/kibana.jsonc index 97b8d1b9cddc2..881d557e5e305 100644 --- a/x-pack/plugins/maps/kibana.jsonc +++ b/x-pack/plugins/maps/kibana.jsonc @@ -33,6 +33,7 @@ "optionalPlugins": [ "cloud", "customIntegrations", + "embeddableEnhanced", "home", "savedObjectsTagging", "charts", diff --git a/x-pack/plugins/maps/public/_index.scss b/x-pack/plugins/maps/public/_index.scss index e43ccb0e8679c..77541eb21d2e5 100644 --- a/x-pack/plugins/maps/public/_index.scss +++ b/x-pack/plugins/maps/public/_index.scss @@ -14,4 +14,4 @@ @import 'components/index'; @import 'classes/index'; @import 'animations'; -@import 'embeddable/index'; \ No newline at end of file +@import 'react_embeddable/index'; \ No newline at end of file diff --git a/x-pack/plugins/maps/public/actions/layer_actions.ts b/x-pack/plugins/maps/public/actions/layer_actions.ts index 9141a37f3eee8..c29376b162a4b 100644 --- a/x-pack/plugins/maps/public/actions/layer_actions.ts +++ b/x-pack/plugins/maps/public/actions/layer_actions.ts @@ -19,7 +19,6 @@ import { getLayerListRaw, getMapColors, getMapReady, - getMapSettings, getSelectedLayerId, } from '../selectors/map_selectors'; import { FLYOUT_STATE } from '../reducers/ui'; @@ -44,12 +43,7 @@ import { UPDATE_LAYER_STYLE, UPDATE_SOURCE_PROP, } from './map_action_constants'; -import { - autoFitToBounds, - clearDataRequests, - syncDataForLayerId, - updateStyleMeta, -} from './data_request_actions'; +import { clearDataRequests, syncDataForLayerId, updateStyleMeta } from './data_request_actions'; import { Attribution, JoinDescriptor, @@ -138,19 +132,10 @@ export function replaceLayerList(newLayerList: LayerDescriptor[]) { }; } -export function updateLayerById(layerDescriptor: LayerDescriptor) { - return async ( - dispatch: ThunkDispatch, - getState: () => MapStoreState - ) => { - dispatch({ - type: UPDATE_LAYER, - layer: layerDescriptor, - }); - await dispatch(syncDataForLayerId(layerDescriptor.id, false)); - if (getMapSettings(getState()).autoFitToDataBounds) { - dispatch(autoFitToBounds()); - } +export function updateLayerDescriptor(layerDescriptor: LayerDescriptor) { + return { + type: UPDATE_LAYER, + layer: layerDescriptor, }; } diff --git a/x-pack/plugins/maps/public/api/start_api.ts b/x-pack/plugins/maps/public/api/start_api.ts index 976a7ec4da084..fcf9dab69e40a 100644 --- a/x-pack/plugins/maps/public/api/start_api.ts +++ b/x-pack/plugins/maps/public/api/start_api.ts @@ -9,7 +9,7 @@ import type { LayerDescriptor } from '../../common/descriptor_types'; import type { CreateLayerDescriptorParams } from '../classes/sources/es_search_source'; import type { SampleValuesConfig, EMSTermJoinConfig } from '../ems_autosuggest'; import type { Props as PassiveMapProps } from '../lens/passive_map'; -import type { Props as MapProps } from '../embeddable/map_component'; +import type { Props as MapProps } from '../react_embeddable/map_renderer'; export interface MapsStartApi { createLayerDescriptors: { diff --git a/x-pack/plugins/maps/public/classes/layers/invalid_layer.ts b/x-pack/plugins/maps/public/classes/layers/invalid_layer.ts index eed2ab37b32e0..854227d4263f3 100644 --- a/x-pack/plugins/maps/public/classes/layers/invalid_layer.ts +++ b/x-pack/plugins/maps/public/classes/layers/invalid_layer.ts @@ -42,6 +42,10 @@ export class InvalidLayer extends AbstractLayer { }; } + isLayerLoading() { + return false; + } + hasErrors() { return true; } diff --git a/x-pack/plugins/maps/public/embeddable/map_api.ts b/x-pack/plugins/maps/public/embeddable/map_api.ts deleted file mode 100644 index fdf2bd215b324..0000000000000 --- a/x-pack/plugins/maps/public/embeddable/map_api.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { - HasParentApi, - HasType, - PublishesDataViews, - PublishesPanelTitle, - PublishesUnifiedSearch, -} from '@kbn/presentation-publishing'; -import { - apiIsOfType, - apiPublishesUnifiedSearch, - apiPublishesPanelTitle, -} from '@kbn/presentation-publishing'; -import type { ILayer } from '../classes/layers/layer'; - -export type MapApi = HasType<'map'> & { - getLayerList: () => ILayer[]; - reload: () => void; -} & PublishesDataViews & - PublishesPanelTitle & - PublishesUnifiedSearch & - Partial>; - -export const isMapApi = (api: unknown): api is MapApi => { - return Boolean( - api && - apiIsOfType(api, 'map') && - typeof (api as MapApi).getLayerList === 'function' && - apiPublishesPanelTitle(api) && - apiPublishesUnifiedSearch(api) - ); -}; diff --git a/x-pack/plugins/maps/public/embeddable/map_component.tsx b/x-pack/plugins/maps/public/embeddable/map_component.tsx deleted file mode 100644 index 9b4f56d9f3ab5..0000000000000 --- a/x-pack/plugins/maps/public/embeddable/map_component.tsx +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { Component, RefObject } from 'react'; -import { first } from 'rxjs'; -import { v4 as uuidv4 } from 'uuid'; -import type { Filter, Query, TimeRange } from '@kbn/es-query'; -import { ViewMode } from '@kbn/embeddable-plugin/public'; -import type { LayerDescriptor, MapCenterAndZoom, MapSettings } from '../../common/descriptor_types'; -import { MapEmbeddable } from './map_embeddable'; -import { createBasemapLayerDescriptor } from '../classes/layers/create_basemap_layer_descriptor'; -import { RenderToolTipContent } from '../classes/tooltips/tooltip_property'; -import { MapApi } from './map_api'; - -export interface Props { - title?: string; - filters?: Filter[]; - query?: Query; - timeRange?: TimeRange; - layerList: LayerDescriptor[]; - mapSettings?: Partial; - hideFilterActions?: boolean; - isLayerTOCOpen?: boolean; - mapCenter?: MapCenterAndZoom; - onInitialRenderComplete?: () => void; - getTooltipRenderer?: () => RenderToolTipContent; - onApiAvailable?: (api: MapApi) => void; - /* - * Set to false to exclude sharing attributes 'data-*'. - */ - isSharable?: boolean; -} - -export class MapComponent extends Component { - private _prevLayerList: LayerDescriptor[]; - private _mapEmbeddable: MapEmbeddable; - private readonly _embeddableRef: RefObject = React.createRef(); - - constructor(props: Props) { - super(props); - this._prevLayerList = this.props.layerList; - this._mapEmbeddable = new MapEmbeddable( - { - editable: false, - }, - { - id: uuidv4(), - attributes: { - title: this.props.title ?? '', - layerListJSON: JSON.stringify(this.getLayerList()), - }, - hidePanelTitles: !Boolean(this.props.title), - viewMode: ViewMode.VIEW, - isLayerTOCOpen: - typeof this.props.isLayerTOCOpen === 'boolean' ? this.props.isLayerTOCOpen : false, - hideFilterActions: - typeof this.props.hideFilterActions === 'boolean' ? this.props.hideFilterActions : false, - mapCenter: this.props.mapCenter, - mapSettings: this.props.mapSettings ?? {}, - } - ); - this._mapEmbeddable.updateInput({ - filters: this.props.filters, - query: this.props.query, - timeRange: this.props.timeRange, - }); - - if (this.props.getTooltipRenderer) { - this._mapEmbeddable.setRenderTooltipContent(this.props.getTooltipRenderer()); - } - if (this.props.onApiAvailable) { - this.props.onApiAvailable(this._mapEmbeddable as MapApi); - } - - if (this.props.onInitialRenderComplete) { - this._mapEmbeddable - .getOnRenderComplete$() - .pipe(first()) - .subscribe(() => { - if (this.props.onInitialRenderComplete) { - this.props.onInitialRenderComplete(); - } - }); - } - - if (this.props.isSharable !== undefined) { - this._mapEmbeddable.setIsSharable(this.props.isSharable); - } - } - - componentDidMount() { - if (this._embeddableRef.current) { - this._mapEmbeddable.render(this._embeddableRef.current); - } - } - - componentWillUnmount() { - this._mapEmbeddable.destroy(); - } - - componentDidUpdate() { - this._mapEmbeddable.updateInput({ - filters: this.props.filters, - query: this.props.query, - timeRange: this.props.timeRange, - }); - - if (this._prevLayerList !== this.props.layerList) { - this._mapEmbeddable.setLayerList(this.getLayerList()); - this._prevLayerList = this.props.layerList; - } - } - - getLayerList(): LayerDescriptor[] { - const basemapLayer = createBasemapLayerDescriptor(); - return basemapLayer ? [basemapLayer, ...this.props.layerList] : this.props.layerList; - } - - render() { - return
; - } -} diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.test.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.test.tsx deleted file mode 100644 index 3c8bc5ccc5282..0000000000000 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.test.tsx +++ /dev/null @@ -1,304 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { v4 as uuidv4 } from 'uuid'; -import { getControlledBy, MapEmbeddable } from './map_embeddable'; -import { buildExistsFilter, disableFilter, pinFilter, toggleFilterNegated } from '@kbn/es-query'; -import type { DataViewFieldBase, DataViewBase } from '@kbn/es-query'; -import { MapEmbeddableConfig, MapEmbeddableInput } from './types'; -import type { MapAttributes } from '../../common/content_management'; - -jest.mock('../kibana_services', () => { - return { - getExecutionContextService() { - return { - get: () => { - return {}; - }, - }; - }, - getHttp() { - return { - basePath: { - prepend: (url: string) => url, - }, - }; - }, - getMapsCapabilities() { - return { save: true }; - }, - getSearchService() { - return { - session: { - getSearchOptions() { - return undefined; - }, - }, - }; - }, - getShowMapsInspectorAdapter() { - return false; - }, - getTimeFilter() { - return { - getTime() { - return { from: 'now-7d', to: 'now' }; - }, - }; - }, - getEMSSettings() { - return { - isEMSUrlSet() { - return false; - }, - }; - }, - }; -}); - -jest.mock('../connected_components/map_container', () => { - return { - MapContainer: () => { - return
mockLayerTOC
; - }, - }; -}); - -jest.mock('../routes/map_page', () => { - class MockSavedMap { - // eslint-disable-next-line @typescript-eslint/no-var-requires - private _store = require('../reducers/store').createMapStore(); - private _attributes: MapAttributes = { - title: 'myMap', - }; - - whenReady = async function () {}; - - getStore() { - return this._store; - } - getAttributes() { - return this._attributes; - } - getAutoFitToBounds() { - return true; - } - getSharingSavedObjectProps() { - return null; - } - } - return { SavedMap: MockSavedMap }; -}); - -function untilInitialized(mapEmbeddable: MapEmbeddable): Promise { - return new Promise((resolve) => { - // @ts-expect-error setInitializationFinished is protected but we are overriding it to know when embeddable is initialized - mapEmbeddable.setInitializationFinished = () => { - resolve(); - }; - }); -} - -function onNextTick(): Promise { - // wait one tick to give observables time to fire - return new Promise((resolve) => setTimeout(resolve, 0)); -} - -describe('shouldFetch$', () => { - test('should not fetch when search context does not change', async () => { - const mapEmbeddable = new MapEmbeddable( - {} as unknown as MapEmbeddableConfig, - { - id: 'map1', - } as unknown as MapEmbeddableInput - ); - await untilInitialized(mapEmbeddable); - - const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); - - mapEmbeddable.updateInput({ - title: 'updated map title', - }); - - await onNextTick(); - - expect(fetchSpy).not.toHaveBeenCalled(); - }); - - describe('on filters change', () => { - test('should fetch on filter change', async () => { - const existsFilter = buildExistsFilter( - { - name: 'myFieldName', - } as DataViewFieldBase, - { - id: 'myDataViewId', - } as DataViewBase - ); - const mapEmbeddable = new MapEmbeddable( - {} as unknown as MapEmbeddableConfig, - { - id: 'map1', - filters: [existsFilter], - } as unknown as MapEmbeddableInput - ); - await untilInitialized(mapEmbeddable); - - const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); - - mapEmbeddable.updateInput({ - filters: [toggleFilterNegated(existsFilter)], - }); - - await onNextTick(); - - expect(fetchSpy).toHaveBeenCalled(); - }); - - test('should not fetch on disabled filter change', async () => { - const disabledFilter = disableFilter( - buildExistsFilter( - { - name: 'myFieldName', - } as DataViewFieldBase, - { - id: 'myDataViewId', - } as DataViewBase - ) - ); - const mapEmbeddable = new MapEmbeddable( - {} as unknown as MapEmbeddableConfig, - { - id: 'map1', - filters: [disabledFilter], - } as unknown as MapEmbeddableInput - ); - await untilInitialized(mapEmbeddable); - - const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); - - mapEmbeddable.updateInput({ - filters: [toggleFilterNegated(disabledFilter)], - }); - - await onNextTick(); - - expect(fetchSpy).not.toHaveBeenCalled(); - }); - - test('should not fetch when unpinned filter is pinned', async () => { - const unpinnedFilter = buildExistsFilter( - { - name: 'myFieldName', - } as DataViewFieldBase, - { - id: 'myDataViewId', - } as DataViewBase - ); - const mapEmbeddable = new MapEmbeddable( - {} as unknown as MapEmbeddableConfig, - { - id: 'map1', - filters: [unpinnedFilter], - } as unknown as MapEmbeddableInput - ); - await untilInitialized(mapEmbeddable); - - const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); - - mapEmbeddable.updateInput({ - filters: [pinFilter(unpinnedFilter)], - }); - - await onNextTick(); - - expect(fetchSpy).not.toHaveBeenCalled(); - }); - - test('should not fetch on filter controlled by map embeddable change', async () => { - const embeddableId = 'map1'; - const filter = buildExistsFilter( - { - name: 'myFieldName', - } as DataViewFieldBase, - { - id: 'myDataViewId', - } as DataViewBase - ); - const controlledByFilter = { - ...filter, - meta: { - ...filter.meta, - controlledBy: getControlledBy(embeddableId), - }, - }; - const mapEmbeddable = new MapEmbeddable( - {} as unknown as MapEmbeddableConfig, - { - id: embeddableId, - filters: [controlledByFilter], - } as unknown as MapEmbeddableInput - ); - await untilInitialized(mapEmbeddable); - - const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); - - mapEmbeddable.updateInput({ - filters: [toggleFilterNegated(controlledByFilter)], - }); - - await onNextTick(); - - expect(fetchSpy).not.toHaveBeenCalled(); - }); - }); - - describe('on searchSessionId change', () => { - test('should fetch when filterByMapExtent is false', async () => { - const mapEmbeddable = new MapEmbeddable( - {} as unknown as MapEmbeddableConfig, - { - id: 'map1', - filterByMapExtent: false, - } as unknown as MapEmbeddableInput - ); - await untilInitialized(mapEmbeddable); - - const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); - - mapEmbeddable.updateInput({ - searchSessionId: uuidv4(), - }); - - await onNextTick(); - - expect(fetchSpy).toHaveBeenCalled(); - }); - - test('should not fetch when filterByMapExtent is true', async () => { - const mapEmbeddable = new MapEmbeddable( - {} as unknown as MapEmbeddableConfig, - { - id: 'map1', - filterByMapExtent: true, - } as unknown as MapEmbeddableInput - ); - await untilInitialized(mapEmbeddable); - - const fetchSpy = jest.spyOn(mapEmbeddable, '_dispatchSetQuery'); - - mapEmbeddable.updateInput({ - searchSessionId: uuidv4(), - }); - - await onNextTick(); - - expect(fetchSpy).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx deleted file mode 100644 index 7fb9543bfb48d..0000000000000 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ /dev/null @@ -1,878 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 _ from 'lodash'; -import React from 'react'; -import { Provider } from 'react-redux'; -import fastIsEqual from 'fast-deep-equal'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { Subscription } from 'rxjs'; -import { - debounceTime, - distinctUntilChanged, - filter as filterOperator, - map, - skip, - startWith, -} from 'rxjs'; -import { Unsubscribe } from 'redux'; -import type { PaletteRegistry } from '@kbn/coloring'; -import type { KibanaExecutionContext } from '@kbn/core/public'; -import { EuiEmptyPrompt } from '@elastic/eui'; -import { Query, type Filter } from '@kbn/es-query'; -import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; -import { - Embeddable, - IContainer, - ReferenceOrValueEmbeddable, - genericEmbeddableInputIsEqual, - VALUE_CLICK_TRIGGER, - omitGenericEmbeddableInput, - FilterableEmbeddable, - shouldFetch$, -} from '@kbn/embeddable-plugin/public'; -import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; -import { APPLY_FILTER_TRIGGER } from '@kbn/data-plugin/public'; -import { ACTION_GLOBAL_APPLY_FILTER } from '@kbn/unified-search-plugin/public'; -import { createExtentFilter } from '../../common/elasticsearch_util'; -import { - replaceLayerList, - setMapSettings, - setQuery, - setReadOnly, - updateLayerById, - setGotoWithCenter, - setEmbeddableSearchContext, - setExecutionContext, -} from '../actions'; -import { getIsLayerTOCOpen, getOpenTOCDetails } from '../selectors/ui_selectors'; -import { - getInspectorAdapters, - setChartsPaletteServiceGetColor, - setEventHandlers, - setOnMapMove, - EventHandlers, -} from '../reducers/non_serializable_instances'; -import { - isMapLoading, - getGeoFieldNames, - getEmbeddableSearchContext, - getLayerList, - getGoto, - getMapCenter, - getMapBuffer, - getMapExtent, - getMapReady, - getMapSettings, - getMapZoom, - getHiddenLayerIds, - getQueryableUniqueIndexPatternIds, -} from '../selectors/map_selectors'; -import { - APP_ID, - getEditPath, - getFullPath, - MAP_EMBEDDABLE_NAME, - MAP_SAVED_OBJECT_TYPE, - RawValue, - RENDER_TIMEOUT, -} from '../../common/constants'; -import { RenderToolTipContent } from '../classes/tooltips/tooltip_property'; -import { - getAnalytics, - getCharts, - getCoreI18n, - getCoreOverlays, - getExecutionContextService, - getHttp, - getSearchService, - getSpacesApi, - getTheme, - getUiActions, -} from '../kibana_services'; -import { LayerDescriptor, MapExtent } from '../../common/descriptor_types'; -import { extractReferences } from '../../common/migrations/references'; -import { MapContainer } from '../connected_components/map_container'; -import { SavedMap } from '../routes/map_page'; -import { getIndexPatternsFromIds } from '../index_pattern_util'; -import { getMapAttributeService } from '../map_attribute_service'; -import { isUrlDrilldown, toValueClickDataFormat } from '../trigger_actions/trigger_utils'; -import { waitUntilTimeLayersLoad$ } from '../routes/map_page/map_app/wait_until_time_layers_load'; -import { mapEmbeddablesSingleton } from './map_embeddables_singleton'; -import { getGeoFieldsLabel } from './get_geo_fields_label'; -import { checkForDuplicateTitle, getMapClient } from '../content_management'; - -import { - MapByValueInput, - MapByReferenceInput, - MapEmbeddableConfig, - MapEmbeddableInput, - MapEmbeddableOutput, -} from './types'; - -async function getChartsPaletteServiceGetColor(): Promise<((value: string) => string) | null> { - const chartsService = getCharts(); - const paletteRegistry: PaletteRegistry | null = chartsService - ? await chartsService.palettes.getPalettes() - : null; - if (!paletteRegistry) { - return null; - } - - const paletteDefinition = paletteRegistry.get('default'); - const chartConfiguration = { syncColors: true }; - return (value: string) => { - const series = [{ name: value, rankAtDepth: 0, totalSeriesAtDepth: 1 }]; - const color = paletteDefinition.getCategoricalColor(series, chartConfiguration); - return color ? color : '#3d3d3d'; - }; -} - -function getIsRestore(searchSessionId?: string) { - if (!searchSessionId) { - return false; - } - const searchSessionOptions = getSearchService().session.getSearchOptions(searchSessionId); - return searchSessionOptions ? searchSessionOptions.isRestore : false; -} - -export function getControlledBy(id: string) { - return `mapEmbeddablePanel${id}`; -} - -export class MapEmbeddable - extends Embeddable - implements ReferenceOrValueEmbeddable, FilterableEmbeddable -{ - type = MAP_SAVED_OBJECT_TYPE; - deferEmbeddableLoad = true; - - private _isActive: boolean; - private _savedMap: SavedMap; - private _renderTooltipContent?: RenderToolTipContent; - private _subscriptions: Subscription[] = []; - private _prevIsRestore: boolean = false; - private _prevMapExtent?: MapExtent; - private _prevSyncColors?: boolean; - private _domNode?: HTMLElement; - private _unsubscribeFromStore?: Unsubscribe; - private _isInitialized = false; - private _controlledBy: string; - private _isSharable = true; - private readonly _onRenderComplete$; - - constructor(config: MapEmbeddableConfig, initialInput: MapEmbeddableInput, parent?: IContainer) { - super( - initialInput, - { - editApp: APP_ID, - editable: config.editable, - indexPatterns: [], - }, - parent - ); - - this._isActive = true; - this._savedMap = new SavedMap({ mapEmbeddableInput: initialInput }); - this._initializeSaveMap(); - this._subscriptions.push(this.getUpdated$().subscribe(() => this.onUpdate())); - this._controlledBy = getControlledBy(this.id); - - this._onRenderComplete$ = this.getOutput$().pipe( - // wrapping distinctUntilChanged with startWith and skip to prime distinctUntilChanged with an initial value. - startWith(this.getOutput()), - distinctUntilChanged((a, b) => a.loading === b.loading), - skip(1), - debounceTime(RENDER_TIMEOUT), - filterOperator((output) => !output.loading), - map(() => { - // Observable notifies subscriber when rendering is complete - // Return void to not expose internal implemenation details of observabale - return; - }) - ); - } - - public getOnRenderComplete$() { - return this._onRenderComplete$; - } - - public reportsEmbeddableLoad() { - return true; - } - - private async _initializeSaveMap() { - try { - await this._savedMap.whenReady(); - } catch (e) { - this.onFatalError(e); - return; - } - this._initializeStore(); - try { - await this._initializeOutput(); - } catch (e) { - this.onFatalError(e); - return; - } - - this._savedMap.getStore().dispatch(setExecutionContext(this.getExecutionContext())); - - // deferred loading of this embeddable is complete - this.setInitializationFinished(); - - this._isInitialized = true; - if (this._domNode) { - this.render(this._domNode); - } - } - - private getExecutionContext() { - const parentContext = getExecutionContextService().get(); - const mapContext: KibanaExecutionContext = { - type: APP_ID, - name: APP_ID, - id: this.id, - url: this.output.editPath, - }; - - return parentContext - ? { - ...parentContext, - child: mapContext, - } - : mapContext; - } - - private _initializeStore() { - this._dispatchSetChartsPaletteServiceGetColor(this.input.syncColors); - - const store = this._savedMap.getStore(); - - store.dispatch(setReadOnly(true)); - store.dispatch( - setMapSettings({ - keydownScrollZoom: true, - showTimesliderToggleButton: false, - }) - ); - - // Passing callback into redux store instead of regular pattern of getting redux state changes for performance reasons - store.dispatch(setOnMapMove(this._propogateMapMovement)); - - this._dispatchSetQuery({ forceRefresh: false }); - this._subscriptions.push( - shouldFetch$(this.getUpdated$(), () => { - return { - ...this.getInput(), - filters: this._getInputFilters(), - searchSessionId: this._getSearchSessionId(), - }; - }).subscribe(() => { - this._dispatchSetQuery({ - forceRefresh: false, - }); - }) - ); - - const mapStateJSON = this._savedMap.getAttributes().mapStateJSON; - if (mapStateJSON) { - try { - const mapState = JSON.parse(mapStateJSON); - store.dispatch( - setEmbeddableSearchContext({ - filters: mapState.filters ? mapState.filters : [], - query: mapState.query, - }) - ); - } catch (e) { - // ignore malformed mapStateJSON, not a critical error for viewing map - map will just use defaults - } - } - - this._unsubscribeFromStore = store.subscribe(() => { - this._handleStoreChanges(); - }); - } - - private async _initializeOutput() { - const { title: savedMapTitle, description: savedMapDescription } = - this._savedMap.getAttributes(); - const input = this.getInput(); - const title = input.hidePanelTitles ? '' : input.title ?? savedMapTitle; - const savedObjectId = 'savedObjectId' in input ? input.savedObjectId : undefined; - this.updateOutput({ - defaultTitle: savedMapTitle, - defaultDescription: savedMapDescription, - title, - editPath: getEditPath(savedObjectId), - editUrl: getHttp().basePath.prepend(getFullPath(savedObjectId)), - indexPatterns: await this._getIndexPatterns(), - }); - } - - public inputIsRefType( - input: MapByValueInput | MapByReferenceInput - ): input is MapByReferenceInput { - return getMapAttributeService().inputIsRefType(input); - } - - public async getInputAsRefType(): Promise { - return getMapAttributeService().getInputAsRefType(this.getExplicitInput(), { - showSaveModal: true, - saveModalTitle: this.getTitle(), - }); - } - - public async getExplicitInputIsEqual( - lastExplicitInput: Partial - ): Promise { - const currentExplicitInput = this.getExplicitInput(); - if (!genericEmbeddableInputIsEqual(lastExplicitInput, currentExplicitInput)) return false; - - // generic embeddable input is equal, now we compare map specific input elements, ignoring 'mapBuffer'. - const lastMapInput = omitGenericEmbeddableInput(_.omit(lastExplicitInput, 'mapBuffer')); - const currentMapInput = omitGenericEmbeddableInput(_.omit(currentExplicitInput, 'mapBuffer')); - return fastIsEqual(lastMapInput, currentMapInput); - } - - public async getInputAsValueType(): Promise { - return getMapAttributeService().getInputAsValueType(this.getExplicitInput()); - } - - public getLayerList() { - return getLayerList(this._savedMap.getStore().getState()); - } - - public getFilters() { - const embeddableSearchContext = getEmbeddableSearchContext( - this._savedMap.getStore().getState() - ); - return embeddableSearchContext ? embeddableSearchContext.filters : []; - } - - public getQuery(): Query | undefined { - const embeddableSearchContext = getEmbeddableSearchContext( - this._savedMap.getStore().getState() - ); - return embeddableSearchContext?.query; - } - - public supportedTriggers(): string[] { - return [APPLY_FILTER_TRIGGER, VALUE_CLICK_TRIGGER]; - } - - setRenderTooltipContent = (renderTooltipContent: RenderToolTipContent) => { - this._renderTooltipContent = renderTooltipContent; - }; - - setEventHandlers = (eventHandlers: EventHandlers) => { - this._savedMap.getStore().dispatch(setEventHandlers(eventHandlers)); - }; - - /* - * Set to false to exclude sharing attributes 'data-*'. - */ - public setIsSharable(isSharable: boolean): void { - this._isSharable = isSharable; - } - - getInspectorAdapters() { - return getInspectorAdapters(this._savedMap.getStore().getState()); - } - - onUpdate() { - if (this.input.syncColors !== this._prevSyncColors) { - this._dispatchSetChartsPaletteServiceGetColor(this.input.syncColors); - } - - const isRestore = getIsRestore(this._getSearchSessionId()); - if (isRestore !== this._prevIsRestore) { - this._prevIsRestore = isRestore; - this._savedMap.getStore().dispatch( - setMapSettings({ - disableInteractive: isRestore, - hideToolbarOverlay: isRestore, - }) - ); - } - } - - _getIsMovementSynchronized = () => { - return this.input.isMovementSynchronized === undefined - ? true - : this.input.isMovementSynchronized; - }; - - _getIsFilterByMapExtent = () => { - return this.input.filterByMapExtent === undefined ? false : this.input.filterByMapExtent; - }; - - _gotoSynchronizedLocation() { - const syncedLocation = mapEmbeddablesSingleton.getLocation(); - if (syncedLocation) { - // set map to synchronized view - this._mapSyncHandler(syncedLocation.lat, syncedLocation.lon, syncedLocation.zoom); - return; - } - - if (!getMapReady(this._savedMap.getStore().getState())) { - // Initialize synchronized view to map's goto - // Use goto because un-rendered map will not have accurate mapCenter and mapZoom. - const goto = getGoto(this._savedMap.getStore().getState()); - if (goto && goto.center) { - mapEmbeddablesSingleton.setLocation( - this.input.id, - goto.center.lat, - goto.center.lon, - goto.center.zoom - ); - return; - } - } - - // Initialize synchronized view to map's view - const center = getMapCenter(this._savedMap.getStore().getState()); - const zoom = getMapZoom(this._savedMap.getStore().getState()); - mapEmbeddablesSingleton.setLocation(this.input.id, center.lat, center.lon, zoom); - } - - _propogateMapMovement = (lat: number, lon: number, zoom: number) => { - if (this._getIsMovementSynchronized()) { - mapEmbeddablesSingleton.setLocation(this.input.id, lat, lon, zoom); - } - }; - - _getInputFilters() { - return this.input.filters - ? this.input.filters.filter( - (filter) => !filter.meta.disabled && filter.meta.controlledBy !== this._controlledBy - ) - : []; - } - - _getSearchSessionId() { - // New search session id causes all layers from elasticsearch to refetch data. - // Dashboard provides a new search session id anytime filters change. - // Thus, filtering embeddable container by map extent causes a new search session id any time the map is moved. - // Disabling search session when filtering embeddable container by map extent. - // The use case for search sessions (restoring results because of slow responses) does not match the use case of - // filtering by map extent (rapid responses as users explore their map). - return this.input.filterByMapExtent ? undefined : this.input.searchSessionId; - } - - _dispatchSetQuery({ forceRefresh }: { forceRefresh: boolean }) { - this._savedMap.getStore().dispatch( - setQuery({ - filters: this._getInputFilters(), - query: this.input.query, - timeFilters: this.input.timeRange, - timeslice: this.input.timeslice - ? { from: this.input.timeslice[0], to: this.input.timeslice[1] } - : undefined, - clearTimeslice: this.input.timeslice === undefined, - forceRefresh, - searchSessionId: this._getSearchSessionId(), - searchSessionMapBuffer: getIsRestore(this._getSearchSessionId()) - ? this.input.mapBuffer - : undefined, - }) - ); - } - - async _dispatchSetChartsPaletteServiceGetColor(syncColors?: boolean) { - this._prevSyncColors = syncColors; - const chartsPaletteServiceGetColor = syncColors - ? await getChartsPaletteServiceGetColor() - : null; - if (syncColors !== this._prevSyncColors) { - return; - } - this._savedMap - .getStore() - .dispatch(setChartsPaletteServiceGetColor(chartsPaletteServiceGetColor)); - } - - /** - * - * @param {HTMLElement} domNode - * @param {ContainerState} containerState - */ - render(domNode: HTMLElement) { - this._domNode = domNode; - if (!this._isInitialized) { - return; - } - - mapEmbeddablesSingleton.register(this.input.id, { - getTitle: () => { - const output = this.getOutput(); - if (output.title) { - return output.title; - } - - if (output.defaultTitle) { - return output.defaultTitle; - } - - return this.input.id; - }, - onLocationChange: this._mapSyncHandler, - getIsMovementSynchronized: this._getIsMovementSynchronized, - setIsMovementSynchronized: (isMovementSynchronized: boolean) => { - this.updateInput({ isMovementSynchronized }); - if (isMovementSynchronized) { - this._gotoSynchronizedLocation(); - } else if (!isMovementSynchronized && this._savedMap.getAutoFitToBounds()) { - // restore autoFitToBounds when isMovementSynchronized disabled - this._savedMap.getStore().dispatch(setMapSettings({ autoFitToDataBounds: true })); - } - }, - getIsFilterByMapExtent: this._getIsFilterByMapExtent, - setIsFilterByMapExtent: (isFilterByMapExtent: boolean) => { - this.updateInput({ filterByMapExtent: isFilterByMapExtent }); - if (isFilterByMapExtent) { - this._setMapExtentFilter(); - } else { - this._clearMapExtentFilter(); - } - }, - getGeoFieldNames: () => { - return getGeoFieldNames(this._savedMap.getStore().getState()); - }, - }); - if (this._getIsMovementSynchronized()) { - this._gotoSynchronizedLocation(); - } - - const sharingSavedObjectProps = this._savedMap.getSharingSavedObjectProps(); - const spaces = getSpacesApi(); - const content = - sharingSavedObjectProps && spaces && sharingSavedObjectProps?.outcome === 'conflict' ? ( -
- -
- ) : ( - - ); - - render( - - {content} - , - this._domNode - ); - } - - setLayerList(layerList: LayerDescriptor[]) { - this._savedMap.getStore().dispatch(replaceLayerList(layerList)); - this._getIndexPatterns().then((indexPatterns) => { - this.updateOutput({ - indexPatterns, - }); - }); - } - - updateLayerById(layerDescriptor: LayerDescriptor) { - this._savedMap.getStore().dispatch(updateLayerById(layerDescriptor)); - } - - private async _getIndexPatterns() { - const queryableIndexPatternIds = getQueryableUniqueIndexPatternIds( - this._savedMap.getStore().getState() - ); - return await getIndexPatternsFromIds(queryableIndexPatternIds); - } - - onSingleValueTrigger = (actionId: string, key: string, value: RawValue) => { - const action = getUiActions().getAction(actionId); - if (!action) { - throw new Error('Unable to apply action, could not locate action'); - } - const executeContext = { - ...this.getActionContext(), - data: { - data: toValueClickDataFormat(key, value), - }, - }; - action.execute(executeContext); - }; - - addFilters = async (filters: Filter[], actionId: string = ACTION_GLOBAL_APPLY_FILTER) => { - const executeContext = { - ...this.getActionContext(), - filters, - }; - const action = getUiActions().getAction(actionId); - if (!action) { - throw new Error('Unable to apply filter, could not locate action'); - } - action.execute(executeContext); - }; - - getFilterActions = async () => { - const filterActions = await getUiActions().getTriggerCompatibleActions(APPLY_FILTER_TRIGGER, { - embeddable: this, - filters: [], - }); - const valueClickActions = await getUiActions().getTriggerCompatibleActions( - VALUE_CLICK_TRIGGER, - { - embeddable: this, - data: { - // uiActions.getTriggerCompatibleActions validates action with provided context - // so if event.key and event.value are used in the URL template but can not be parsed from context - // then the action is filtered out. - // To prevent filtering out actions, provide dummy context when initially fetching actions. - data: toValueClickDataFormat('anyfield', 'anyvalue'), - }, - } - ); - return [...filterActions, ...valueClickActions.filter(isUrlDrilldown)]; - }; - - getActionContext = () => { - const trigger = getUiActions().getTrigger(APPLY_FILTER_TRIGGER); - if (!trigger) { - throw new Error('Unable to get context, could not locate trigger'); - } - return { - embeddable: this, - trigger, - } as ActionExecutionContext; - }; - - // remove legacy library tranform methods - linkToLibrary = undefined; - unlinkFromLibrary = undefined; - // add implemenation for library transform methods - checkForDuplicateTitle = async ( - newTitle: string, - isTitleDuplicateConfirmed: boolean, - onTitleDuplicate: () => void - ) => { - await checkForDuplicateTitle( - { - title: newTitle, - copyOnSave: false, - lastSavedTitle: '', - isTitleDuplicateConfirmed, - getDisplayName: () => MAP_EMBEDDABLE_NAME, - onTitleDuplicate, - }, - { - overlays: getCoreOverlays(), - } - ); - }; - saveToLibrary = async (title: string) => { - const { attributes, references } = extractReferences({ - attributes: this._savedMap.getAttributes(), - }); - - const { - item: { id: savedObjectId }, - } = await getMapClient().create({ - data: { - ...attributes, - title, - }, - options: { references }, - }); - return savedObjectId; - }; - getByReferenceState = (libraryId: string) => { - return { - ..._.omit(this.getExplicitInput(), 'attributes'), - savedObjectId: libraryId, - }; - }; - getByValueState = () => { - return { - ..._.omit(this.getExplicitInput(), 'savedObjectId'), - attributes: this._savedMap.getAttributes(), - }; - }; - - // Timing bug for dashboard with multiple maps with synchronized movement and filter by map extent enabled - // When moving map with filterByMapExtent:false, previous map extent filter(s) does not get removed - // Cuased by syncDashboardContainerInput applyContainerChangesToState. - // 1) _setMapExtentFilter executes ACTION_GLOBAL_APPLY_FILTER action, - // removing previous map extent filter and adding new map extent filter - // 2) applyContainerChangesToState then re-adds stale input.filters (which contains previous map extent filter) - // Add debounce to fix timing issue. - // 1) applyContainerChangesToState now runs first and does its thing - // 2) _setMapExtentFilter executes ACTION_GLOBAL_APPLY_FILTER action, - // removing previous map extent filter and adding new map extent filter - _setMapExtentFilter = _.debounce(() => { - const mapExtent = getMapExtent(this._savedMap.getStore().getState()); - const geoFieldNames = mapEmbeddablesSingleton.getGeoFieldNames(); - - if (mapExtent === undefined || geoFieldNames.length === 0) { - return; - } - - this._prevMapExtent = mapExtent; - - const mapExtentFilter = createExtentFilter(mapExtent, geoFieldNames); - mapExtentFilter.meta.controlledBy = this._controlledBy; - mapExtentFilter.meta.alias = i18n.translate('xpack.maps.embeddable.boundsFilterLabel', { - defaultMessage: '{geoFieldsLabel} within map bounds', - values: { geoFieldsLabel: getGeoFieldsLabel(geoFieldNames) }, - }); - - const executeContext = { - ...this.getActionContext(), - filters: [mapExtentFilter], - controlledBy: this._controlledBy, - }; - const action = getUiActions().getAction(ACTION_GLOBAL_APPLY_FILTER); - if (!action) { - throw new Error('Unable to apply map extent filter, could not locate action'); - } - action.execute(executeContext); - }, 100); - - _clearMapExtentFilter() { - this._prevMapExtent = undefined; - const executeContext = { - ...this.getActionContext(), - filters: [], - controlledBy: this._controlledBy, - }; - const action = getUiActions().getAction(ACTION_GLOBAL_APPLY_FILTER); - if (!action) { - throw new Error('Unable to apply map extent filter, could not locate action'); - } - action.execute(executeContext); - } - - destroy() { - super.destroy(); - mapEmbeddablesSingleton.unregister(this.input.id); - this._isActive = false; - if (this._unsubscribeFromStore) { - this._unsubscribeFromStore(); - } - - if (this._domNode) { - unmountComponentAtNode(this._domNode); - } - - this._subscriptions.forEach((subscription) => { - subscription.unsubscribe(); - }); - } - - reload() { - this._dispatchSetQuery({ - forceRefresh: true, - }); - } - - _mapSyncHandler = (lat: number, lon: number, zoom: number) => { - // auto fit to bounds is not compatable with map synchronization - // auto fit to bounds may cause map location to never stablize and bound back and forth between bounds on different maps - if (getMapSettings(this._savedMap.getStore().getState()).autoFitToDataBounds) { - this._savedMap.getStore().dispatch(setMapSettings({ autoFitToDataBounds: false })); - } - this._savedMap.getStore().dispatch(setGotoWithCenter({ lat, lon, zoom })); - }; - - _handleStoreChanges() { - if (!this._isActive || !getMapReady(this._savedMap.getStore().getState())) { - return; - } - - const mapExtent = getMapExtent(this._savedMap.getStore().getState()); - if (this._getIsFilterByMapExtent() && !_.isEqual(this._prevMapExtent, mapExtent)) { - this._setMapExtentFilter(); - } - - const center = getMapCenter(this._savedMap.getStore().getState()); - const zoom = getMapZoom(this._savedMap.getStore().getState()); - - const mapCenter = this.input.mapCenter || undefined; - if ( - !mapCenter || - mapCenter.lat !== center.lat || - mapCenter.lon !== center.lon || - mapCenter.zoom !== zoom - ) { - this.updateInput({ - mapCenter: { - lat: center.lat, - lon: center.lon, - zoom, - }, - mapBuffer: getMapBuffer(this._savedMap.getStore().getState()), - }); - } - - const isLayerTOCOpen = getIsLayerTOCOpen(this._savedMap.getStore().getState()); - if (this.input.isLayerTOCOpen !== isLayerTOCOpen) { - this.updateInput({ - isLayerTOCOpen, - }); - } - - const openTOCDetails = getOpenTOCDetails(this._savedMap.getStore().getState()); - if (!_.isEqual(this.input.openTOCDetails, openTOCDetails)) { - this.updateInput({ - openTOCDetails, - }); - } - - const hiddenLayerIds = getHiddenLayerIds(this._savedMap.getStore().getState()); - if (!_.isEqual(this.input.hiddenLayers, hiddenLayerIds)) { - this.updateInput({ - hiddenLayers: hiddenLayerIds, - }); - } - - const isLoading = isMapLoading(this._savedMap.getStore().getState()); - if (this.getOutput().loading !== isLoading) { - /** - * Maps emit rendered when the data is loaded, as we don't have feedback from the maps rendering library atm. - * This means that the DASHBOARD_LOADED_EVENT event might be fired while a map is still rendering in some cases. - * For more details please contact the maps team. - */ - this.updateOutput({ - loading: isLoading, - rendered: !isLoading, - // do not surface layer errors as output.error - // output.error blocks entire embeddable display and prevents map from displaying - // layer errors are better surfaced in legend while still keeping the map usable - }); - } - } -} diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.ts b/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.ts deleted file mode 100644 index 102d45f092e62..0000000000000 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable_factory.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { first } from 'rxjs'; -import { i18n } from '@kbn/i18n'; -import { EmbeddableFactoryDefinition, IContainer } from '@kbn/embeddable-plugin/public'; -import { MAP_SAVED_OBJECT_TYPE, APP_ICON, MAP_EMBEDDABLE_NAME } from '../../common/constants'; -import { extract, inject } from '../../common/embeddable'; -import { MapByReferenceInput, MapEmbeddableInput } from './types'; -import { getApplication, getMapsCapabilities, getUsageCollection } from '../kibana_services'; - -export class MapEmbeddableFactory implements EmbeddableFactoryDefinition { - type = MAP_SAVED_OBJECT_TYPE; - savedObjectMetaData = { - name: i18n.translate('xpack.maps.mapSavedObjectLabel', { - defaultMessage: 'Map', - }), - type: MAP_SAVED_OBJECT_TYPE, - getIconForSavedObject: () => APP_ICON, - }; - - async isEditable() { - return getMapsCapabilities().save as boolean; - } - - // Not supported yet for maps types. - canCreateNew() { - return false; - } - - getDisplayName() { - return MAP_EMBEDDABLE_NAME; - } - - createFromSavedObject = async ( - savedObjectId: string, - input: MapEmbeddableInput, - parent?: IContainer - ) => { - if (!(input as MapByReferenceInput).savedObjectId) { - (input as MapByReferenceInput).savedObjectId = savedObjectId; - } - return this.create(input, parent); - }; - - create = async (input: MapEmbeddableInput, parent?: IContainer) => { - const { MapEmbeddable } = await import('./map_embeddable'); - const usageCollection = getUsageCollection(); - if (usageCollection) { - // currentAppId$ is a BehaviorSubject exposed as an observable so subscription gets last value upon subscribe - getApplication() - .currentAppId$.pipe(first()) - .subscribe((appId) => { - if (appId) usageCollection.reportUiCounter('map', 'loaded', `open_maps_vis_${appId}`); - }); - } - return new MapEmbeddable( - { - editable: await this.isEditable(), - }, - input, - parent - ); - }; - - inject = inject; - - extract = extract; -} diff --git a/x-pack/plugins/maps/public/embeddable/types.ts b/x-pack/plugins/maps/public/embeddable/types.ts deleted file mode 100644 index 95b37b32048b2..0000000000000 --- a/x-pack/plugins/maps/public/embeddable/types.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { Observable } from 'rxjs'; -import type { DataView } from '@kbn/data-plugin/common'; -import { - Embeddable, - EmbeddableInput, - EmbeddableOutput, - SavedObjectEmbeddableInput, -} from '@kbn/embeddable-plugin/public'; -import type { Filter, Query, TimeRange } from '@kbn/es-query'; -import { MapCenterAndZoom, MapExtent, MapSettings } from '../../common/descriptor_types'; -import type { MapAttributes } from '../../common/content_management'; - -export interface MapEmbeddableConfig { - editable: boolean; -} - -interface MapEmbeddableState { - isLayerTOCOpen?: boolean; - openTOCDetails?: string[]; - mapCenter?: MapCenterAndZoom; - mapBuffer?: MapExtent; - mapSettings?: Partial; - hiddenLayers?: string[]; - hideFilterActions?: boolean; - filters?: Filter[]; - query?: Query; - timeRange?: TimeRange; - timeslice?: [number, number]; - filterByMapExtent?: boolean; - isMovementSynchronized?: boolean; -} -export type MapByValueInput = { - attributes: MapAttributes; -} & EmbeddableInput & - MapEmbeddableState; -export type MapByReferenceInput = SavedObjectEmbeddableInput & MapEmbeddableState; -export type MapEmbeddableInput = MapByValueInput | MapByReferenceInput; - -export type MapEmbeddableOutput = EmbeddableOutput & { - indexPatterns: DataView[]; -}; - -export type MapEmbeddableType = Embeddable & { - getOnRenderComplete$(): Observable; - setIsSharable(isSharable: boolean): void; -}; diff --git a/x-pack/plugins/maps/public/index.ts b/x-pack/plugins/maps/public/index.ts index 4f8a1090436fe..fc6c3b0fc7616 100644 --- a/x-pack/plugins/maps/public/index.ts +++ b/x-pack/plugins/maps/public/index.ts @@ -28,8 +28,7 @@ export type { export type { MapsSetupApi, MapsStartApi } from './api'; export type { CreateLayerDescriptorParams } from './classes/sources/es_search_source/create_layer_descriptor'; -export type { MapEmbeddable, MapEmbeddableInput, MapEmbeddableOutput } from './embeddable'; -export { type MapApi, isMapApi } from './embeddable/map_api'; +export { type MapApi, type MapSerializedState, isMapApi } from './react_embeddable/types'; export type { EMSTermJoinConfig, SampleValuesConfig } from './ems_autosuggest'; diff --git a/x-pack/plugins/maps/public/kibana_services.ts b/x-pack/plugins/maps/public/kibana_services.ts index 6212c28c60f66..e7dfeb2e08957 100644 --- a/x-pack/plugins/maps/public/kibana_services.ts +++ b/x-pack/plugins/maps/public/kibana_services.ts @@ -8,9 +8,23 @@ import type { CoreStart } from '@kbn/core/public'; import type { EMSSettings } from '@kbn/maps-ems-plugin/common/ems_settings'; import { MapsEmsPluginPublicStart } from '@kbn/maps-ems-plugin/public'; +import { BehaviorSubject } from 'rxjs'; import type { MapsConfigType } from '../config'; import type { MapsPluginStartDependencies } from './plugin'; +const servicesReady$ = new BehaviorSubject(false); +export const untilPluginStartServicesReady = () => { + if (servicesReady$.value) return Promise.resolve(); + return new Promise((resolve) => { + const subscription = servicesReady$.subscribe((isInitialized) => { + if (isInitialized) { + subscription.unsubscribe(); + resolve(); + } + }); + }); +}; + let isDarkMode = false; let coreStart: CoreStart; let pluginsStart: MapsPluginStartDependencies; @@ -25,6 +39,8 @@ export function setStartServices(core: CoreStart, plugins: MapsPluginStartDepend core.theme.theme$.subscribe(({ darkMode }) => { isDarkMode = darkMode; }); + + servicesReady$.next(true); } let isCloudEnabled = false; @@ -84,6 +100,7 @@ export const isScreenshotMode = () => { return pluginsStart.screenshotMode ? pluginsStart.screenshotMode.isScreenshotMode() : false; }; export const getServerless = () => pluginsStart.serverless; +export const getEmbeddableEnhanced = () => pluginsStart.embeddableEnhanced; // xpack.maps.* kibana.yml settings from this plugin let mapAppConfig: MapsConfigType; diff --git a/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_renderer.tsx b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_renderer.tsx index 66c623bf524a7..3e09a7e0fd81d 100644 --- a/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_renderer.tsx +++ b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_renderer.tsx @@ -9,8 +9,10 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import type { ExpressionRenderDefinition } from '@kbn/expressions-plugin/common'; import { dynamic } from '@kbn/shared-ux-utility'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import type { RegionMapVisRenderValue } from './region_map_fn'; import { REGION_MAP_RENDER } from './types'; +import { getAnalytics, getCoreI18n, getTheme } from '../../kibana_services'; const Component = dynamic(async () => { const { RegionMapVisualization } = await import('./region_map_visualization'); @@ -37,6 +39,15 @@ export const regionMapRenderer = { visConfig, }; - render(, domNode); + render( + + + , + domNode + ); }, } as ExpressionRenderDefinition; diff --git a/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_visualization.tsx b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_visualization.tsx index 701b7baf002b2..d8bc9a56f062a 100644 --- a/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_visualization.tsx +++ b/x-pack/plugins/maps/public/legacy_visualizations/region_map/region_map_visualization.tsx @@ -6,10 +6,12 @@ */ import React, { useMemo } from 'react'; +import useMountedState from 'react-use/lib/useMountedState'; +import { first } from 'rxjs'; import type { Filter } from '@kbn/es-query'; import type { Query, TimeRange } from '@kbn/es-query'; import { RegionMapVisConfig } from './types'; -import { MapComponent } from '../../embeddable/map_component'; +import { MapRenderer } from '../../react_embeddable/map_renderer'; import { createRegionMapLayerDescriptor } from '../../classes/layers/create_region_map_layer_descriptor'; interface Props { @@ -21,6 +23,7 @@ interface Props { } export function RegionMapVisualization(props: Props) { + const isMounted = useMountedState(); const initialMapCenter = useMemo(() => { return { lat: props.visConfig.mapCenter[0], @@ -37,7 +40,7 @@ export function RegionMapVisualization(props: Props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( - { + api.onRenderComplete$.pipe(first()).subscribe(() => { + if (isMounted()) { + props.onInitialRenderComplete(); + } + }); + }} isSharable={false} /> ); diff --git a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_renderer.tsx b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_renderer.tsx index e784cf340f420..9bc828e617bb6 100644 --- a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_renderer.tsx +++ b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_renderer.tsx @@ -9,8 +9,10 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import type { ExpressionRenderDefinition } from '@kbn/expressions-plugin/common'; import { dynamic } from '@kbn/shared-ux-utility'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import type { TileMapVisRenderValue } from './tile_map_fn'; import { TILE_MAP_RENDER } from './types'; +import { getAnalytics, getCoreI18n, getTheme } from '../../kibana_services'; const Component = dynamic(async () => { const { TileMapVisualization } = await import('./tile_map_visualization'); @@ -37,6 +39,15 @@ export const tileMapRenderer = { visConfig, }; - render(, domNode); + render( + + + , + domNode + ); }, } as ExpressionRenderDefinition; diff --git a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_visualization.tsx b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_visualization.tsx index f8f9c74360e8c..f1cc8d437e082 100644 --- a/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_visualization.tsx +++ b/x-pack/plugins/maps/public/legacy_visualizations/tile_map/tile_map_visualization.tsx @@ -6,10 +6,12 @@ */ import React, { useMemo } from 'react'; +import useMountedState from 'react-use/lib/useMountedState'; +import { first } from 'rxjs'; import type { Filter } from '@kbn/es-query'; import type { Query, TimeRange } from '@kbn/es-query'; import type { TileMapVisConfig } from './types'; -import { MapComponent } from '../../embeddable/map_component'; +import { MapRenderer } from '../../react_embeddable/map_renderer'; import { createTileMapLayerDescriptor } from '../../classes/layers/create_tile_map_layer_descriptor'; interface Props { @@ -21,6 +23,7 @@ interface Props { } export function TileMapVisualization(props: Props) { + const isMounted = useMountedState(); const initialMapCenter = useMemo(() => { return { lat: props.visConfig.mapCenter[0], @@ -37,7 +40,7 @@ export function TileMapVisualization(props: Props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( - { + api.onRenderComplete$.pipe(first()).subscribe(() => { + if (isMounted()) { + props.onInitialRenderComplete(); + } + }); + }} isSharable={false} /> ); diff --git a/x-pack/plugins/maps/public/lens/passive_map.tsx b/x-pack/plugins/maps/public/lens/passive_map.tsx index 31e90ee8e91f8..30a4b8013b8ab 100644 --- a/x-pack/plugins/maps/public/lens/passive_map.tsx +++ b/x-pack/plugins/maps/public/lens/passive_map.tsx @@ -5,25 +5,20 @@ * 2.0. */ -import React, { Component, RefObject } from 'react'; +import React, { useEffect, useRef } from 'react'; +import useMountedState from 'react-use/lib/useMountedState'; import { Subscription } from 'rxjs'; -import { v4 as uuidv4 } from 'uuid'; -import { EuiLoadingChart } from '@elastic/eui'; -import { ViewMode } from '@kbn/embeddable-plugin/public'; +import { ReactEmbeddableRenderer, ViewMode } from '@kbn/embeddable-plugin/public'; import type { LayerDescriptor } from '../../common/descriptor_types'; -import { INITIAL_LOCATION } from '../../common'; -import { MapEmbeddable } from '../embeddable'; +import { INITIAL_LOCATION, MAP_SAVED_OBJECT_TYPE } from '../../common'; import { createBasemapLayerDescriptor } from '../classes/layers/create_basemap_layer_descriptor'; +import { MapApi, MapRuntimeState, MapSerializedState } from '../react_embeddable/types'; export interface Props { passiveLayer: LayerDescriptor; onRenderComplete?: () => void; } -interface State { - mapEmbeddable: MapEmbeddable | null; -} - /* * PassiveMap compoment is a wrapper around a map embeddable where passive layer descriptor provides features * and layer does not auto-fetch features based on changes to pan, zoom, filter, query, timeRange, and other state changes. @@ -31,88 +26,76 @@ interface State { * Contrast with traditional map (active map), where layers independently auto-fetch features * based on changes to pan, zoom, filter, query, timeRange, and other state changes */ -export class PassiveMap extends Component { - private _isMounted = false; - private _prevPassiveLayer = this.props.passiveLayer; - private readonly _embeddableRef: RefObject = React.createRef(); - private _onRenderSubscription: Subscription | undefined; - - state: State = { mapEmbeddable: null }; - - componentDidMount() { - this._isMounted = true; - this._setupEmbeddable(); - } - - componentWillUnmount() { - this._isMounted = false; - if (this.state.mapEmbeddable) { - this.state.mapEmbeddable.destroy(); - } - if (this._onRenderSubscription) { - this._onRenderSubscription.unsubscribe(); - } - } +export function PassiveMap(props: Props) { + const isMounted = useMountedState(); + const mapApiRef = useRef(undefined); + const beforeApiReadyPassiveLayerRef = useRef(undefined); + const onRenderCompleteSubscriptionRef = useRef(undefined); - componentDidUpdate() { - if (this.state.mapEmbeddable && this._prevPassiveLayer !== this.props.passiveLayer) { - this.state.mapEmbeddable.updateLayerById(this.props.passiveLayer); - this._prevPassiveLayer = this.props.passiveLayer; + useEffect(() => { + if (mapApiRef.current) { + mapApiRef.current.updateLayerById(props.passiveLayer); + } else { + beforeApiReadyPassiveLayerRef.current = props.passiveLayer; } - } + }, [props.passiveLayer]); - async _setupEmbeddable() { - const basemapLayerDescriptor = createBasemapLayerDescriptor(); - const intialLayers = basemapLayerDescriptor ? [basemapLayerDescriptor] : []; - const mapEmbeddable = new MapEmbeddable( - { - editable: false, - }, - { - id: uuidv4(), - attributes: { - title: '', - layerListJSON: JSON.stringify([...intialLayers, this.props.passiveLayer]), - }, - filters: [], - hidePanelTitles: true, - viewMode: ViewMode.VIEW, - isLayerTOCOpen: false, - hideFilterActions: true, - mapSettings: { - disableInteractive: false, - hideToolbarOverlay: false, - hideLayerControl: false, - hideViewControl: false, - initialLocation: INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS, // this will startup based on data-extent - autoFitToDataBounds: true, // this will auto-fit when there are changes to the filter and/or query - }, + useEffect(() => { + return () => { + if (onRenderCompleteSubscriptionRef.current) { + onRenderCompleteSubscriptionRef.current.unsubscribe(); } - ); - - if (this.props.onRenderComplete) { - this._onRenderSubscription = mapEmbeddable.getOnRenderComplete$().subscribe(() => { - if (this._isMounted && this.props.onRenderComplete) { - this.props.onRenderComplete(); - } - }); - } - - if (this._isMounted) { - mapEmbeddable.setIsSharable(false); - this.setState({ mapEmbeddable }, () => { - if (this.state.mapEmbeddable && this._embeddableRef.current) { - this.state.mapEmbeddable.render(this._embeddableRef.current); - } - }); - } - } - - render() { - if (!this.state.mapEmbeddable) { - return ; - } + }; + }, []); - return
; - } + return ( +
+ + type={MAP_SAVED_OBJECT_TYPE} + getParentApi={() => ({ + getSerializedStateForChild: () => { + const basemapLayerDescriptor = createBasemapLayerDescriptor(); + const intialLayers = basemapLayerDescriptor ? [basemapLayerDescriptor] : []; + return { + rawState: { + attributes: { + title: '', + layerListJSON: JSON.stringify([...intialLayers, props.passiveLayer]), + }, + filters: [], + hidePanelTitles: true, + viewMode: ViewMode.VIEW, + isLayerTOCOpen: false, + hideFilterActions: true, + mapSettings: { + disableInteractive: false, + hideToolbarOverlay: false, + hideLayerControl: false, + hideViewControl: false, + initialLocation: INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS, // this will startup based on data-extent + autoFitToDataBounds: true, // this will auto-fit when there are changes to the filter and/or query + }, + isSharable: false, + }, + references: [], + }; + }, + })} + onApiAvailable={(api) => { + mapApiRef.current = api; + if (beforeApiReadyPassiveLayerRef.current) { + api.updateLayerById(beforeApiReadyPassiveLayerRef.current); + } + if (props.onRenderComplete) { + onRenderCompleteSubscriptionRef.current = api.onRenderComplete$.subscribe(() => { + if (isMounted() && props.onRenderComplete) { + props.onRenderComplete(); + } + }); + } + }} + hidePanelChrome={true} + /> +
+ ); } diff --git a/x-pack/plugins/maps/public/map_attribute_service.ts b/x-pack/plugins/maps/public/map_attribute_service.ts deleted file mode 100644 index 5f07f3954eecd..0000000000000 --- a/x-pack/plugins/maps/public/map_attribute_service.ts +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { SavedObjectReference } from '@kbn/core/types'; -import type { ResolvedSimpleSavedObject } from '@kbn/core/public'; -import { AttributeService } from '@kbn/embeddable-plugin/public'; -import type { OnSaveProps } from '@kbn/saved-objects-plugin/public'; -import { SavedObjectCommon } from '@kbn/saved-objects-finder-plugin/common'; -import type { MapAttributes } from '../common/content_management'; -import { MAP_EMBEDDABLE_NAME, MAP_SAVED_OBJECT_TYPE } from '../common/constants'; -import { getCoreOverlays, getEmbeddableService } from './kibana_services'; -import { extractReferences, injectReferences } from '../common/migrations/references'; -import { getMapClient, checkForDuplicateTitle } from './content_management'; -import { MapByValueInput, MapByReferenceInput } from './embeddable/types'; - -export interface SharingSavedObjectProps { - outcome?: ResolvedSimpleSavedObject['outcome']; - aliasTargetId?: ResolvedSimpleSavedObject['alias_target_id']; - aliasPurpose?: ResolvedSimpleSavedObject['alias_purpose']; - sourceId?: string; -} - -type MapDoc = MapAttributes & { - references?: SavedObjectReference[]; -}; -export interface MapUnwrapMetaInfo { - sharingSavedObjectProps: SharingSavedObjectProps; - // Is this map managed by the system? - managed: boolean; -} - -export type MapAttributeService = AttributeService< - MapDoc, - MapByValueInput, - MapByReferenceInput, - MapUnwrapMetaInfo ->; - -export const savedObjectToEmbeddableAttributes = ( - savedObject: SavedObjectCommon -) => { - const { attributes } = injectReferences(savedObject); - - return { - ...attributes, - references: savedObject.references, - }; -}; - -let mapAttributeService: MapAttributeService | null = null; - -export function getMapAttributeService(): MapAttributeService { - if (mapAttributeService) { - return mapAttributeService; - } - - mapAttributeService = getEmbeddableService().getAttributeService< - MapDoc, - MapByValueInput, - MapByReferenceInput, - MapUnwrapMetaInfo - >(MAP_SAVED_OBJECT_TYPE, { - saveMethod: async (attributes: MapDoc, savedObjectId?: string) => { - // AttributeService "attributes" contains "references" as a child. - // SavedObjectClient "attributes" uses "references" as a sibling. - // https://github.com/elastic/kibana/issues/83133 - const savedObjectClientReferences = attributes.references; - const savedObjectClientAttributes = { ...attributes }; - delete savedObjectClientAttributes.references; - const { attributes: updatedAttributes, references } = extractReferences({ - attributes: savedObjectClientAttributes, - references: savedObjectClientReferences, - }); - - const { - item: { id }, - } = await (savedObjectId - ? getMapClient().update({ - id: savedObjectId, - data: updatedAttributes, - options: { references }, - }) - : getMapClient().create({ data: updatedAttributes, options: { references } })); - return { id }; - }, - unwrapMethod: async ( - savedObjectId: string - ): Promise<{ - attributes: MapDoc; - metaInfo: MapUnwrapMetaInfo; - }> => { - const { - item: savedObject, - meta: { outcome, aliasPurpose, aliasTargetId }, - } = await getMapClient().get(savedObjectId); - - if (savedObject.error) { - throw savedObject.error; - } - - return { - attributes: savedObjectToEmbeddableAttributes(savedObject), - metaInfo: { - sharingSavedObjectProps: { - aliasTargetId, - outcome, - aliasPurpose, - sourceId: savedObjectId, - }, - managed: Boolean(savedObject.managed), - }, - }; - }, - checkForDuplicateTitle: (props: OnSaveProps) => { - return checkForDuplicateTitle( - { - title: props.newTitle, - copyOnSave: false, - lastSavedTitle: '', - isTitleDuplicateConfirmed: props.isTitleDuplicateConfirmed, - getDisplayName: () => MAP_EMBEDDABLE_NAME, - onTitleDuplicate: props.onTitleDuplicate, - }, - { - overlays: getCoreOverlays(), - } - ); - }, - }); - - return mapAttributeService; -} diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 2e9cbcba8d96f..42d40cc64e64a 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -25,6 +25,7 @@ import type { VisualizationsSetup, VisualizationsStart } from '@kbn/visualizatio import type { Plugin as ExpressionsPublicPlugin } from '@kbn/expressions-plugin/public'; import { VISUALIZE_GEO_FIELD_TRIGGER } from '@kbn/ui-actions-plugin/public'; import { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public'; +import { EmbeddableEnhancedPluginStart } from '@kbn/embeddable-enhanced-plugin/public'; import { CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public'; import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; import type { MapsEmsPluginPublicStart } from '@kbn/maps-ems-plugin/public'; @@ -69,11 +70,10 @@ import { suggestEMSTermJoinConfig, } from './api'; import { MapsXPackConfig, MapsConfigType } from '../config'; -import { MapEmbeddableFactory } from './embeddable/map_embeddable_factory'; import { filterByMapExtentAction } from './trigger_actions/filter_by_map_extent/action'; import { synchronizeMovementAction } from './trigger_actions/synchronize_movement/action'; import { visualizeGeoFieldAction } from './trigger_actions/visualize_geo_field_action'; -import { APP_NAME, APP_ICON_SOLUTION, APP_ID, MAP_SAVED_OBJECT_TYPE } from '../common/constants'; +import { APP_NAME, APP_ICON_SOLUTION, APP_ID } from '../common/constants'; import { getMapsVisTypeAlias } from './maps_vis_type_alias'; import { featureCatalogueEntry } from './feature_catalogue_entry'; import { @@ -81,15 +81,15 @@ import { setMapAppConfig, setSpaceId, setStartServices, + untilPluginStartServicesReady, } from './kibana_services'; import { MapInspectorView } from './inspector/map_adapter/map_inspector_view'; import { VectorTileInspectorView } from './inspector/vector_tile_adapter/vector_tile_inspector_view'; import { PassiveMapLazy, setupLensChoroplethChart } from './lens'; -import { CONTENT_ID, LATEST_VERSION, MapAttributes } from '../common/content_management'; -import { savedObjectToEmbeddableAttributes } from './map_attribute_service'; -import { MapByValueInput } from './embeddable'; -import { MapComponentLazy } from './embeddable/map_component_lazy'; +import { CONTENT_ID, LATEST_VERSION } from '../common/content_management'; +import { setupMapEmbeddable } from './react_embeddable/setup_map_embeddable'; +import { MapRendererLazy } from './react_embeddable/map_renderer_lazy'; export interface MapsPluginSetupDependencies { cloud?: CloudSetup; @@ -112,6 +112,7 @@ export interface MapsPluginStartDependencies { data: DataPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; embeddable: EmbeddableStart; + embeddableEnhanced?: EmbeddableEnhancedPluginStart; fieldFormats: FieldFormatsStart; fileUpload: FileUploadPluginStart; inspector: InspectorStartContract; @@ -194,7 +195,6 @@ export class MapsPlugin plugins.home.featureCatalogue.register(featureCatalogueEntry); } plugins.visualizations.registerAlias(getMapsVisTypeAlias()); - plugins.embeddable.registerEmbeddableFactory(MAP_SAVED_OBJECT_TYPE, new MapEmbeddableFactory()); core.application.register({ id: APP_ID, @@ -204,14 +204,18 @@ export class MapsPlugin euiIconType: APP_ICON_SOLUTION, category: DEFAULT_APP_CATEGORIES.kibana, async mount(params: AppMountParameters) { - const [coreStart, { savedObjectsTagging, spaces }] = await core.getStartServices(); + const [, startServices, { renderApp }] = await Promise.all([ + untilPluginStartServicesReady(), + core.getStartServices(), + import('./render_app'), + ]); + const [coreStart, { savedObjectsTagging, spaces }] = startServices; const UsageTracker = plugins.usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment; const activeSpace = await spaces?.getActiveSpace(); if (activeSpace) { setSpaceId(activeSpace.id); } - const { renderApp } = await import('./render_app'); return renderApp(params, { coreStart, AppUsageTracker: UsageTracker, savedObjectsTagging }); }, }); @@ -224,18 +228,7 @@ export class MapsPlugin name: APP_NAME, }); - plugins.embeddable.registerSavedObjectToPanelMethod( - CONTENT_ID, - (savedObject) => { - if (!savedObject.managed) { - return { savedObjectId: savedObject.id }; - } - - return { - attributes: savedObjectToEmbeddableAttributes(savedObject), - }; - } - ); + setupMapEmbeddable(plugins.embeddable); setupLensChoroplethChart(core, plugins.expressions, plugins.lens); @@ -273,7 +266,7 @@ export class MapsPlugin return { createLayerDescriptors, suggestEMSTermJoinConfig, - Map: MapComponentLazy, + Map: MapRendererLazy, PassiveMap: PassiveMapLazy, }; } diff --git a/x-pack/plugins/maps/public/embeddable/_index.scss b/x-pack/plugins/maps/public/react_embeddable/_index.scss similarity index 100% rename from x-pack/plugins/maps/public/embeddable/_index.scss rename to x-pack/plugins/maps/public/react_embeddable/_index.scss diff --git a/x-pack/plugins/maps/public/embeddable/get_geo_fields_label.test.ts b/x-pack/plugins/maps/public/react_embeddable/get_geo_fields_label.test.ts similarity index 100% rename from x-pack/plugins/maps/public/embeddable/get_geo_fields_label.test.ts rename to x-pack/plugins/maps/public/react_embeddable/get_geo_fields_label.test.ts diff --git a/x-pack/plugins/maps/public/embeddable/get_geo_fields_label.ts b/x-pack/plugins/maps/public/react_embeddable/get_geo_fields_label.ts similarity index 100% rename from x-pack/plugins/maps/public/embeddable/get_geo_fields_label.ts rename to x-pack/plugins/maps/public/react_embeddable/get_geo_fields_label.ts diff --git a/x-pack/plugins/maps/public/react_embeddable/initialize_action_handlers.ts b/x-pack/plugins/maps/public/react_embeddable/initialize_action_handlers.ts new file mode 100644 index 0000000000000..cbb933cda8b15 --- /dev/null +++ b/x-pack/plugins/maps/public/react_embeddable/initialize_action_handlers.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Filter } from '@kbn/es-query'; +import type { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; +import { APPLY_FILTER_TRIGGER } from '@kbn/data-plugin/public'; +import { ACTION_GLOBAL_APPLY_FILTER } from '@kbn/unified-search-plugin/public'; +import { VALUE_CLICK_TRIGGER } from '@kbn/embeddable-plugin/public'; +import { RawValue } from '../../common/constants'; +import type { MapApi } from './types'; +import { getUiActions } from '../kibana_services'; +import { isUrlDrilldown, toValueClickDataFormat } from '../trigger_actions/trigger_utils'; + +export function initializeActionHandlers(getApi: () => MapApi | undefined) { + function getActionContext() { + const trigger = getUiActions().getTrigger(APPLY_FILTER_TRIGGER); + if (!trigger) { + throw new Error('Unable to get context, could not locate trigger'); + } + return { + embeddable: getApi(), + trigger, + } as ActionExecutionContext; + } + + return { + addFilters: async (filters: Filter[], actionId: string = ACTION_GLOBAL_APPLY_FILTER) => { + const executeContext = { + ...getActionContext(), + filters, + }; + const action = getUiActions().getAction(actionId); + if (!action) { + throw new Error('Unable to apply filter, could not locate action'); + } + action.execute(executeContext); + }, + getActionContext, + getFilterActions: async () => { + const filterActions = await getUiActions().getTriggerCompatibleActions(APPLY_FILTER_TRIGGER, { + embeddable: getApi(), + filters: [], + }); + const valueClickActions = await getUiActions().getTriggerCompatibleActions( + VALUE_CLICK_TRIGGER, + { + embeddable: getApi(), + data: { + // uiActions.getTriggerCompatibleActions validates action with provided context + // so if event.key and event.value are used in the URL template but can not be parsed from context + // then the action is filtered out. + // To prevent filtering out actions, provide dummy context when initially fetching actions. + data: toValueClickDataFormat('anyfield', 'anyvalue'), + }, + } + ); + return [...filterActions, ...valueClickActions.filter(isUrlDrilldown)]; + }, + onSingleValueTrigger: (actionId: string, key: string, value: RawValue) => { + const action = getUiActions().getAction(actionId); + if (!action) { + throw new Error('Unable to apply action, could not locate action'); + } + const executeContext = { + ...getActionContext(), + data: { + data: toValueClickDataFormat(key, value), + }, + }; + action.execute(executeContext); + }, + }; +} diff --git a/x-pack/plugins/maps/public/react_embeddable/initialize_cross_panel_actions.ts b/x-pack/plugins/maps/public/react_embeddable/initialize_cross_panel_actions.ts new file mode 100644 index 0000000000000..29d572f2d67d5 --- /dev/null +++ b/x-pack/plugins/maps/public/react_embeddable/initialize_cross_panel_actions.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 _ from 'lodash'; +import { ACTION_GLOBAL_APPLY_FILTER } from '@kbn/unified-search-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { ActionExecutionContext } from '@kbn/ui-actions-plugin/public'; +import { BehaviorSubject } from 'rxjs'; +import { getPanelTitle, StateComparators } from '@kbn/presentation-publishing'; +import { createExtentFilter } from '../../common/elasticsearch_util'; +import { SavedMap } from '../routes/map_page'; +import { mapEmbeddablesSingleton } from './map_embeddables_singleton'; +import { + getGeoFieldNames, + getGoto, + getMapCenter, + getMapExtent, + getMapReady, + getMapSettings, + getMapZoom, +} from '../selectors/map_selectors'; +import { setGotoWithCenter, setMapSettings } from '../actions'; +import { MapExtent } from '../../common/descriptor_types'; +import { getUiActions } from '../kibana_services'; +import { getGeoFieldsLabel } from './get_geo_fields_label'; +import { MapApi, MapSerializedState } from './types'; +import { setOnMapMove } from '../reducers/non_serializable_instances'; + +export function initializeCrossPanelActions({ + controlledBy, + getActionContext, + getApi, + savedMap, + state, + uuid, +}: { + controlledBy: string; + getActionContext: () => ActionExecutionContext; + getApi: () => MapApi | undefined; + savedMap: SavedMap; + state: MapSerializedState; + uuid: string; +}) { + const isMovementSynchronized$ = new BehaviorSubject( + state.isMovementSynchronized + ); + function getIsMovementSynchronized() { + return isMovementSynchronized$.value ?? true; + } + function setIsMovementSynchronized(next: boolean) { + isMovementSynchronized$.next(next); + } + + const isFilterByMapExtent$ = new BehaviorSubject(state.filterByMapExtent); + function getIsFilterByMapExtent() { + return isFilterByMapExtent$.value ?? false; + } + function setIsFilterByMapExtent(next: boolean) { + isFilterByMapExtent$.next(next); + } + + let prevMapExtent: MapExtent | undefined; + + function mapSyncHandler(lat: number, lon: number, zoom: number) { + // auto fit to bounds is not compatable with map synchronization + // auto fit to bounds may cause map location to never stablize and bound back and forth between bounds on different maps + if (getMapSettings(savedMap.getStore().getState()).autoFitToDataBounds) { + savedMap.getStore().dispatch(setMapSettings({ autoFitToDataBounds: false })); + } + savedMap.getStore().dispatch(setGotoWithCenter({ lat, lon, zoom })); + } + + function gotoSynchronizedLocation() { + const syncedLocation = mapEmbeddablesSingleton.getLocation(); + if (syncedLocation) { + // set map to synchronized view + mapSyncHandler(syncedLocation.lat, syncedLocation.lon, syncedLocation.zoom); + return; + } + + if (!getMapReady(savedMap.getStore().getState())) { + // Initialize synchronized view to map's goto + // Use goto because un-rendered map will not have accurate mapCenter and mapZoom. + const goto = getGoto(savedMap.getStore().getState()); + if (goto && goto.center) { + mapEmbeddablesSingleton.setLocation( + uuid, + goto.center.lat, + goto.center.lon, + goto.center.zoom + ); + return; + } + } + + // Initialize synchronized view to map's view + const center = getMapCenter(savedMap.getStore().getState()); + const zoom = getMapZoom(savedMap.getStore().getState()); + mapEmbeddablesSingleton.setLocation(uuid, center.lat, center.lon, zoom); + } + + // debounce to fix timing issue for dashboard with multiple maps with synchronized movement and filter by map extent enabled + const setMapExtentFilter = _.debounce(() => { + const mapExtent = getMapExtent(savedMap.getStore().getState()); + const geoFieldNames = mapEmbeddablesSingleton.getGeoFieldNames(); + + if (mapExtent === undefined || geoFieldNames.length === 0) { + return; + } + + prevMapExtent = mapExtent; + + const mapExtentFilter = createExtentFilter(mapExtent, geoFieldNames); + mapExtentFilter.meta.controlledBy = controlledBy; + mapExtentFilter.meta.alias = i18n.translate('xpack.maps.embeddable.boundsFilterLabel', { + defaultMessage: '{geoFieldsLabel} within map bounds', + values: { geoFieldsLabel: getGeoFieldsLabel(geoFieldNames) }, + }); + + const executeContext = { + ...getActionContext(), + filters: [mapExtentFilter], + controlledBy, + }; + const action = getUiActions().getAction(ACTION_GLOBAL_APPLY_FILTER); + if (!action) { + throw new Error('Unable to apply map extent filter, could not locate action'); + } + action.execute(executeContext); + }, 100); + + function clearMapExtentFilter() { + prevMapExtent = undefined; + const executeContext = { + ...getActionContext(), + filters: [], + controlledBy, + }; + const action = getUiActions().getAction(ACTION_GLOBAL_APPLY_FILTER); + if (!action) { + throw new Error('Unable to apply map extent filter, could not locate action'); + } + action.execute(executeContext); + } + + mapEmbeddablesSingleton.register(uuid, { + getTitle: () => { + const mapApi = getApi(); + const title = mapApi ? getPanelTitle(mapApi) : undefined; + return title + ? title + : i18n.translate('xpack.maps.embeddable.untitleMap', { + defaultMessage: 'Untitled map', + }); + }, + onLocationChange: mapSyncHandler, + getIsMovementSynchronized, + setIsMovementSynchronized: (isMovementSynchronized: boolean) => { + setIsMovementSynchronized(isMovementSynchronized); + if (isMovementSynchronized) { + gotoSynchronizedLocation(); + } else if (!isMovementSynchronized && savedMap.getAutoFitToBounds()) { + // restore autoFitToBounds when isMovementSynchronized disabled + savedMap.getStore().dispatch(setMapSettings({ autoFitToDataBounds: true })); + } + }, + getIsFilterByMapExtent, + setIsFilterByMapExtent: (isFilterByMapExtent: boolean) => { + setIsFilterByMapExtent(isFilterByMapExtent); + if (isFilterByMapExtent) { + setMapExtentFilter(); + } else { + clearMapExtentFilter(); + } + }, + getGeoFieldNames: () => { + return getGeoFieldNames(savedMap.getStore().getState()); + }, + }); + + if (getIsMovementSynchronized()) { + gotoSynchronizedLocation(); + } + + // Passing callback into redux store instead of regular pattern of getting redux state changes for performance reasons + savedMap.getStore().dispatch( + setOnMapMove((lat: number, lon: number, zoom: number) => { + if (getIsMovementSynchronized()) { + mapEmbeddablesSingleton.setLocation(uuid, lat, lon, zoom); + } + }) + ); + + const unsubscribeFromStore = savedMap.getStore().subscribe(() => { + if (!getMapReady(savedMap.getStore().getState())) { + return; + } + + if ( + getIsFilterByMapExtent() && + !_.isEqual(prevMapExtent, getMapExtent(savedMap.getStore().getState())) + ) { + setMapExtentFilter(); + } + }); + + return { + cleanup: () => { + mapEmbeddablesSingleton.unregister(uuid); + unsubscribeFromStore(); + }, + comparators: { + isMovementSynchronized: [isMovementSynchronized$, setIsMovementSynchronized], + filterByMapExtent: [isFilterByMapExtent$, setIsFilterByMapExtent], + } as StateComparators>, + getIsFilterByMapExtent, + serialize: () => { + return { + isMovementSynchronized: isMovementSynchronized$.value, + filterByMapExtent: isFilterByMapExtent$.value, + }; + }, + }; +} diff --git a/x-pack/plugins/maps/public/react_embeddable/initialize_data_views.test.ts b/x-pack/plugins/maps/public/react_embeddable/initialize_data_views.test.ts new file mode 100644 index 0000000000000..b240208bc6050 --- /dev/null +++ b/x-pack/plugins/maps/public/react_embeddable/initialize_data_views.test.ts @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { DataView } from '@kbn/data-plugin/common'; +import { createMapStore } from '../reducers/store'; +import { initializeDataViews } from './initialize_data_views'; +import { createLayerDescriptor } from '../classes/sources/es_search_source'; +import { ES_GEO_FIELD_TYPE } from '../../common/constants'; +import { skip } from 'rxjs'; + +jest.mock('../kibana_services', () => { + return { + getIsDarkMode() { + return false; + }, + getMapsCapabilities() { + return { save: true }; + }, + getShowMapsInspectorAdapter() { + return false; + }, + getEMSSettings() { + return { + isEMSUrlSet() { + return false; + }, + }; + }, + }; +}); +jest.mock('../index_pattern_util', () => { + return { + getIndexPatternsFromIds: async (ids: string[]) => { + return ids.length + ? ids.map( + (id) => + ({ + id, + } as unknown as DataView) + ) + : []; + }, + }; +}); + +describe('dataViews$', () => { + const onEmitMock = jest.fn(); + + beforeEach(() => { + onEmitMock.mockReset(); + }); + + test('Should emit when data view added', async () => { + const dataViewApi = initializeDataViews(createMapStore()); + const subscription = dataViewApi.dataViews.pipe(skip(1)).subscribe(onEmitMock); + + dataViewApi.setLayerList([ + createLayerDescriptor({ + indexPatternId: '1234', + geoFieldName: 'location', + geoFieldType: ES_GEO_FIELD_TYPE.GEO_POINT, + }), + ]); + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(onEmitMock.mock.calls).toHaveLength(1); + expect(onEmitMock.mock.calls[0][0]).toEqual([ + { + id: '1234', + }, + ]); + + subscription.unsubscribe(); + }); + + test('Should emit when data view removed', async () => { + const dataViewApi = initializeDataViews(createMapStore()); + dataViewApi.setLayerList([ + createLayerDescriptor({ + indexPatternId: '1234', + geoFieldName: 'location', + geoFieldType: ES_GEO_FIELD_TYPE.GEO_POINT, + }), + ]); + const subscription = dataViewApi.dataViews.pipe(skip(1)).subscribe(onEmitMock); + + dataViewApi.setLayerList([]); + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(onEmitMock.mock.calls).toHaveLength(1); + expect(onEmitMock.mock.calls[0][0]).toEqual([]); + + subscription.unsubscribe(); + }); + + test('Should emit not emit when data view ids do not change', async () => { + const dataViewApi = initializeDataViews(createMapStore()); + dataViewApi.setLayerList([ + createLayerDescriptor({ + indexPatternId: '1234', + geoFieldName: 'location', + geoFieldType: ES_GEO_FIELD_TYPE.GEO_POINT, + }), + createLayerDescriptor({ + indexPatternId: '4567', + geoFieldName: 'location', + geoFieldType: ES_GEO_FIELD_TYPE.GEO_POINT, + }), + ]); + await new Promise((resolve) => setTimeout(resolve, 0)); + + const subscription = dataViewApi.dataViews.pipe(skip(1)).subscribe(onEmitMock); + + dataViewApi.setLayerList([ + createLayerDescriptor({ + indexPatternId: '4567', + geoFieldName: 'location', + geoFieldType: ES_GEO_FIELD_TYPE.GEO_POINT, + }), + createLayerDescriptor({ + indexPatternId: '1234', + geoFieldName: 'location', + geoFieldType: ES_GEO_FIELD_TYPE.GEO_POINT, + }), + ]); + await new Promise((resolve) => setTimeout(resolve, 0)); + expect(onEmitMock).not.toHaveBeenCalled(); + + subscription.unsubscribe(); + }); +}); diff --git a/x-pack/plugins/maps/public/react_embeddable/initialize_data_views.ts b/x-pack/plugins/maps/public/react_embeddable/initialize_data_views.ts new file mode 100644 index 0000000000000..6361f59eeef6c --- /dev/null +++ b/x-pack/plugins/maps/public/react_embeddable/initialize_data_views.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { BehaviorSubject } from 'rxjs'; +import type { DataView } from '@kbn/data-plugin/common'; +import { isEqual } from 'lodash'; +import { LayerDescriptor } from '../../common'; +import { replaceLayerList, updateLayerDescriptor } from '../actions'; +import { MapStore } from '../reducers/store'; +import { getIndexPatternsFromIds } from '../index_pattern_util'; +import { getMapSettings, getQueryableUniqueIndexPatternIds } from '../selectors/map_selectors'; +import { autoFitToBounds, syncDataForLayerId } from '../actions/data_request_actions'; + +export function initializeDataViews(store: MapStore) { + const dataViews$ = new BehaviorSubject(undefined); + let dataViewsFetchToken: symbol | undefined; + + async function updateDataViews() { + const queryableDataViewIds = getQueryableUniqueIndexPatternIds(store.getState()); + const prevDataViewIds = dataViews$.getValue()?.map((dataView) => { + return dataView.id; + }); + if (isEqual(queryableDataViewIds.sort(), prevDataViewIds?.sort())) { + return; + } + const currentDataViewsFetchToken = Symbol(); + dataViewsFetchToken = currentDataViewsFetchToken; + const dataViews = await getIndexPatternsFromIds(queryableDataViewIds); + // ignore responses from obsolete requests + if (currentDataViewsFetchToken !== dataViewsFetchToken) { + return; + } + dataViews$.next(dataViews); + } + + updateDataViews(); + + const syncLayerTokens: Record = {}; + + return { + dataViews: dataViews$, + setLayerList(layerList: LayerDescriptor[]) { + store.dispatch(replaceLayerList(layerList)); + updateDataViews(); + }, + updateLayerById: (layerDescriptor: LayerDescriptor) => { + store.dispatch(updateLayerDescriptor(layerDescriptor)); + updateDataViews(); + (async () => { + const currentSyncLayerToken = Symbol(); + syncLayerTokens[layerDescriptor.id] = currentSyncLayerToken; + await store.dispatch(syncDataForLayerId(layerDescriptor.id, false)); + // stop processing responses from obsolete requests + if (currentSyncLayerToken !== syncLayerTokens[layerDescriptor.id]) { + return; + } + if (getMapSettings(store.getState()).autoFitToDataBounds) { + store.dispatch(autoFitToBounds()); + } + })(); + }, + }; +} diff --git a/x-pack/plugins/maps/public/react_embeddable/initialize_edit_api.ts b/x-pack/plugins/maps/public/react_embeddable/initialize_edit_api.ts new file mode 100644 index 0000000000000..a9274ed41f0fb --- /dev/null +++ b/x-pack/plugins/maps/public/react_embeddable/initialize_edit_api.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { apiHasAppContext } from '@kbn/presentation-publishing'; +import { APP_ID, getEditPath, getFullPath, MAP_EMBEDDABLE_NAME } from '../../common/constants'; +import { getEmbeddableService, getHttp, getMapsCapabilities } from '../kibana_services'; +import { MapSerializedState } from './types'; + +export function initializeEditApi( + uuid: string, + getState: () => MapSerializedState, + parentApi?: unknown, + savedObjectId?: string +) { + if (!parentApi || !apiHasAppContext(parentApi)) { + return {}; + } + + const parentApiContext = parentApi.getAppContext(); + + return { + getTypeDisplayName: () => { + return MAP_EMBEDDABLE_NAME; + }, + onEdit: async () => { + const stateTransfer = getEmbeddableService().getStateTransfer(); + await stateTransfer.navigateToEditor(APP_ID, { + path: getEditPath(savedObjectId), + state: { + embeddableId: uuid, + valueInput: getState(), + originatingApp: parentApiContext.currentAppId, + originatingPath: parentApiContext.getCurrentPath?.(), + }, + }); + }, + isEditingEnabled: () => { + return getMapsCapabilities().save as boolean; + }, + getEditHref: async () => { + return getHttp().basePath.prepend(getFullPath(savedObjectId)); + }, + }; +} diff --git a/x-pack/plugins/maps/public/react_embeddable/initialize_fetch.ts b/x-pack/plugins/maps/public/react_embeddable/initialize_fetch.ts new file mode 100644 index 0000000000000..83697f9552bfa --- /dev/null +++ b/x-pack/plugins/maps/public/react_embeddable/initialize_fetch.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FetchContext, fetch$ } from '@kbn/presentation-publishing'; +import { Query } from '@kbn/es-query'; +import { MapExtent } from '../../common/descriptor_types'; +import { getSearchService } from '../kibana_services'; +import { MapStore } from '../reducers/store'; +import { MapApi } from './types'; +import { setMapSettings, setQuery } from '../actions'; + +function getIsRestore(searchSessionId?: string) { + if (!searchSessionId) { + return false; + } + const searchSessionOptions = getSearchService().session.getSearchOptions(searchSessionId); + return searchSessionOptions ? searchSessionOptions.isRestore : false; +} + +export function initializeFetch({ + api, + controlledBy, + getIsFilterByMapExtent, + searchSessionMapBuffer, + store, +}: { + api: MapApi; + controlledBy: string; + getIsFilterByMapExtent: () => boolean; + searchSessionMapBuffer?: MapExtent; + store: MapStore; +}) { + let prevIsRestore: boolean | undefined; + const fetchSubscription = fetch$(api).subscribe((fetchContext: FetchContext) => { + // New search session id causes all layers from elasticsearch to refetch data. + // Dashboard provides a new search session id anytime filters change. + // Thus, filtering embeddable container by map extent causes a new search session id any time the map is moved. + // Disabling search session when filtering embeddable container by map extent. + // The use case for search sessions (restoring results because of slow responses) does not match the use case of + // filtering by map extent (rapid responses as users explore their map). + const searchSessionId = getIsFilterByMapExtent() ? undefined : fetchContext.searchSessionId; + const isRestore = getIsRestore(searchSessionId); + + // Map can not be interacted with when viewing session restore. + // Session restore only show data for cached extent and new data can not be fetch + if (isRestore !== prevIsRestore) { + prevIsRestore = isRestore; + store.dispatch( + setMapSettings({ + disableInteractive: isRestore, + hideToolbarOverlay: isRestore, + }) + ); + } + + store.dispatch( + setQuery({ + filters: fetchContext.filters + ? fetchContext.filters.filter( + (filter) => !filter.meta.disabled && filter.meta.controlledBy !== controlledBy + ) + : [], + query: fetchContext.query as Query | undefined, + timeFilters: fetchContext.timeRange, + timeslice: fetchContext.timeslice + ? { from: fetchContext.timeslice[0], to: fetchContext.timeslice[1] } + : undefined, + clearTimeslice: fetchContext.timeslice === undefined, + forceRefresh: fetchContext.isReload, + searchSessionId, + searchSessionMapBuffer: isRestore ? searchSessionMapBuffer : undefined, + }) + ); + }); + return () => { + fetchSubscription.unsubscribe(); + }; +} diff --git a/x-pack/plugins/maps/public/react_embeddable/initialize_redux_sync.ts b/x-pack/plugins/maps/public/react_embeddable/initialize_redux_sync.ts new file mode 100644 index 0000000000000..97e19582e0206 --- /dev/null +++ b/x-pack/plugins/maps/public/react_embeddable/initialize_redux_sync.ts @@ -0,0 +1,279 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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, debounceTime, filter, map, Subscription } from 'rxjs'; +import fastIsEqual from 'fast-deep-equal'; +import { PublishingSubject, StateComparators } from '@kbn/presentation-publishing'; +import { KibanaExecutionContext } from '@kbn/core-execution-context-common'; +import { PaletteRegistry } from '@kbn/coloring'; +import { AggregateQuery, Filter, Query } from '@kbn/es-query'; +import { MapCenterAndZoom } from '../../common/descriptor_types'; +import { APP_ID, getEditPath, RENDER_TIMEOUT } from '../../common/constants'; +import { MapStoreState } from '../reducers/store'; +import { getIsLayerTOCOpen, getOpenTOCDetails } from '../selectors/ui_selectors'; +import { + getLayerList, + getLayerListRaw, + getMapBuffer, + getMapCenter, + getMapReady, + getMapZoom, + isMapLoading, +} from '../selectors/map_selectors'; +import { + setEmbeddableSearchContext, + setExecutionContext, + setGotoWithCenter, + setHiddenLayers, + setIsLayerTOCOpen, + setMapSettings, + setOpenTOCDetails, + setQuery, + setReadOnly, +} from '../actions'; +import type { MapSerializedState } from './types'; +import { getCharts, getExecutionContextService } from '../kibana_services'; +import { + EventHandlers, + getInspectorAdapters, + setChartsPaletteServiceGetColor, + setEventHandlers, +} from '../reducers/non_serializable_instances'; +import { SavedMap } from '../routes'; + +function getMapCenterAndZoom(state: MapStoreState) { + return { + ...getMapCenter(state), + zoom: getMapZoom(state), + }; +} + +function getHiddenLayerIds(state: MapStoreState) { + return getLayerListRaw(state) + .filter((layer) => !layer.visible) + .map((layer) => layer.id); +} + +export function initializeReduxSync({ + savedMap, + state, + syncColors$, + uuid, +}: { + savedMap: SavedMap; + state: MapSerializedState; + syncColors$?: PublishingSubject; + uuid: string; +}) { + const store = savedMap.getStore(); + + // initializing comparitor publishing subjects to state instead of store state values + // because store is not settled until map is rendered and mapReady is true + const hiddenLayers$ = new BehaviorSubject( + state.hiddenLayers ?? getHiddenLayerIds(store.getState()) + ); + const isLayerTOCOpen$ = new BehaviorSubject( + state.isLayerTOCOpen ?? getIsLayerTOCOpen(store.getState()) + ); + const mapCenterAndZoom$ = new BehaviorSubject( + state.mapCenter ?? getMapCenterAndZoom(store.getState()) + ); + const openTOCDetails$ = new BehaviorSubject( + state.openTOCDetails ?? getOpenTOCDetails(store.getState()) + ); + const dataLoading$ = new BehaviorSubject(undefined); + + const unsubscribeFromStore = store.subscribe(() => { + if (!getMapReady(store.getState())) { + return; + } + const nextHiddenLayers = getHiddenLayerIds(store.getState()); + if (!fastIsEqual(hiddenLayers$.value, nextHiddenLayers)) { + hiddenLayers$.next(nextHiddenLayers); + } + + const nextIsLayerTOCOpen = getIsLayerTOCOpen(store.getState()); + if (isLayerTOCOpen$.value !== nextIsLayerTOCOpen) { + isLayerTOCOpen$.next(nextIsLayerTOCOpen); + } + + const nextMapCenterAndZoom = getMapCenterAndZoom(store.getState()); + if (!fastIsEqual(mapCenterAndZoom$.value, nextMapCenterAndZoom)) { + mapCenterAndZoom$.next(nextMapCenterAndZoom); + } + + const nextOpenTOCDetails = getOpenTOCDetails(store.getState()); + if (!fastIsEqual(openTOCDetails$.value, nextOpenTOCDetails)) { + openTOCDetails$.next(nextOpenTOCDetails); + } + + const nextIsMapLoading = isMapLoading(store.getState()); + if (nextIsMapLoading !== dataLoading$.value) { + dataLoading$.next(nextIsMapLoading); + } + }); + + store.dispatch(setReadOnly(true)); + store.dispatch( + setMapSettings({ + keydownScrollZoom: true, + showTimesliderToggleButton: false, + }) + ); + store.dispatch(setExecutionContext(getExecutionContext(uuid, state.savedObjectId))); + + const filters$ = new BehaviorSubject(undefined); + const query$ = new BehaviorSubject(undefined); + const mapStateJSON = savedMap.getAttributes().mapStateJSON; + if (mapStateJSON) { + try { + const mapState = JSON.parse(mapStateJSON); + if (mapState.filters) { + filters$.next(mapState.filters); + } + if (mapState.query) { + query$.next(mapState.query); + } + store.dispatch( + setEmbeddableSearchContext({ + filters: mapState.filters, + query: mapState.query, + }) + ); + } catch (e) { + // ignore malformed mapStateJSON, not a critical error for viewing map - map will just use defaults + } + } + + let syncColorsSubscription: Subscription | undefined; + let syncColorsSymbol: symbol | undefined; + if (syncColors$) { + syncColorsSubscription = syncColors$.subscribe(async (syncColors: boolean | undefined) => { + const currentSyncColorsSymbol = Symbol(); + syncColorsSymbol = currentSyncColorsSymbol; + const chartsPaletteServiceGetColor = syncColors + ? await getChartsPaletteServiceGetColor() + : null; + if (syncColorsSymbol === currentSyncColorsSymbol) { + store.dispatch(setChartsPaletteServiceGetColor(chartsPaletteServiceGetColor)); + } + }); + } + + return { + cleanup: () => { + if (syncColorsSubscription) syncColorsSubscription.unsubscribe(); + unsubscribeFromStore(); + }, + api: { + dataLoading: dataLoading$, + filters$, + getInspectorAdapters: () => { + return getInspectorAdapters(store.getState()); + }, + getLayerList: () => { + return getLayerList(store.getState()); + }, + onRenderComplete$: dataLoading$.pipe( + filter((isDataLoading) => typeof isDataLoading === 'boolean' && !isDataLoading), + debounceTime(RENDER_TIMEOUT), + map(() => { + // Observable notifies subscriber when rendering is complete + // Return void to not expose internal implemenation details of observabale + return; + }) + ), + query$, + reload: () => { + store.dispatch( + setQuery({ + forceRefresh: true, + }) + ); + }, + setEventHandlers: (eventHandlers: EventHandlers) => { + store.dispatch(setEventHandlers(eventHandlers)); + }, + }, + comparators: { + // mapBuffer comparator intentionally omitted and is not part of unsaved changes check + hiddenLayers: [ + hiddenLayers$, + (nextValue: string[]) => { + store.dispatch(setHiddenLayers(nextValue)); + }, + fastIsEqual, + ], + isLayerTOCOpen: [ + isLayerTOCOpen$, + (nextValue: boolean) => { + store.dispatch(setIsLayerTOCOpen(nextValue)); + }, + ], + mapCenter: [ + mapCenterAndZoom$, + (nextValue: MapCenterAndZoom) => { + store.dispatch(setGotoWithCenter(nextValue)); + }, + fastIsEqual, + ], + openTOCDetails: [ + openTOCDetails$, + (nextValue: string[]) => { + store.dispatch(setOpenTOCDetails(nextValue)); + }, + fastIsEqual, + ], + } as StateComparators< + Pick + >, + serialize: () => { + return { + hiddenLayers: getHiddenLayerIds(store.getState()), + isLayerTOCOpen: getIsLayerTOCOpen(store.getState()), + mapBuffer: getMapBuffer(store.getState()), + mapCenter: getMapCenterAndZoom(store.getState()), + openTOCDetails: getOpenTOCDetails(store.getState()), + }; + }, + }; +} + +function getExecutionContext(uuid: string, savedObjectId: string | undefined) { + const parentContext = getExecutionContextService().get(); + const mapContext: KibanaExecutionContext = { + type: APP_ID, + name: APP_ID, + id: uuid, + url: getEditPath(savedObjectId), + }; + + return parentContext + ? { + ...parentContext, + child: mapContext, + } + : mapContext; +} + +async function getChartsPaletteServiceGetColor(): Promise<((value: string) => string) | null> { + const chartsService = getCharts(); + const paletteRegistry: PaletteRegistry | null = chartsService + ? await chartsService.palettes.getPalettes() + : null; + if (!paletteRegistry) { + return null; + } + + const paletteDefinition = paletteRegistry.get('default'); + const chartConfiguration = { syncColors: true }; + return (value: string) => { + const series = [{ name: value, rankAtDepth: 0, totalSeriesAtDepth: 1 }]; + const color = paletteDefinition.getCategoricalColor(series, chartConfiguration); + return color ? color : '#3d3d3d'; + }; +} diff --git a/x-pack/plugins/maps/public/react_embeddable/library_transforms.ts b/x-pack/plugins/maps/public/react_embeddable/library_transforms.ts new file mode 100644 index 0000000000000..f12c1d59f4955 --- /dev/null +++ b/x-pack/plugins/maps/public/react_embeddable/library_transforms.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SerializedPanelState } from '@kbn/presentation-containers'; +import { HasLibraryTransforms } from '@kbn/presentation-publishing'; +import { getCore, getCoreOverlays } from '../kibana_services'; +import type { MapAttributes } from '../../common/content_management'; +import { SavedMap } from '../routes/map_page'; +import { checkForDuplicateTitle, getMapClient } from '../content_management'; +import { MAP_EMBEDDABLE_NAME } from '../../common/constants'; +import { MapSerializedState } from './types'; + +export function getByReferenceState(state: MapSerializedState | undefined, savedObjectId: string) { + const { attributes, ...byRefState } = state ?? {}; + return { + ...byRefState, + savedObjectId, + }; +} + +export function getByValueState(state: MapSerializedState | undefined, attributes: MapAttributes) { + const { savedObjectId, ...byValueState } = state ?? {}; + return { + ...byValueState, + attributes, + }; +} + +export function initializeLibraryTransforms( + savedMap: SavedMap, + serializeState: () => SerializedPanelState +): HasLibraryTransforms { + return { + canLinkToLibrary: async () => { + const { maps } = getCore().application.capabilities; + return maps.save && savedMap.getSavedObjectId() === undefined; + }, + saveToLibrary: async (title: string) => { + const state = serializeState(); + const { + item: { id: savedObjectId }, + } = await getMapClient().create({ + data: { + ...(state.rawState?.attributes ?? {}), + title, + }, + options: { references: state.references ?? [] }, + }); + return savedObjectId; + }, + getByReferenceState: (libraryId: string) => { + return getByReferenceState(serializeState().rawState, libraryId); + }, + checkForDuplicateTitle: async ( + newTitle: string, + isTitleDuplicateConfirmed: boolean, + onTitleDuplicate: () => void + ) => { + await checkForDuplicateTitle( + { + title: newTitle, + copyOnSave: false, + lastSavedTitle: '', + isTitleDuplicateConfirmed, + getDisplayName: () => MAP_EMBEDDABLE_NAME, + onTitleDuplicate, + }, + { + overlays: getCoreOverlays(), + } + ); + }, + canUnlinkFromLibrary: async () => { + return savedMap.getSavedObjectId() !== undefined; + }, + getByValueState: () => { + return getByValueState(serializeState().rawState, savedMap.getAttributes()); + }, + }; +} diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddables_singleton.ts b/x-pack/plugins/maps/public/react_embeddable/map_embeddables_singleton.ts similarity index 100% rename from x-pack/plugins/maps/public/embeddable/map_embeddables_singleton.ts rename to x-pack/plugins/maps/public/react_embeddable/map_embeddables_singleton.ts diff --git a/x-pack/plugins/maps/public/react_embeddable/map_react_embeddable.tsx b/x-pack/plugins/maps/public/react_embeddable/map_react_embeddable.tsx new file mode 100644 index 0000000000000..415e3819cdadb --- /dev/null +++ b/x-pack/plugins/maps/public/react_embeddable/map_react_embeddable.tsx @@ -0,0 +1,238 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; +import { Provider } from 'react-redux'; +import { EuiEmptyPrompt } from '@elastic/eui'; +import { APPLY_FILTER_TRIGGER } from '@kbn/data-plugin/public'; +import { ReactEmbeddableFactory, VALUE_CLICK_TRIGGER } from '@kbn/embeddable-plugin/public'; +import { EmbeddableStateWithType } from '@kbn/embeddable-plugin/common'; +import { + areTriggersDisabled, + getUnchangingComparator, + initializeTimeRange, + initializeTitles, + useBatchedPublishingSubjects, +} from '@kbn/presentation-publishing'; +import { BehaviorSubject } from 'rxjs'; +import { apiPublishesSettings } from '@kbn/presentation-containers/interfaces/publishes_settings'; +import { MAP_SAVED_OBJECT_TYPE } from '../../common/constants'; +import { inject } from '../../common/embeddable'; +import type { MapApi, MapRuntimeState, MapSerializedState } from './types'; +import { SavedMap } from '../routes/map_page'; +import { initializeReduxSync } from './initialize_redux_sync'; +import { + getByReferenceState, + getByValueState, + initializeLibraryTransforms, +} from './library_transforms'; +import { getEmbeddableEnhanced, getSpacesApi } from '../kibana_services'; +import { initializeActionHandlers } from './initialize_action_handlers'; +import { MapContainer } from '../connected_components/map_container'; +import { waitUntilTimeLayersLoad$ } from '../routes/map_page/map_app/wait_until_time_layers_load'; +import { initializeCrossPanelActions } from './initialize_cross_panel_actions'; +import { initializeDataViews } from './initialize_data_views'; +import { initializeFetch } from './initialize_fetch'; +import { initializeEditApi } from './initialize_edit_api'; +import { extractReferences } from '../../common/migrations/references'; + +export function getControlledBy(id: string) { + return `mapEmbeddablePanel${id}`; +} + +export const mapEmbeddableFactory: ReactEmbeddableFactory< + MapSerializedState, + MapRuntimeState, + MapApi +> = { + type: MAP_SAVED_OBJECT_TYPE, + deserializeState: (state) => { + return state.rawState + ? (inject( + state.rawState as EmbeddableStateWithType, + state.references ?? [] + ) as unknown as MapSerializedState) + : {}; + }, + buildEmbeddable: async (state, buildApi, uuid, parentApi) => { + const savedMap = new SavedMap({ + mapSerializedState: state, + }); + await savedMap.whenReady(); + + // eslint bug, eslint thinks api is never reassigned even though it is + // eslint-disable-next-line prefer-const + let api: MapApi | undefined; + const getApi = () => api; + const sharingSavedObjectProps = savedMap.getSharingSavedObjectProps(); + const spaces = getSpacesApi(); + const controlledBy = getControlledBy(uuid); + const title = initializeTitles(state); + const timeRange = initializeTimeRange(state); + const dynamicActionsApi = getEmbeddableEnhanced()?.initializeReactEmbeddableDynamicActions( + uuid, + () => title.titlesApi.panelTitle.getValue(), + state + ); + const maybeStopDynamicActions = dynamicActionsApi?.startDynamicActions(); + + const defaultPanelTitle$ = new BehaviorSubject( + savedMap.getAttributes().title + ); + const defaultPanelDescription$ = new BehaviorSubject( + savedMap.getAttributes().description + ); + const reduxSync = initializeReduxSync({ + savedMap, + state, + syncColors$: apiPublishesSettings(parentApi) ? parentApi.settings.syncColors$ : undefined, + uuid, + }); + const actionHandlers = initializeActionHandlers(getApi); + const crossPanelActions = initializeCrossPanelActions({ + controlledBy, + getActionContext: actionHandlers.getActionContext, + getApi, + state, + savedMap, + uuid, + }); + + function getState() { + return { + ...state, + ...timeRange.serialize(), + ...title.serializeTitles(), + ...(dynamicActionsApi?.serializeDynamicActions() ?? {}), + ...crossPanelActions.serialize(), + ...reduxSync.serialize(), + }; + } + + function serializeState() { + const rawState = getState(); + + // by-reference embeddable + if (rawState.savedObjectId) { + // No references to extract for by-reference embeddable since all references are stored with by-reference saved object + return { + rawState: getByReferenceState(rawState, rawState.savedObjectId), + references: [], + }; + } + + // by-value embeddable + const { attributes, references } = extractReferences({ + attributes: savedMap.getAttributes(), + }); + + return { + rawState: getByValueState(rawState, attributes), + references, + }; + } + + api = buildApi( + { + defaultPanelTitle: defaultPanelTitle$, + defaultPanelDescription: defaultPanelDescription$, + ...timeRange.api, + ...(dynamicActionsApi?.dynamicActionsApi ?? {}), + ...title.titlesApi, + ...reduxSync.api, + ...initializeEditApi(uuid, getState, parentApi, state.savedObjectId), + ...initializeLibraryTransforms(savedMap, serializeState), + ...initializeDataViews(savedMap.getStore()), + serializeState, + supportedTriggers: () => { + return [APPLY_FILTER_TRIGGER, VALUE_CLICK_TRIGGER]; + }, + }, + { + ...timeRange.comparators, + ...title.titleComparators, + ...(dynamicActionsApi?.dynamicActionsComparator ?? { + enhancements: getUnchangingComparator(), + }), + ...crossPanelActions.comparators, + ...reduxSync.comparators, + // readonly comparators + attributes: getUnchangingComparator(), + mapBuffer: getUnchangingComparator(), + savedObjectId: getUnchangingComparator(), + mapSettings: getUnchangingComparator(), + hideFilterActions: getUnchangingComparator(), + isSharable: getUnchangingComparator(), + tooltipRenderer: getUnchangingComparator(), + } + ); + + const unsubscribeFromFetch = initializeFetch({ + api, + controlledBy, + getIsFilterByMapExtent: crossPanelActions.getIsFilterByMapExtent, + searchSessionMapBuffer: state.mapBuffer, + store: savedMap.getStore(), + }); + + return { + api, + Component: () => { + const [defaultPanelTitle, panelTitle, defaultPanelDescription, panelDescription] = + useBatchedPublishingSubjects( + defaultPanelTitle$, + title.titlesApi.panelTitle, + defaultPanelDescription$, + title.titlesApi.panelDescription + ); + + useEffect(() => { + return () => { + crossPanelActions.cleanup(); + reduxSync.cleanup(); + unsubscribeFromFetch(); + maybeStopDynamicActions?.stopDynamicActions(); + }; + }, []); + + return sharingSavedObjectProps && + spaces && + sharingSavedObjectProps?.outcome === 'conflict' ? ( +
+ +
+ ) : ( + + + + ); + }, + }; + }, +}; diff --git a/x-pack/plugins/maps/public/react_embeddable/map_renderer.tsx b/x-pack/plugins/maps/public/react_embeddable/map_renderer.tsx new file mode 100644 index 0000000000000..7701c7ebbe11b --- /dev/null +++ b/x-pack/plugins/maps/public/react_embeddable/map_renderer.tsx @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useRef } from 'react'; +import type { Filter, Query, TimeRange } from '@kbn/es-query'; +import { ReactEmbeddableRenderer } from '@kbn/embeddable-plugin/public'; +import { useSearchApi } from '@kbn/presentation-publishing'; +import type { LayerDescriptor, MapCenterAndZoom, MapSettings } from '../../common/descriptor_types'; +import { createBasemapLayerDescriptor } from '../classes/layers/create_basemap_layer_descriptor'; +import { MapApi, MapRuntimeState, MapSerializedState } from './types'; +import { MAP_SAVED_OBJECT_TYPE } from '../../common/constants'; +import { RenderToolTipContent } from '../classes/tooltips/tooltip_property'; + +function getLayers(layerList: LayerDescriptor[]) { + const basemapLayer = createBasemapLayerDescriptor(); + return basemapLayer ? [basemapLayer, ...layerList] : layerList; +} + +export interface Props { + title?: string; + filters?: Filter[]; + query?: Query; + timeRange?: TimeRange; + layerList: LayerDescriptor[]; + mapSettings?: Partial; + hideFilterActions?: boolean; + isLayerTOCOpen?: boolean; + mapCenter?: MapCenterAndZoom; + getTooltipRenderer?: () => RenderToolTipContent; + onApiAvailable?: (api: MapApi) => void; + /* + * Set to false to exclude sharing attributes 'data-*'. + */ + isSharable?: boolean; +} + +export function MapRenderer(props: Props) { + const mapApiRef = useRef(undefined); + const beforeApiReadyLayerListRef = useRef(undefined); + + useEffect(() => { + if (mapApiRef.current) { + mapApiRef.current.setLayerList(getLayers(props.layerList)); + } else { + beforeApiReadyLayerListRef.current = getLayers(props.layerList); + } + }, [props.layerList]); + + const searchApi = useSearchApi({ + filters: props.filters, + query: props.query, + timeRange: props.timeRange, + }); + + return ( +
+ + type={MAP_SAVED_OBJECT_TYPE} + getParentApi={() => ({ + ...searchApi, + getSerializedStateForChild: () => { + const rawState: MapSerializedState = { + attributes: { + title: props.title ?? '', + layerListJSON: JSON.stringify(getLayers(props.layerList)), + }, + hidePanelTitles: !Boolean(props.title), + isLayerTOCOpen: + typeof props.isLayerTOCOpen === 'boolean' ? props.isLayerTOCOpen : false, + hideFilterActions: + typeof props.hideFilterActions === 'boolean' ? props.hideFilterActions : false, + mapCenter: props.mapCenter, + mapSettings: props.mapSettings ?? {}, + isSharable: props.isSharable, + }; + if (props.getTooltipRenderer) { + rawState.tooltipRenderer = props.getTooltipRenderer(); + } + return { + rawState, + references: [], + }; + }, + })} + onApiAvailable={(api) => { + mapApiRef.current = api; + if (beforeApiReadyLayerListRef.current) { + api.setLayerList(beforeApiReadyLayerListRef.current); + } + + if (props.onApiAvailable) { + props.onApiAvailable(api); + } + }} + hidePanelChrome={true} + /> +
+ ); +} diff --git a/x-pack/plugins/maps/public/embeddable/map_component_lazy.tsx b/x-pack/plugins/maps/public/react_embeddable/map_renderer_lazy.tsx similarity index 70% rename from x-pack/plugins/maps/public/embeddable/map_component_lazy.tsx rename to x-pack/plugins/maps/public/react_embeddable/map_renderer_lazy.tsx index 2fff281a23ffd..2ef3689b4e327 100644 --- a/x-pack/plugins/maps/public/embeddable/map_component_lazy.tsx +++ b/x-pack/plugins/maps/public/react_embeddable/map_renderer_lazy.tsx @@ -7,15 +7,15 @@ import React from 'react'; import { dynamic } from '@kbn/shared-ux-utility'; -import type { Props } from './map_component'; +import type { Props } from './map_renderer'; const Component = dynamic(async () => { - const { MapComponent } = await import('./map_component'); + const { MapRenderer } = await import('./map_renderer'); return { - default: MapComponent, + default: MapRenderer, }; }); -export function MapComponentLazy(props: Props) { +export function MapRendererLazy(props: Props) { return ; } diff --git a/x-pack/plugins/maps/public/react_embeddable/setup_map_embeddable.ts b/x-pack/plugins/maps/public/react_embeddable/setup_map_embeddable.ts new file mode 100644 index 0000000000000..efaf3238a8068 --- /dev/null +++ b/x-pack/plugins/maps/public/react_embeddable/setup_map_embeddable.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 { EmbeddableSetup } from '@kbn/embeddable-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { MapAttributes } from '../../common/content_management'; +import { MAP_SAVED_OBJECT_TYPE, APP_ICON } from '../../common/constants'; +import { untilPluginStartServicesReady } from '../kibana_services'; + +export function setupMapEmbeddable(embeddableSetup: EmbeddableSetup) { + embeddableSetup.registerReactEmbeddableFactory(MAP_SAVED_OBJECT_TYPE, async () => { + const startServicesPromise = untilPluginStartServicesReady(); + const [, { mapEmbeddableFactory }] = await Promise.all([ + startServicesPromise, + import('./map_react_embeddable'), + ]); + + return mapEmbeddableFactory; + }); + + embeddableSetup.registerReactEmbeddableSavedObject({ + onAdd: (container, savedObject) => { + container.addNewPanel({ + panelType: MAP_SAVED_OBJECT_TYPE, + initialState: { savedObjectId: savedObject.id }, + }); + }, + embeddableType: MAP_SAVED_OBJECT_TYPE, + savedObjectType: MAP_SAVED_OBJECT_TYPE, + savedObjectName: i18n.translate('xpack.maps.mapSavedObjectLabel', { + defaultMessage: 'Map', + }), + getIconForSavedObject: () => APP_ICON, + }); +} diff --git a/x-pack/plugins/maps/public/react_embeddable/types.ts b/x-pack/plugins/maps/public/react_embeddable/types.ts new file mode 100644 index 0000000000000..f020e75723c6c --- /dev/null +++ b/x-pack/plugins/maps/public/react_embeddable/types.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public'; +import { TimeRange } from '@kbn/es-query'; +import { HasInspectorAdapters } from '@kbn/inspector-plugin/public'; +import { + apiIsOfType, + apiPublishesPanelTitle, + apiPublishesUnifiedSearch, + HasEditCapabilities, + HasLibraryTransforms, + HasSupportedTriggers, + PublishesDataLoading, + PublishesDataViews, + PublishesUnifiedSearch, + SerializedTitles, +} from '@kbn/presentation-publishing'; +import { HasDynamicActions } from '@kbn/embeddable-enhanced-plugin/public'; +import { DynamicActionsSerializedState } from '@kbn/embeddable-enhanced-plugin/public/plugin'; +import { Observable } from 'rxjs'; +import { MapAttributes } from '../../common/content_management'; +import { + LayerDescriptor, + MapCenterAndZoom, + MapExtent, + MapSettings, +} from '../../common/descriptor_types'; +import { ILayer } from '../classes/layers/layer'; +import { RenderToolTipContent } from '../classes/tooltips/tooltip_property'; +import { EventHandlers } from '../reducers/non_serializable_instances'; + +export type MapSerializedState = SerializedTitles & + Partial & { + // by-valye + attributes?: MapAttributes; + // by-reference + savedObjectId?: string; + + isLayerTOCOpen?: boolean; + openTOCDetails?: string[]; + mapCenter?: MapCenterAndZoom; + mapBuffer?: MapExtent; + mapSettings?: Partial; + hiddenLayers?: string[]; + hideFilterActions?: boolean; + timeRange?: TimeRange; + filterByMapExtent?: boolean; + isMovementSynchronized?: boolean; + + // Configuration item that are never persisted + // Putting in state as a temporary work around + isSharable?: boolean; + tooltipRenderer?: RenderToolTipContent; + }; + +export type MapRuntimeState = MapSerializedState; + +export type MapApi = DefaultEmbeddableApi & + HasDynamicActions & + Partial & + HasInspectorAdapters & + HasSupportedTriggers & + PublishesDataLoading & + PublishesDataViews & + PublishesUnifiedSearch & + HasLibraryTransforms & { + getLayerList: () => ILayer[]; + reload: () => void; + setEventHandlers: (eventHandlers: EventHandlers) => void; + setLayerList: (layerList: LayerDescriptor[]) => void; + updateLayerById: (layerDescriptor: LayerDescriptor) => void; + onRenderComplete$: Observable; + }; + +export const isMapApi = (api: unknown): api is MapApi => { + return Boolean( + api && + apiIsOfType(api, 'map') && + typeof (api as MapApi).getLayerList === 'function' && + apiPublishesPanelTitle(api) && + apiPublishesUnifiedSearch(api) + ); +}; diff --git a/x-pack/plugins/maps/public/render_app.tsx b/x-pack/plugins/maps/public/render_app.tsx index 2c22564b5e405..71caec5db8c49 100644 --- a/x-pack/plugins/maps/public/render_app.tsx +++ b/x-pack/plugins/maps/public/render_app.tsx @@ -27,9 +27,9 @@ import { getCore, } from './kibana_services'; import { ListPage, MapPage } from './routes'; -import { MapByValueInput, MapByReferenceInput } from './embeddable/types'; import { APP_ID } from '../common/constants'; import { registerLayerWizards } from './classes/layers/wizards/load_layer_wizards'; +import { MapSerializedState } from './react_embeddable/types'; function setAppChrome() { if (!getMapsCapabilities().save) { @@ -83,19 +83,19 @@ export async function renderApp( const { embeddableId, originatingApp, valueInput, originatingPath } = stateTransfer.getIncomingEditorState(APP_ID) || {}; - let mapEmbeddableInput; + let mapSerializedState: MapSerializedState | undefined; if (routeProps.match.params.savedMapId) { - mapEmbeddableInput = { + mapSerializedState = { savedObjectId: routeProps.match.params.savedMapId, - } as MapByReferenceInput; + }; } else if (valueInput) { - mapEmbeddableInput = valueInput as MapByValueInput; + mapSerializedState = valueInput as MapSerializedState; } return ( { this.state = { savedMap: new SavedMap({ defaultLayers: getInitialLayersFromUrlParam(), - mapEmbeddableInput: props.mapEmbeddableInput, + mapSerializedState: props.mapSerializedState, embeddableId: props.embeddableId, originatingApp: props.originatingApp, originatingPath: props.originatingPath, diff --git a/x-pack/plugins/maps/public/routes/map_page/saved_map/load_from_library.ts b/x-pack/plugins/maps/public/routes/map_page/saved_map/load_from_library.ts new file mode 100644 index 0000000000000..7cbd33d522b26 --- /dev/null +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/load_from_library.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ResolvedSimpleSavedObject, SavedObjectReference } from '@kbn/core/public'; +import type { MapAttributes } from '../../../../common/content_management'; +import { getMapClient } from '../../../content_management'; +import { injectReferences } from '../../../../common/migrations/references'; + +export interface SharingSavedObjectProps { + outcome?: ResolvedSimpleSavedObject['outcome']; + aliasTargetId?: ResolvedSimpleSavedObject['alias_target_id']; + aliasPurpose?: ResolvedSimpleSavedObject['alias_purpose']; + sourceId?: string; +} + +export async function loadFromLibrary(savedObjectId: string): Promise<{ + attributes: MapAttributes; + sharingSavedObjectProps: SharingSavedObjectProps; + managed: boolean; + references?: SavedObjectReference[]; +}> { + const { + item: savedObject, + meta: { outcome, aliasPurpose, aliasTargetId }, + } = await getMapClient().get(savedObjectId); + + if (savedObject.error) { + throw savedObject.error; + } + + const { attributes } = injectReferences(savedObject); + + return { + attributes, + sharingSavedObjectProps: { + aliasTargetId, + outcome, + aliasPurpose, + sourceId: savedObjectId, + }, + managed: Boolean(savedObject.managed), + references: savedObject.references, + }; +} diff --git a/x-pack/plugins/maps/public/routes/map_page/saved_map/save_to_library.ts b/x-pack/plugins/maps/public/routes/map_page/saved_map/save_to_library.ts new file mode 100644 index 0000000000000..a45572507bf07 --- /dev/null +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/save_to_library.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectReference } from '@kbn/core/public'; +import type { MapAttributes } from '../../../../common/content_management'; +import { getMapClient } from '../../../content_management'; + +export async function saveToLibrary( + attributes: MapAttributes, + references: SavedObjectReference[], + savedObjectId?: string +) { + const { + item: { id }, + } = await (savedObjectId + ? getMapClient().update({ + id: savedObjectId, + data: attributes, + options: { references }, + }) + : getMapClient().create({ data: attributes, options: { references } })); + return { id }; +} diff --git a/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts b/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts index 26838d6f5ec13..197052a19df98 100644 --- a/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts +++ b/x-pack/plugins/maps/public/routes/map_page/saved_map/saved_map.ts @@ -35,8 +35,9 @@ import { setHiddenLayers, } from '../../../actions'; import { getIsLayerTOCOpen, getOpenTOCDetails } from '../../../selectors/ui_selectors'; -import { getMapAttributeService, SharingSavedObjectProps } from '../../../map_attribute_service'; -import { MapByReferenceInput, MapEmbeddableInput } from '../../../embeddable/types'; +import { loadFromLibrary, SharingSavedObjectProps } from './load_from_library'; +import { saveToLibrary } from './save_to_library'; +import { MapSerializedState } from '../../../react_embeddable/types'; import { getCoreChrome, getIndexPatternService, @@ -56,6 +57,8 @@ import { ParsedMapStateJSON, ParsedUiStateJSON } from './types'; import { setAutoOpenLayerWizardId } from '../../../actions/ui_actions'; import { LayerStatsCollector, MapSettingsCollector } from '../../../../common/telemetry'; import { getIndexPatternsFromIds } from '../../../index_pattern_util'; +import { extractReferences } from '../../../../common/migrations/references'; +import { getByReferenceState, getByValueState } from '../../../react_embeddable/library_transforms'; function setMapSettingsFromEncodedState(settings: Partial) { const decodedCustomIcons = settings.customIcons @@ -76,7 +79,7 @@ export class SavedMap { private readonly _defaultLayers: LayerDescriptor[]; private readonly _embeddableId?: string; private _initialLayerListConfig: LayerDescriptor[] = []; - private _mapEmbeddableInput?: MapEmbeddableInput; + private _mapSerializedState?: MapSerializedState; private readonly _onSaveCallback?: () => void; private _originatingApp?: string; private _originatingPath?: string; @@ -88,7 +91,7 @@ export class SavedMap { constructor({ defaultLayers = [], - mapEmbeddableInput, + mapSerializedState, embeddableId, onSaveCallback, originatingApp, @@ -97,7 +100,7 @@ export class SavedMap { defaultLayerWizard, }: { defaultLayers?: LayerDescriptor[]; - mapEmbeddableInput?: MapEmbeddableInput; + mapSerializedState?: MapSerializedState; embeddableId?: string; onSaveCallback?: () => void; originatingApp?: string; @@ -106,7 +109,7 @@ export class SavedMap { defaultLayerWizard?: string; }) { this._defaultLayers = defaultLayers; - this._mapEmbeddableInput = mapEmbeddableInput; + this._mapSerializedState = mapSerializedState; this._embeddableId = embeddableId; this._onSaveCallback = onSaveCallback; this._originatingApp = originatingApp; @@ -124,25 +127,25 @@ export class SavedMap { async whenReady() { await whenLicenseInitialized(); - if (!this._mapEmbeddableInput) { - this._attributes = { - title: '', - description: '', - }; - } else { - const { attributes: doc, metaInfo } = await getMapAttributeService().unwrapAttributes( - this._mapEmbeddableInput + if (this._mapSerializedState?.savedObjectId) { + const { attributes, managed, references, sharingSavedObjectProps } = await loadFromLibrary( + this._mapSerializedState.savedObjectId ); - const { references, ...savedObjectAttributes } = doc; - this._attributes = savedObjectAttributes; - if (metaInfo?.sharingSavedObjectProps) { - this._sharingSavedObjectProps = metaInfo.sharingSavedObjectProps; + this._attributes = attributes; + if (sharingSavedObjectProps) { + this._sharingSavedObjectProps = sharingSavedObjectProps; } - this._managed = Boolean(metaInfo?.managed); + this._managed = managed; const savedObjectsTagging = getSavedObjectsTagging(); if (savedObjectsTagging && references && references.length) { this._tags = savedObjectsTagging.ui.getTagIdsFromReferences(references); } + } else { + this._attributes = this._mapSerializedState?.attributes + ? this._mapSerializedState.attributes + : { + title: '', + }; } this._reportUsage(); @@ -162,8 +165,8 @@ export class SavedMap { } } - if (this._mapEmbeddableInput && this._mapEmbeddableInput.mapSettings !== undefined) { - this._store.dispatch(setMapSettingsFromEncodedState(this._mapEmbeddableInput.mapSettings)); + if (this._mapSerializedState?.mapSettings !== undefined) { + this._store.dispatch(setMapSettingsFromEncodedState(this._mapSerializedState.mapSettings)); } else if (this._attributes?.mapStateJSON) { try { const mapState = JSON.parse(this._attributes.mapStateJSON) as ParsedMapStateJSON; @@ -176,8 +179,8 @@ export class SavedMap { } let isLayerTOCOpen = DEFAULT_IS_LAYER_TOC_OPEN; - if (this._mapEmbeddableInput && this._mapEmbeddableInput.isLayerTOCOpen !== undefined) { - isLayerTOCOpen = this._mapEmbeddableInput.isLayerTOCOpen; + if (this._mapSerializedState?.isLayerTOCOpen !== undefined) { + isLayerTOCOpen = this._mapSerializedState.isLayerTOCOpen; } else if (this._attributes?.uiStateJSON) { try { const uiState = JSON.parse(this._attributes.uiStateJSON) as ParsedUiStateJSON; @@ -191,8 +194,8 @@ export class SavedMap { this._store.dispatch(setIsLayerTOCOpen(isLayerTOCOpen)); let openTOCDetails: string[] = []; - if (this._mapEmbeddableInput && this._mapEmbeddableInput.openTOCDetails !== undefined) { - openTOCDetails = this._mapEmbeddableInput.openTOCDetails; + if (this._mapSerializedState?.openTOCDetails !== undefined) { + openTOCDetails = this._mapSerializedState.openTOCDetails; } else if (this._attributes?.uiStateJSON) { try { const uiState = JSON.parse(this._attributes.uiStateJSON) as ParsedUiStateJSON; @@ -205,12 +208,12 @@ export class SavedMap { } this._store.dispatch(setOpenTOCDetails(openTOCDetails)); - if (this._mapEmbeddableInput && this._mapEmbeddableInput.mapCenter !== undefined) { + if (this._mapSerializedState?.mapCenter !== undefined) { this._store.dispatch( setGotoWithCenter({ - lat: this._mapEmbeddableInput.mapCenter.lat, - lon: this._mapEmbeddableInput.mapCenter.lon, - zoom: this._mapEmbeddableInput.mapCenter.zoom, + lat: this._mapSerializedState.mapCenter.lat, + lon: this._mapSerializedState.mapCenter.lon, + zoom: this._mapSerializedState.mapCenter.zoom, }) ); } else if (this._attributes?.mapStateJSON) { @@ -245,8 +248,8 @@ export class SavedMap { } } this._store.dispatch(replaceLayerList(layerList)); - if (this._mapEmbeddableInput && this._mapEmbeddableInput.hiddenLayers !== undefined) { - this._store.dispatch(setHiddenLayers(this._mapEmbeddableInput.hiddenLayers)); + if (this._mapSerializedState?.hiddenLayers !== undefined) { + this._store.dispatch(setHiddenLayers(this._mapSerializedState.hiddenLayers)); } this._initialLayerListConfig = copyPersistentState(layerList); @@ -283,7 +286,7 @@ export class SavedMap { } private _getPageTitle(): string { - if (!this._mapEmbeddableInput) { + if (!this._mapSerializedState) { return i18n.translate('xpack.maps.breadcrumbsCreate', { defaultMessage: 'Create', }); @@ -354,8 +357,8 @@ export class SavedMap { } public getSavedObjectId(): string | undefined { - return this._mapEmbeddableInput && 'savedObjectId' in this._mapEmbeddableInput - ? (this._mapEmbeddableInput as MapByReferenceInput).savedObjectId + return this._mapSerializedState?.savedObjectId + ? this._mapSerializedState.savedObjectId : undefined; } @@ -404,11 +407,8 @@ export class SavedMap { } public getAutoFitToBounds(): boolean { - if ( - this._mapEmbeddableInput && - this._mapEmbeddableInput?.mapSettings?.autoFitToDataBounds !== undefined - ) { - return this._mapEmbeddableInput.mapSettings.autoFitToDataBounds; + if (this._mapSerializedState?.mapSettings?.autoFitToDataBounds !== undefined) { + return this._mapSerializedState.mapSettings.autoFitToDataBounds; } if (!this._attributes || !this._attributes.mapStateJSON) { @@ -445,13 +445,13 @@ export class SavedMap { newTitle, newCopyOnSave, returnToOrigin, - newTags, + tags, saveByReference, dashboardId, history, }: OnSaveProps & { returnToOrigin?: boolean; - newTags?: string[]; + tags?: string[]; saveByReference: boolean; dashboardId?: string | null; history: ScopedHistory; @@ -462,36 +462,44 @@ export class SavedMap { const prevTitle = this._attributes.title; const prevDescription = this._attributes.description; - const prevTags = this._tags; this._attributes.title = newTitle; this._attributes.description = newDescription; - if (newTags) { - this._tags = newTags; - } await this._syncAttributesWithStore(); - let updatedMapEmbeddableInput: MapEmbeddableInput; - try { - const savedObjectsTagging = getSavedObjectsTagging(); - // Attribute service deviates from Saved Object client by including references as a child to attributes in stead of a sibling - const attributes = - savedObjectsTagging && newTags - ? { - ...this._attributes, - references: savedObjectsTagging.ui.updateTagsReferences([], newTags), - } - : this._attributes; - updatedMapEmbeddableInput = (await getMapAttributeService().wrapAttributes( - attributes, - saveByReference, - newCopyOnSave ? undefined : this._mapEmbeddableInput - )) as MapEmbeddableInput; - } catch (e) { - // Error toast displayed by wrapAttributes - this._attributes.title = prevTitle; - this._attributes.description = prevDescription; - this._tags = prevTags; - return; + let mapSerializedState: MapSerializedState | undefined; + if (saveByReference) { + try { + const { attributes, references } = extractReferences({ + attributes: this._attributes, + }); + const savedObjectsTagging = getSavedObjectsTagging(); + const tagReferences = + savedObjectsTagging && tags ? savedObjectsTagging.ui.updateTagsReferences([], tags) : []; + const { id: savedObjectId } = await saveToLibrary( + attributes, + [...references, ...tagReferences], + newCopyOnSave ? undefined : this._mapSerializedState?.savedObjectId + ); + mapSerializedState = getByReferenceState(this._mapSerializedState, savedObjectId); + } catch (e) { + this._attributes.title = prevTitle; + this._attributes.description = prevDescription; + getToasts().addDanger({ + title: i18n.translate('xpack.maps.saveToLibraryError', { + defaultMessage: `An error occurred while saving. Error: {errorMessage}`, + values: { + errorMessage: e.message, + }, + }), + }); + return; + } + } else { + mapSerializedState = getByValueState(this._mapSerializedState, this._attributes); + } + + if (tags) { + this._tags = tags; } if (returnToOrigin) { @@ -511,7 +519,7 @@ export class SavedMap { state: { embeddableId: newCopyOnSave ? undefined : this._embeddableId, type: MAP_SAVED_OBJECT_TYPE, - input: updatedMapEmbeddableInput, + input: mapSerializedState, }, path: this._originatingPath, }); @@ -520,14 +528,14 @@ export class SavedMap { await this._getStateTransfer().navigateToWithEmbeddablePackage('dashboards', { state: { type: MAP_SAVED_OBJECT_TYPE, - input: updatedMapEmbeddableInput, + input: mapSerializedState, }, path: dashboardId === 'new' ? '#/create' : `#/view/${dashboardId}`, }); return; } - this._mapEmbeddableInput = updatedMapEmbeddableInput; + this._mapSerializedState = mapSerializedState; // break connection to originating application this._originatingApp = undefined; diff --git a/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx b/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx index 221d90ac4f2ca..706b3b58ca979 100644 --- a/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx +++ b/x-pack/plugins/maps/public/routes/map_page/top_nav_config.tsx @@ -150,15 +150,15 @@ export function getTopNavConfig({ } }, run: () => { - let selectedTags = savedMap.getTags(); - function onTagsSelected(newTags: string[]) { - selectedTags = newTags; + let tags = savedMap.getTags(); + function onTagsSelected(nextTags: string[]) { + tags = nextTags; } const savedObjectsTagging = getSavedObjectsTagging(); const tagSelector = savedObjectsTagging ? ( @@ -193,7 +193,7 @@ export function getTopNavConfig({ await savedMap.save({ ...props, - newTags: selectedTags, + tags, saveByReference: props.addToLibrary, history, }); diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.ts b/x-pack/plugins/maps/public/selectors/map_selectors.ts index 78950c1ab2e7f..ff47c2ccfa874 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.ts @@ -373,10 +373,6 @@ export function getLayerById(layerId: string | null, state: MapStoreState): ILay }); } -export const getHiddenLayerIds = createSelector(getLayerListRaw, (layers) => - layers.filter((layer) => !layer.visible).map((layer) => layer.id) -); - export const getSelectedLayer = createSelector( getSelectedLayerId, getLayerList, diff --git a/x-pack/plugins/maps/public/trigger_actions/filter_by_map_extent/modal.tsx b/x-pack/plugins/maps/public/trigger_actions/filter_by_map_extent/modal.tsx index 9e5127f9329ba..0a992e02a6cfd 100644 --- a/x-pack/plugins/maps/public/trigger_actions/filter_by_map_extent/modal.tsx +++ b/x-pack/plugins/maps/public/trigger_actions/filter_by_map_extent/modal.tsx @@ -15,7 +15,7 @@ import { EuiSwitchEvent, } from '@elastic/eui'; import { createReactOverlays } from '@kbn/kibana-react-plugin/public'; -import { mapEmbeddablesSingleton } from '../../embeddable/map_embeddables_singleton'; +import { mapEmbeddablesSingleton } from '../../react_embeddable/map_embeddables_singleton'; import { getCore } from '../../kibana_services'; export function openModal(title: string) { diff --git a/x-pack/plugins/maps/public/trigger_actions/synchronize_movement/is_compatible.ts b/x-pack/plugins/maps/public/trigger_actions/synchronize_movement/is_compatible.ts index 520ed2e1cc92e..6741b0eff4276 100644 --- a/x-pack/plugins/maps/public/trigger_actions/synchronize_movement/is_compatible.ts +++ b/x-pack/plugins/maps/public/trigger_actions/synchronize_movement/is_compatible.ts @@ -10,7 +10,7 @@ import { apiHasVisualizeConfig } from '@kbn/visualizations-plugin/public'; import { isLensApi } from '@kbn/lens-plugin/public'; import { MAP_SAVED_OBJECT_TYPE } from '../../../common/constants'; import { isLegacyMapApi } from '../../legacy_visualizations/is_legacy_map'; -import { mapEmbeddablesSingleton } from '../../embeddable/map_embeddables_singleton'; +import { mapEmbeddablesSingleton } from '../../react_embeddable/map_embeddables_singleton'; import type { SynchronizeMovementActionApi } from './types'; export function isCompatible(api: SynchronizeMovementActionApi) { diff --git a/x-pack/plugins/maps/public/trigger_actions/synchronize_movement/modal.tsx b/x-pack/plugins/maps/public/trigger_actions/synchronize_movement/modal.tsx index fa3ad5bfa8031..a51a9e59dbae3 100644 --- a/x-pack/plugins/maps/public/trigger_actions/synchronize_movement/modal.tsx +++ b/x-pack/plugins/maps/public/trigger_actions/synchronize_movement/modal.tsx @@ -16,7 +16,7 @@ import { EuiSwitchEvent, } from '@elastic/eui'; import { createReactOverlays } from '@kbn/kibana-react-plugin/public'; -import { mapEmbeddablesSingleton } from '../../embeddable/map_embeddables_singleton'; +import { mapEmbeddablesSingleton } from '../../react_embeddable/map_embeddables_singleton'; import { getCore } from '../../kibana_services'; export function openModal() { diff --git a/x-pack/plugins/maps/tsconfig.json b/x-pack/plugins/maps/tsconfig.json index 96fa01c4c6f62..5821e1e82a21e 100644 --- a/x-pack/plugins/maps/tsconfig.json +++ b/x-pack/plugins/maps/tsconfig.json @@ -84,11 +84,12 @@ "@kbn/code-editor", "@kbn/managed-content-badge", "@kbn/presentation-publishing", - "@kbn/saved-objects-finder-plugin", "@kbn/esql-utils", "@kbn/apm-data-view", "@kbn/shared-ux-utility", "@kbn/react-kibana-context-render", + "@kbn/embeddable-enhanced-plugin", + "@kbn/presentation-containers", "@kbn/field-utils" ], "exclude": [ diff --git a/x-pack/plugins/ml/common/openapi/ml_apis.yaml b/x-pack/plugins/ml/common/openapi/ml_apis.yaml index 9f2a7de498184..d2854d049098e 100644 --- a/x-pack/plugins/ml/common/openapi/ml_apis.yaml +++ b/x-pack/plugins/ml/common/openapi/ml_apis.yaml @@ -1,8 +1,8 @@ -openapi: 3.1.0 +openapi: 3.0.3 info: title: Machine learning APIs description: Kibana APIs for the machine learning feature - version: "1.0.2" + version: "1.0.1" license: name: Elastic License 2.0 url: https://www.elastic.co/licensing/elastic-license @@ -14,8 +14,11 @@ servers: paths: /api/ml/saved_objects/sync: get: - summary: Synchronizes Kibana saved objects for machine learning jobs and trained models. - description: This API runs automatically when you start Kibana and periodically thereafter. + summary: Sync saved objects in the default space + description: > + Synchronizes Kibana saved objects for machine learning jobs and trained models in the default space. + You must have `all` privileges for the **Machine Learning** feature in the **Analytics** section of the Kibana feature privileges. + This API runs automatically when you start Kibana and periodically thereafter. operationId: mlSync tags: - ml @@ -40,8 +43,9 @@ paths: /s/{spaceId}/api/ml/saved_objects/sync: get: - summary: Synchronizes Kibana saved objects for machine learning jobs and trained models. + summary: Sync saved objects description: > + Synchronizes Kibana saved objects for machine learning jobs and trained models. You must have `all` privileges for the **Machine Learning** feature in the **Analytics** section of the Kibana feature privileges. This API runs automatically when you start Kibana and periodically thereafter. operationId: mlSyncWithSpaceId @@ -82,8 +86,7 @@ components: required: false schema: type: boolean - examples: - - true + example: 'true' securitySchemes: basicAuth: type: http @@ -188,14 +191,12 @@ components: properties: error: type: string - examples: - - Unauthorized + example: Unauthorized message: type: string statusCode: type: integer - examples: - - 401 + example: 401 examples: mlSyncExample: summary: Two anomaly detection jobs required synchronization in this example. diff --git a/x-pack/plugins/ml/common/openapi/ml_apis_serverless.yaml b/x-pack/plugins/ml/common/openapi/ml_apis_serverless.yaml index 6ff44e29517e6..d09b78c1155ae 100644 --- a/x-pack/plugins/ml/common/openapi/ml_apis_serverless.yaml +++ b/x-pack/plugins/ml/common/openapi/ml_apis_serverless.yaml @@ -1,8 +1,8 @@ -openapi: 3.1.0 +openapi: 3.0.3 info: title: Machine learning APIs description: Kibana APIs for the machine learning feature - version: "1.0.2" + version: "1.0.1" license: name: Elastic License 2.0 url: https://www.elastic.co/licensing/elastic-license @@ -14,8 +14,10 @@ servers: paths: /api/ml/saved_objects/sync: get: - summary: Synchronizes Kibana saved objects for machine learning jobs and trained models. - description: This API runs automatically when you start Kibana and periodically thereafter. + summary: Sync machine learning saved objects + description: > + Synchronizes Kibana saved objects for machine learning jobs and trained models. + This API runs automatically when you start Kibana and periodically thereafter. operationId: mlSync tags: - ml @@ -46,8 +48,7 @@ components: required: false schema: type: boolean - examples: - - true + example: 'true' securitySchemes: apiKeyAuth: type: apiKey @@ -149,14 +150,12 @@ components: properties: error: type: string - examples: - - Unauthorized + example: Unauthorized message: type: string statusCode: type: integer - examples: - - 401 + example: 401 examples: mlSyncExample: summary: Two anomaly detection jobs required synchronization in this example. diff --git a/x-pack/plugins/ml/common/types/trained_models.ts b/x-pack/plugins/ml/common/types/trained_models.ts index 0d71e90ac30ff..3f493b01e8066 100644 --- a/x-pack/plugins/ml/common/types/trained_models.ts +++ b/x-pack/plugins/ml/common/types/trained_models.ts @@ -300,3 +300,8 @@ export interface TrainedModelStatsResponse extends estypes.MlTrainedModelStats { deployment_stats?: Omit; model_size_stats?: TrainedModelModelSizeStats; } + +export interface ModelDownloadState { + total_parts: number; + downloaded_parts: number; +} 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 eb39cd6c7a7a6..b8f04a24f9655 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 @@ -33,8 +33,6 @@ export const LogRateAnalysisPage: FC = () => { {dataView && ( void) | null; } -export const ImportJobsFlyout: FC = ({ isDisabled }) => { +export const ImportJobsFlyout: FC = ({ isDisabled, onImportComplete }) => { const { services: { data: { @@ -204,6 +205,9 @@ export const ImportJobsFlyout: FC = ({ isDisabled }) => { setImporting(false); setShowFlyout(false); + if (typeof onImportComplete === 'function') { + onImportComplete(); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [jobType, jobIdObjects, adJobs, dfaJobs]); diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/__snapshots__/actions_section.test.js.snap b/x-pack/plugins/ml/public/application/components/rule_editor/__snapshots__/actions_section.test.js.snap index 9b4c14dfa6bef..fec3fa9121d13 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/__snapshots__/actions_section.test.js.snap +++ b/x-pack/plugins/ml/public/application/components/rule_editor/__snapshots__/actions_section.test.js.snap @@ -23,10 +23,7 @@ exports[`ActionsSection renders with no actions selected 1`] = ` > @@ -393,10 +391,8 @@ exports[`RuleEditorFlyout renders the flyout after setting the rule to edit 1`] @@ -629,10 +625,8 @@ exports[`RuleEditorFlyout renders the flyout for creating a rule with conditions diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/__snapshots__/scope_expression.test.js.snap b/x-pack/plugins/ml/public/application/components/rule_editor/__snapshots__/scope_expression.test.js.snap index 4347958d240c0..f5b266b67062c 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/__snapshots__/scope_expression.test.js.snap +++ b/x-pack/plugins/ml/public/application/components/rule_editor/__snapshots__/scope_expression.test.js.snap @@ -10,10 +10,7 @@ exports[`ScopeExpression renders when empty list of filter IDs is supplied 1`] = > @@ -46,10 +43,7 @@ exports[`ScopeExpression renders when enabled set to false 1`] = ` > @@ -190,10 +184,7 @@ exports[`ScopeExpression renders when filter ID and type supplied 1`] = ` > @@ -334,10 +325,7 @@ exports[`ScopeExpression renders when no filter ID or type supplied 1`] = ` > diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/__snapshots__/scope_section.test.js.snap b/x-pack/plugins/ml/public/application/components/rule_editor/__snapshots__/scope_section.test.js.snap index c61f9a3d30d7d..110530cd03e97 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/__snapshots__/scope_section.test.js.snap +++ b/x-pack/plugins/ml/public/application/components/rule_editor/__snapshots__/scope_section.test.js.snap @@ -17,10 +17,7 @@ exports[`ScopeSection false canGetFilters privilege show NoPermissionCallOut whe /> = ({ const [isPlatinumOrTrialLicense, setIsPlatinumOrTrialLicense] = useState(true); const [showSyncFlyout, setShowSyncFlyout] = useState(false); const [currentTabId, setCurrentTabId] = useState('anomaly-detector'); + // callback to allow import flyout to refresh jobs list + const [refreshJobs, setRefreshJobs] = useState<(() => void) | null>(null); const mlServices = useMemo( () => getMlGlobalServices(coreStart.http, data.dataViews, usageCollection), @@ -109,6 +111,9 @@ export const JobsListPage: FC = ({ } function onCloseSyncFlyout() { + if (typeof refreshJobs === 'function') { + refreshJobs(); + } setShowSyncFlyout(false); } @@ -203,10 +208,14 @@ export const JobsListPage: FC = ({ /> - + - + diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/space_management/space_management.tsx b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/space_management/space_management.tsx index fd65469f884d5..76c3293f97061 100644 --- a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/space_management/space_management.tsx +++ b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/space_management/space_management.tsx @@ -34,10 +34,11 @@ import { getFilters } from './filters'; interface Props { spacesApi?: SpacesPluginStart; - setCurrentTab: (tabId: MlSavedObjectType) => void; + onTabChange: (tabId: MlSavedObjectType) => void; + onReload: React.Dispatch void) | null>>; } -export const SpaceManagement: FC = ({ spacesApi, setCurrentTab }) => { +export const SpaceManagement: FC = ({ spacesApi, onTabChange, onReload }) => { const { getList } = useManagementApiService(); const [currentTabId, setCurrentTabId] = useState(null); @@ -101,12 +102,19 @@ export const SpaceManagement: FC = ({ spacesApi, setCurrentTab }) => { [getList, loadingTab] ); + useEffect(() => { + onReload(() => () => refresh(currentTabId)); + return () => { + onReload(null); + }; + }, [currentTabId, refresh, onReload]); + useEffect( function refreshOnTabChange() { setItems(undefined); if (currentTabId !== null) { setColumns(createColumns()); - setCurrentTab(currentTabId); + onTabChange(currentTabId); refresh(currentTabId); setPageIndex(0); } diff --git a/x-pack/plugins/ml/public/application/model_management/get_model_state_color.tsx b/x-pack/plugins/ml/public/application/model_management/get_model_state_color.tsx index 1f065ea673d33..d6051847de4ae 100644 --- a/x-pack/plugins/ml/public/application/model_management/get_model_state_color.tsx +++ b/x-pack/plugins/ml/public/application/model_management/get_model_state_color.tsx @@ -23,7 +23,7 @@ export const getModelStateColor = ( }; case MODEL_STATE.DOWNLOADING: return { - color: 'warning', + color: 'primary', name: i18n.translate('xpack.ml.trainedModels.modelsList.modelState.downloadingName', { defaultMessage: 'Downloading...', }), diff --git a/x-pack/plugins/ml/public/application/model_management/models_list.tsx b/x-pack/plugins/ml/public/application/model_management/models_list.tsx index 242db5904252d..d65db2280a0e8 100644 --- a/x-pack/plugins/ml/public/application/model_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/model_management/models_list.tsx @@ -6,6 +6,7 @@ */ import type { FC } from 'react'; +import { useRef } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import type { SearchFilterConfig } from '@elastic/eui'; import { @@ -22,8 +23,9 @@ import { EuiSpacer, EuiTitle, EuiToolTip, + EuiProgress, } from '@elastic/eui'; -import { groupBy } from 'lodash'; +import { groupBy, isEmpty } from 'lodash'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/basic_table'; @@ -47,6 +49,7 @@ import { import { isDefined } from '@kbn/ml-is-defined'; import { useStorage } from '@kbn/ml-local-storage'; import { dynamic } from '@kbn/shared-ux-utility'; +import useMountedState from 'react-use/lib/useMountedState'; import { getModelStateColor } from './get_model_state_color'; import { ML_ELSER_CALLOUT_DISMISSED } from '../../../common/types/storage'; import { TechnicalPreviewBadge } from '../components/technical_preview_badge'; @@ -57,6 +60,7 @@ import { StatsBar } from '../components/stats_bar'; import { useMlKibana } from '../contexts/kibana'; import { useTrainedModelsApiService } from '../services/ml_api_service/trained_models'; import type { + ModelDownloadState, ModelPipelines, TrainedModelConfigResponse, TrainedModelDeploymentStatsResponse, @@ -94,6 +98,7 @@ export type ModelItem = TrainedModelConfigResponse & { arch?: string; softwareLicense?: string; licenseUrl?: string; + downloadState?: ModelDownloadState; }; export type ModelItemFull = Required; @@ -127,10 +132,14 @@ interface Props { updatePageState?: (update: Partial) => void; } +const DOWNLOAD_POLL_INTERVAL = 3000; + export const ModelsList: FC = ({ pageState: pageStateExternal, updatePageState: updatePageStateExternal, }) => { + const isMounted = useMountedState(); + const { services: { application: { capabilities }, @@ -320,6 +329,9 @@ export const ModelsList: FC = ({ } setIsInitialized(true); setIsLoading(false); + + await fetchDownloadStatus(); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [itemIdToExpandedRowMap, isNLPEnabled]); @@ -400,6 +412,82 @@ export const ModelsList: FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const downLoadStatusFetchInProgress = useRef(false); + /** + * Updates model list with download status + */ + const fetchDownloadStatus = useCallback( + /** + * @param downloadInProgress Set of model ids that reports download in progress + */ + async (downloadInProgress: Set = new Set()) => { + // Allows only single fetch to be in progress + if (downLoadStatusFetchInProgress.current && downloadInProgress.size === 0) return; + + try { + downLoadStatusFetchInProgress.current = true; + + const downloadStatus = await trainedModelsApiService.getModelsDownloadStatus(); + + if (isMounted()) { + setItems((prevItems) => { + return prevItems.map((item) => { + const newItem = { ...item }; + if (downloadStatus[item.model_id]) { + newItem.downloadState = downloadStatus[item.model_id]; + } else { + if (downloadInProgress.has(item.model_id)) { + // Change downloading state to downloaded + delete newItem.downloadState; + newItem.state = MODEL_STATE.DOWNLOADED; + } + } + return newItem; + }); + }); + } + + const downloadedModelIds = Array.from(downloadInProgress).filter( + (v) => !downloadStatus[v] + ); + + if (downloadedModelIds.length > 0) { + // Show success toast + displaySuccessToast( + i18n.translate('xpack.ml.trainedModels.modelsList.downloadCompleteSuccess', { + defaultMessage: + '"{modelIds}" {modelIdsLength, plural, one {has} other {have}} been downloaded successfully.', + values: { + modelIds: downloadedModelIds.join(', '), + modelIdsLength: downloadedModelIds.length, + }, + }) + ); + } + + Object.keys(downloadStatus).forEach((modelId) => { + if (downloadStatus[modelId]) { + downloadInProgress.add(modelId); + } + }); + downloadedModelIds.forEach((v) => { + downloadInProgress.delete(v); + }); + + if (isEmpty(downloadStatus)) { + downLoadStatusFetchInProgress.current = false; + return; + } + + await new Promise((resolve) => setTimeout(resolve, DOWNLOAD_POLL_INTERVAL)); + await fetchDownloadStatus(downloadInProgress); + } catch (e) { + downLoadStatusFetchInProgress.current = false; + } + }, + [trainedModelsApiService, displaySuccessToast, isMounted] + ); + /** * Unique inference types from models */ @@ -589,19 +677,47 @@ export const ModelsList: FC = ({ }, { width: '10%', - field: 'state', name: i18n.translate('xpack.ml.trainedModels.modelsList.stateHeader', { defaultMessage: 'State', }), align: 'left', truncateText: false, - render: (state: ModelState) => { + render: ({ state, downloadState }: ModelItem) => { const config = getModelStateColor(state); - return config ? ( + if (!config) return null; + + const isDownloadInProgress = state === MODEL_STATE.DOWNLOADING && downloadState; + + const label = ( {config.name} - ) : null; + ); + + return ( + + {isDownloadInProgress ? ( + + + {((downloadState.downloaded_parts / downloadState.total_parts) * 100).toFixed( + 0 + ) + '%'} + + } + value={downloadState?.downloaded_parts} + max={downloadState?.total_parts} + size="xs" + color={config.color} + /> + + ) : ( + {label} + )} + + ); }, 'data-test-subj': 'mlModelsTableColumnDeploymentState', }, diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts index b71eea19ac200..3070c3a4b7441 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts @@ -25,6 +25,7 @@ import type { TrainedModelStat, NodesOverviewResponse, MemoryUsageInfo, + ModelDownloadState, } from '../../../../common/types/trained_models'; export interface InferenceQueryParams { decompress_definition?: boolean; @@ -288,6 +289,14 @@ export function trainedModelsApiProvider(httpService: HttpService) { version: '1', }); }, + + getModelsDownloadStatus() { + return httpService.http>({ + path: `${ML_INTERNAL_BASE_PATH}/trained_models/download_status`, + method: 'GET', + version: '1', + }); + }, }; } diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/imported_events/__snapshots__/imported_events.test.js.snap b/x-pack/plugins/ml/public/application/settings/calendars/edit/imported_events/__snapshots__/imported_events.test.js.snap index b72b338b17a34..87740b17bafb4 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/imported_events/__snapshots__/imported_events.test.js.snap +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/imported_events/__snapshots__/imported_events.test.js.snap @@ -49,10 +49,7 @@ exports[`ImportedEvents Renders imported events 1`] = ` > ) => { - const factory: ReactEmbeddableFactory = - { - type: ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE, - deserializeState: (state) => state.rawState, - buildEmbeddable: async (state, buildApi, uuid, parentApi) => { - if (!apiHasExecutionContext(parentApi)) { - throw new Error('Parent API does not have execution context'); - } - const [coreStartServices, pluginsStartServices] = await getStartServices(); - const anomalyChartsDependencies = await getAnomalyChartsServiceDependencies( - coreStartServices, - pluginsStartServices - ); + const factory: ReactEmbeddableFactory< + AnomalyChartsEmbeddableState, + AnomalyChartsEmbeddableState, + AnomalyChartsEmbeddableApi + > = { + type: ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE, + deserializeState: (state) => state.rawState, + buildEmbeddable: async (state, buildApi, uuid, parentApi) => { + if (!apiHasExecutionContext(parentApi)) { + throw new Error('Parent API does not have execution context'); + } + const [coreStartServices, pluginsStartServices] = await getStartServices(); + const anomalyChartsDependencies = await getAnomalyChartsServiceDependencies( + coreStartServices, + pluginsStartServices + ); - const [, , mlServices] = anomalyChartsDependencies; + const [, , mlServices] = anomalyChartsDependencies; - const subscriptions = new Subscription(); + const subscriptions = new Subscription(); - const { titlesApi, titleComparators, serializeTitles } = initializeTitles(state); - const { - api: timeRangeApi, - comparators: timeRangeComparators, - serialize: serializeTimeRange, - } = initializeTimeRange(state); + const { titlesApi, titleComparators, serializeTitles } = initializeTitles(state); + const { + api: timeRangeApi, + comparators: timeRangeComparators, + serialize: serializeTimeRange, + } = initializeTimeRange(state); - const { - anomalyChartsControlsApi, - dataLoadingApi, - serializeAnomalyChartsState, - anomalyChartsComparators, - onAnomalyChartsDestroy, - } = initializeAnomalyChartsControls(state, titlesApi, parentApi); + const { + anomalyChartsControlsApi, + dataLoadingApi, + serializeAnomalyChartsState, + anomalyChartsComparators, + onAnomalyChartsDestroy, + } = initializeAnomalyChartsControls(state, titlesApi, parentApi); - const api = buildApi( - { - isEditingEnabled: () => true, - getTypeDisplayName: () => - i18n.translate('xpack.ml.components.mlAnomalyExplorerEmbeddable.typeDisplayName', { - defaultMessage: 'anomaly charts', - }), - onEdit: async () => { - try { - const { resolveEmbeddableAnomalyChartsUserInput } = await import( - './anomaly_charts_setup_flyout' - ); - const result = await resolveEmbeddableAnomalyChartsUserInput( - coreStartServices, - pluginsStartServices, - parentApi, - uuid, - { - ...serializeTitles(), - ...serializeAnomalyChartsState(), - } - ); - anomalyChartsControlsApi.updateUserInput(result); - } catch (e) { - // eslint-disable-next-line no-console - console.error(e); - return Promise.reject(); - } - }, - ...titlesApi, - ...timeRangeApi, - ...anomalyChartsControlsApi, - ...dataLoadingApi, - dataViews: buildDataViewPublishingApi( - { - anomalyDetectorService: mlServices.anomalyDetectorService, - dataViewsService: pluginsStartServices.data.dataViews, - }, - { jobIds: anomalyChartsControlsApi.jobIds$ }, - subscriptions - ), - serializeState: () => { - return { - rawState: { - timeRange: undefined, + const api = buildApi( + { + isEditingEnabled: () => true, + getTypeDisplayName: () => + i18n.translate('xpack.ml.components.mlAnomalyExplorerEmbeddable.typeDisplayName', { + defaultMessage: 'anomaly charts', + }), + onEdit: async () => { + try { + const { resolveEmbeddableAnomalyChartsUserInput } = await import( + './anomaly_charts_setup_flyout' + ); + const result = await resolveEmbeddableAnomalyChartsUserInput( + coreStartServices, + pluginsStartServices, + parentApi, + uuid, + { ...serializeTitles(), - ...serializeTimeRange(), ...serializeAnomalyChartsState(), - }, - references: [], - }; + } + ); + anomalyChartsControlsApi.updateUserInput(result); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + return Promise.reject(); + } + }, + ...titlesApi, + ...timeRangeApi, + ...anomalyChartsControlsApi, + ...dataLoadingApi, + dataViews: buildDataViewPublishingApi( + { + anomalyDetectorService: mlServices.anomalyDetectorService, + dataViewsService: pluginsStartServices.data.dataViews, }, + { jobIds: anomalyChartsControlsApi.jobIds$ }, + subscriptions + ), + serializeState: () => { + return { + rawState: { + timeRange: undefined, + ...serializeTitles(), + ...serializeTimeRange(), + ...serializeAnomalyChartsState(), + }, + references: [], + }; }, - { - ...timeRangeComparators, - ...titleComparators, - ...anomalyChartsComparators, - } - ); + }, + { + ...timeRangeComparators, + ...titleComparators, + ...anomalyChartsComparators, + } + ); - const appliedTimeRange$: Observable = fetch$(api).pipe( - map((fetchContext) => fetchContext.timeRange), - distinctUntilChanged(fastIsEqual) - ); + const appliedTimeRange$: Observable = fetch$(api).pipe( + map((fetchContext) => fetchContext.timeRange), + distinctUntilChanged(fastIsEqual) + ); - const { onRenderComplete, onLoading, onError } = dataLoadingApi; - const contextServices = { - mlServices: { - ...mlServices, - }, - ...coreStartServices, - ...pluginsStartServices, - }; + const { onRenderComplete, onLoading, onError } = dataLoadingApi; + const contextServices = { + mlServices: { + ...mlServices, + }, + ...coreStartServices, + ...pluginsStartServices, + }; - return { - api, - Component: () => { - if (!apiHasExecutionContext(parentApi)) { - throw new Error('Parent API does not have execution context'); - } + return { + api, + Component: () => { + if (!apiHasExecutionContext(parentApi)) { + throw new Error('Parent API does not have execution context'); + } - useReactEmbeddableExecutionContext( - coreStartServices.executionContext, - parentApi.executionContext, - ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE, - uuid - ); + useReactEmbeddableExecutionContext( + coreStartServices.executionContext, + parentApi.executionContext, + ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE, + uuid + ); - useUnmount(() => { - onAnomalyChartsDestroy(); - subscriptions.unsubscribe(); - }); - const { euiTheme } = useEuiTheme(); + useUnmount(() => { + onAnomalyChartsDestroy(); + subscriptions.unsubscribe(); + }); + const { euiTheme } = useEuiTheme(); - return ( - - -
- -
-
-
- ); - }, - }; - }, - }; + return ( + + +
+ +
+
+
+ ); + }, + }; + }, + }; return factory; }; diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.test.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.test.tsx index 4765950b341f9..61a2fadeb6df8 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.test.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.test.tsx @@ -97,7 +97,11 @@ describe('getAnomalySwimLaneEmbeddableFactory', () => { >; render( - + maybeId={'maybe_id'} type={ANOMALY_SWIMLANE_EMBEDDABLE_TYPE} onApiAvailable={onApiAvailable} diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.tsx index c67c2ed157cda..6de07c5db851e 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable_factory.tsx @@ -89,8 +89,8 @@ export const getAnomalySwimLaneEmbeddableFactory = ( ) => { const factory: ReactEmbeddableFactory< AnomalySwimLaneEmbeddableState, - AnomalySwimLaneEmbeddableApi, - AnomalySwimlaneRuntimeState + AnomalySwimlaneRuntimeState, + AnomalySwimLaneEmbeddableApi > = { type: ANOMALY_SWIMLANE_EMBEDDABLE_TYPE, deserializeState: (state) => state.rawState, diff --git a/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_embeddable_factory.tsx b/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_embeddable_factory.tsx index 3fa630d7194e9..dcddb9f2d373e 100644 --- a/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_embeddable_factory.tsx +++ b/x-pack/plugins/ml/public/embeddables/single_metric_viewer/single_metric_viewer_embeddable_factory.tsx @@ -36,8 +36,8 @@ export const getSingleMetricViewerEmbeddableFactory = ( ) => { const factory: ReactEmbeddableFactory< SingleMetricViewerEmbeddableState, - SingleMetricViewerEmbeddableApi, - SingleMetricViewerRuntimeState + SingleMetricViewerRuntimeState, + SingleMetricViewerEmbeddableApi > = { type: ANOMALY_SINGLE_METRIC_VIEWER_EMBEDDABLE_TYPE, deserializeState: (state) => state.rawState, diff --git a/x-pack/plugins/ml/public/shared_components/anomaly_swim_lane.tsx b/x-pack/plugins/ml/public/shared_components/anomaly_swim_lane.tsx index b7748a753cc5c..5c995d0ddcb65 100644 --- a/x-pack/plugins/ml/public/shared_components/anomaly_swim_lane.tsx +++ b/x-pack/plugins/ml/public/shared_components/anomaly_swim_lane.tsx @@ -116,7 +116,11 @@ export const AnomalySwimLane: FC = ({ ); return ( - + maybeId={id} type={ANOMALY_SWIMLANE_EMBEDDABLE_TYPE} getParentApi={() => parentApi} diff --git a/x-pack/plugins/ml/scripts/apidoc_scripts/apidoc_config/apidoc.json b/x-pack/plugins/ml/scripts/apidoc_scripts/apidoc_config/apidoc.json index 4ee93e298c9a8..c1dde5fda0507 100644 --- a/x-pack/plugins/ml/scripts/apidoc_scripts/apidoc_config/apidoc.json +++ b/x-pack/plugins/ml/scripts/apidoc_scripts/apidoc_config/apidoc.json @@ -183,6 +183,7 @@ "GetTrainedModelDownloadList", "GetElserConfig", "InstallElasticTrainedModel", + "ModelsDownloadStatus", "Alerting", "PreviewAlert", diff --git a/x-pack/plugins/ml/server/models/model_management/__mocks__/mock_download_tasks.json b/x-pack/plugins/ml/server/models/model_management/__mocks__/mock_download_tasks.json new file mode 100644 index 0000000000000..7e38582dc48e9 --- /dev/null +++ b/x-pack/plugins/ml/server/models/model_management/__mocks__/mock_download_tasks.json @@ -0,0 +1,46 @@ +{ + "tasks": [ + { + "node": "ONkVoIlIRa-gtI1Lr6Zj7Q", + "id": 16582586, + "type": "model_import", + "action": "xpack/ml/model_import[n]", + "status": { + "total_parts": 418, + "downloaded_parts": 0 + }, + "description": "model_id-.elser_model_2", + "start_time_in_millis": 1717594443997, + "running_time_in_nanos": 533070792, + "cancellable": true, + "cancelled": false, + "parent_task_id": "ONkVoIlIRa-gtI1Lr6Zj7Q:16582585", + "headers": { + "X-elastic-product-origin": "kibana", + "trace.id": "fc0078d94fb25500d1e1e24a3315e453", + "X-Opaque-Id": "fb9651ab-ee33-4c52-8629-508bab1d4fc8;kibana:application:ml:%2Ftrained_models;application:ml:%2Finternal%2Fml%2Ftrained_models%2Finstall_elastic_trained_model%2F.elser_model_2" + } + }, + { + "node": "ONkVoIlIRa-gtI1Lr6Zj7Q", + "id": 16581272, + "type": "model_import", + "action": "xpack/ml/model_import[n]", + "status": { + "total_parts": 263, + "downloaded_parts": 96 + }, + "description": "model_id-.elser_model_2_linux-x86_64", + "start_time_in_millis": 1717594437574, + "running_time_in_nanos": 6956247000, + "cancellable": true, + "cancelled": false, + "parent_task_id": "ONkVoIlIRa-gtI1Lr6Zj7Q:16581271", + "headers": { + "X-elastic-product-origin": "kibana", + "trace.id": "3a63a7395996bdc50515b7821b5df4c7", + "X-Opaque-Id": "6027219b-92a4-447e-9962-ffa3859fe7cd;kibana:application:ml:%2Ftrained_models;application:ml:%2Finternal%2Fml%2Ftrained_models%2Finstall_elastic_trained_model%2F.elser_model_2_linux-x86_64" + } + } + ] +} diff --git a/x-pack/plugins/ml/server/models/model_management/model_provider.test.ts b/x-pack/plugins/ml/server/models/model_management/model_provider.test.ts index 48c80ceca1a95..85d11ebf983e5 100644 --- a/x-pack/plugins/ml/server/models/model_management/model_provider.test.ts +++ b/x-pack/plugins/ml/server/models/model_management/model_provider.test.ts @@ -9,6 +9,7 @@ import { modelsProvider } from './models_provider'; import { type IScopedClusterClient } from '@kbn/core/server'; import { cloudMock } from '@kbn/cloud-plugin/server/mocks'; import type { MlClient } from '../../lib/ml_client'; +import downloadTasksResponse from './__mocks__/mock_download_tasks.json'; describe('modelsProvider', () => { const mockClient = { @@ -34,6 +35,9 @@ describe('modelsProvider', () => { }, }), }, + tasks: { + list: jest.fn().mockResolvedValue({ tasks: [] }), + }, }, } as unknown as jest.Mocked; @@ -263,4 +267,21 @@ describe('modelsProvider', () => { expect(result.model_id).toEqual('.multilingual-e5-small'); }); }); + + describe('getModelsDownloadStatus', () => { + test('returns null if no model download is in progress', async () => { + const result = await modelService.getModelsDownloadStatus(); + expect(result).toEqual({}); + }); + test('provides download status for all models', async () => { + (mockClient.asInternalUser.tasks.list as jest.Mock).mockResolvedValueOnce( + downloadTasksResponse + ); + const result = await modelService.getModelsDownloadStatus(); + expect(result).toEqual({ + '.elser_model_2': { downloaded_parts: 0, total_parts: 418 }, + '.elser_model_2_linux-x86_64': { downloaded_parts: 96, total_parts: 263 }, + }); + }); + }); }); diff --git a/x-pack/plugins/ml/server/models/model_management/models_provider.ts b/x-pack/plugins/ml/server/models/model_management/models_provider.ts index 033c6fafff7af..c56e6ba599d0d 100644 --- a/x-pack/plugins/ml/server/models/model_management/models_provider.ts +++ b/x-pack/plugins/ml/server/models/model_management/models_provider.ts @@ -12,6 +12,7 @@ import { flatten } from 'lodash'; import type { InferenceModelConfig, InferenceTaskType, + TasksTaskInfo, TransformGetTransformTransformSummary, } from '@elastic/elasticsearch/lib/api/types'; import type { IndexName, IndicesIndexState } from '@elastic/elasticsearch/lib/api/types'; @@ -28,7 +29,7 @@ import { } from '@kbn/ml-trained-models-utils'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { ElasticCuratedModelName } from '@kbn/ml-trained-models-utils'; -import type { PipelineDefinition } from '../../../common/types/trained_models'; +import type { ModelDownloadState, PipelineDefinition } from '../../../common/types/trained_models'; import type { MlClient } from '../../lib/ml_client'; import type { MLSavedObjectService } from '../../saved_objects'; @@ -602,4 +603,28 @@ export class ModelsProvider { model_config: modelConfig, }); } + + async getModelsDownloadStatus() { + const result = await this._client.asInternalUser.tasks.list({ + actions: 'xpack/ml/model_import[n]', + detailed: true, + group_by: 'none', + }); + + if (!result.tasks?.length) { + return {}; + } + + // Groups results by model id + const byModelId = (result.tasks as TasksTaskInfo[]).reduce((acc, task) => { + const modelId = task.description!.replace(`model_id-`, ''); + acc[modelId] = { + downloaded_parts: task.status.downloaded_parts, + total_parts: task.status.total_parts, + }; + return acc; + }, {} as Record); + + return byModelId; + } } diff --git a/x-pack/plugins/ml/server/routes/trained_models.ts b/x-pack/plugins/ml/server/routes/trained_models.ts index 6981443cadf04..cdd4d8128c219 100644 --- a/x-pack/plugins/ml/server/routes/trained_models.ts +++ b/x-pack/plugins/ml/server/routes/trained_models.ts @@ -579,10 +579,15 @@ export function trainedModelsRoutes( routeGuard.fullLicenseAPIGuard(async ({ mlClient, request, response }) => { try { const { modelId } = request.params; - const body = await mlClient.startTrainedModelDeployment({ - model_id: modelId, - ...(request.query ? request.query : {}), - }); + const body = await mlClient.startTrainedModelDeployment( + { + model_id: modelId, + ...(request.query ? request.query : {}), + }, + { + maxRetries: 0, + } + ); return response.ok({ body, }); @@ -880,6 +885,41 @@ export function trainedModelsRoutes( mlSavedObjectService ); + return response.ok({ + body, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + } + ) + ); + + /** + * @apiGroup TrainedModels + * + * @api {get} /internal/ml/trained_models/download_status Gets models download status + * @apiName ModelsDownloadStatus + * @apiDescription Gets download status for all currently downloading models + */ + router.versioned + .get({ + path: `${ML_INTERNAL_BASE_PATH}/trained_models/download_status`, + access: 'internal', + options: { + tags: ['access:ml:canCreateTrainedModels'], + }, + }) + .addVersion( + { + version: '1', + validate: false, + }, + routeGuard.fullLicenseAPIGuard( + async ({ client, mlClient, request, response, mlSavedObjectService }) => { + try { + const body = await modelsProvider(client, mlClient, cloud).getModelsDownloadStatus(); + return response.ok({ body, }); diff --git a/x-pack/plugins/ml/server/saved_objects/initialization/space_overrides/logs.ts b/x-pack/plugins/ml/server/saved_objects/initialization/space_overrides/logs.ts index 8c1a548d58727..5e4f64b5d555d 100644 --- a/x-pack/plugins/ml/server/saved_objects/initialization/space_overrides/logs.ts +++ b/x-pack/plugins/ml/server/saved_objects/initialization/space_overrides/logs.ts @@ -6,7 +6,6 @@ */ import type { IScopedClusterClient } from '@kbn/core/server'; -import RE2 from 're2'; import { mlLog } from '../../../lib/log'; const GROUP = 'logs-ui'; @@ -42,7 +41,7 @@ export async function logJobsSpaces({ } function findLogJobSpaceFactory() { - const reg = new RE2(`${MODULE_PREFIX}-(.+)-(${SOURCES.join('|')})-(${JOB_IDS.join('|')})`); + const reg = new RegExp(`${MODULE_PREFIX}-(.+)-(${SOURCES.join('|')})-(${JOB_IDS.join('|')})`); return (jobId: string) => { const result = reg.exec(jobId); diff --git a/x-pack/plugins/ml/server/saved_objects/initialization/space_overrides/metrics.ts b/x-pack/plugins/ml/server/saved_objects/initialization/space_overrides/metrics.ts index 3169be12ad7e8..cbe55fa76db55 100644 --- a/x-pack/plugins/ml/server/saved_objects/initialization/space_overrides/metrics.ts +++ b/x-pack/plugins/ml/server/saved_objects/initialization/space_overrides/metrics.ts @@ -6,7 +6,6 @@ */ import type { IScopedClusterClient } from '@kbn/core/server'; -import RE2 from 're2'; import { mlLog } from '../../../lib/log'; const GROUP = 'metrics'; @@ -49,7 +48,7 @@ export async function metricsJobsSpaces({ } function findMetricsJobSpaceFactory() { - const reg = new RE2(`${MODULE_PREFIX}-(.+)-(${SOURCES.join('|')})-(${JOB_IDS.join('|')})`); + const reg = new RegExp(`${MODULE_PREFIX}-(.+)-(${SOURCES.join('|')})-(${JOB_IDS.join('|')})`); return (jobId: string) => { const result = reg.exec(jobId); diff --git a/x-pack/plugins/ml/server/saved_objects/service.ts b/x-pack/plugins/ml/server/saved_objects/service.ts index 64925dabeb892..eed08b2a74d63 100644 --- a/x-pack/plugins/ml/server/saved_objects/service.ts +++ b/x-pack/plugins/ml/server/saved_objects/service.ts @@ -5,7 +5,6 @@ * 2.0. */ -import RE2 from 're2'; import { memoize } from 'lodash'; import type { KibanaRequest, @@ -329,7 +328,7 @@ export function mlSavedObjectServiceFactory( if (id.match('\\*') === null) { return jobIds.includes(id); } - const regex = new RE2(id.replace('*', '.*')); + const regex = new RegExp(id.replace('*', '.*')); return jobIds.some((jId) => typeof jId === 'string' && regex.exec(jId)); }); } @@ -641,7 +640,7 @@ export function mlSavedObjectServiceFactory( if (id.match('\\*') === null) { return modelIds.includes(id); } - const regex = new RE2(id.replace('*', '.*')); + const regex = new RegExp(id.replace('*', '.*')); return modelIds.some((jId) => typeof jId === 'string' && regex.exec(jId)); }); } diff --git a/x-pack/plugins/ml/server/shared_services/shared_services.ts b/x-pack/plugins/ml/server/shared_services/shared_services.ts index ec62ccebb8905..dc65a5bc01817 100644 --- a/x-pack/plugins/ml/server/shared_services/shared_services.ts +++ b/x-pack/plugins/ml/server/shared_services/shared_services.ts @@ -270,6 +270,7 @@ function getRequestItemsProvider( scopedClient = { asInternalUser, asCurrentUser: asInternalUser, + asSecondaryAuthUser: asInternalUser, }; mlSavedObjectService = getSobSavedObjectService(scopedClient); mlClient = getMlClient(scopedClient, mlSavedObjectService); diff --git a/x-pack/plugins/monitoring/server/config.test.ts b/x-pack/plugins/monitoring/server/config.test.ts index ddaabf7bef2c7..bde75fe4138ee 100644 --- a/x-pack/plugins/monitoring/server/config.test.ts +++ b/x-pack/plugins/monitoring/server/config.test.ts @@ -69,6 +69,7 @@ describe('config schema', () => { "logFetchCount": 10, "logQueries": false, "maxIdleSockets": 256, + "maxResponseSize": false, "maxSockets": 800, "pingTimeout": "PT30S", "requestHeadersWhitelist": Array [ diff --git a/x-pack/plugins/observability_solution/apm/docs/openapi/apm.yaml b/x-pack/plugins/observability_solution/apm/docs/openapi/apm.yaml index c110abbfafa92..60c8a74d75b88 100644 --- a/x-pack/plugins/observability_solution/apm/docs/openapi/apm.yaml +++ b/x-pack/plugins/observability_solution/apm/docs/openapi/apm.yaml @@ -2,11 +2,21 @@ openapi: 3.0.0 info: title: APM UI version: 1.0.0 +tags: + - name: APM agent keys + description: > + Configure APM agent keys to authorize requests from APM agents to the APM Server. + - name: APM annotations + description: > + Annotate visualizations in the APM app with significant events. + Annotations enable you to easily see how events are impacting the performance of your applications. paths: /api/apm/agent_keys: post: summary: Create an APM agent key description: Create a new agent key for APM. + tags: + - APM agent keys requestBody: required: true content: @@ -46,6 +56,8 @@ paths: get: summary: Search for annotations description: Search for annotations related to a specific service. + tags: + - APM annotations parameters: - name: serviceName in: path @@ -98,6 +110,8 @@ paths: post: summary: Create a service annotation description: Create a new annotation for a specific service. + tags: + - APM annotations parameters: - name: serviceName in: path diff --git a/x-pack/plugins/observability_solution/apm/public/components/alerting/ui_components/alert_details_app_section/failed_transaction_chart.tsx b/x-pack/plugins/observability_solution/apm/public/components/alerting/ui_components/alert_details_app_section/failed_transaction_chart.tsx index 23edfb1822163..02273f0f43141 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/alerting/ui_components/alert_details_app_section/failed_transaction_chart.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/alerting/ui_components/alert_details_app_section/failed_transaction_chart.tsx @@ -7,6 +7,7 @@ /* Error Rate */ import React from 'react'; +import chroma from 'chroma-js'; import { EuiFlexItem, EuiPanel, @@ -15,11 +16,13 @@ import { EuiIconTip, RecursivePartial, useEuiTheme, + transparentize, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { BoolQuery } from '@kbn/es-query'; import { UI_SETTINGS } from '@kbn/data-plugin/public'; import { Theme } from '@elastic/charts'; +import { AlertActiveTimeRangeAnnotation, AlertAnnotation } from '@kbn/observability-alert-details'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { DEFAULT_DATE_FORMAT } from './constants'; import { useFetcher } from '../../../../hooks/use_fetcher'; @@ -33,7 +36,6 @@ import { usePreferredDataSourceAndBucketSize } from '../../../../hooks/use_prefe import { ApmDocumentType } from '../../../../../common/document_type'; import { TransactionTypeSelect } from './transaction_type_select'; import { ViewInAPMButton } from './view_in_apm_button'; -import { getAlertStartAnnotation } from './get_alert_start_annotation'; type ErrorRate = APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/charts/error_rate'>; @@ -73,12 +75,12 @@ function FailedTransactionChart({ environment: string; start: string; end: string; - alertStart?: number; - alertEnd?: number; comparisonChartTheme: RecursivePartial; timeZone: string; kuery?: string; filters?: BoolQuery; + alertStart?: number; + alertEnd?: number; }) { const { euiTheme } = useEuiTheme(); const { @@ -149,14 +151,25 @@ function FailedTransactionChart({ const showTransactionTypeSelect = setTransactionType && transactionTypes; const getFailedTransactionChartAdditionalData = () => { if (alertStart) { - return getAlertStartAnnotation({ - alertStart, - alertEnd, - color: euiTheme.colors.danger, - dateFormat: (uiSettings && uiSettings.get(UI_SETTINGS.DATE_FORMAT)) || DEFAULT_DATE_FORMAT, - }); + return [ + , + , + ]; } - return []; }; return ( diff --git a/x-pack/plugins/observability_solution/apm/public/components/alerting/ui_components/alert_details_app_section/get_alert_start_annotation.tsx b/x-pack/plugins/observability_solution/apm/public/components/alerting/ui_components/alert_details_app_section/get_alert_start_annotation.tsx deleted file mode 100644 index c259121647175..0000000000000 --- a/x-pack/plugins/observability_solution/apm/public/components/alerting/ui_components/alert_details_app_section/get_alert_start_annotation.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import React from 'react'; -import { AlertActiveTimeRangeAnnotation, AlertAnnotation } from '@kbn/observability-alert-details'; - -export function getAlertStartAnnotation({ - alertStart, - alertEnd, - dateFormat, - color, -}: { - alertStart: number; - alertEnd?: number; - dateFormat: string; - color: string; -}) { - return [ - , - , - ]; -} diff --git a/x-pack/plugins/observability_solution/apm/public/components/alerting/ui_components/alert_details_app_section/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/alerting/ui_components/alert_details_app_section/index.tsx index 96ed1f8d9128d..b9a7f4e8d8254 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/alerting/ui_components/alert_details_app_section/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/alerting/ui_components/alert_details_app_section/index.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import moment from 'moment'; + import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { formatAlertEvaluationValue } from '@kbn/observability-plugin/public'; @@ -74,37 +74,6 @@ export function AlertDetailsAppSection({ ), value: formatAlertEvaluationValue(alertRuleTypeId, alertEvaluationThreshold), }, - { - label: ( - - ), - value: environment, - }, - { - label: ( - - ), - value: serviceName, - }, - ...(transactionName - ? [ - { - label: ( - - ), - value: transactionName, - }, - ] - : []), ]; setAlertSummaryFields(alertSummaryFields); }, [ @@ -145,8 +114,6 @@ export function AlertDetailsAppSection({ ); } - const alertEnd = alert.fields[ALERT_END] ? moment(alert.fields[ALERT_END]).valueOf() : undefined; - return ( diff --git a/x-pack/plugins/observability_solution/apm/public/components/alerting/ui_components/alert_details_app_section/latency_chart.tsx b/x-pack/plugins/observability_solution/apm/public/components/alerting/ui_components/alert_details_app_section/latency_chart.tsx index 7d1686ade2d22..82fa091fa6617 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/alerting/ui_components/alert_details_app_section/latency_chart.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/alerting/ui_components/alert_details_app_section/latency_chart.tsx @@ -15,8 +15,10 @@ import { getDurationFormatter } from '@kbn/observability-plugin/common'; import { ALERT_RULE_TYPE_ID, ALERT_EVALUATION_THRESHOLD, ALERT_END } from '@kbn/rule-data-utils'; import type { TopAlert } from '@kbn/observability-plugin/public'; import { + AlertActiveTimeRangeAnnotation, AlertThresholdAnnotation, AlertThresholdTimeRangeRect, + AlertAnnotation, } from '@kbn/observability-alert-details'; import { useEuiTheme } from '@elastic/eui'; import { useKibana } from '@kbn/kibana-react-plugin/public'; @@ -39,7 +41,6 @@ import { usePreferredDataSourceAndBucketSize } from '../../../../hooks/use_prefe import { DEFAULT_DATE_FORMAT } from './constants'; import { TransactionTypeSelect } from './transaction_type_select'; import { ViewInAPMButton } from './view_in_apm_button'; -import { getAlertStartAnnotation } from './get_alert_start_annotation'; function LatencyChart({ alert, @@ -159,20 +160,26 @@ function LatencyChart({ isLatencyThresholdRuleType(alert.fields[ALERT_RULE_TYPE_ID]) || customAlertEvaluationThreshold ) { - return [...alertEvalThresholdChartData]; - } - return []; - }; - const getLatencyChartAlertStartData = () => { - if (alert.start) { - return getAlertStartAnnotation({ - alertStart: alert.start, - alertEnd, - color: euiTheme.colors.danger, - dateFormat: (uiSettings && uiSettings.get(UI_SETTINGS.DATE_FORMAT)) || DEFAULT_DATE_FORMAT, - }); + return [ + , + , + ...alertEvalThresholdChartData, + ]; } - return []; }; const memoizedData = useMemo( () => @@ -240,7 +247,7 @@ function LatencyChart({ ; comparisonEnabled: boolean; offset: string; @@ -71,10 +62,6 @@ function ThroughputChart({ kuery?: string; filters?: BoolQuery; }) { - const { euiTheme } = useEuiTheme(); - const { - services: { uiSettings }, - } = useKibana(); const preferred = usePreferredDataSourceAndBucketSize({ start, end, @@ -142,17 +129,6 @@ function ThroughputChart({ ] : []), ]; - const getThroughputChartAdditionalData = () => { - if (alertStart) { - return getAlertStartAnnotation({ - alertStart, - alertEnd, - color: euiTheme.colors.danger, - dateFormat: (uiSettings && uiSettings.get(UI_SETTINGS.DATE_FORMAT)) || DEFAULT_DATE_FORMAT, - }); - } - return []; - }; const showTransactionTypeSelect = setTransactionType && transactionTypes; @@ -214,7 +190,6 @@ function ThroughputChart({ timeseries={timeseriesThroughput} yLabelFormat={asExactTransactionRate} timeZone={timeZone} - annotations={getThroughputChartAdditionalData()} /> diff --git a/x-pack/plugins/observability_solution/apm/public/embeddable/alerting/alerting_failed_transactions_chart/react_embeddable_factory.tsx b/x-pack/plugins/observability_solution/apm/public/embeddable/alerting/alerting_failed_transactions_chart/react_embeddable_factory.tsx index d6f2e43c724b2..6418799c4ad38 100644 --- a/x-pack/plugins/observability_solution/apm/public/embeddable/alerting/alerting_failed_transactions_chart/react_embeddable_factory.tsx +++ b/x-pack/plugins/observability_solution/apm/public/embeddable/alerting/alerting_failed_transactions_chart/react_embeddable_factory.tsx @@ -19,6 +19,7 @@ export const APM_ALERTING_FAILED_TRANSACTIONS_CHART_EMBEDDABLE = export const getApmAlertingFailedTransactionsChartEmbeddableFactory = (deps: EmbeddableDeps) => { const factory: ReactEmbeddableFactory< + EmbeddableApmAlertingVizProps, EmbeddableApmAlertingVizProps, DefaultEmbeddableApi > = { diff --git a/x-pack/plugins/observability_solution/apm/public/embeddable/alerting/alerting_latency_chart/react_embeddable_factory.tsx b/x-pack/plugins/observability_solution/apm/public/embeddable/alerting/alerting_latency_chart/react_embeddable_factory.tsx index f828a35a89d69..0795c821fc465 100644 --- a/x-pack/plugins/observability_solution/apm/public/embeddable/alerting/alerting_latency_chart/react_embeddable_factory.tsx +++ b/x-pack/plugins/observability_solution/apm/public/embeddable/alerting/alerting_latency_chart/react_embeddable_factory.tsx @@ -18,6 +18,7 @@ export const APM_ALERTING_LATENCY_CHART_EMBEDDABLE = 'APM_ALERTING_LATENCY_CHART export const getApmAlertingLatencyChartEmbeddableFactory = (deps: EmbeddableDeps) => { const factory: ReactEmbeddableFactory< + EmbeddableApmAlertingLatencyVizProps, EmbeddableApmAlertingLatencyVizProps, DefaultEmbeddableApi > = { diff --git a/x-pack/plugins/observability_solution/apm/public/embeddable/alerting/alerting_throughput_chart/chart.tsx b/x-pack/plugins/observability_solution/apm/public/embeddable/alerting/alerting_throughput_chart/chart.tsx index 0a0785497d0dd..d98c27fc52d1d 100644 --- a/x-pack/plugins/observability_solution/apm/public/embeddable/alerting/alerting_throughput_chart/chart.tsx +++ b/x-pack/plugins/observability_solution/apm/public/embeddable/alerting/alerting_throughput_chart/chart.tsx @@ -4,9 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import moment from 'moment'; + import React from 'react'; -import { ALERT_END } from '@kbn/rule-data-utils'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import ThroughputChart from '../../../components/alerting/ui_components/alert_details_app_section/throughput_chart'; import { EmbeddableApmAlertingVizProps } from '../types'; @@ -16,7 +15,6 @@ import { ServiceNameCallout } from '../service_name_callout'; export function APMAlertingThroughputChart({ rule, - alert, rangeFrom = 'now-15m', rangeTo = 'now', transactionName, @@ -48,8 +46,6 @@ export function APMAlertingThroughputChart({ return ; } - const alertEnd = alert.fields[ALERT_END] ? moment(alert.fields[ALERT_END]).valueOf() : undefined; - return ( => { const apmEventClient = await getApmEventClient(resources); const { params, plugins, context, request, logger, config } = resources; @@ -416,7 +416,7 @@ const serviceAnnotationsRoute = createApmServerRoute({ const serviceAnnotationsCreateRoute = createApmServerRoute({ endpoint: 'POST /api/apm/services/{serviceName}/annotation 2023-10-31', options: { - tags: ['access:apm', 'access:apm_write'], + tags: ['access:apm', 'access:apm_write', 'oas-tag:APM annotations'], }, params: t.type({ path: t.type({ diff --git a/x-pack/plugins/observability_solution/apm/server/routes/typings.ts b/x-pack/plugins/observability_solution/apm/server/routes/typings.ts index c8d9cc1477926..8ee9a3849a6fb 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/typings.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/typings.ts @@ -56,6 +56,8 @@ export interface APMRouteCreateOptions { | 'access:ml:canCreateJob' | 'access:ml:canCloseJob' | 'access:ai_assistant' + | 'oas-tag:APM agent keys' + | 'oas-tag:APM annotations' >; body?: { accepts: Array<'application/json' | 'multipart/form-data'> }; disableTelemetry?: boolean; diff --git a/x-pack/plugins/observability_solution/apm/tsconfig.json b/x-pack/plugins/observability_solution/apm/tsconfig.json index 981980f93b92e..c763acff6ea85 100644 --- a/x-pack/plugins/observability_solution/apm/tsconfig.json +++ b/x-pack/plugins/observability_solution/apm/tsconfig.json @@ -99,7 +99,6 @@ "@kbn/profiling-data-access-plugin", "@kbn/profiling-utils", "@kbn/core-analytics-server", - "@kbn/analytics-client", "@kbn/monaco", "@kbn/deeplinks-observability", "@kbn/custom-icons", diff --git a/x-pack/plugins/observability_solution/dataset_quality/common/api_types.ts b/x-pack/plugins/observability_solution/dataset_quality/common/api_types.ts index ec725d573dbd4..ce84985a39405 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/common/api_types.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/common/api_types.ts @@ -7,9 +7,26 @@ import * as rt from 'io-ts'; +const userPrivilegesRt = rt.type({ + canMonitor: rt.boolean, +}); + +export type DataStreamUserPrivileges = rt.TypeOf; + +const datasetUserPrivilegesRt = rt.intersection([ + userPrivilegesRt, + rt.type({ + canRead: rt.boolean, + canViewIntegrations: rt.boolean, + }), +]); + +export type DatasetUserPrivileges = rt.TypeOf; + export const dataStreamStatRt = rt.intersection([ rt.type({ name: rt.string, + userPrivileges: userPrivilegesRt, }), rt.partial({ size: rt.string, @@ -111,12 +128,14 @@ export const dataStreamDetailsRt = rt.partial({ sizeBytes: rt.union([rt.null, rt.number]), // rt.null is only needed for https://github.com/elastic/kibana/issues/178954 services: rt.record(rt.string, rt.array(rt.string)), hosts: rt.record(rt.string, rt.array(rt.string)), + userPrivileges: userPrivilegesRt, }); export type DataStreamDetails = rt.TypeOf; export const getDataStreamsStatsResponseRt = rt.exact( rt.type({ + datasetUserPrivileges: datasetUserPrivilegesRt, dataStreamsStats: rt.array(dataStreamStatRt), }) ); diff --git a/x-pack/plugins/observability_solution/dataset_quality/common/data_stream_details/errors.ts b/x-pack/plugins/observability_solution/dataset_quality/common/data_stream_details/errors.ts index 2ba8bac9f6b51..eb74f08ab1336 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/common/data_stream_details/errors.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/common/data_stream_details/errors.ts @@ -6,9 +6,13 @@ */ export class GetDataStreamsDetailsError extends Error { - constructor(message: string) { + readonly statusCode?: number; + + constructor(message: string, statusCode?: number) { super(message); Object.setPrototypeOf(this, new.target.prototype); this.name = 'GetDataStreamsDetailsError'; + + this.statusCode = statusCode; } } diff --git a/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/data_stream_stat.ts b/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/data_stream_stat.ts index 806fd17962a49..5968c369732de 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/data_stream_stat.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/data_stream_stat.ts @@ -21,6 +21,7 @@ export class DataStreamStat { size?: DataStreamStatType['size']; // total datastream size sizeBytes?: DataStreamStatType['sizeBytes']; // total datastream size lastActivity?: DataStreamStatType['lastActivity']; + userPrivileges?: DataStreamStatType['userPrivileges']; totalDocs?: DataStreamStatType['totalDocs']; // total datastream docs count integration?: Integration; degradedDocs: { @@ -39,6 +40,7 @@ export class DataStreamStat { this.size = dataStreamStat.size; this.sizeBytes = dataStreamStat.sizeBytes; this.lastActivity = dataStreamStat.lastActivity; + this.userPrivileges = dataStreamStat.userPrivileges; this.totalDocs = dataStreamStat.totalDocs; this.integration = dataStreamStat.integration; this.degradedDocs = { @@ -61,6 +63,7 @@ export class DataStreamStat { size: dataStreamStat.size, sizeBytes: dataStreamStat.sizeBytes, lastActivity: dataStreamStat.lastActivity, + userPrivileges: dataStreamStat.userPrivileges, totalDocs: dataStreamStat.totalDocs, degradedDocs: DEFAULT_DEGRADED_DOCS, }; diff --git a/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/errors.ts b/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/errors.ts index de47f4cb8c39d..aa68ed0b5972f 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/errors.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/errors.ts @@ -6,9 +6,12 @@ */ export class GetDataStreamsStatsError extends Error { - constructor(message: string) { + readonly statusCode?: number; + + constructor(message: string, statusCode?: number) { super(message); Object.setPrototypeOf(this, new.target.prototype); this.name = 'GetDataStreamsStatsError'; + this.statusCode = statusCode; } } diff --git a/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/types.ts b/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/types.ts index b2426ef53aeb5..42fbeeb2cff52 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/types.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/common/data_streams_stats/types.ts @@ -13,7 +13,7 @@ export type GetDataStreamsStatsQuery = GetDataStreamsStatsParams['query']; export type GetDataStreamsStatsResponse = APIReturnType<`GET /internal/dataset_quality/data_streams/stats`>; export type DataStreamStatType = GetDataStreamsStatsResponse['dataStreamsStats'][0]; -export type DataStreamStatServiceResponse = DataStreamStatType[]; +export type DataStreamStatServiceResponse = GetDataStreamsStatsResponse; export type GetIntegrationsParams = APIClientRequestParamsOf<`GET /internal/dataset_quality/integrations`>['params']; diff --git a/x-pack/plugins/observability_solution/dataset_quality/common/translations.ts b/x-pack/plugins/observability_solution/dataset_quality/common/translations.ts index 616cfb295a21f..6ab0fde0f033d 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/common/translations.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/common/translations.ts @@ -31,6 +31,10 @@ export const tableSummaryOfText = i18n.translate('xpack.datasetQuality.tableSumm defaultMessage: 'of', }); +export const notAvailableLabel = i18n.translate('xpack.datasetQuality.notAvailableLabel', { + defaultMessage: 'N/A', +}); + /* Flyout */ diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/common/index.ts b/x-pack/plugins/observability_solution/dataset_quality/public/components/common/index.ts index 36c9d7f744a58..3769cbedb6c0a 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/common/index.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/common/index.ts @@ -6,3 +6,4 @@ */ export * from './integration_icon'; +export * from './insufficient_privileges'; diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/common/insufficient_privileges.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/common/insufficient_privileges.tsx new file mode 100644 index 0000000000000..de9c99a7eda9c --- /dev/null +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/common/insufficient_privileges.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback } from 'react'; +import useToggle from 'react-use/lib/useToggle'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiLink, + EuiButtonIcon, + EuiButtonIconProps, + EuiPopover, + EuiToolTip, + EuiIcon, + EuiFlexGroup, + EuiButtonIconPropsForButton, +} from '@elastic/eui'; + +const insufficientPrivilegesText = i18n.translate( + 'xpack.datasetQuality.insufficientPrivilegesMessage', + { + defaultMessage: "You don't have sufficient privileges to access this information.", + } +); + +// @ts-ignore // TODO: Add link to Dataset Quality permissions documentation +const LearnMoreLink = () => ( + + + +); + +export const PrivilegesWarningIconWrapper = ({ + hasPrivileges, + title, + mode = 'popover', + iconColor = 'warning', + popoverCss, + children, +}: { + hasPrivileges: boolean; + title: string; + mode?: 'tooltip' | 'popover'; + iconColor?: EuiButtonIconPropsForButton['color']; + popoverCss?: EuiButtonIconProps['css']; + children: React.ReactNode; +}) => { + const [isPopoverOpen, togglePopover] = useToggle(false); + + const handleButtonClick = useCallback(() => togglePopover(true), [togglePopover]); + + if (hasPrivileges) { + return <>{children}; + } + + return mode === 'popover' ? ( + + } + isOpen={isPopoverOpen} + closePopover={togglePopover} + > + {insufficientPrivilegesText} {/* TODO: Add docs link when available */} + + ) : ( + + + + {children} + + + ); +}; diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/dataset_quality.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/dataset_quality.tsx index 57c8d7a9e44cf..44d72802bc868 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/dataset_quality.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/dataset_quality.tsx @@ -54,6 +54,7 @@ export const createDatasetQuality = ({ const Header = dynamic(() => import('./header')); const Warnings = dynamic(() => import('./warnings/warnings')); +const EmptyStateWrapper = dynamic(() => import('./empty_state/empty_state')); const Table = dynamic(() => import('./table/table')); const Filters = dynamic(() => import('./filters/filters')); const SummaryPanel = dynamic(() => import('./summary_panel/summary_panel')); @@ -67,15 +68,18 @@ function DatasetQuality() { - - - - - - - - - + + + + + + + + + +
+ + ); } diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/empty_state/empty_state.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/empty_state/empty_state.tsx new file mode 100644 index 0000000000000..0bba8f2b98b37 --- /dev/null +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/empty_state/empty_state.tsx @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiEmptyPrompt, EuiCode } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { DEFAULT_DATASET_TYPE, DEFAULT_LOGS_DATA_VIEW } from '../../../../common/constants'; +import { useEmptyState } from '../../../hooks/use_empty_state'; + +// Allow for lazy loading +// eslint-disable-next-line import/no-default-export +export default function EmptyStateWrapper({ children }: { children: React.ReactNode }) { + const { canReadDataset, isDatasetEmpty } = useEmptyState(); + + if (!canReadDataset) { + return ( + + {i18n.translate('xpack.datasetQuality.emptyState.noPrivileges.title', { + defaultMessage: `Datasets couldn't be loaded`, + })} + + } + body={ +

+ {`${DEFAULT_DATASET_TYPE}-*`}, + }} + /> + {/* TODO: Learn more link to docs */} +

+ } + /> + ); + } + + if (isDatasetEmpty) { + return ( + + {i18n.translate('xpack.datasetQuality.emptyState.noData.title', { + defaultMessage: 'No datasets found', + })} + + } + body={ +

+ {DEFAULT_LOGS_DATA_VIEW}, + }} + /> +

+ } + /> + ); + } + + return <>{children}; +} diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/data_placeholder.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/data_placeholder.tsx index 0e926291356ac..179ed804783b5 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/data_placeholder.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/data_placeholder.tsx @@ -16,14 +16,24 @@ import { EuiIconTip, EuiSkeletonTitle, } from '@elastic/eui'; +import { PrivilegesWarningIconWrapper } from '../../common'; +import { notAvailableLabel } from '../../../../common/translations'; + interface DataPlaceholderParams { title: string; tooltip: string; value: string | number; isLoading: boolean; + isUserAuthorizedForDataset: boolean; } -export function DataPlaceholder({ title, tooltip, value, isLoading }: DataPlaceholderParams) { +export function DataPlaceholder({ + title, + tooltip, + value, + isLoading, + isUserAuthorizedForDataset, +}: DataPlaceholderParams) { return ( @@ -32,12 +42,22 @@ export function DataPlaceholder({ title, tooltip, value, isLoading }: DataPlaceh + + + <> + + {isLoading ? ( ) : ( -

{value}

+

{isUserAuthorizedForDataset ? value : notAvailableLabel}

)} diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/datasets_activity.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/datasets_activity.tsx index d8841439f2315..0596e7b05c74e 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/datasets_activity.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/datasets_activity.tsx @@ -16,7 +16,8 @@ import { import { DataPlaceholder } from './data_placeholder'; export function DatasetsActivity() { - const { datasetsActivity, isDatasetsActivityLoading } = useSummaryPanelContext(); + const { datasetsActivity, isDatasetsActivityLoading, isUserAuthorizedForDataset } = + useSummaryPanelContext(); const text = `${datasetsActivity.active} ${tableSummaryOfText} ${datasetsActivity.total}`; return ( @@ -25,6 +26,7 @@ export function DatasetsActivity() { tooltip={summaryPanelDatasetsActivityTooltipText} value={text} isLoading={isDatasetsActivityLoading} + isUserAuthorizedForDataset={isUserAuthorizedForDataset} /> ); } diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/estimated_data.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/estimated_data.tsx index 045ebec9e97b7..b8778d70e7d13 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/estimated_data.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/estimated_data.tsx @@ -17,7 +17,8 @@ import { import { DataPlaceholder } from './data_placeholder'; export function EstimatedData() { - const { estimatedData, isEstimatedDataLoading } = useSummaryPanelContext(); + const { estimatedData, isEstimatedDataLoading, isUserAuthorizedForDataset } = + useSummaryPanelContext(); return ( ); } diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/summary_panel.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/summary_panel.tsx index bc83969d510e8..9100e25fdce8f 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/summary_panel.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/summary_panel/summary_panel.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { EuiFlexGroup } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { useSummaryPanelContext } from '../../../hooks'; import { DatasetsQualityIndicators } from './datasets_quality_indicators'; import { DatasetsActivity } from './datasets_activity'; @@ -19,11 +19,16 @@ export default function SummaryPanel() { const { isEstimatedDataDisabled } = useSummaryPanelContext(); return ( - - - - {!isEstimatedDataDisabled && } - + + + + + + + + {!isEstimatedDataDisabled && } + + ); } diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/table/columns.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/table/columns.tsx index cc944c5e3986a..60f959176866b 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/table/columns.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/table/columns.tsx @@ -32,7 +32,7 @@ import { } from '../../../../common/constants'; import { DataStreamStat } from '../../../../common/data_streams_stats/data_stream_stat'; import { DatasetQualityIndicator, QualityIndicator } from '../../quality_indicator'; -import { IntegrationIcon } from '../../common'; +import { PrivilegesWarningIconWrapper, IntegrationIcon } from '../../common'; import { useRedirectLink } from '../../../hooks'; import { FlyoutDataset } from '../../../state_machines/dataset_quality_controller'; import { DegradedDocsPercentageLink } from './degraded_docs_percentage_link'; @@ -157,6 +157,8 @@ const datasetQualityColumnTooltip = ( export const getDatasetQualityTableColumns = ({ fieldFormats, + canUserMonitorDataset, + canUserMonitorAnyDataStream, selectedDataset, openFlyout, loadingDataStreamStats, @@ -166,6 +168,8 @@ export const getDatasetQualityTableColumns = ({ isActiveDataset, }: { fieldFormats: FieldFormatsStart; + canUserMonitorDataset: boolean; + canUserMonitorAnyDataStream: boolean; selectedDataset?: FlyoutDataset; loadingDataStreamStats: boolean; loadingDegradedStats: boolean; @@ -230,7 +234,7 @@ export const getDatasetQualityTableColumns = ({ ), width: '160px', }, - ...(isSizeStatsAvailable + ...(isSizeStatsAvailable && canUserMonitorDataset && canUserMonitorAnyDataStream ? [ { name: sizeColumnName, @@ -238,17 +242,22 @@ export const getDatasetQualityTableColumns = ({ sortable: true, render: (_: any, dataStreamStat: DataStreamStat) => { return ( - - {formatNumber( - DataStreamStat.calculateFilteredSize(dataStreamStat), - BYTE_NUMBER_FORMAT - )} - + + {formatNumber( + DataStreamStat.calculateFilteredSize(dataStreamStat), + BYTE_NUMBER_FORMAT + )} + + ); }, width: '100px', @@ -290,33 +299,42 @@ export const getDatasetQualityTableColumns = ({ ), width: '140px', }, - { - name: lastActivityColumnName, - field: 'lastActivity', - render: (timestamp: number) => ( - - {!isActiveDataset(timestamp) ? ( - - {inactiveDatasetActivityColumnDescription} - - - - - ) : ( - fieldFormats - .getDefaultInstance(KBN_FIELD_TYPES.DATE, [ES_FIELD_TYPES.DATE]) - .convert(timestamp) - )} - - ), - width: '300px', - sortable: true, - }, + ...(canUserMonitorDataset && canUserMonitorAnyDataStream + ? [ + { + name: lastActivityColumnName, + field: 'lastActivity', + render: (timestamp: number, { userPrivileges, title }: DataStreamStat) => ( + + + {!isActiveDataset(timestamp) ? ( + + {inactiveDatasetActivityColumnDescription} + + + + + ) : ( + fieldFormats + .getDefaultInstance(KBN_FIELD_TYPES.DATE, [ES_FIELD_TYPES.DATE]) + .convert(timestamp) + )} + + + ), + width: '300px', + sortable: true, + }, + ] + : []), { name: actionsColumnName, render: (dataStreamStat: DataStreamStat) => ( diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/table/table.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/table/table.tsx index e4ef50266de2d..9316573e237e0 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/table/table.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/dataset_quality/table/table.tsx @@ -42,6 +42,8 @@ export const Table = () => { closeFlyout, showInactiveDatasets, showFullDatasetNames, + canUserMonitorDataset, + canUserMonitorAnyDataStream, toggleInactiveDatasets, toggleFullDatasetNames, } = useDatasetQualityTable(); @@ -65,12 +67,14 @@ export const Table = () => { tooltipText={fullDatasetNameDescription} onToggle={toggleFullDatasetNames} /> - + {canUserMonitorDataset && canUserMonitorAnyDataStream && ( + + )} diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/fields_list.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/fields_list.tsx index 4d5d561977fc5..1861d23b69a45 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/fields_list.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/fields_list.tsx @@ -22,13 +22,15 @@ export function FieldsList({ title, fields, actionsMenu: ActionsMenu, + dataTestSubj = `datasetQualityFlyoutFieldsList-${title.toLowerCase().split(' ').join('_')}`, }: { title: string; fields: Array<{ fieldTitle: string; fieldValue: ReactNode; isLoading: boolean }>; actionsMenu?: ReactNode; + dataTestSubj?: string; }) { return ( - + {title} diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/flyout_summary_kpi_item.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/flyout_summary_kpi_item.tsx index 358f0eaaacbb5..3f27521cd54fd 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/flyout_summary_kpi_item.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/flyout_summary_kpi_item.tsx @@ -18,11 +18,15 @@ import { EuiSkeletonRectangle, } from '@elastic/eui'; +import { PrivilegesWarningIconWrapper } from '../../common'; +import { notAvailableLabel } from '../../../../common/translations'; + export function FlyoutSummaryKpiItem({ title, value, link, isLoading, + userHasPrivilege, }: { title: string; value: string; @@ -31,6 +35,7 @@ export function FlyoutSummaryKpiItem({ href: string; }; isLoading: boolean; + userHasPrivilege: boolean; }) { const { euiTheme } = useEuiTheme(); @@ -44,9 +49,20 @@ export function FlyoutSummaryKpiItem({ > - -
{title}
-
+ + +
{title}
+
+ + + <> + +
{link ? ( -

{value}

+

{userHasPrivilege ? value : notAvailableLabel}

diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/get_summary_kpis.test.ts b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/get_summary_kpis.test.ts index 4768fc1b09d5a..42ac8bc5e7b28 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/get_summary_kpis.test.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/get_summary_kpis.test.ts @@ -68,20 +68,24 @@ describe('getSummaryKpis', () => { { title: flyoutDocsCountTotalText, value: '1,000', + userHasPrivilege: true, }, { title: flyoutSizeText, value: formatNumber(dataStreamDetails.sizeBytes ?? 0, BYTE_NUMBER_FORMAT), + userHasPrivilege: true, }, { title: flyoutServicesText, value: '3', link: undefined, + userHasPrivilege: true, }, { title: flyoutHostsText, value: '3', link: undefined, + userHasPrivilege: true, }, { title: flyoutDegradedDocsText, @@ -90,6 +94,7 @@ describe('getSummaryKpis', () => { label: flyoutShowAllText, href: degradedDocsHref, }, + userHasPrivilege: true, }, ]); }); @@ -122,20 +127,24 @@ describe('getSummaryKpis', () => { { title: flyoutDocsCountTotalText, value: '1,000', + userHasPrivilege: true, }, { title: flyoutSizeText, value: formatNumber(dataStreamDetails.sizeBytes ?? 0, BYTE_NUMBER_FORMAT), + userHasPrivilege: true, }, { title: flyoutServicesText, value: '50+', link: undefined, + userHasPrivilege: true, }, { title: flyoutHostsText, value: '54+', link: undefined, + userHasPrivilege: true, }, { title: flyoutDegradedDocsText, @@ -144,6 +153,7 @@ describe('getSummaryKpis', () => { label: flyoutShowAllText, href: degradedDocsHref, }, + userHasPrivilege: true, }, ]); }); diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/get_summary_kpis.ts b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/get_summary_kpis.ts index a0aa0163411c4..6dcf61838b834 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/get_summary_kpis.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/flyout_summary/get_summary_kpis.ts @@ -37,7 +37,12 @@ export function getSummaryKpis({ hostsLocator?: ReturnType< typeof useKibanaContextForPlugin >['services']['observabilityShared']['locators']['infra']['hostsLocator']; -}): Array<{ title: string; value: string; link?: { label: string; href: string } }> { +}): Array<{ + title: string; + value: string; + link?: { label: string; href: string }; + userHasPrivilege: boolean; +}> { const services = dataStreamDetails?.services ?? {}; const serviceKeys = Object.keys(services); const countOfServices = serviceKeys @@ -56,6 +61,7 @@ export function getSummaryKpis({ { title: flyoutDocsCountTotalText, value: formatNumber(dataStreamDetails?.docsCount ?? 0, NUMBER_FORMAT), + userHasPrivilege: true, }, // dataStreamDetails.sizeBytes = null indicates it's Serverless where `_stats` API isn't available ...(dataStreamDetails?.sizeBytes !== null // Only show when not in Serverless @@ -63,6 +69,7 @@ export function getSummaryKpis({ { title: flyoutSizeText, value: formatNumber(dataStreamDetails?.sizeBytes ?? 0, BYTE_NUMBER_FORMAT), + userHasPrivilege: dataStreamDetails?.userPrivileges?.canMonitor ?? true, }, ] : []), @@ -70,12 +77,14 @@ export function getSummaryKpis({ title: flyoutServicesText, value: formatMetricValueForMax(countOfServices, MAX_HOSTS_METRIC_VALUE, NUMBER_FORMAT), link: servicesLink, + userHasPrivilege: true, }, getHostsKpi(dataStreamDetails?.hosts, timeRange, hostsLocator), { title: flyoutDegradedDocsText, value: formatNumber(dataStreamDetails?.degradedDocsCount ?? 0, NUMBER_FORMAT), link: degradedDocsLink, + userHasPrivilege: true, }, ]; } @@ -126,6 +135,7 @@ function getHostsKpi( NUMBER_FORMAT ), link: undefined, + userHasPrivilege: true, }; } diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/integration_actions_menu.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/integration_actions_menu.tsx index 9705cd8b5ffd2..9b0e45ce27e90 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/integration_actions_menu.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/components/flyout/integration_actions_menu.tsx @@ -45,7 +45,9 @@ export function IntegrationActionsMenu({ integration: Integration; dashboardsLoading: boolean; }) { - const { type, name } = useDatasetQualityFlyout().dataStreamStat!; + const { dataStreamStat, canUserAccessDashboards, canUserViewIntegrations } = + useDatasetQualityFlyout(); + const { type, name } = dataStreamStat!; const { dashboards = [], version, name: integrationName } = integration; const { isOpen, @@ -71,11 +73,13 @@ export function IntegrationActionsMenu({ buttonText, routerLinkProps, iconType, + disabled = false, }: { dataTestSubject: string; - buttonText: string; + buttonText: string | React.ReactNode; routerLinkProps: RouterLinkProps; iconType: string; + disabled?: boolean; }) => ( {buttonText} @@ -93,16 +98,21 @@ export function IntegrationActionsMenu({ const panelItems = useMemo(() => { const firstLevelItems: EuiContextMenuPanelItemDescriptor[] = [ - { - renderItem: () => ( - - ), - }, + ...(canUserViewIntegrations + ? [ + { + renderItem: () => ( + + ), + }, + ] + : []), { renderItem: () => ( { datasetIntegrationsLoading: state.matches('flyout.initializing.integrationDashboards.fetching'), })); + const canUserAccessDashboards = useSelector( + service, + (state) => !state.matches('flyout.initializing.integrationDashboards.unauthorized') + ); + + const canUserViewIntegrations = useSelector( + service, + (state) => state.context.datasetUserPrivileges.canViewIntegrations + ); + return { dataStreamStat, dataStreamSettings, @@ -43,5 +53,7 @@ export const useDatasetQualityFlyout = () => { breakdownField, loadingState, flyoutLoading: !dataStreamStat, + canUserAccessDashboards, + canUserViewIntegrations, }; }; diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_dataset_quality_table.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_dataset_quality_table.tsx index dd82b52a7e743..347f817f7bf0f 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_dataset_quality_table.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_dataset_quality_table.tsx @@ -36,10 +36,22 @@ export const useDatasetQualityTable = () => { const { service } = useDatasetQualityContext(); const { page, rowsPerPage, sort } = useSelector(service, (state) => state.context.table); + const isSizeStatsAvailable = useSelector(service, (state) => state.context.isSizeStatsAvailable); + const canUserMonitorDataset = useSelector( + service, + (state) => state.context.datasetUserPrivileges.canMonitor + ); + const canUserMonitorAnyDataStream = useSelector( + service, + (state) => + !state.context.dataStreamStats || + !state.context.dataStreamStats.length || + state.context.dataStreamStats.some((s) => s.userPrivileges.canMonitor) + ); const { - inactive: showInactiveDatasets, + inactive, fullNames: showFullDatasetNames, timeRange, integrations, @@ -47,6 +59,7 @@ export const useDatasetQualityTable = () => { qualities, query, } = useSelector(service, (state) => state.context.filters); + const showInactiveDatasets = inactive || !canUserMonitorDataset; const flyout = useSelector(service, (state) => state.context.flyout); @@ -112,6 +125,8 @@ export const useDatasetQualityTable = () => { () => getDatasetQualityTableColumns({ fieldFormats, + canUserMonitorDataset, + canUserMonitorAnyDataStream, selectedDataset: flyout?.dataset, openFlyout, loadingDataStreamStats, @@ -122,6 +137,8 @@ export const useDatasetQualityTable = () => { }), [ fieldFormats, + canUserMonitorDataset, + canUserMonitorAnyDataStream, flyout?.dataset, openFlyout, loadingDataStreamStats, @@ -220,6 +237,8 @@ export const useDatasetQualityTable = () => { selectedDataset: flyout?.dataset, showInactiveDatasets, showFullDatasetNames, + canUserMonitorDataset, + canUserMonitorAnyDataStream, toggleInactiveDatasets, toggleFullDatasetNames, isSizeStatsAvailable, diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_empty_state.ts b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_empty_state.ts new file mode 100644 index 0000000000000..a7de315a35895 --- /dev/null +++ b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_empty_state.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { useSelector } from '@xstate/react'; +import { useDatasetQualityContext } from '../components/dataset_quality/context'; + +export function useEmptyState() { + const { service } = useDatasetQualityContext(); + + const canReadDataset = useSelector( + service, + (state) => state.context.datasetUserPrivileges.canRead + ); + + const isDatasetEmpty = useSelector( + service, + (state) => + !state.matches('datasets.fetching') && + !state.matches('integrations.fetching') && + !state.matches('degradedDocs.fetching') && + (state.context.datasets?.length ?? 0) === 0 + ); + + return { canReadDataset, isDatasetEmpty }; +} diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_summary_panel.tsx b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_summary_panel.tsx index 4c37c925500e6..a000115c82284 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_summary_panel.tsx +++ b/x-pack/plugins/observability_solution/dataset_quality/public/hooks/use_summary_panel.tsx @@ -14,7 +14,12 @@ import { filterInactiveDatasets } from '../utils'; const useSummaryPanel = () => { const { service } = useDatasetQualityContext(); - const { filteredItems, isSizeStatsAvailable } = useDatasetQualityTable(); + const { + filteredItems, + isSizeStatsAvailable, + canUserMonitorDataset, + canUserMonitorAnyDataStream, + } = useDatasetQualityTable(); const { timeRange } = useSelector(service, (state) => state.context.filters); @@ -30,6 +35,16 @@ const useSummaryPanel = () => { state.matches('degradedDocs.fetching') ); + /* + User Authorization + */ + const canUserMonitorAllFilteredDataStreams = filteredItems.every( + (item) => item.userPrivileges?.canMonitor ?? true + ); + + const isUserAuthorizedForDataset = + canUserMonitorDataset && canUserMonitorAnyDataStream && canUserMonitorAllFilteredDataStreams; + /* Datasets Activity */ @@ -62,6 +77,8 @@ const useSummaryPanel = () => { datasetsQuality, isDatasetsQualityLoading, + isUserAuthorizedForDataset, + isEstimatedDataLoading, estimatedData, isEstimatedDataDisabled: !isSizeStatsAvailable, diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/services/data_stream_details/data_stream_details_client.ts b/x-pack/plugins/observability_solution/dataset_quality/public/services/data_stream_details/data_stream_details_client.ts index e1282eb494999..52bdf3c53ea51 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/services/data_stream_details/data_stream_details_client.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/services/data_stream_details/data_stream_details_client.ts @@ -38,7 +38,10 @@ export class DataStreamDetailsClient implements IDataStreamDetailsClient { `/internal/dataset_quality/data_streams/${dataStream}/settings` ) .catch((error) => { - throw new GetDataStreamsStatsError(`Failed to fetch data stream settings": ${error}`); + throw new GetDataStreamsStatsError( + `Failed to fetch data stream settings": ${error}`, + error.body.statusCode + ); }); const dataStreamSettings = decodeOrThrow( @@ -59,7 +62,10 @@ export class DataStreamDetailsClient implements IDataStreamDetailsClient { } ) .catch((error) => { - throw new GetDataStreamsStatsError(`Failed to fetch data stream details": ${error}`); + throw new GetDataStreamsStatsError( + `Failed to fetch data stream details": ${error}`, + error.body.statusCode + ); }); const dataStreamDetails = decodeOrThrow( @@ -85,7 +91,8 @@ export class DataStreamDetailsClient implements IDataStreamDetailsClient { ) .catch((error) => { throw new GetDataStreamsDetailsError( - `Failed to fetch data stream degraded fields": ${error}` + `Failed to fetch data stream degraded fields": ${error}`, + error.body.statusCode ); }); @@ -104,7 +111,10 @@ export class DataStreamDetailsClient implements IDataStreamDetailsClient { `/internal/dataset_quality/integrations/${integration}/dashboards` ) .catch((error) => { - throw new GetDataStreamsStatsError(`Failed to fetch integration dashboards": ${error}`); + throw new GetDataStreamsStatsError( + `Failed to fetch integration dashboards": ${error}`, + error.body.statusCode + ); }); const integrationDashboards = decodeOrThrow( diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts b/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts index 37cecebbde589..6ffa91e7151da 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/services/data_streams_stats/data_streams_stats_client.ts @@ -40,16 +40,19 @@ export class DataStreamsStatsClient implements IDataStreamsStatsClient { query: params, }) .catch((error) => { - throw new GetDataStreamsStatsError(`Failed to fetch data streams stats: ${error}`); + throw new GetDataStreamsStatsError( + `Failed to fetch data streams stats: ${error}`, + error.body.statusCode + ); }); - const { dataStreamsStats } = decodeOrThrow( + const { dataStreamsStats, datasetUserPrivileges } = decodeOrThrow( getDataStreamsStatsResponseRt, (message: string) => new GetDataStreamsStatsError(`Failed to decode data streams stats response: ${message}`) )(response); - return dataStreamsStats; + return { dataStreamsStats, datasetUserPrivileges }; } public async getDataStreamsDegradedStats(params: GetDataStreamsDegradedDocsStatsQuery) { @@ -64,7 +67,10 @@ export class DataStreamsStatsClient implements IDataStreamsStatsClient { } ) .catch((error) => { - throw new GetDataStreamsStatsError(`Failed to fetch data streams degraded stats: ${error}`); + throw new GetDataStreamsStatsError( + `Failed to fetch data streams degraded stats: ${error}`, + error.body.statusCode + ); }); const { degradedDocs } = decodeOrThrow( @@ -90,7 +96,10 @@ export class DataStreamsStatsClient implements IDataStreamsStatsClient { } ) .catch((error) => { - throw new GetDataStreamsStatsError(`Failed to fetch non aggregatable datasets: ${error}`); + throw new GetDataStreamsStatsError( + `Failed to fetch non aggregatable datasets: ${error}`, + error.body.statusCode + ); }); const nonAggregatableDatasets = decodeOrThrow( @@ -110,7 +119,10 @@ export class DataStreamsStatsClient implements IDataStreamsStatsClient { query: params, }) .catch((error) => { - throw new GetDataStreamsStatsError(`Failed to fetch integrations: ${error}`); + throw new GetDataStreamsStatsError( + `Failed to fetch integrations: ${error}`, + error.body.statusCode + ); }); const { integrations } = decodeOrThrow( diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/defaults.ts b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/defaults.ts index a1b823d6f60fb..90fe913c9113e 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/defaults.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/defaults.ts @@ -26,6 +26,12 @@ export const DEFAULT_CONTEXT: DefaultDatasetQualityControllerState = { direction: DEFAULT_SORT_DIRECTION, }, }, + datasetUserPrivileges: { + canRead: true, + canMonitor: true, + canViewIntegrations: true, + }, + dataStreamStats: [], filters: { inactive: true, fullNames: false, diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts index d7546aea93c9d..4c2b516fc4662 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/state_machine.ts @@ -97,13 +97,20 @@ export const createPureDatasetQualityControllerStateMachine = ( target: 'loaded', actions: ['storeDegradedDocStats', 'storeDatasets'], }, - onError: { - target: 'loaded', - actions: ['notifyFetchDegradedStatsFailed'], - }, + onError: [ + { + target: 'unauthorized', + cond: 'checkIfActionForbidden', + }, + { + target: 'loaded', + actions: ['notifyFetchDegradedStatsFailed'], + }, + ], }, }, loaded: {}, + unauthorized: { type: 'final' }, }, on: { UPDATE_TIME_RANGE: { @@ -187,13 +194,20 @@ export const createPureDatasetQualityControllerStateMachine = ( target: 'loaded', actions: ['storeNonAggregatableDatasets'], }, - onError: { - target: 'loaded', - actions: ['notifyFetchNonAggregatableDatasetsFailed'], - }, + onError: [ + { + target: 'unauthorized', + cond: 'checkIfActionForbidden', + }, + { + target: 'loaded', + actions: ['notifyFetchNonAggregatableDatasetsFailed'], + }, + ], }, }, loaded: {}, + unauthorized: { type: 'final' }, }, on: { UPDATE_TIME_RANGE: { @@ -300,15 +314,24 @@ export const createPureDatasetQualityControllerStateMachine = ( target: 'done', actions: ['storeIntegrationDashboards'], }, - onError: { - target: 'done', - actions: ['notifyFetchIntegrationDashboardsFailed'], - }, + onError: [ + { + target: 'unauthorized', + cond: 'checkIfActionForbidden', + }, + { + target: 'done', + actions: ['notifyFetchIntegrationDashboardsFailed'], + }, + ], }, }, done: { type: 'final', }, + unauthorized: { + type: 'final', + }, }, }, dataStreamDegradedFields: { @@ -505,15 +528,18 @@ export const createPureDatasetQualityControllerStateMachine = ( }), resetFlyoutOptions: assign((_context, _event) => ({ flyout: DEFAULT_CONTEXT.flyout })), storeDataStreamStats: assign((_context, event) => { - if ('data' in event) { - const dataStreamStats = event.data as DataStreamStat[]; + if ('data' in event && 'dataStreamsStats' in event.data) { + const dataStreamStats = event.data.dataStreamsStats as DataStreamStat[]; + const datasetUserPrivileges = event.data.datasetUserPrivileges; // Check if any DataStreamStat has null; to check for serverless - const isSizeStatsAvailable = dataStreamStats.some((stat) => stat.totalDocs !== null); + const isSizeStatsAvailable = + !dataStreamStats.length || dataStreamStats.some((stat) => stat.totalDocs !== null); return { dataStreamStats, isSizeStatsAvailable, + datasetUserPrivileges, }; } return {}; @@ -625,6 +651,11 @@ export const createPureDatasetQualityControllerStateMachine = ( : {}; }), }, + guards: { + checkIfActionForbidden: (context, event) => { + return 'data' in event && 'statusCode' in event.data && event.data.statusCode === 403; + }, + }, } ); diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts index 286f621fe432c..c75c648020d61 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/state_machines/dataset_quality_controller/src/types.ts @@ -8,6 +8,7 @@ import { DoneInvokeEvent } from 'xstate'; import { RefreshInterval, TimeRange } from '@kbn/data-plugin/common'; import { QualityIndicators, SortDirection } from '../../../../common/types'; +import { DatasetUserPrivileges } from '../../../../common/api_types'; import { Integration } from '../../../../common/data_streams_stats/integration'; import { DatasetTableSortField, DegradedFieldSortField } from '../../../hooks'; import { DegradedDocsStat } from '../../../../common/data_streams_stats/malformed_docs_stat'; @@ -79,6 +80,7 @@ export interface WithFilters { } export interface WithDataStreamStats { + datasetUserPrivileges: DatasetUserPrivileges; dataStreamStats: DataStreamStatType[]; } @@ -100,7 +102,7 @@ export interface WithIntegrations { } export type DefaultDatasetQualityControllerState = { type: string } & WithTableOptions & - Partial & + WithDataStreamStats & Partial & WithFlyoutOptions & WithDatasets & @@ -153,7 +155,9 @@ export type DatasetQualityControllerTypeState = context: DefaultDatasetQualityStateContext; } | { - value: 'flyout.initializing.integrationDashboards.fetching'; + value: + | 'flyout.initializing.integrationDashboards.fetching' + | 'flyout.initializing.integrationDashboards.unauthorized'; context: DefaultDatasetQualityStateContext; }; diff --git a/x-pack/plugins/observability_solution/dataset_quality/public/utils/generate_datasets.test.ts b/x-pack/plugins/observability_solution/dataset_quality/public/utils/generate_datasets.test.ts index 34f3812fd8e37..b6ce00628dd51 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/public/utils/generate_datasets.test.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/public/utils/generate_datasets.test.ts @@ -41,12 +41,18 @@ describe('generateDatasets', () => { size: '82.1kb', sizeBytes: 84160, integration: 'system', + userPrivileges: { + canMonitor: true, + }, }, { name: 'logs-synth-default', lastActivity: 1712911241117, size: '62.5kb', sizeBytes: 64066, + userPrivileges: { + canMonitor: true, + }, }, ]; @@ -115,6 +121,7 @@ describe('generateDatasets', () => { lastActivity: undefined, size: undefined, sizeBytes: undefined, + userPrivileges: undefined, namespace: indexNameToDataStreamParts(degradedDocs[0].dataset).namespace, title: integrations[0].datasets[indexNameToDataStreamParts(degradedDocs[0].dataset).dataset], @@ -133,6 +140,7 @@ describe('generateDatasets', () => { lastActivity: undefined, size: undefined, sizeBytes: undefined, + userPrivileges: undefined, namespace: indexNameToDataStreamParts(degradedDocs[1].dataset).namespace, title: indexNameToDataStreamParts(degradedDocs[1].dataset).dataset, integration: undefined, @@ -192,6 +200,9 @@ describe('generateDatasets', () => { size: '82.1kb', sizeBytes: 84160, integration: 'system', + userPrivileges: { + canMonitor: true, + }, }; const datasets = generateDatasets([nonDefaultDataset], undefined, integrations); diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_stream_details/get_data_stream_details.test.ts b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_stream_details/get_data_stream_details.test.ts index 1b850ff2c9fd9..c3758a4b2100c 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_stream_details/get_data_stream_details.test.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_stream_details/get_data_stream_details.test.ts @@ -5,19 +5,24 @@ * 2.0. */ -import { SearchTotalHitsRelation } from '@elastic/elasticsearch/lib/api/types'; +import { + IndicesDataStreamsStatsResponse, + SearchTotalHitsRelation, +} from '@elastic/elasticsearch/lib/api/types'; import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { findInventoryFields, InventoryItemType, inventoryModels, } from '@kbn/metrics-data-access-plugin/common'; +import { DataStreamDetails } from '../../../../common/api_types'; import { getDataStreamDetails } from '.'; const accessLogsDataStream = 'logs-nginx.access-default'; const errorLogsDataStream = 'logs-nginx.error-default'; +const nonExistentDataStream = 'non-existent'; -const defaultSummaryStats = { +const defaultSummaryStats: DataStreamDetails = { degradedDocsCount: 98841, docsCount: 617680, hosts: { @@ -33,6 +38,10 @@ const defaultSummaryStats = { 'service.name': ['synth-service-0', 'synth-service-1', 'synth-service-2'], }, sizeBytes: 72596354, + lastActivity: 1715941303175, + userPrivileges: { + canMonitor: true, + }, }; const start = Number(new Date('2020-01-01T00:00:00.000Z')); @@ -47,10 +56,11 @@ describe('getDataStreamDetails', () => { const esClientMock = elasticsearchServiceMock.createElasticsearchClient(); esClientMock.indices.getSettings.mockRejectedValue(MOCK_INDEX_ERROR); esClientMock.search.mockRejectedValue(MOCK_INDEX_ERROR); + esClientMock.security.hasPrivileges.mockResolvedValue(MOCK_HAS_PRIVILEGES_RESPONSE); const dataStreamDetails = await getDataStreamDetails({ esClient: esClientMock, - dataStream: 'non-existent', + dataStream: nonExistentDataStream, start, end, }); @@ -61,7 +71,11 @@ describe('getDataStreamDetails', () => { it('returns summary of a data stream', async () => { const esClientMock = elasticsearchServiceMock.createElasticsearchClient(); esClientMock.indices.stats.mockReturnValue(Promise.resolve(MOCK_STATS_RESPONSE)); + esClientMock.indices.dataStreamsStats.mockReturnValue( + Promise.resolve(MOCK_DATA_STREAM_RESPONSE as IndicesDataStreamsStatsResponse) + ); esClientMock.search.mockReturnValue(Promise.resolve(MOCK_SEARCH_RESPONSE)); + esClientMock.security.hasPrivileges.mockResolvedValue(MOCK_HAS_PRIVILEGES_RESPONSE); const dataStreamDetails = await getDataStreamDetails({ esClient: esClientMock, @@ -76,6 +90,10 @@ describe('getDataStreamDetails', () => { it('returns the correct service.name list', async () => { const esClientMock = elasticsearchServiceMock.createElasticsearchClient(); esClientMock.indices.stats.mockReturnValue(Promise.resolve(MOCK_STATS_RESPONSE)); + esClientMock.indices.dataStreamsStats.mockReturnValue( + Promise.resolve(MOCK_DATA_STREAM_RESPONSE as IndicesDataStreamsStatsResponse) + ); + esClientMock.security.hasPrivileges.mockResolvedValue(MOCK_HAS_PRIVILEGES_RESPONSE); const serviceName = 'service.name'; const testServiceName = ['tst-srv-0', 'tst-srv-1']; @@ -98,6 +116,10 @@ describe('getDataStreamDetails', () => { it('returns the correct host.name list', async () => { const esClientMock = elasticsearchServiceMock.createElasticsearchClient(); esClientMock.indices.stats.mockReturnValue(Promise.resolve(MOCK_STATS_RESPONSE)); + esClientMock.security.hasPrivileges.mockResolvedValue(MOCK_HAS_PRIVILEGES_RESPONSE); + esClientMock.indices.dataStreamsStats.mockReturnValue( + Promise.resolve(MOCK_DATA_STREAM_RESPONSE as IndicesDataStreamsStatsResponse) + ); const hostName = 'host.name'; const testHostName = ['tst-host-0', 'tst-host-1']; @@ -133,6 +155,10 @@ describe('getDataStreamDetails', () => { it('returns correct size in bytes', async () => { const esClientMock = elasticsearchServiceMock.createElasticsearchClient(); + esClientMock.security.hasPrivileges.mockResolvedValue(MOCK_HAS_PRIVILEGES_RESPONSE); + esClientMock.indices.dataStreamsStats.mockReturnValue( + Promise.resolve(MOCK_DATA_STREAM_RESPONSE as IndicesDataStreamsStatsResponse) + ); const docsCount = 536; const storeDocsCount = 1220; @@ -160,9 +186,13 @@ describe('getDataStreamDetails', () => { // This covers https://github.com/elastic/kibana/issues/178954 it('returns size as NaN for when sizeStatsAvailable is false (serverless mode)', async () => { const esClientMock = elasticsearchServiceMock.createElasticsearchClient(); + esClientMock.security.hasPrivileges.mockResolvedValue(MOCK_HAS_PRIVILEGES_RESPONSE); esClientMock.indices.stats.mockReturnValue(Promise.resolve(MOCK_STATS_RESPONSE)); esClientMock.search.mockReturnValue(Promise.resolve(MOCK_SEARCH_RESPONSE)); + esClientMock.indices.dataStreamsStats.mockReturnValue( + Promise.resolve(MOCK_DATA_STREAM_RESPONSE as IndicesDataStreamsStatsResponse) + ); const dataStreamDetails = await getDataStreamDetails({ esClient: esClientMock, @@ -173,6 +203,31 @@ describe('getDataStreamDetails', () => { }); expect(dataStreamDetails.sizeBytes).toBeNaN(); }); + + it('returns empty lastActivity and correct user privileges for an underprivileged user', async () => { + const esClientMock = elasticsearchServiceMock.createElasticsearchClient(); + esClientMock.security.hasPrivileges.mockResolvedValue({ + ...MOCK_HAS_PRIVILEGES_RESPONSE, + index: { + [accessLogsDataStream]: { + monitor: false, + }, + }, + }); + esClientMock.search.mockReturnValue(Promise.resolve(MOCK_SEARCH_RESPONSE)); + esClientMock.indices.dataStreamsStats.mockReturnValue( + Promise.resolve(MOCK_DATA_STREAM_RESPONSE as IndicesDataStreamsStatsResponse) + ); + + const dataStreamDetails = await getDataStreamDetails({ + esClient: esClientMock, + dataStream: accessLogsDataStream, + start, + end, + }); + + expect(dataStreamDetails.userPrivileges?.canMonitor ?? true).toBe(false); + }); }); const MOCK_INDEX_ERROR = { @@ -333,3 +388,40 @@ const MOCK_STATS_RESPONSE = { }, indices: {}, }; + +const MOCK_DATA_STREAM_RESPONSE = { + data_streams: [ + { + data_stream: errorLogsDataStream, + backing_indices: 1, + store_size: '19.1mb', + store_size_bytes: 20070975, + maximum_timestamp: 1715941303175, + }, + { + data_stream: accessLogsDataStream, + backing_indices: 1, + store_size: '11.3mb', + store_size_bytes: 20078875, + maximum_timestamp: 1715941304573, + }, + ], +}; + +const MOCK_HAS_PRIVILEGES_RESPONSE = { + username: 'elastic', + has_all_requested: true, + cluster: {}, + index: { + [nonExistentDataStream]: { + monitor: false, + }, + [accessLogsDataStream]: { + monitor: true, + }, + [errorLogsDataStream]: { + monitor: true, + }, + }, + application: {}, +}; diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_stream_details/index.ts b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_stream_details/index.ts index c6ee429c27f96..52804c5d9369c 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_stream_details/index.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_stream_details/index.ts @@ -18,7 +18,8 @@ import { MAX_HOSTS_METRIC_VALUE } from '../../../../common/constants'; import { _IGNORED } from '../../../../common/es_fields'; import { DataStreamDetails, DataStreamSettings } from '../../../../common/api_types'; import { createDatasetQualityESClient } from '../../../utils'; -import { dataStreamService } from '../../../services'; +import { dataStreamService, datasetQualityPrivileges } from '../../../services'; +import { getDataStreamsStats } from '../get_data_streams_stats'; export async function getDataStreamSettings({ esClient, @@ -51,6 +52,20 @@ export async function getDataStreamDetails({ }): Promise { throwIfInvalidDataStreamParams(dataStream); + const hasAccessToDataStream = ( + await datasetQualityPrivileges.getHasIndexPrivileges(esClient, [dataStream], ['monitor']) + )[dataStream]; + + const lastActivity = hasAccessToDataStream + ? ( + await getDataStreamsStats({ + esClient, + dataStreams: [dataStream], + sizeStatsAvailable, + }) + ).items[0]?.lastActivity + : undefined; + try { const dataStreamSummaryStats = await getDataStreamSummaryStats( esClient, @@ -61,7 +76,7 @@ export async function getDataStreamDetails({ const whenSizeStatsNotAvailable = NaN; // This will indicate size cannot be calculated const avgDocSizeInBytes = sizeStatsAvailable - ? dataStreamSummaryStats.docsCount > 0 + ? hasAccessToDataStream && dataStreamSummaryStats.docsCount > 0 ? await getAvgDocSizeInBytes(esClient, dataStream) : 0 : whenSizeStatsNotAvailable; @@ -70,6 +85,10 @@ export async function getDataStreamDetails({ return { ...dataStreamSummaryStats, sizeBytes, + lastActivity, + userPrivileges: { + canMonitor: hasAccessToDataStream, + }, }; } catch (e) { // Respond with empty object if data stream does not exist diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams/get_data_streams.test.ts b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams/get_data_streams.test.ts index 0b45d6fa8b34d..13e889cefc684 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams/get_data_streams.test.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams/get_data_streams.test.ts @@ -6,121 +6,118 @@ */ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; -import { dataStreamService } from '../../../services'; +import { dataStreamService, datasetQualityPrivileges } from '../../../services'; import { getDataStreams } from '.'; -jest.mock('../../../services/data_stream', () => { - return { - dataStreamService: { - getMatchingDataStreams: jest.fn().mockImplementation(() => { - return [ - { - name: 'logs-elastic_agent-default', - timestamp_field: { name: '@timestamp' }, - indices: [ - { - index_name: '.ds-logs-elastic_agent-default-2023.05.17-000001', - index_uuid: 'EcqQR36PTNCKVnfAftq_Rw', - }, - ], - generation: 1, - _meta: { managed_by: 'fleet', managed: true, package: { name: 'elastic_agent' } }, - status: 'YELLOW', - template: 'logs-elastic_agent', - ilm_policy: 'logs', - hidden: false, - system: false, - allow_custom_routing: false, - replicated: false, - }, - { - name: 'logs-elastic_agent.filebeat-default', - timestamp_field: { name: '@timestamp' }, - indices: [ - { - index_name: '.ds-logs-elastic_agent.filebeat-default-2023.05.17-000001', - index_uuid: 'v5uEn55TRrurU3Bf4CBtzw', - }, - ], - generation: 1, - _meta: { managed_by: 'fleet', managed: true, package: { name: 'elastic_agent' } }, - status: 'YELLOW', - template: 'logs-elastic_agent.filebeat', - ilm_policy: 'logs', - hidden: false, - system: false, - allow_custom_routing: false, - replicated: false, - }, - { - name: 'logs-elastic_agent.fleet_server-default', - timestamp_field: { name: '@timestamp' }, - indices: [ - { - index_name: '.ds-logs-elastic_agent.fleet_server-default-2023.05.17-000001', - index_uuid: 'nThe6dkaQnagAlyNsAyYsA', - }, - ], - generation: 1, - _meta: { managed_by: 'fleet', managed: true, package: { name: 'elastic_agent' } }, - status: 'YELLOW', - template: 'logs-elastic_agent.fleet_server', - ilm_policy: 'logs', - hidden: false, - system: false, - allow_custom_routing: false, - replicated: false, - }, - { - name: 'logs-elastic_agent.metricbeat-default', - timestamp_field: { name: '@timestamp' }, - indices: [ - { - index_name: '.ds-logs-elastic_agent.metricbeat-default-2023.05.17-000001', - index_uuid: 'Y5vQ7V6-QSSMM-CPdqOkCg', - }, - ], - generation: 1, - _meta: { managed_by: 'fleet', managed: true, package: { name: 'elastic_agent' } }, - status: 'YELLOW', - template: 'logs-elastic_agent.metricbeat', - ilm_policy: 'logs', - hidden: false, - system: false, - allow_custom_routing: false, - replicated: false, - }, - { - name: 'logs-test.test-default', - timestamp_field: { name: '@timestamp' }, - indices: [ - { - index_name: '.ds-logs-elastic_agent.metricbeat-default-2023.05.17-000001', - index_uuid: 'Y5vQ7V6-QSSMM-CPdqOkCg', - }, - ], - }, - ]; - }), - }, - }; -}); +const mockGetMockMatchingDataStreams = jest.fn().mockImplementation(() => MATCHING_DATA_STREAMS); +const mockGetDatasetPrivileges = jest.fn().mockImplementation(() => ({ + canRead: true, + canMonitor: true, + canViewIntegrations: true, +})); +const mockGetMockDataStreamPrivileges = jest.fn().mockImplementation(() => DATA_STREAMS_PRIVILEGES); describe('getDataStreams', () => { + beforeAll(() => { + // Mock dataStreamService + jest + .spyOn(dataStreamService, 'getMatchingDataStreams') + .mockImplementation(mockGetMockMatchingDataStreams); + jest + .spyOn(datasetQualityPrivileges, 'getDatasetPrivileges') + .mockImplementation(mockGetDatasetPrivileges); + jest + .spyOn(datasetQualityPrivileges, 'getHasIndexPrivileges') + .mockImplementation(mockGetMockDataStreamPrivileges); + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + + it('Returns empty list when user doesnt have access to dataset', async () => { + const esClientMock = elasticsearchServiceMock.createElasticsearchClient(); + mockGetDatasetPrivileges.mockImplementationOnce(() => ({ + canRead: false, + canMonitor: false, + canViewIntegrations: false, + })); + + const result = await getDataStreams({ + esClient: esClientMock, + type: 'logs', + datasetQuery: 'nginx', + uncategorisedOnly: false, + }); + expect(result.items).toEqual([]); + expect(result.datasetUserPrivileges.canMonitor).toBe(false); + + expect(dataStreamService.getMatchingDataStreams).not.toHaveBeenCalled(); + }); + it('Passes the correct parameters to the DataStreamService', async () => { const esClientMock = elasticsearchServiceMock.createElasticsearchClient(); - await getDataStreams({ + const result = await getDataStreams({ esClient: esClientMock, type: 'logs', datasetQuery: 'nginx', uncategorisedOnly: true, }); - expect(dataStreamService.getMatchingDataStreams).toHaveBeenCalledWith(expect.anything(), { + expect(dataStreamService.getMatchingDataStreams).toHaveBeenCalledWith( + expect.anything(), + 'logs-*nginx*' + ); + + expect(result.datasetUserPrivileges.canMonitor).toBe(true); + }); + + it('Formats the items correctly', async () => { + const esClientMock = elasticsearchServiceMock.createElasticsearchClient(); + + const results = await getDataStreams({ + esClient: esClientMock, type: 'logs', - dataset: '*nginx*', + uncategorisedOnly: false, }); + expect(results.items.sort()).toEqual([ + { + name: 'logs-elastic_agent-default', + integration: 'elastic_agent', + userPrivileges: { + canMonitor: true, + }, + }, + { + name: 'logs-elastic_agent.filebeat-default', + integration: 'elastic_agent', + userPrivileges: { + canMonitor: true, + }, + }, + { + name: 'logs-elastic_agent.fleet_server-default', + integration: 'elastic_agent', + userPrivileges: { + canMonitor: true, + }, + }, + { + name: 'logs-elastic_agent.metricbeat-default', + integration: 'elastic_agent', + userPrivileges: { + canMonitor: true, + }, + }, + { + name: 'logs-test.test-default', + userPrivileges: { + canMonitor: true, + }, + }, + ]); }); + describe('uncategorisedOnly option', () => { it('Returns the correct number of results when true', async () => { const esClientMock = elasticsearchServiceMock.createElasticsearchClient(); @@ -143,31 +140,99 @@ describe('getDataStreams', () => { expect(results.items.length).toBe(5); }); }); - it('Formats the items correctly', async () => { - const esClientMock = elasticsearchServiceMock.createElasticsearchClient(); - const results = await getDataStreams({ - esClient: esClientMock, - type: 'logs', - uncategorisedOnly: false, - }); - expect(results.items.sort()).toEqual([ +}); + +const MATCHING_DATA_STREAMS = [ + { + name: 'logs-elastic_agent-default', + timestamp_field: { name: '@timestamp' }, + indices: [ { - name: 'logs-elastic_agent-default', - integration: 'elastic_agent', + index_name: '.ds-logs-elastic_agent-default-2023.05.17-000001', + index_uuid: 'EcqQR36PTNCKVnfAftq_Rw', }, + ], + generation: 1, + _meta: { managed_by: 'fleet', managed: true, package: { name: 'elastic_agent' } }, + status: 'YELLOW', + template: 'logs-elastic_agent', + ilm_policy: 'logs', + hidden: false, + system: false, + allow_custom_routing: false, + replicated: false, + }, + { + name: 'logs-elastic_agent.filebeat-default', + timestamp_field: { name: '@timestamp' }, + indices: [ { - name: 'logs-elastic_agent.filebeat-default', - integration: 'elastic_agent', + index_name: '.ds-logs-elastic_agent.filebeat-default-2023.05.17-000001', + index_uuid: 'v5uEn55TRrurU3Bf4CBtzw', }, + ], + generation: 1, + _meta: { managed_by: 'fleet', managed: true, package: { name: 'elastic_agent' } }, + status: 'YELLOW', + template: 'logs-elastic_agent.filebeat', + ilm_policy: 'logs', + hidden: false, + system: false, + allow_custom_routing: false, + replicated: false, + }, + { + name: 'logs-elastic_agent.fleet_server-default', + timestamp_field: { name: '@timestamp' }, + indices: [ { - name: 'logs-elastic_agent.fleet_server-default', - integration: 'elastic_agent', + index_name: '.ds-logs-elastic_agent.fleet_server-default-2023.05.17-000001', + index_uuid: 'nThe6dkaQnagAlyNsAyYsA', }, + ], + generation: 1, + _meta: { managed_by: 'fleet', managed: true, package: { name: 'elastic_agent' } }, + status: 'YELLOW', + template: 'logs-elastic_agent.fleet_server', + ilm_policy: 'logs', + hidden: false, + system: false, + allow_custom_routing: false, + replicated: false, + }, + { + name: 'logs-elastic_agent.metricbeat-default', + timestamp_field: { name: '@timestamp' }, + indices: [ { - name: 'logs-elastic_agent.metricbeat-default', - integration: 'elastic_agent', + index_name: '.ds-logs-elastic_agent.metricbeat-default-2023.05.17-000001', + index_uuid: 'Y5vQ7V6-QSSMM-CPdqOkCg', }, - { name: 'logs-test.test-default' }, - ]); - }); -}); + ], + generation: 1, + _meta: { managed_by: 'fleet', managed: true, package: { name: 'elastic_agent' } }, + status: 'YELLOW', + template: 'logs-elastic_agent.metricbeat', + ilm_policy: 'logs', + hidden: false, + system: false, + allow_custom_routing: false, + replicated: false, + }, + { + name: 'logs-test.test-default', + timestamp_field: { name: '@timestamp' }, + indices: [ + { + index_name: '.ds-logs-elastic_agent.metricbeat-default-2023.05.17-000001', + index_uuid: 'Y5vQ7V6-QSSMM-CPdqOkCg', + }, + ], + }, +]; + +const DATA_STREAMS_PRIVILEGES = Object.values(MATCHING_DATA_STREAMS).reduce((acc, stream) => { + acc[stream.name] = true; + + return acc; +}, {} as Record); diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams/index.ts b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams/index.ts index d101985db4661..b2820c559c174 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams/index.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams/index.ts @@ -6,8 +6,9 @@ */ import type { ElasticsearchClient } from '@kbn/core/server'; +import { streamPartsToIndexPattern } from '../../../../common/utils'; import { DataStreamType } from '../../../../common/types'; -import { dataStreamService } from '../../../services'; +import { dataStreamService, datasetQualityPrivileges } from '../../../services'; export async function getDataStreams(options: { esClient: ElasticsearchClient; @@ -17,23 +18,49 @@ export async function getDataStreams(options: { }) { const { esClient, type, datasetQuery, uncategorisedOnly } = options; - const allDataStreams = await dataStreamService.getMatchingDataStreams(esClient, { - type: type ?? '*', - dataset: datasetQuery ? `*${datasetQuery}*` : '*', + const datasetName = streamPartsToIndexPattern({ + typePattern: type ?? '*', + datasetPattern: datasetQuery ? `*${datasetQuery}*` : '*', }); + const datasetUserPrivileges = await datasetQualityPrivileges.getDatasetPrivileges( + esClient, + datasetName + ); + + if (!datasetUserPrivileges.canMonitor) { + return { + items: [], + datasetUserPrivileges, + }; + } + + const allDataStreams = await dataStreamService.getMatchingDataStreams(esClient, datasetName); + const filteredDataStreams = uncategorisedOnly ? allDataStreams.filter((stream) => { return !stream._meta || !stream._meta.managed_by || stream._meta.managed_by !== 'fleet'; }) : allDataStreams; + const dataStreamsPrivileges = filteredDataStreams.length + ? await datasetQualityPrivileges.getHasIndexPrivileges( + esClient, + filteredDataStreams.map(({ name }) => name), + ['monitor'] + ) + : {}; + const mappedDataStreams = filteredDataStreams.map((dataStream) => ({ name: dataStream.name, integration: dataStream._meta?.package?.name, + userPrivileges: { + canMonitor: dataStreamsPrivileges[dataStream.name], + }, })); return { items: mappedDataStreams, + datasetUserPrivileges, }; } diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams_stats/get_data_streams_stats.test.ts b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams_stats/get_data_streams_stats.test.ts index a3f530a943d4f..5aa429ea52f9a 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams_stats/get_data_streams_stats.test.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams_stats/get_data_streams_stats.test.ts @@ -13,7 +13,7 @@ import { getDataStreamsStats } from '.'; jest.mock('../../../services/data_stream', () => { return { dataStreamService: { - getMatchingDataStreamsStats: jest.fn().mockImplementation(() => { + getStreamsStats: jest.fn().mockImplementation(() => { return [ { data_stream: 'logs-elastic_agent-default', @@ -72,24 +72,41 @@ jest.mock('../../../services/index_stats', () => { }; }); +const dataStream = 'logs-nginx.access-default'; + describe('getDataStreams', () => { it('Passes the correct parameters to the DataStreamService', async () => { const esClientMock = elasticsearchServiceMock.createElasticsearchClient(); await getDataStreamsStats({ esClient: esClientMock, - type: 'logs', - datasetQuery: 'nginx', + dataStreams: [dataStream], }); - expect(dataStreamService.getMatchingDataStreamsStats).toHaveBeenCalledWith(expect.anything(), { - type: 'logs', - dataset: '*nginx*', + expect(dataStreamService.getStreamsStats).toHaveBeenCalledWith(expect.anything(), [dataStream]); + }); + + it('returns an empty list when no dataStreams are provided', async () => { + jest.clearAllMocks(); + + const esClientMock = elasticsearchServiceMock.createElasticsearchClient(); + const results = await getDataStreamsStats({ + esClient: esClientMock, + dataStreams: [], }); + expect(dataStreamService.getStreamsStats).not.toHaveBeenCalled(); + expect(results.items).toEqual([]); }); + it('Formats the items correctly', async () => { const esClientMock = elasticsearchServiceMock.createElasticsearchClient(); const results = await getDataStreamsStats({ esClient: esClientMock, - type: 'logs', + dataStreams: [ + 'logs-elastic_agent-default', + 'logs-elastic_agent.filebeat-default', + 'logs-elastic_agent.fleet_server-default', + 'logs-elastic_agent.metricbeat-default', + 'logs-test.test-default', + ], }); expect(results.items.sort()).toEqual([ { diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams_stats/index.ts b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams_stats/index.ts index bb49b244c628f..41f89fa7ac156 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams_stats/index.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_data_streams_stats/index.ts @@ -6,28 +6,28 @@ */ import type { ElasticsearchClient } from '@kbn/core/server'; -import { DataStreamType } from '../../../../common/types'; import { dataStreamService } from '../../../services'; import { indexStatsService } from '../../../services'; export async function getDataStreamsStats({ esClient, - type, - datasetQuery, + dataStreams, sizeStatsAvailable = true, }: { esClient: ElasticsearchClient; - type?: DataStreamType; - datasetQuery?: string; + dataStreams: string[]; sizeStatsAvailable?: boolean; // Only Needed to determine whether `_stats` endpoint is available https://github.com/elastic/kibana/issues/178954 }) { - const matchingDataStreamsStats = dataStreamService.getMatchingDataStreamsStats(esClient, { - type: type ?? '*', - dataset: datasetQuery ? `*${datasetQuery}*` : '*', - }); + if (!dataStreams.length) { + return { + items: [], + }; + } + + const matchingDataStreamsStats = await dataStreamService.getStreamsStats(esClient, dataStreams); const indicesDocsCount = sizeStatsAvailable - ? indexStatsService.getIndicesDocCounts(esClient, type ?? '*') + ? indexStatsService.getIndicesDocCounts(esClient, dataStreams) : Promise.resolve(null); const [indicesDocsCountStats, dataStreamsStats] = await Promise.all([ diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_degraded_docs.ts b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_degraded_docs.ts index bba1f1dce3ec5..fa876d047abd7 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_degraded_docs.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/get_degraded_docs.ts @@ -23,6 +23,8 @@ interface ResultBucket { count: number; } +const SIZE_LIMIT = 10000; + export async function getDegradedDocsPaginated(options: { esClient: ElasticsearchClient; type?: DataStreamType; @@ -65,7 +67,7 @@ export async function getDegradedDocsPaginated(options: { datasets: { composite: { ...(afterKey ? { after: afterKey } : {}), - size: 10000, + size: SIZE_LIMIT, sources: [ { dataset: { terms: { field: 'data_stream.dataset' } } }, { namespace: { terms: { field: 'data_stream.namespace' } } }, @@ -99,9 +101,10 @@ export async function getDegradedDocsPaginated(options: { aggs: aggs(after?.docsCount), }, ]); + const [degradedDocsResponse, totalDocsResponse] = response.responses; const currDegradedDocs = - response.responses[0].aggregations?.datasets.buckets.map((bucket) => ({ + degradedDocsResponse.aggregations?.datasets.buckets.map((bucket) => ({ dataset: `${type}-${bucket.key.dataset}-${bucket.key.namespace}`, count: bucket.doc_count, })) ?? []; @@ -109,7 +112,7 @@ export async function getDegradedDocsPaginated(options: { const degradedDocs = [...prevResults.degradedDocs, ...currDegradedDocs]; const currTotalDocs = - response.responses[1].aggregations?.datasets.buckets.map((bucket) => ({ + totalDocsResponse.aggregations?.datasets.buckets.map((bucket) => ({ dataset: `${type}-${bucket.key.dataset}-${bucket.key.namespace}`, count: bucket.doc_count, })) ?? []; @@ -117,8 +120,8 @@ export async function getDegradedDocsPaginated(options: { const docsCount = [...prevResults.docsCount, ...currTotalDocs]; if ( - response.responses[0].aggregations?.datasets.after_key || - response.responses[1].aggregations?.datasets.after_key + totalDocsResponse.aggregations?.datasets.after_key && + totalDocsResponse.aggregations?.datasets.buckets.length === SIZE_LIMIT ) { return getDegradedDocsPaginated({ esClient, @@ -128,12 +131,12 @@ export async function getDegradedDocsPaginated(options: { datasetQuery, after: { degradedDocs: - (response.responses[0].aggregations?.datasets.after_key as { + (degradedDocsResponse.aggregations?.datasets.after_key as { dataset: string; namespace: string; }) || after?.degradedDocs, docsCount: - (response.responses[1].aggregations?.datasets.after_key as { + (totalDocsResponse.aggregations?.datasets.after_key as { dataset: string; namespace: string; }) || after?.docsCount, diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/routes.ts b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/routes.ts index 26761e53575fd..9862b11cf16e0 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/routes.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/routes/data_streams/routes.ts @@ -14,10 +14,11 @@ import { DegradedDocs, NonAggregatableDatasets, DegradedFieldResponse, + DatasetUserPrivileges, } from '../../../common/api_types'; -import { indexNameToDataStreamParts } from '../../../common/utils'; import { rangeRt, typeRt } from '../../types/default_api_types'; import { createDatasetQualityServerRoute } from '../create_datasets_quality_server_route'; +import { datasetQualityPrivileges } from '../../services'; import { getDataStreamDetails, getDataStreamSettings } from './get_data_stream_details'; import { getDataStreams } from './get_data_streams'; import { getDataStreamsStats } from './get_data_streams_stats'; @@ -39,6 +40,7 @@ const statsRoute = createDatasetQualityServerRoute({ tags: [], }, async handler(resources): Promise<{ + datasetUserPrivileges: DatasetUserPrivileges; dataStreamsStats: DataStreamStat[]; }> { const { context, params, getEsCapabilities } = resources; @@ -48,19 +50,24 @@ const statsRoute = createDatasetQualityServerRoute({ // Query datastreams as the current user as the Kibana internal user may not have all the required permissions const esClient = coreContext.elasticsearch.client.asCurrentUser; - const [dataStreams, dataStreamsStats] = await Promise.all([ - getDataStreams({ - esClient, - ...params.query, - uncategorisedOnly: false, - }), - getDataStreamsStats({ esClient, sizeStatsAvailable, ...params.query }), - ]); + const { items, datasetUserPrivileges } = await getDataStreams({ + esClient, + ...params.query, + uncategorisedOnly: false, + }); + + const privilegedDataStreams = items.filter((stream) => { + return stream.userPrivileges.canMonitor; + }); + const dataStreamsStats = await getDataStreamsStats({ + esClient, + dataStreams: privilegedDataStreams.map((stream) => stream.name), + sizeStatsAvailable, + }); return { - dataStreamsStats: values( - merge(keyBy(dataStreams.items, 'name'), keyBy(dataStreamsStats.items, 'name')) - ), + datasetUserPrivileges, + dataStreamsStats: values(merge(keyBy(items, 'name'), keyBy(dataStreamsStats.items, 'name'))), }; }, }); @@ -87,6 +94,12 @@ const degradedDocsRoute = createDatasetQualityServerRoute({ const esClient = coreContext.elasticsearch.client.asCurrentUser; + await datasetQualityPrivileges.throwIfCannotReadDataset( + esClient, + params.query.type, + params.query.datasetQuery + ); + const degradedDocs = await getDegradedDocsPaginated({ esClient, ...params.query, @@ -118,6 +131,8 @@ const nonAggregatableDatasetsRoute = createDatasetQualityServerRoute({ const esClient = coreContext.elasticsearch.client.asCurrentUser; + await datasetQualityPrivileges.throwIfCannotReadDataset(esClient, params.query.type); + return await getNonAggregatableDataStreams({ esClient, ...params.query, @@ -198,27 +213,16 @@ const dataStreamDetailsRoute = createDatasetQualityServerRoute({ // Query datastreams as the current user as the Kibana internal user may not have all the required permissions const esClient = coreContext.elasticsearch.client.asCurrentUser; - const { type, dataset, namespace } = indexNameToDataStreamParts(dataStream); const sizeStatsAvailable = !(await getEsCapabilities()).serverless; + const dataStreamDetails = await getDataStreamDetails({ + esClient, + dataStream, + start, + end, + sizeStatsAvailable, + }); - const [dataStreamsStats, dataStreamDetails] = await Promise.all([ - getDataStreamsStats({ - esClient, - type, - datasetQuery: `${dataset}-${namespace}`, - sizeStatsAvailable, - }), - getDataStreamDetails({ esClient, dataStream, start, end, sizeStatsAvailable }), - ]); - - return { - docsCount: dataStreamDetails?.docsCount, - degradedDocsCount: dataStreamDetails?.degradedDocsCount, - services: dataStreamDetails?.services, - hosts: dataStreamDetails?.hosts, - sizeBytes: dataStreamDetails?.sizeBytes, - lastActivity: dataStreamsStats.items?.[0]?.lastActivity, - }; + return dataStreamDetails; }, }); diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/services/data_stream.ts b/x-pack/plugins/observability_solution/dataset_quality/server/services/data_stream.ts index 9bd93efc9341c..a1b701e524c92 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/services/data_stream.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/services/data_stream.ts @@ -16,17 +16,11 @@ import { streamPartsToIndexPattern } from '../../common/utils'; class DataStreamService { public async getMatchingDataStreams( esClient: ElasticsearchClient, - dataStreamParts: { - dataset: string; - type: string; - } + datasetName: string ): Promise { try { const { data_streams: dataStreamsInfo } = await esClient.indices.getDataStream({ - name: streamPartsToIndexPattern({ - typePattern: dataStreamParts.type, - datasetPattern: dataStreamParts.dataset, - }), + name: datasetName, }); return dataStreamsInfo; @@ -63,6 +57,25 @@ class DataStreamService { } } + public async getStreamsStats( + esClient: ElasticsearchClient, + dataStreams: string[] + ): Promise { + try { + const { data_streams: dataStreamsStats } = await esClient.indices.dataStreamsStats({ + name: dataStreams.join(','), + human: true, + }); + + return dataStreamsStats; + } catch (e) { + if (e.statusCode === 404) { + return []; + } + throw e; + } + } + public async getDataSteamIndexSettings( esClient: ElasticsearchClient, dataStream: string diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/services/index.ts b/x-pack/plugins/observability_solution/dataset_quality/server/services/index.ts index 702d1d4fcc76b..f1da1af720137 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/services/index.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/services/index.ts @@ -7,3 +7,4 @@ export { dataStreamService } from './data_stream'; export { indexStatsService } from './index_stats'; +export { datasetQualityPrivileges } from './privileges'; diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/services/index_stats.ts b/x-pack/plugins/observability_solution/dataset_quality/server/services/index_stats.ts index 78db557e5f68e..967a8c393bcc8 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/services/index_stats.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/services/index_stats.ts @@ -16,10 +16,10 @@ interface IndexStatsResponse { class IndexStatsService { public async getIndicesDocCounts( esClient: ElasticsearchClient, - type: string + dataStreams: string[] ): Promise { try { - const index = `${type}-*-*`; + const index = dataStreams; const { indices } = await esClient.indices.stats({ index, metric: ['docs'] }); diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/services/privileges.ts b/x-pack/plugins/observability_solution/dataset_quality/server/services/privileges.ts new file mode 100644 index 0000000000000..d091ff849fdde --- /dev/null +++ b/x-pack/plugins/observability_solution/dataset_quality/server/services/privileges.ts @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { forbidden } from '@hapi/boom'; +import type { SecurityIndexPrivilege } from '@elastic/elasticsearch/lib/api/types'; +import type { ElasticsearchClient } from '@kbn/core/server'; + +import { streamPartsToIndexPattern } from '../../common/utils'; +import { DEFAULT_DATASET_TYPE } from '../../common/constants'; + +class DatasetQualityPrivileges { + public async getHasIndexPrivileges( + esClient: ElasticsearchClient, + indexes: string[], + privileges: SecurityIndexPrivilege[] + ): Promise>> { + const indexPrivileges = await esClient.security.hasPrivileges({ + index: indexes.map((dataStream) => ({ names: dataStream, privileges })), + }); + + const indexesList = Object.keys(indexPrivileges.index); + return indexesList.reduce>((acc, index) => { + const privilegesList = Object.values(indexPrivileges.index[index]); + const hasAllPrivileges = privilegesList.every((hasPrivilege) => hasPrivilege); + + return Object.assign(acc, { [index]: hasAllPrivileges }); + }, {}); + } + + public async getCanViewIntegrations( + esClient: ElasticsearchClient, + space = '*' + ): Promise { + const applicationPrivileges = await esClient.security.hasPrivileges({ + application: [ + { + application: 'kibana-.kibana', + privileges: ['feature_fleet.read'], + resources: [space], + }, + ], + }); + + return ( + applicationPrivileges.application?.['kibana-.kibana']?.[space]?.['feature_fleet.read'] ?? + false + ); + } + + public async getDatasetPrivileges( + esClient: ElasticsearchClient, + dataset: string, + space = '*' + ): Promise<{ canRead: boolean; canMonitor: boolean; canViewIntegrations: boolean }> { + const indexPrivileges = await esClient.security.hasPrivileges({ + index: [{ names: dataset, privileges: ['read', 'monitor', 'view_index_metadata'] }], + }); + + const canRead = indexPrivileges.index[dataset]?.read ?? false; + const canViewIndexMetadata = indexPrivileges.index[dataset]?.view_index_metadata ?? false; + + const canViewIntegrations = await this.getCanViewIntegrations(esClient, space); + + return { canRead, canMonitor: canViewIndexMetadata, canViewIntegrations }; + } + + public async canReadDataset( + esClient: ElasticsearchClient, + type = DEFAULT_DATASET_TYPE, + datasetQuery = '*', + space = '*' + ): Promise { + const datasetName = streamPartsToIndexPattern({ + typePattern: type, + datasetPattern: datasetQuery, + }); + + const datasetUserPrivileges = await datasetQualityPrivileges.getDatasetPrivileges( + esClient, + datasetName, + space + ); + + return datasetUserPrivileges.canRead; + } + + public async throwIfCannotReadDataset( + esClient: ElasticsearchClient, + type = DEFAULT_DATASET_TYPE, + datasetQuery = '*', + space = '*' + ): Promise { + if (!(await this.canReadDataset(esClient, type, datasetQuery, space))) { + const datasetName = streamPartsToIndexPattern({ + typePattern: type, + datasetPattern: datasetQuery, + }); + + throw forbidden(`Unauthorized to read dataset ${datasetName}`); + } + } +} + +export const datasetQualityPrivileges = new DatasetQualityPrivileges(); diff --git a/x-pack/plugins/observability_solution/dataset_quality/server/test_helpers/create_dataset_quality_users/authentication.ts b/x-pack/plugins/observability_solution/dataset_quality/server/test_helpers/create_dataset_quality_users/authentication.ts index 4b30ccbf00d9b..b5b177c644f59 100644 --- a/x-pack/plugins/observability_solution/dataset_quality/server/test_helpers/create_dataset_quality_users/authentication.ts +++ b/x-pack/plugins/observability_solution/dataset_quality/server/test_helpers/create_dataset_quality_users/authentication.ts @@ -8,12 +8,14 @@ export enum DatasetQualityUsername { noAccessUser = 'no_access_user', viewerUser = 'viewer', + readUser = 'readUser', editorUser = 'editor', datasetQualityLogsUser = 'dataset_quality_logs_user', } export enum DatasetQualityCustomRolename { datasetQualityLogsUser = 'dataset_quality_logs_user', + datasetQualityReadUser = 'dataset_quality_read_user', } export const customRoles = { @@ -22,7 +24,17 @@ export const customRoles = { indices: [ { names: ['logs-*-*'], - privileges: ['monitor'], + privileges: ['monitor', 'view_index_metadata'], + }, + ], + }, + }, + [DatasetQualityCustomRolename.datasetQualityReadUser]: { + elasticsearch: { + indices: [ + { + names: ['logs-*-*'], + privileges: ['read'], }, ], }, @@ -47,6 +59,9 @@ export const users: Record< builtInRoleNames: ['editor'], customRoleNames: [DatasetQualityCustomRolename.datasetQualityLogsUser], }, + [DatasetQualityUsername.readUser]: { + customRoleNames: [DatasetQualityCustomRolename.datasetQualityReadUser], + }, }; export const DATASET_QUALITY_TEST_PASSWORD = 'changeme'; diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/common/types.ts b/x-pack/plugins/observability_solution/infra/common/alerting/types.ts similarity index 100% rename from x-pack/plugins/observability_solution/infra/server/lib/alerting/common/types.ts rename to x-pack/plugins/observability_solution/infra/common/alerting/types.ts diff --git a/x-pack/plugins/observability_solution/infra/common/http_api/metadata_api.ts b/x-pack/plugins/observability_solution/infra/common/http_api/metadata_api.ts index 194cce74258f2..f9db4df10ed1c 100644 --- a/x-pack/plugins/observability_solution/infra/common/http_api/metadata_api.ts +++ b/x-pack/plugins/observability_solution/infra/common/http_api/metadata_api.ts @@ -44,6 +44,14 @@ export const InfraMetadataHostRT = rt.partial({ containerized: rt.boolean, }); +export const InfraMetadataContainerRT = rt.partial({ + name: rt.string, + id: rt.string, + runtime: rt.string, + imageName: rt.string, + image: rt.partial({ name: rt.string }), +}); + export const InfraMetadataInstanceRT = rt.partial({ id: rt.string, name: rt.string, @@ -71,6 +79,7 @@ export const InfraMetadataCloudRT = rt.partial({ project: InfraMetadataProjectRT, machine: InfraMetadataMachineRT, region: rt.string, + imageId: rt.string, }); export const InfraMetadataAgentRT = rt.partial({ @@ -82,6 +91,7 @@ export const InfraMetadataAgentRT = rt.partial({ export const InfraMetadataInfoRT = rt.partial({ cloud: InfraMetadataCloudRT, host: InfraMetadataHostRT, + container: InfraMetadataContainerRT, agent: InfraMetadataAgentRT, '@timestamp': rt.string, }); @@ -89,6 +99,7 @@ export const InfraMetadataInfoRT = rt.partial({ export const InfraMetadataInfoResponseRT = rt.partial({ cloud: InfraMetadataCloudRT, host: InfraMetadataHostRT, + container: InfraMetadataContainerRT, agent: InfraMetadataAgentRT, timestamp: rt.string, }); @@ -123,4 +134,6 @@ export type InfraMetadataMachine = rt.TypeOf; export type InfraMetadataHost = rt.TypeOf; +export type InfraMetadataContainer = rt.TypeOf; + export type InfraMetadataOS = rt.TypeOf; diff --git a/x-pack/plugins/observability_solution/infra/public/alerting/log_threshold/components/alert_details_app_section/index.tsx b/x-pack/plugins/observability_solution/infra/public/alerting/log_threshold/components/alert_details_app_section/index.tsx index ae0021adb2e01..36e2e91af9db0 100644 --- a/x-pack/plugins/observability_solution/infra/public/alerting/log_threshold/components/alert_details_app_section/index.tsx +++ b/x-pack/plugins/observability_solution/infra/public/alerting/log_threshold/components/alert_details_app_section/index.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useEffect } from 'react'; +import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { LEGACY_LIGHT_THEME } from '@elastic/charts'; import { EuiPanel } from '@elastic/eui'; @@ -37,16 +37,11 @@ import { useLicense } from '../../../../hooks/use_license'; const formatThreshold = (threshold: number) => String(threshold); -const AlertDetailsAppSection = ({ - rule, - alert, - setAlertSummaryFields, -}: AlertDetailsAppSectionProps) => { +const AlertDetailsAppSection = ({ rule, alert }: AlertDetailsAppSectionProps) => { const { logsShared } = useKibanaContextForPlugin().services; const theme = useTheme(); const timeRange = getPaddedAlertTimeRange(alert.fields[ALERT_START]!, alert.fields[ALERT_END]); const alertEnd = alert.fields[ALERT_END] ? moment(alert.fields[ALERT_END]).valueOf() : undefined; - const alertContext = alert.fields[ALERT_CONTEXT]; const interval = `${rule.params.timeSize}${rule.params.timeUnit}`; const thresholdFill = convertComparatorToFill(rule.params.count.comparator); const filter = rule.params.groupBy @@ -71,33 +66,6 @@ const AlertDetailsAppSection = ({ const { hasAtLeast } = useLicense(); const hasLicenseForLogRateAnalysis = hasAtLeast('platinum'); - useEffect(() => { - /** - * The `CriterionPreview` chart shows all the series/data stacked when there is a GroupBy in the rule parameters. - * e.g., `host.name`, the chart will show stacks of data by hostname. - * We only need the chart to show the series that is related to the selected alert. - * The chart series are built based on the GroupBy in the rule params - * Each series have an id which is the just a joining of fields value of the GroupBy `getChartGroupNames` - * We filter down the series using this group name - */ - const alertFieldsFromGroupBy = - rule.params.groupBy?.reduce( - (selectedFields: Record, field) => ({ - ...selectedFields, - ...{ - [field]: get(alertContext, ['groupByKeys', ...field.split('.')], null), - }, - }), - {} - ) || {}; - - const alertSummaryFields = Object.entries(alertFieldsFromGroupBy).map(([label, value]) => ({ - label, - value, - })); - setAlertSummaryFields(alertSummaryFields); - }, [alertContext, rule.params.groupBy, setAlertSummaryFields]); - const getLogRatioChart = () => { if (isRatioRule(rule.params.criteria)) { const numeratorKql = rule.params.criteria[0] diff --git a/x-pack/plugins/observability_solution/infra/public/alerting/log_threshold/components/alert_details_app_section/types.ts b/x-pack/plugins/observability_solution/infra/public/alerting/log_threshold/components/alert_details_app_section/types.ts index ee89bc3baea63..c76778aa81251 100644 --- a/x-pack/plugins/observability_solution/infra/public/alerting/log_threshold/components/alert_details_app_section/types.ts +++ b/x-pack/plugins/observability_solution/infra/public/alerting/log_threshold/components/alert_details_app_section/types.ts @@ -14,8 +14,3 @@ export interface AlertDetailsAppSectionProps { alert: TopAlert>; setAlertSummaryFields: React.Dispatch>; } - -export interface Group { - field: string; - value: string; -} diff --git a/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap b/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap index 72381f6e62d63..af8a5b3d8e0a9 100644 --- a/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap +++ b/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/__snapshots__/alert_details_app_section.test.tsx.snap @@ -3,37 +3,61 @@ exports[`AlertDetailsAppSection should render annotations 1`] = ` Array [ Object { + "additionalFilters": undefined, "annotations": Array [ - , - , + Object { + "color": "#BD271E", + "icon": "alert", + "id": "metric_threshold_alert_start_annotation", + "key": Object { + "timestamp": "2023-03-28T13:40:00.000Z", + "type": "point_in_time", + }, + "label": "Alert", + "type": "manual", + }, + Object { + "color": "#F04E9833", + "id": "metric_threshold_active_alert_range_annotation", + "key": Object { + "endTimestamp": "2024-06-13T07:00:33.381Z", + "timestamp": "2023-03-28T13:40:00.000Z", + "type": "range", + }, + "label": "Active alert", + "type": "manual", + }, ], - "chartType": "line", - "expression": Object { - "aggType": "count", + "chartOptions": Object { + "seriesType": "bar_stacked", + }, + "dataView": "index", + "groupBy": Array [ + "host.hostname", + ], + "metricExpression": Object { "comparator": ">", + "metrics": Array [ + Object { + "aggType": "count", + "field": "", + "name": "A", + }, + ], "threshold": Array [ 2000, ], "timeSize": 15, "timeUnit": "m", + "warningComparator": undefined, + "warningThreshold": undefined, + }, + "searchConfiguration": Object { + "query": Object { + "language": "", + "query": "", + }, }, - "filterQuery": undefined, - "groupBy": Array [ - "host.hostname", - ], - "groupInstance": Array [ - "host-1", - ], - "hideTitle": true, "timeRange": Object { "from": "2023-03-28T10:43:13.802Z", "to": "2023-03-29T13:14:09.581Z", diff --git a/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/alert_details_app_section.test.tsx b/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/alert_details_app_section.test.tsx index f7827fcbcadbb..5f8b99629eeb8 100644 --- a/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/alert_details_app_section.test.tsx +++ b/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/alert_details_app_section.test.tsx @@ -6,7 +6,6 @@ */ import React from 'react'; -import { EuiLink } from '@elastic/eui'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { coreMock as mockCoreMock } from '@kbn/core/public/mocks'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; @@ -17,17 +16,35 @@ import { buildMetricThresholdRule, } from '../mocks/metric_threshold_rule'; import { AlertDetailsAppSection } from './alert_details_app_section'; -import { ExpressionChart } from './expression_chart'; -import { Groups } from './groups'; -import { Tags } from './tags'; +import { RuleConditionChart } from '@kbn/observability-plugin/public'; +import { lensPluginMock } from '@kbn/lens-plugin/public/mocks'; const mockedChartStartContract = chartPluginMock.createStartContract(); +const mockedLensStartContract = lensPluginMock.createStartContract(); + +Date.now = jest.fn(() => new Date('2024-06-13T07:00:33.381Z').getTime()); + +jest.mock('../../../containers/metrics_source', () => ({ + useMetricsDataViewContext: () => ({ + metricsView: { dataViewReference: 'index' }, + }), + withSourceProvider: + (Component: React.FC) => + () => { + return function ComponentWithSourceProvider(props: ComponentProps) { + return
; + }; + }, +})); jest.mock('@kbn/observability-alert-details', () => ({ AlertAnnotation: () => {}, AlertActiveTimeRangeAnnotation: () => {}, })); - +jest.mock('@kbn/observability-alert-details', () => ({ + AlertAnnotation: () => {}, + AlertActiveTimeRangeAnnotation: () => {}, +})); jest.mock('@kbn/observability-get-padded-alert-time-range-util', () => ({ getPaddedAlertTimeRange: () => ({ from: '2023-03-28T10:43:13.802Z', @@ -35,8 +52,9 @@ jest.mock('@kbn/observability-get-padded-alert-time-range-util', () => ({ }), })); -jest.mock('./expression_chart', () => ({ - ExpressionChart: jest.fn(() =>
), +jest.mock('@kbn/observability-plugin/public', () => ({ + RuleConditionChart: jest.fn(() =>
), + getGroupFilters: jest.fn(), })); jest.mock('../../../hooks/use_kibana', () => ({ @@ -44,6 +62,7 @@ jest.mock('../../../hooks/use_kibana', () => ({ services: { ...mockCoreMock.createStart(), charts: mockedChartStartContract, + lens: mockedLensStartContract, }, }), })); @@ -51,7 +70,6 @@ jest.mock('../../../hooks/use_kibana', () => ({ describe('AlertDetailsAppSection', () => { const queryClient = new QueryClient(); const mockedSetAlertSummaryFields = jest.fn(); - const ruleLink = 'ruleLink'; const renderComponent = () => { return render( @@ -59,7 +77,6 @@ describe('AlertDetailsAppSection', () => { @@ -78,45 +95,12 @@ describe('AlertDetailsAppSection', () => { expect(result.getByTestId('threshold-2000-2500')).toBeTruthy(); }); - it('should render alert summary fields', async () => { - renderComponent(); - - expect(mockedSetAlertSummaryFields).toBeCalledTimes(1); - expect(mockedSetAlertSummaryFields).toBeCalledWith([ - { - label: 'Source', - value: ( - - ), - }, - { - label: 'Tags', - value: , - }, - { - label: 'Rule', - value: ( - - Monitoring hosts - - ), - }, - ]); - }); - it('should render annotations', async () => { - const mockedExpressionChart = jest.fn(() =>
); - (ExpressionChart as jest.Mock).mockImplementation(mockedExpressionChart); + const mockedRuleConditionChart = jest.fn(() =>
); + (RuleConditionChart as jest.Mock).mockImplementation(mockedRuleConditionChart); renderComponent(); - expect(mockedExpressionChart).toHaveBeenCalledTimes(3); - expect(mockedExpressionChart.mock.calls[0]).toMatchSnapshot(); + expect(mockedRuleConditionChart).toHaveBeenCalledTimes(3); + expect(mockedRuleConditionChart.mock.calls[0]).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/alert_details_app_section.tsx b/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/alert_details_app_section.tsx index 6a9e4999714bc..78d908d85ad8c 100644 --- a/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/alert_details_app_section.tsx +++ b/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/alert_details_app_section.tsx @@ -7,46 +7,40 @@ import { i18n } from '@kbn/i18n'; import { convertToBuiltInComparators } from '@kbn/observability-plugin/common'; -import React, { useEffect } from 'react'; +import React from 'react'; import moment from 'moment'; import { EuiFlexGroup, EuiFlexItem, - EuiLink, EuiPanel, EuiSpacer, EuiTitle, + transparentize, useEuiTheme, } from '@elastic/eui'; -import { AlertSummaryField, TopAlert } from '@kbn/observability-plugin/public'; -import { - ALERT_END, - ALERT_START, - ALERT_EVALUATION_VALUES, - ALERT_GROUP, - TAGS, -} from '@kbn/rule-data-utils'; -import { Rule } from '@kbn/alerting-plugin/common'; -import { AlertAnnotation, AlertActiveTimeRangeAnnotation } from '@kbn/observability-alert-details'; +import chroma from 'chroma-js'; + +import { AlertSummaryField, RuleConditionChart, TopAlert } from '@kbn/observability-plugin/public'; +import { ALERT_END, ALERT_START, ALERT_EVALUATION_VALUES, ALERT_GROUP } from '@kbn/rule-data-utils'; +import { Rule, RuleTypeParams } from '@kbn/alerting-plugin/common'; import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util'; +import type { + EventAnnotationConfig, + PointInTimeEventAnnotationConfig, + RangeEventAnnotationConfig, +} from '@kbn/event-annotation-common'; + +import { getGroupFilters } from '@kbn/observability-plugin/public'; +import type { GenericAggType } from '@kbn/observability-plugin/public'; import { metricValueFormatter } from '../../../../common/alerting/metrics/metric_value_formatter'; import { Threshold } from '../../common/components/threshold'; -import { withSourceProvider } from '../../../containers/metrics_source'; +import { useMetricsDataViewContext, withSourceProvider } from '../../../containers/metrics_source'; import { generateUniqueKey } from '../lib/generate_unique_key'; -import { MetricsExplorerChartType } from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; -import { MetricThresholdRuleTypeParams } from '..'; -import { ExpressionChart } from './expression_chart'; -import { Groups } from './groups'; -import { Tags } from './tags'; +import { AlertParams } from '../types'; // TODO Use a generic props for app sections https://github.com/elastic/kibana/issues/152690 -export type MetricThresholdRule = Rule< - MetricThresholdRuleTypeParams & { - filterQueryText?: string; - groupBy?: string | string[]; - } ->; +export type MetricThresholdRule = Rule; interface Group { field: string; @@ -60,80 +54,49 @@ interface MetricThresholdAlertField { export type MetricThresholdAlert = TopAlert; -const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD HH:mm'; -const ALERT_START_ANNOTATION_ID = 'alert_start_annotation'; -const ALERT_TIME_RANGE_ANNOTATION_ID = 'alert_time_range_annotation'; - interface AppSectionProps { alert: MetricThresholdAlert; rule: MetricThresholdRule; - ruleLink: string; setAlertSummaryFields: React.Dispatch>; } -export function AlertDetailsAppSection({ - alert, - rule, - ruleLink, - setAlertSummaryFields, -}: AppSectionProps) { - const { uiSettings, charts } = useKibanaContextForPlugin().services; +export function AlertDetailsAppSection({ alert, rule, setAlertSummaryFields }: AppSectionProps) { + const { charts } = useKibanaContextForPlugin().services; const { euiTheme } = useEuiTheme(); - const groupInstance = alert.fields[ALERT_GROUP]?.map((group: Group) => group.value); const groups = alert.fields[ALERT_GROUP]; - const tags = alert.fields[TAGS]; - + const { metricsView } = useMetricsDataViewContext(); const chartProps = { baseTheme: charts.theme.useChartsBaseTheme(), }; - const alertEnd = alert.fields[ALERT_END] ? moment(alert.fields[ALERT_END]).valueOf() : undefined; - const annotations = [ - , - , - ]; - useEffect(() => { - const alertSummaryFields = []; - if (groups) { - alertSummaryFields.push({ - label: i18n.translate('xpack.infra.metrics.alertDetailsAppSection.summaryField.source', { - defaultMessage: 'Source', - }), - value: , - }); - } - if (tags && tags.length > 0) { - alertSummaryFields.push({ - label: i18n.translate('xpack.infra.metrics.alertDetailsAppSection.summaryField.tags', { - defaultMessage: 'Tags', - }), - value: , - }); - } - alertSummaryFields.push({ - label: i18n.translate('xpack.infra.metrics.alertDetailsAppSection.summaryField.rule', { - defaultMessage: 'Rule', - }), - value: ( - - {rule.name} - - ), - }); + const alertEnd = alert.fields[ALERT_END]; + const alertStart = alert.fields[ALERT_START]; - setAlertSummaryFields(alertSummaryFields); - }, [groups, tags, rule, ruleLink, setAlertSummaryFields]); + const alertStartAnnotation: PointInTimeEventAnnotationConfig = { + label: 'Alert', + type: 'manual', + key: { + type: 'point_in_time', + timestamp: alertStart!, + }, + color: euiTheme.colors.danger, + icon: 'alert', + id: 'metric_threshold_alert_start_annotation', + }; + + const alertRangeAnnotation: RangeEventAnnotationConfig = { + label: `${alertEnd ? 'Alert duration' : 'Active alert'}`, + type: 'manual', + key: { + type: 'range', + timestamp: alertStart!, + endTimestamp: alertEnd ?? moment().toISOString(), + }, + color: chroma(transparentize('#F04E981A', 0.2)).hex().toUpperCase(), + id: `metric_threshold_${alertEnd ? 'recovered' : 'active'}_alert_range_annotation`, + }; + + const annotations: EventAnnotationConfig[] = []; + annotations.push(alertStartAnnotation, alertRangeAnnotation); return !!rule.params.criteria ? ( @@ -142,10 +105,25 @@ export function AlertDetailsAppSection({ alert.fields[ALERT_START]!, alert.fields[ALERT_END], { - size: criterion.timeSize, - unit: criterion.timeUnit, + size: criterion.timeSize!, + unit: criterion.timeUnit!, } ); + let metricExpression = [ + { + aggType: criterion.aggType as GenericAggType, + name: String.fromCharCode('A'.charCodeAt(0) + index), + field: criterion.metric || '', + }, + ]; + if (criterion.customMetrics) { + metricExpression = criterion.customMetrics.map((metric) => ({ + name: metric.name, + aggType: metric.aggType as GenericAggType, + field: metric.field || '', + filter: metric.filter, + })); + } return ( @@ -183,16 +161,30 @@ export function AlertDetailsAppSection({ /> - + {metricsView && ( + + )} diff --git a/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/expression.tsx b/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/expression.tsx index 191c6ed8cd847..9eaf5e2bd7b45 100644 --- a/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/expression.tsx +++ b/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/expression.tsx @@ -28,6 +28,7 @@ import { } from '@kbn/triggers-actions-ui-plugin/public'; import { TimeUnitChar } from '@kbn/observability-plugin/common/utils/formatters/duration'; import { COMPARATORS } from '@kbn/alerting-comparators'; +import { GenericAggType, RuleConditionChart } from '@kbn/observability-plugin/public'; import { Aggregators, QUERY_INVALID } from '../../../../common/alerting/metrics'; import { useMetricsDataViewContext, @@ -40,7 +41,6 @@ import { MetricsExplorerKueryBar } from '../../../pages/metrics/metrics_explorer import { MetricsExplorerOptions } from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options'; import { convertKueryToElasticSearchQuery } from '../../../utils/kuery'; import { AlertContextMeta, AlertParams, MetricExpression } from '../types'; -import { ExpressionChart } from './expression_chart'; import { ExpressionRow } from './expression_row'; const FILTER_TYPING_DEBOUNCE_MS = 500; @@ -69,7 +69,6 @@ export const Expressions: React.FC = (props) => { const { docLinks } = useKibanaContextForPlugin().services; const { source } = useSourceContext(); const { metricsView } = useMetricsDataViewContext(); - const [timeSize, setTimeSize] = useState(1); const [timeUnit, setTimeUnit] = useState('m'); @@ -304,8 +303,24 @@ export const Expressions: React.FC = (props) => { - {ruleParams.criteria && + {metricsView && ruleParams.criteria.map((e, idx) => { + let metricExpression = [ + { + aggType: e.aggType as GenericAggType, + // RuleConditionChart uses A,B,C etc in its parser to identify multiple conditions + name: String.fromCharCode('A'.charCodeAt(0) + idx), + field: e.metric || '', + }, + ]; + if (e.customMetrics) { + metricExpression = e.customMetrics.map((metric) => ({ + name: metric.name, + aggType: metric.aggType as GenericAggType, + field: metric.field || '', + filter: metric.filter, + })); + } return ( 1) || false} @@ -317,9 +332,26 @@ export const Expressions: React.FC = (props) => { errors={(errors[idx] as IErrorObject) || emptyError} expression={e || {}} > - diff --git a/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/groups.tsx b/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/groups.tsx deleted file mode 100644 index c68fa2a391448..0000000000000 --- a/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/groups.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -export function Groups({ groups }: { groups: Array<{ field: string; value: string }> }) { - return ( - <> - {groups && - groups.map((group) => { - return ( - - {group.field}: {group.value} -
-
- ); - })} - - ); -} diff --git a/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/mocks/metric_threshold_rule.ts b/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/mocks/metric_threshold_rule.ts index 0ef6478ff12d7..f7ec9022b4cad 100644 --- a/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/mocks/metric_threshold_rule.ts +++ b/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/mocks/metric_threshold_rule.ts @@ -87,6 +87,7 @@ export const buildMetricThresholdRule = ( filterQuery: '{"bool":{"filter":[{"bool":{"should":[{"term":{"host.hostname":{"value":"Users-System.local"}}}],"minimum_should_match":1}},{"bool":{"should":[{"term":{"service.type":{"value":"system"}}}],"minimum_should_match":1}}]}}', groupBy: ['host.hostname'], + sourceId: 'sourceId', }, monitoring: { run: { diff --git a/x-pack/plugins/observability_solution/infra/public/common/asset_details_config/asset_details_tabs.tsx b/x-pack/plugins/observability_solution/infra/public/common/asset_details_config/asset_details_tabs.tsx index 173d39726292f..3aa55f0581496 100644 --- a/x-pack/plugins/observability_solution/infra/public/common/asset_details_config/asset_details_tabs.tsx +++ b/x-pack/plugins/observability_solution/infra/public/common/asset_details_config/asset_details_tabs.tsx @@ -111,8 +111,14 @@ export const hostDetailsTabs: Tab[] = [ export const hostDetailsFlyoutTabs: Tab[] = [...hostDetailsTabs, linkToApmTab]; // The profiling tab would be added in next iteration -export const containerDetailsTabs: Tab[] = [overviewTab, metadataTab, logsTab]; -export const containerDetailsFlyoutTabs: Tab[] = [overviewTab, metadataTab, logsTab, linkToApmTab]; +export const containerDetailsTabs: Tab[] = [overviewTab, metadataTab, metricsTab, logsTab]; +export const containerDetailsFlyoutTabs: Tab[] = [ + overviewTab, + metadataTab, + metricsTab, + logsTab, + linkToApmTab, +]; export const getAssetDetailsTabs = (type: string): Tab[] => { switch (type) { diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/charts/docker_charts.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/charts/docker_charts.tsx index 2a5fdbc96533c..b474de0ae8e49 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/charts/docker_charts.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/charts/docker_charts.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common'; import { FormattedMessage } from '@kbn/i18n-react'; import { css, cx } from '@emotion/css'; -import { EuiText, EuiLink } from '@elastic/eui'; +import { EuiText, EuiLink, EuiButtonEmpty } from '@elastic/eui'; import { useDockerContainerPageViewMetricsCharts } from '../hooks/use_container_metrics_charts'; import { Section } from '../components/section'; import { ChartsGrid } from '../charts_grid/charts_grid'; @@ -26,7 +26,7 @@ interface Props extends MetricsChartsFields { const FRAGMENT_BASE = 'key-metrics'; export const DockerCharts = React.forwardRef( - ({ assetId, dataView, dateRange, metric }, ref) => { + ({ assetId, dataView, dateRange, metric, onShowAll }, ref) => { const { charts } = useDockerContainerPageViewMetricsCharts({ metric, metricsDataViewId: dataView?.id, @@ -67,8 +67,25 @@ export const DockerCharts = React.forwardRef( /> } data-test-subj={`infraAssetDetailsDockerChartsSection${metric}`} - id="dockerContainerCharts" + id={metric} ref={ref} + extraAction={ + onShowAll ? ( + onShowAll(metric)} + size="xs" + flush="both" + iconSide="right" + iconType="sortRight" + > + + + ) : null + } > {charts.map((chart) => ( diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/charts/kubernetes_charts.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/charts/kubernetes_charts.tsx index 55771a47d09b2..b9f78aa0bdf93 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/charts/kubernetes_charts.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/charts/kubernetes_charts.tsx @@ -19,7 +19,7 @@ import { Chart } from './chart'; import { useIntegrationCheck } from '../hooks/use_integration_check'; import { useK8sContainerPageViewMetricsCharts } from '../hooks/use_container_metrics_charts'; import { CONTAINER_METRICS_DOC_HREF } from '../../../common/visualizations/constants'; -import { ContainerMetricTypes, MetricsChartsFields } from './types'; +import { KubernetesContainerMetrics, MetricsChartsFields } from './types'; const FRAGMENT_BASE = 'key-metrics'; @@ -79,8 +79,8 @@ export const KubernetesNodeCharts = React.forwardRef(({ assetId, dataView, dateRange, metric }, ref) => { + MetricsChartsFields & { metric: KubernetesContainerMetrics } +>(({ assetId, dataView, dateRange, metric, onShowAll }, ref) => { const { charts } = useK8sContainerPageViewMetricsCharts({ metric, metricsDataViewId: dataView?.id, @@ -121,9 +121,26 @@ export const KubernetesContainerCharts = React.forwardRef< } /> } - data-test-subj="infraAssetDetailsK8ContainerChartsSection" - id="k8sContainerCharts" + data-test-subj={`infraAssetDetailsK8ContainerChartsSection${metric}`} + id={metric} ref={ref} + extraAction={ + onShowAll ? ( + onShowAll(metric)} + size="xs" + flush="both" + iconSide="right" + iconType="sortRight" + > + + + ) : null + } > {charts.map((chart) => ( diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/components/expandable_content.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/components/expandable_content.tsx index 1d96dd143b01b..d96ee11101464 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/components/expandable_content.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/components/expandable_content.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; @@ -27,8 +27,10 @@ export const ExpandableContent = (props: ExpandableContentProps) => { return ( -
- {first} + + +

{first}

+
{shouldShowMore && ( <> {' ... '} @@ -46,7 +48,7 @@ export const ExpandableContent = (props: ExpandableContentProps) => { )} -
+ {isExpanded && others.map((item, index) => {item})} {hasOthers && isExpanded && ( diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/components/kpis/container_kpi_charts.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/components/kpis/container_kpi_charts.tsx index 26ecccc7f1541..c2f61b97257fc 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/components/kpis/container_kpi_charts.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/components/kpis/container_kpi_charts.tsx @@ -44,24 +44,30 @@ export const ContainerKpiCharts = ({ if (!isDockerContainer && !isKubernetesContainer) { return null; } - return isKubernetesContainer ? ( - - ) : ( - + + return ( + <> + {isDockerContainer && ( + + )} + {!isDockerContainer && isKubernetesContainer && ( + + )} + ); }; diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/components/metadata_explanation.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/components/metadata_explanation.tsx index ddf3cbb6a2316..eb24ed2c38131 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/components/metadata_explanation.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/components/metadata_explanation.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { EuiText, EuiLink } from '@elastic/eui'; import { FormattedDate, FormattedMessage, FormattedTime } from '@kbn/i18n-react'; +import { InventoryItemType } from '@kbn/metrics-data-access-plugin/common'; import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import { Popover } from '../tabs/common/popover'; import { useMetadataStateContext } from '../hooks/use_metadata_state'; @@ -15,45 +16,53 @@ import { useMetadataStateContext } from '../hooks/use_metadata_state'; const HOSTNAME_DOCS_LINK = 'https://www.elastic.co/guide/en/ecs/current/ecs-host.html#field-host-name'; -const MetadataExplanationTooltipContent = React.memo(() => { - const onClick = (e: React.MouseEvent) => { - e.stopPropagation(); - }; +const CONTAINER_ID_DOCS_LINK = + 'https://www.elastic.co/guide/en/ecs/current/ecs-container.html#field-container-id'; - return ( - - - - - ), - hostName: ( - - - - ), - }} - /> - - ); -}); +const MetadataExplanationTooltipContent = React.memo( + ({ docsLink, metadataField }: { docsLink: string; metadataField: string }) => { + const onClick = (e: React.MouseEvent) => { + e.stopPropagation(); + }; -export const MetadataExplanationMessage = () => { + return ( + + + + + ), + metadataField: ( + + + + ), + }} + /> + + ); + } +); + +export const MetadataExplanationMessage = ({ assetType }: { assetType: InventoryItemType }) => { const { metadata, loading } = useMetadataStateContext(); + const docsLink = assetType === 'host' ? HOSTNAME_DOCS_LINK : CONTAINER_ID_DOCS_LINK; + const metadataField = assetType === 'host' ? 'host.name' : 'container.id'; return loading && !metadata ? ( @@ -93,7 +102,7 @@ export const MetadataExplanationMessage = () => { icon="iInCircle" data-test-subj="infraAssetDetailsMetadataPopoverButton" > - +
diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/constants.ts b/x-pack/plugins/observability_solution/infra/public/components/asset_details/constants.ts index 49a174ba22c1c..e189c8e3524f3 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/constants.ts +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/constants.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { DockerContainerMetrics, KubernetesContainerMetrics } from './charts/types'; import { INTEGRATION_NAME, ASSET_DETAILS_ASSET_TYPE } from './types'; export const ASSET_DETAILS_FLYOUT_COMPONENT_NAME = 'infraAssetDetailsFlyout'; @@ -25,3 +26,6 @@ export const INTEGRATIONS = { [INTEGRATION_NAME.kubernetesContainer]: 'kubernetes.container', [INTEGRATION_NAME.docker]: 'docker', }; + +export const DOCKER_METRIC_TYPES: DockerContainerMetrics[] = ['cpu', 'memory', 'network', 'disk']; +export const KUBERNETES_METRIC_TYPES: KubernetesContainerMetrics[] = ['cpu', 'memory']; diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/logs/logs.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/logs/logs.tsx index a8236069fe3a7..72ad37ce1cfda 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/logs/logs.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/logs/logs.tsx @@ -60,7 +60,7 @@ export const Logs = () => { const filter = useMemo(() => { const query = [ - `${findInventoryFields(asset.type).id}: "${asset.name}"`, + `${findInventoryFields(asset.type).id}: "${asset.id}"`, ...(textQueryDebounced !== '' ? [textQueryDebounced] : []), ].join(' and '); @@ -68,7 +68,7 @@ export const Logs = () => { language: 'kuery', query, }; - }, [asset.type, asset.name, textQueryDebounced]); + }, [asset.type, asset.id, textQueryDebounced]); const onQueryChange = useCallback((e: React.ChangeEvent) => { setTextQuery(e.target.value); @@ -82,12 +82,12 @@ export const Logs = () => { const logsUrl = useMemo(() => { return nodeLogsLocator.getRedirectUrl({ nodeField: findInventoryFields(asset.type).id, - nodeId: asset.name, + nodeId: asset.id, time: state.startTimestamp, filter: textQueryDebounced, logView, }); - }, [nodeLogsLocator, asset.name, asset.type, state.startTimestamp, textQueryDebounced, logView]); + }, [nodeLogsLocator, asset.id, asset.type, state.startTimestamp, textQueryDebounced, logView]); return ( diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/metadata/metadata.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/metadata/metadata.tsx index 02168778c3676..0a0e50baec31a 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/metadata/metadata.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/metadata/metadata.tsx @@ -22,7 +22,7 @@ export interface MetadataSearchUrlState { export const Metadata = () => { const [urlState, setUrlState] = useAssetDetailsUrlState(); - const { overrides } = useAssetDetailsRenderPropsContext(); + const { overrides, asset } = useAssetDetailsRenderPropsContext(); const { metadata, loading: metadataLoading, @@ -45,7 +45,7 @@ export const Metadata = () => { return ( <> - +
{ expect(getAllFields(result)).toStrictEqual([{ name: 'host.os.name', value: 'Ubuntu' }]); }); + it('should map metadata with nested properties with container data removing >3th level nesting', async () => { + const result = { + id: 'host1', + name: 'host1', + features: [ + { + name: 'system.core', + source: 'metrics', + }, + ], + info: { + container: { + id: '33d16f043d5f8a7dcc2f9a2164920d0d7ca4c13a9f737bff3dbedb507d954b8e', + name: 'load-generator', + image: { + name: 'ghcr.io/open-telemetry/demo:latest-loadgenerator', // accept + }, + runtime: 'docker', + network: { + ingress: { + bytes: 1410228770498, // ignore + }, + egress: { + bytes: 23527514469, // ignore + }, + }, + }, + }, + } as InfraMetadata; + expect(getAllFields(result)).toStrictEqual([ + { + name: 'container.id', + value: '33d16f043d5f8a7dcc2f9a2164920d0d7ca4c13a9f737bff3dbedb507d954b8e', + }, + { name: 'container.name', value: 'load-generator' }, + { + name: 'container.image.name', + value: 'ghcr.io/open-telemetry/demo:latest-loadgenerator', + }, + { + name: 'container.runtime', + value: 'docker', + }, + ]); + }); + it('should map metadata with partial host, agent, could data', async () => { const result: InfraMetadata = { id: 'host1', diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/metadata/utils.ts b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/metadata/utils.ts index e41a09b7534e6..9e99816874565 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/metadata/utils.ts +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/metadata/utils.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { isPlainObject } from 'lodash'; import type { InfraMetadata } from '../../../../../common/http_api'; export interface Field { @@ -18,7 +19,10 @@ interface FieldsByCategory { export const getAllFields = (metadata: InfraMetadata | null) => { if (!metadata?.info) return []; - const mapNestedProperties = (category: 'cloud' | 'host' | 'agent', property: string) => { + const mapNestedProperties = ( + category: 'cloud' | 'host' | 'agent' | 'container', + property: string + ) => { const fieldsByCategory: FieldsByCategory = metadata?.info?.[`${category}`] ?? {}; if (fieldsByCategory.hasOwnProperty(property)) { const value = fieldsByCategory[property]; @@ -36,10 +40,17 @@ export const getAllFields = (metadata: InfraMetadata | null) => { value, }; } else { - return Object.entries(value ?? {}).map(([prop, subProp]) => ({ - name: `${category}.${property}.${prop}`, - value: subProp, - })); + return Object.entries(value ?? {}) + .map(([prop, subProp]) => { + if (!Array.isArray(subProp) && isPlainObject(subProp)) { + return { name: '', value: '' }; + } + return { + name: `${category}.${property}.${prop}`, + value: subProp, + }; + }) + .filter(({ name }) => name); } } return []; @@ -54,8 +65,11 @@ export const getAllFields = (metadata: InfraMetadata | null) => { const host = Object.keys(metadata?.info?.host ?? {}).flatMap((prop) => mapNestedProperties('host', prop) ); + const container = Object.keys(metadata?.info?.container ?? {}).flatMap((prop) => + mapNestedProperties('container', prop) + ); - return prune([...host, ...agent, ...cloud]); + return prune([...host, ...container, ...agent, ...cloud]); }; const prune = (fields: Field[]) => fields.filter((f) => !!f?.value); diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/metrics/container_metrics.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/metrics/container_metrics.tsx new file mode 100644 index 0000000000000..f49fa026892b1 --- /dev/null +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/metrics/container_metrics.tsx @@ -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 React, { useRef } from 'react'; +import { useDatePickerContext } from '../../hooks/use_date_picker'; +import { useAssetDetailsRenderPropsContext } from '../../hooks/use_asset_details_render_props'; +import { useDataViewsContext } from '../../hooks/use_data_views'; +import { useIntersectingState } from '../../hooks/use_intersecting_state'; +import { MetricsTemplate } from './metrics_template'; +import { DockerCharts, KubernetesContainerCharts } from '../../charts'; +import { DOCKER_METRIC_TYPES, INTEGRATIONS, KUBERNETES_METRIC_TYPES } from '../../constants'; +import { useIntegrationCheck } from '../../hooks/use_integration_check'; + +export const ContainerMetrics = () => { + const ref = useRef(null); + const { dateRange } = useDatePickerContext(); + const { asset } = useAssetDetailsRenderPropsContext(); + const { metrics } = useDataViewsContext(); + + const state = useIntersectingState(ref, { dateRange }); + + const isDockerContainer = useIntegrationCheck({ dependsOn: INTEGRATIONS.docker }); + const isKubernetesContainer = useIntegrationCheck({ + dependsOn: INTEGRATIONS.kubernetesContainer, + }); + + if (!isDockerContainer && !isKubernetesContainer) { + return null; + } + + return ( + + {isDockerContainer && + DOCKER_METRIC_TYPES.map((metric) => ( + + ))} + {!isDockerContainer && + isKubernetesContainer && + KUBERNETES_METRIC_TYPES.map((metric) => ( + + ))} + + ); +}; diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/metrics/metrics.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/metrics/metrics.tsx index 6ac49ba9abfe5..2ad097808b1d3 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/metrics/metrics.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/metrics/metrics.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; import { HostMetrics } from './host_metrics'; +import { ContainerMetrics } from './container_metrics'; import { useAssetDetailsRenderPropsContext } from '../../hooks/use_asset_details_render_props'; export const Metrics = () => { @@ -14,6 +15,8 @@ export const Metrics = () => { switch (asset.type) { case 'host': return ; + case 'container': + return ; default: return null; } diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/alerts/alerts.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/alerts/alerts.tsx index f33fa9bca6bdf..304e67a0debde 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/alerts/alerts.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/alerts/alerts.tsx @@ -21,6 +21,8 @@ import { type AlertsCount } from '../../../../../hooks/use_alerts_count'; import { AlertsOverview } from '../../../../shared/alerts/alerts_overview'; import { CreateAlertRuleButton } from '../../../../shared/alerts/links/create_alert_rule_button'; import { LinkToAlertsPage } from '../../../../shared/alerts/links/link_to_alerts_page'; +import { useIntegrationCheck } from '../../../hooks/use_integration_check'; +import { INTEGRATIONS } from '../../../constants'; export const AlertsSummaryContent = ({ assetId, @@ -47,6 +49,10 @@ export const AlertsSummaryContent = ({ }; const assetIdField = findInventoryFields(assetType).id; + const isDockerContainer = useIntegrationCheck({ dependsOn: INTEGRATIONS.docker }); + const showCreateRuleFeature = + featureFlags.inventoryThresholdAlertRuleEnabled && + (assetType !== 'container' || isDockerContainer); return ( <> @@ -59,7 +65,7 @@ export const AlertsSummaryContent = ({ initialTriggerValue={collapsibleStatus} extraAction={ - {featureFlags.inventoryThresholdAlertRuleEnabled && ( + {showCreateRuleFeature && ( } > - + - {featureFlags.inventoryThresholdAlertRuleEnabled && ( + {showCreateRuleFeature && ( [ +const hostExtendedMetadata = (metadataInfo: InfraMetadata['info']): MetadataData[] => [ { field: 'cloudProvider', value: metadataInfo?.cloud?.provider, @@ -56,7 +59,7 @@ const extendedMetadata = (metadataInfo: InfraMetadata['info']): MetadataData[] = }, ]; -const metadataData = (metadataInfo: InfraMetadata['info']): MetadataData[] => [ +const hostMetadataData = (metadataInfo: InfraMetadata['info']): MetadataData[] => [ { field: 'hostIp', value: metadataInfo?.host?.ip, @@ -70,9 +73,51 @@ const metadataData = (metadataInfo: InfraMetadata['info']): MetadataData[] => [ }, ]; +const containerExtendedMetadata = (metadataInfo: InfraMetadata['info']): MetadataData[] => [ + { + field: 'runtime', + value: metadataInfo?.container?.runtime, + tooltipFieldLabel: 'container.runtime', + }, + { + field: 'cloudInstanceId', + value: metadataInfo?.cloud?.instance?.id, + tooltipFieldLabel: 'cloud.instance.id', + }, + { + field: 'cloudImageId', + value: metadataInfo?.cloud?.imageId, + tooltipFieldLabel: 'cloud.image.id', + }, + { + field: 'cloudProvider', + value: metadataInfo?.cloud?.provider, + tooltipFieldLabel: 'cloud.provider', + }, +]; + +const containerMetadataData = (metadataInfo: InfraMetadata['info']): MetadataData[] => [ + { + field: 'containerId', + value: metadataInfo?.container?.id, + tooltipFieldLabel: 'container.id', + }, + { + field: 'containerImageName', + value: metadataInfo?.container?.imageName, + tooltipFieldLabel: 'container.image.name', + }, + { + field: 'hostName', + value: metadataInfo?.host?.name, + tooltipFieldLabel: 'host.name', + }, +]; + const MetadataSummaryListWrapper = ({ loading: metadataLoading, visibleMetadata, + assetType, }: MetadataSummaryWrapperProps) => { const { showTab } = useTabSwitcherContext(); @@ -114,13 +159,13 @@ const MetadataSummaryListWrapper = ({ } > <> - + {visibleMetadata .filter((metadataValue) => metadataValue) .map((metadataValue) => ( - + @@ -138,13 +183,62 @@ const MetadataSummaryListWrapper = ({ ); }; -export const MetadataSummaryList = ({ metadata, loading }: MetadataSummaryProps) => ( - -); +export const MetadataSummaryList = ({ metadata, loading, assetType }: MetadataSummaryProps) => { + switch (assetType) { + case 'host': + return ( + + ); + case 'container': + return ( + + ); + default: + return ( + + ); + } +}; -export const MetadataSummaryListCompact = ({ metadata, loading }: MetadataSummaryProps) => ( - -); +export const MetadataSummaryListCompact = ({ + metadata, + loading, + assetType, +}: MetadataSummaryProps) => { + switch (assetType) { + case 'host': + return ( + + ); + case 'container': + return ( + + ); + default: + return ( + + ); + } +}; diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/metrics/container_metrics.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/metrics/container_metrics.tsx index abe14d6d5807a..73b0897b82484 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/metrics/container_metrics.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/metrics/container_metrics.tsx @@ -9,9 +9,11 @@ import { EuiFlexGroup, EuiFlexGrid } from '@elastic/eui'; import type { TimeRange } from '@kbn/es-query'; import type { DataView } from '@kbn/data-views-plugin/public'; import { DockerCharts } from '../../../charts/docker_charts'; -import { INTEGRATIONS } from '../../../constants'; +import { DOCKER_METRIC_TYPES, INTEGRATIONS, KUBERNETES_METRIC_TYPES } from '../../../constants'; import { useIntegrationCheck } from '../../../hooks/use_integration_check'; import { KubernetesContainerCharts } from '../../../charts/kubernetes_charts'; +import { useTabSwitcherContext } from '../../../hooks/use_tab_switcher'; +import { ContentTabIds } from '../../../types'; interface Props { assetId: string; @@ -20,11 +22,16 @@ interface Props { } export const ContainerMetrics = (props: Props) => { + const { showTab } = useTabSwitcherContext(); const isDockerContainer = useIntegrationCheck({ dependsOn: INTEGRATIONS.docker }); const isKubernetesContainer = useIntegrationCheck({ dependsOn: INTEGRATIONS.kubernetesContainer, }); + const onClick = (metric: string) => { + showTab(ContentTabIds.METRICS, { scrollTo: metric }); + }; + if (!isDockerContainer && !isKubernetesContainer) { return null; } @@ -32,20 +39,20 @@ export const ContainerMetrics = (props: Props) => { return ( - {isDockerContainer && ( - <> - - - - - - )} - {!isDockerContainer && isKubernetesContainer && ( - <> - - - - )} + {isDockerContainer && + DOCKER_METRIC_TYPES.map((metric) => ( + + ))} + {!isDockerContainer && + isKubernetesContainer && + KUBERNETES_METRIC_TYPES.map((metric) => ( + + ))} ); diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/overview.tsx b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/overview.tsx index 99723fce587ba..f12c71df0aae9 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/overview.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/tabs/overview/overview.tsx @@ -34,15 +34,18 @@ export const Overview = () => { error: fetchMetadataError, } = useMetadataStateContext(); const { metrics } = useDataViewsContext(); - const isFullPageView = renderMode.mode === 'page'; const state = useIntersectingState(ref, { dateRange }); const metadataSummarySection = isFullPageView ? ( - + ) : ( - + ); return ( @@ -61,7 +64,7 @@ export const Overview = () => { {fetchMetadataError && !metadataLoading ? : metadataSummarySection} - {asset.type === 'host' ? ( + {asset.type === 'host' || asset.type === 'container' ? ( = { + const factory: ReactEmbeddableFactory< + LogStreamSerializedState, + LogStreamSerializedState, + LogStreamApi + > = { type: LOG_STREAM_EMBEDDABLE, deserializeState: (state) => state.rawState, buildEmbeddable: async (state, buildApi) => { diff --git a/x-pack/plugins/observability_solution/infra/public/components/logging/log_analysis_setup/initial_configuration_step/index_setup_dataset_filter.tsx b/x-pack/plugins/observability_solution/infra/public/components/logging/log_analysis_setup/initial_configuration_step/index_setup_dataset_filter.tsx index 7d494e97ea656..1e7888ae81250 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/logging/log_analysis_setup/initial_configuration_step/index_setup_dataset_filter.tsx +++ b/x-pack/plugins/observability_solution/infra/public/components/logging/log_analysis_setup/initial_configuration_step/index_setup_dataset_filter.tsx @@ -68,7 +68,7 @@ export const IndexSetupDatasetFilter: React.FC<{ > void; onRangeSelection?: HostsStateUpdater; + assetType?: InventoryItemType; } const alertFeatureIds = [...infraAlertFeatureIds, AlertConsumers.OBSERVABILITY]; @@ -33,6 +35,7 @@ export const AlertsOverview = ({ dateRange, onLoaded, onRangeSelection, + assetType, }: AlertsOverviewProps) => { const { services } = useKibanaContextForPlugin(); const [urlState, setUrlState] = useAssetDetailsUrlState(); @@ -56,8 +59,9 @@ export const AlertsOverview = ({ dateRange, assetIds: [assetId], status: alertStatus, + assetType, }), - [assetId, dateRange, alertStatus] + [dateRange, assetId, alertStatus, assetType] ); const alertsEsQuery = useMemo( @@ -66,8 +70,9 @@ export const AlertsOverview = ({ dateRange, assetIds: [assetId], status: ALERT_STATUS_ALL, + assetType, }), - [assetId, dateRange] + [assetId, assetType, dateRange] ); const summaryTimeRange = useSummaryTimeRange(dateRange); diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx index b6918ab140df1..a75940714dec6 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metrics_explorer/components/kuery_bar.tsx @@ -9,9 +9,10 @@ import { i18n } from '@kbn/i18n'; import { fromKueryExpression } from '@kbn/es-query'; import React, { useEffect, useState } from 'react'; import { QuerySuggestion } from '@kbn/unified-search-plugin/public'; +import { AutocompleteField } from '@kbn/observability-plugin/public'; +import { useEuiTheme } from '@elastic/eui'; import { useMetricsDataViewContext } from '../../../../containers/metrics_source'; import { WithKueryAutocompletion } from '../../../../containers/with_kuery_autocompletion'; -import { AutocompleteField } from '../../../../components/autocomplete_field'; type LoadSuggestionsFn = ( e: string, @@ -58,6 +59,8 @@ export const MetricsExplorerKueryBar = ({ } }, [value]); + const { euiTheme } = useEuiTheme(); + const handleChange = (query: string) => { setValidation(validateQuery(query)); setDraftQuery(query); @@ -87,6 +90,7 @@ export const MetricsExplorerKueryBar = ({ placeholder={placeholder || defaultPlaceholder} suggestions={suggestions} value={draftQuery} + theme={euiTheme} /> )} diff --git a/x-pack/plugins/observability_solution/infra/public/services/telemetry/types.ts b/x-pack/plugins/observability_solution/infra/public/services/telemetry/types.ts index 16bdb5658f740..1ac068d5bb231 100644 --- a/x-pack/plugins/observability_solution/infra/public/services/telemetry/types.ts +++ b/x-pack/plugins/observability_solution/infra/public/services/telemetry/types.ts @@ -5,8 +5,7 @@ * 2.0. */ -import type { RootSchema } from '@kbn/analytics-client'; -import type { AnalyticsServiceSetup } from '@kbn/core/public'; +import type { AnalyticsServiceSetup, RootSchema } from '@kbn/core/public'; export interface TelemetryServiceSetupParams { analytics: AnalyticsServiceSetup; diff --git a/x-pack/plugins/observability_solution/infra/public/utils/filters/create_alerts_es_query.ts b/x-pack/plugins/observability_solution/infra/public/utils/filters/create_alerts_es_query.ts index e80ac2cdf9ac0..5184151619e67 100644 --- a/x-pack/plugins/observability_solution/infra/public/utils/filters/create_alerts_es_query.ts +++ b/x-pack/plugins/observability_solution/infra/public/utils/filters/create_alerts_es_query.ts @@ -8,7 +8,10 @@ import { getTime } from '@kbn/data-plugin/common'; import { ALERT_TIME_RANGE } from '@kbn/rule-data-utils'; import { BoolQuery, buildEsQuery, Filter, type TimeRange } from '@kbn/es-query'; import type { AlertStatus } from '@kbn/observability-plugin/common/typings'; -import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common'; +import { + findInventoryFields, + type InventoryItemType, +} from '@kbn/metrics-data-access-plugin/common'; import { buildCombinedAssetFilter } from './build'; import { ALERT_STATUS_QUERY } from '../../components/shared/alerts/constants'; @@ -20,16 +23,18 @@ export const createAlertsEsQuery = ({ dateRange, assetIds, status, + assetType, }: { dateRange: TimeRange; assetIds: string[]; status?: AlertStatus; + assetType?: InventoryItemType; }): AlertsEsQuery => { const alertStatusFilter = createAlertStatusFilter(status); const dateFilter = createDateFilter(dateRange); const hostsFilter = buildCombinedAssetFilter({ - field: findInventoryFields('host').id, + field: findInventoryFields(assetType ?? 'host').id, values: assetIds, }); diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/common/utils.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/common/utils.ts index 9c506d215355a..edfb50df0a788 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/common/utils.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/common/utils.ts @@ -28,7 +28,7 @@ import { AlertExecutionDetails, InventoryMetricConditions, } from '../../../../common/alerting/metrics/types'; -import { Group } from './types'; +import { Group } from '../../../../common/alerting/types'; const ALERT_CONTEXT_CONTAINER = 'container'; const ALERT_CONTEXT_ORCHESTRATOR = 'orchestrator'; diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts index 01eed0023dda8..68eea51669e87 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts @@ -12,6 +12,7 @@ import { ALERT_CONTEXT, ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, + ALERT_GROUP, ALERT_REASON, } from '@kbn/rule-data-utils'; import { ElasticsearchClient, IBasePath } from '@kbn/core/server'; @@ -76,15 +77,20 @@ import { LogThresholdRuleTypeParams, positiveComparators, } from '../../../../common/alerting/logs/log_threshold/query_helpers'; +import { Group } from '../../../../common/alerting/types'; export type LogThresholdActionGroups = ActionGroupIdsOf; export type LogThresholdRuleTypeState = RuleTypeState; // no specific state used export type LogThresholdAlertState = AlertState; // no specific state used export type LogThresholdAlertContext = AlertContext; // no specific instance context used -export type LogThresholdAlert = Omit & { +export type LogThresholdAlert = Omit< + ObservabilityLogsAlert, + 'kibana.alert.evaluation.values' | 'kibana.alert.group' +> & { // Defining a custom type for this because the schema generation script doesn't allow explicit null values 'kibana.alert.evaluation.values'?: Array; + [ALERT_GROUP]?: Group[]; }; export type LogThresholdAlertReporter = ( @@ -169,11 +175,21 @@ export const createLogThresholdExecutor = alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, uuid), }; + const instances = alertInstanceId.split(','); + const groups = + alertInstanceId !== '*' + ? params.groupBy?.reduce((resultGroups, groupByItem, index) => { + resultGroups.push({ field: groupByItem, value: instances[index].trim() }); + return resultGroups; + }, []) + : undefined; + const payload = { [ALERT_EVALUATION_THRESHOLD]: threshold, [ALERT_EVALUATION_VALUE]: value, [ALERT_REASON]: reason, [ALERT_CONTEXT]: alertContext, + [ALERT_GROUP]: groups, ...flattenAdditionalContext(rootLevelContext), }; diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts index 9cc5712ce09b6..d95a24478a6ff 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts @@ -30,7 +30,7 @@ import { ALERT_REASON, ALERT_GROUP, } from '@kbn/rule-data-utils'; -import { Group } from '../common/types'; +import { Group } from '../../../../common/alerting/types'; jest.mock('./lib/evaluate_rule', () => ({ evaluateRule: jest.fn() })); diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index 49129e2058cc1..f05c98d6cc222 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -48,11 +48,11 @@ import { getFormattedGroupBy, } from '../common/utils'; import { getEvaluationValues, getThresholds } from '../common/get_values'; -import { Group } from '../common/types'; import { EvaluatedRuleParams, evaluateRule, Evaluation } from './lib/evaluate_rule'; import { MissingGroupsRecord } from './lib/check_missing_group'; import { convertStringsToMissingGroupsRecord } from './lib/convert_strings_to_missing_groups_record'; +import { Group } from '../../../../common/alerting/types'; export type MetricThresholdAlert = Omit< ObservabilityMetricsAlert, diff --git a/x-pack/plugins/observability_solution/infra/server/lib/host_details/process_list_chart.ts b/x-pack/plugins/observability_solution/infra/server/lib/host_details/process_list_chart.ts index befe8fc017ad4..95b51d07072c3 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/host_details/process_list_chart.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/host_details/process_list_chart.ts @@ -19,6 +19,8 @@ export const getProcessListChart = async ( search: ESSearchClient, { hostTerm, indexPattern, to, command }: ProcessListAPIChartRequest ) => { + const from = to - 60 * 15 * 1000; // 15 minutes + const body = { size: 0, query: { @@ -27,7 +29,7 @@ export const getProcessListChart = async ( { range: { [TIMESTAMP_FIELD]: { - gte: to - 60 * 1000, // 1 minute + gte: from, lte: to, format: 'epoch_millis', }, @@ -64,7 +66,7 @@ export const getProcessListChart = async ( field: TIMESTAMP_FIELD, fixed_interval: '1m', extended_bounds: { - min: to - 60 * 15 * 1000, // 15 minutes, + min: from, max: to, }, }, diff --git a/x-pack/plugins/observability_solution/infra/server/routes/metadata/lib/get_node_info.ts b/x-pack/plugins/observability_solution/infra/server/routes/metadata/lib/get_node_info.ts index cc03c6e50e2af..e1de012cc2679 100644 --- a/x-pack/plugins/observability_solution/infra/server/routes/metadata/lib/get_node_info.ts +++ b/x-pack/plugins/observability_solution/infra/server/routes/metadata/lib/get_node_info.ts @@ -59,7 +59,7 @@ export const getNodeInfo = async ( index: sourceConfiguration.metricAlias, body: { size: 1, - _source: ['host.*', 'cloud.*', 'agent.*', TIMESTAMP_FIELD], + _source: ['host.*', 'cloud.*', 'agent.*', 'container.*', TIMESTAMP_FIELD], sort: [{ [TIMESTAMP_FIELD]: 'desc' }], query: { bool: { diff --git a/x-pack/plugins/observability_solution/infra/tsconfig.json b/x-pack/plugins/observability_solution/infra/tsconfig.json index cb98f8ab0859e..f23c9f7b30c34 100644 --- a/x-pack/plugins/observability_solution/infra/tsconfig.json +++ b/x-pack/plugins/observability_solution/infra/tsconfig.json @@ -56,7 +56,6 @@ "@kbn/charts-plugin", "@kbn/lens-plugin", "@kbn/core-analytics-server", - "@kbn/analytics-client", "@kbn/shared-ux-router", "@kbn/alerts-as-data-utils", "@kbn/cases-plugin", @@ -64,7 +63,6 @@ "@kbn/shared-ux-router", "@kbn/shared-ux-link-redirect-app", "@kbn/discover-plugin", - "@kbn/observability-alert-details", "@kbn/observability-shared-plugin", "@kbn/observability-ai-assistant-plugin", "@kbn/ui-theme", @@ -106,7 +104,8 @@ "@kbn/react-kibana-context-theme", "@kbn/presentation-publishing", "@kbn/presentation-containers", - "@kbn/deeplinks-observability" + "@kbn/deeplinks-observability", + "@kbn/event-annotation-common" ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/constants.ts b/x-pack/plugins/observability_solution/logs_explorer/common/constants.ts index ae8eff46fe7e2..54ef1b50c587c 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/common/constants.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/common/constants.ts @@ -59,6 +59,3 @@ export const FILTER_OUT_FIELDS_PREFIXES_FOR_CONTENT = [ 'log.', 'service.', ]; - -export const DEFAULT_ALLOWED_DATA_VIEWS = ['logs', 'auditbeat', 'filebeat', 'winlogbeat']; -export const DEFAULT_ALLOWED_LOGS_DATA_VIEWS = ['logs', 'auditbeat', 'filebeat', 'winlogbeat']; diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.ts b/x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.ts index 5b36b8e8bbe8b..b5fe3d1f58c0f 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/common/data_views/models/data_view_descriptor.ts @@ -5,15 +5,13 @@ * 2.0. */ -import { DEFAULT_ALLOWED_LOGS_DATA_VIEWS } from '../../constants'; +import { createRegExpPatternFrom, testPatternAgainstAllowedList } from '@kbn/data-view-utils'; +import { DEFAULT_ALLOWED_LOGS_BASE_PATTERNS } from '@kbn/discover-utils'; import { DataViewSpecWithId } from '../../data_source_selection'; import { DataViewDescriptorType } from '../types'; -import { buildIndexPatternRegExp } from '../utils'; -type AllowedList = Array; - -const LOGS_ALLOWED_LIST: AllowedList = [ - buildIndexPatternRegExp(DEFAULT_ALLOWED_LOGS_DATA_VIEWS), +const LOGS_ALLOWED_LIST = [ + createRegExpPatternFrom(DEFAULT_ALLOWED_LOGS_BASE_PATTERNS), // Add more strings or regex patterns as needed ]; @@ -60,7 +58,9 @@ export class DataViewDescriptor { } testAgainstAllowedList(allowedList: string[]) { - return this.title ? isAllowed(this.title, [buildIndexPatternRegExp(allowedList)]) : false; + return this.title + ? testPatternAgainstAllowedList([createRegExpPatternFrom(allowedList)])(this.title) + : false; } public static create({ id, namespaces, title, type, name }: DataViewDescriptorFactoryParams) { @@ -79,7 +79,7 @@ export class DataViewDescriptor { } static #extractDataType(title: string): DataViewDescriptorType['dataType'] { - if (isAllowed(title, LOGS_ALLOWED_LIST)) { + if (testPatternAgainstAllowedList(LOGS_ALLOWED_LIST)(title)) { return 'logs'; } @@ -98,17 +98,3 @@ export class DataViewDescriptor { return this.dataType === 'unresolved'; } } - -function isAllowed(value: string, allowList: AllowedList) { - for (const allowedItem of allowList) { - if (typeof allowedItem === 'string') { - return value === allowedItem; - } - if (allowedItem instanceof RegExp) { - return allowedItem.test(value); - } - } - - // If no match is found in the allowList, return false - return false; -} diff --git a/x-pack/plugins/observability_solution/logs_explorer/common/ui_settings.ts b/x-pack/plugins/observability_solution/logs_explorer/common/ui_settings.ts index bbd3543d0dc69..65983c3993488 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/common/ui_settings.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/common/ui_settings.ts @@ -7,9 +7,9 @@ import { schema } from '@kbn/config-schema'; import { UiSettingsParams } from '@kbn/core-ui-settings-common'; +import { DEFAULT_ALLOWED_LOGS_BASE_PATTERNS } from '@kbn/discover-utils'; import { i18n } from '@kbn/i18n'; import { OBSERVABILITY_LOGS_EXPLORER_ALLOWED_DATA_VIEWS_ID } from '@kbn/management-settings-ids'; -import { DEFAULT_ALLOWED_DATA_VIEWS } from './constants'; /** * uiSettings definitions for Logs Explorer. @@ -20,7 +20,7 @@ export const uiSettings: Record = { name: i18n.translate('xpack.logsExplorer.allowedDataViews', { defaultMessage: 'Logs Explorer allowed data views', }), - value: DEFAULT_ALLOWED_DATA_VIEWS, + value: DEFAULT_ALLOWED_LOGS_BASE_PATTERNS, description: i18n.translate('xpack.logsExplorer.allowedDataViewsDescription', { defaultMessage: 'A list of base patterns to match and explore data views in Logs Explorer. Remote clusters will be automatically matched for the provided base patterns.', diff --git a/x-pack/plugins/observability_solution/logs_explorer/tsconfig.json b/x-pack/plugins/observability_solution/logs_explorer/tsconfig.json index b30605fd567fd..b3f8c978a67b4 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/tsconfig.json +++ b/x-pack/plugins/observability_solution/logs_explorer/tsconfig.json @@ -44,6 +44,7 @@ "@kbn/unified-search-plugin", "@kbn/xstate-utils", "@kbn/esql-utils", + "@kbn/data-view-utils", ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/observability_solution/observability/common/custom_threshold_rule/get_view_in_app_url.ts b/x-pack/plugins/observability_solution/observability/common/custom_threshold_rule/get_view_in_app_url.ts index 9715b8b5f9c86..5411eff43bc3d 100644 --- a/x-pack/plugins/observability_solution/observability/common/custom_threshold_rule/get_view_in_app_url.ts +++ b/x-pack/plugins/observability_solution/observability/common/custom_threshold_rule/get_view_in_app_url.ts @@ -10,8 +10,9 @@ import type { TimeRange } from '@kbn/es-query'; import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util'; import type { LocatorPublic } from '@kbn/share-plugin/common'; import { getGroupFilters } from './helpers/get_group'; -import { Group, SearchConfigurationWithExtractedReferenceType } from './types'; +import { SearchConfigurationWithExtractedReferenceType } from './types'; import type { CustomThresholdExpressionMetric } from './types'; +import { Group } from '../typings'; export interface GetViewInAppUrlArgs { searchConfiguration?: SearchConfigurationWithExtractedReferenceType; diff --git a/x-pack/plugins/observability_solution/observability/common/custom_threshold_rule/helpers/get_group.ts b/x-pack/plugins/observability_solution/observability/common/custom_threshold_rule/helpers/get_group.ts index 3194fc4e14b2d..67437421e4bad 100644 --- a/x-pack/plugins/observability_solution/observability/common/custom_threshold_rule/helpers/get_group.ts +++ b/x-pack/plugins/observability_solution/observability/common/custom_threshold_rule/helpers/get_group.ts @@ -7,7 +7,7 @@ import { Filter } from '@kbn/es-query'; import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; -import { Group } from '../types'; +import { Group } from '../../typings'; /* * groupFieldName diff --git a/x-pack/plugins/observability_solution/observability/common/custom_threshold_rule/types.ts b/x-pack/plugins/observability_solution/observability/common/custom_threshold_rule/types.ts index 0e4a0e7167069..c2a616682cfdb 100644 --- a/x-pack/plugins/observability_solution/observability/common/custom_threshold_rule/types.ts +++ b/x-pack/plugins/observability_solution/observability/common/custom_threshold_rule/types.ts @@ -106,16 +106,6 @@ export enum InfraFormatterType { percent = 'percent', } -export interface Group { - field: string; - value: string; -} - -export interface TimeRange { - from?: string; - to?: string; -} - export interface SearchConfigurationType { index: SerializedSearchSourceFields; query: { @@ -135,11 +125,6 @@ export interface SearchConfigurationWithExtractedReferenceType { filter?: Filter[]; } -// Custom threshold alert types - -// Alert fields['kibana.alert.group] type -export type GroupBy = Group[]; - /* * Utils * diff --git a/x-pack/plugins/observability_solution/observability/common/typings.ts b/x-pack/plugins/observability_solution/observability/common/typings.ts index dfddf89992c19..bfdcd6d5209dc 100644 --- a/x-pack/plugins/observability_solution/observability/common/typings.ts +++ b/x-pack/plugins/observability_solution/observability/common/typings.ts @@ -41,3 +41,16 @@ export interface AlertStatusFilter { query: string; label: string; } + +export interface Group { + field: string; + value: string; +} + +export interface TimeRange { + from?: string; + to?: string; +} + +// Alert fields['kibana.alert.group] type +export type GroupBy = Group[]; diff --git a/x-pack/plugins/observability_solution/observability/public/components/alert_overview/alert_overview.tsx b/x-pack/plugins/observability_solution/observability/public/components/alert_overview/alert_overview.tsx index 95e043dd3da1c..57a34d7fdf1f9 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/alert_overview/alert_overview.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/alert_overview/alert_overview.tsx @@ -34,7 +34,6 @@ import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-tim import { get } from 'lodash'; import { paths } from '../../../common/locators/paths'; -import { TimeRange } from '../../../common/custom_threshold_rule/types'; import { TopAlert } from '../../typings/alerts'; import { useFetchBulkCases } from '../../hooks/use_fetch_bulk_cases'; import { useCaseViewNavigation } from '../../hooks/use_case_view_navigation'; @@ -44,8 +43,9 @@ import { mapRuleParamsWithFlyout, } from './helpers/map_rules_params_with_flyout'; import { ColumnIDs, overviewColumns } from './overview_columns'; -import { getSources } from './helpers/get_sources'; +import { getSources } from '../alert_sources/get_sources'; import { RULE_DETAILS_PAGE_ID } from '../../pages/rule_details/constants'; +import { TimeRange } from '../../../common/typings'; export const AlertOverview = memo( ({ diff --git a/x-pack/plugins/observability_solution/observability/public/components/alert_overview/overview_columns.tsx b/x-pack/plugins/observability_solution/observability/public/components/alert_overview/overview_columns.tsx index a1581333294ea..d2c3eb58854ee 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/alert_overview/overview_columns.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/alert_overview/overview_columns.tsx @@ -15,11 +15,11 @@ import React from 'react'; import { Tooltip as CaseTooltip } from '@kbn/cases-components'; import { COMPARATORS } from '@kbn/alerting-comparators'; import { LEGACY_COMPARATORS } from '../../../common/utils/convert_legacy_outside_comparator'; -import type { Group } from '../../../common/custom_threshold_rule/types'; import { NavigateToCaseView } from '../../hooks/use_case_view_navigation'; -import { Groups } from '../custom_threshold/components/alert_details_app_section/groups'; import { formatCase } from './helpers/format_cases'; import { FlyoutThresholdData } from './helpers/map_rules_params_with_flyout'; +import { Groups } from '../alert_sources/groups'; +import type { Group } from '../../../common/typings'; interface AlertOverviewField { id: string; diff --git a/x-pack/plugins/observability_solution/observability/common/custom_threshold_rule/helpers/get_alert_source_links.test.ts b/x-pack/plugins/observability_solution/observability/public/components/alert_sources/get_alert_source_links.test.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability/common/custom_threshold_rule/helpers/get_alert_source_links.test.ts rename to x-pack/plugins/observability_solution/observability/public/components/alert_sources/get_alert_source_links.test.ts diff --git a/x-pack/plugins/observability_solution/observability/common/custom_threshold_rule/helpers/get_alert_source_links.ts b/x-pack/plugins/observability_solution/observability/public/components/alert_sources/get_alert_source_links.ts similarity index 96% rename from x-pack/plugins/observability_solution/observability/common/custom_threshold_rule/helpers/get_alert_source_links.ts rename to x-pack/plugins/observability_solution/observability/public/components/alert_sources/get_alert_source_links.ts index 0595d7554fb0b..ff1d39d85b1d4 100644 --- a/x-pack/plugins/observability_solution/observability/common/custom_threshold_rule/helpers/get_alert_source_links.ts +++ b/x-pack/plugins/observability_solution/observability/public/components/alert_sources/get_alert_source_links.ts @@ -13,8 +13,8 @@ import { } from '@kbn/observability-shared-plugin/common'; import { LocatorPublic } from '@kbn/share-plugin/common'; import { SerializableRecord } from '@kbn/utility-types'; -import { getApmAppLocator } from '../get_apm_app_url'; -import { Group, TimeRange } from '../types'; +import { getApmAppLocator } from './get_apm_app_url'; +import { Group, TimeRange } from '../../../common/typings'; const HOST_NAME = 'host.name'; const CONTAINER_ID = 'container.id'; diff --git a/x-pack/plugins/observability_solution/observability/common/custom_threshold_rule/get_apm_app_url.ts b/x-pack/plugins/observability_solution/observability/public/components/alert_sources/get_apm_app_url.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability/common/custom_threshold_rule/get_apm_app_url.ts rename to x-pack/plugins/observability_solution/observability/public/components/alert_sources/get_apm_app_url.ts diff --git a/x-pack/plugins/observability_solution/observability/public/components/alert_overview/helpers/get_sources.ts b/x-pack/plugins/observability_solution/observability/public/components/alert_sources/get_sources.ts similarity index 75% rename from x-pack/plugins/observability_solution/observability/public/components/alert_overview/helpers/get_sources.ts rename to x-pack/plugins/observability_solution/observability/public/components/alert_sources/get_sources.ts index cfc64d9d414aa..3832e744a31e6 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/alert_overview/helpers/get_sources.ts +++ b/x-pack/plugins/observability_solution/observability/public/components/alert_sources/get_sources.ts @@ -5,18 +5,19 @@ * 2.0. */ -import { ALERT_GROUP_FIELD, ALERT_GROUP_VALUE } from '@kbn/rule-data-utils'; -import { - apmSources, - infraSources, -} from '../../../../common/custom_threshold_rule/helpers/get_alert_source_links'; -import { TopAlert } from '../../..'; +import { ALERT_GROUP_FIELD, ALERT_GROUP_VALUE, ALERT_GROUP } from '@kbn/rule-data-utils'; +import { TopAlert } from '../../typings/alerts'; +import { apmSources, infraSources } from './get_alert_source_links'; interface AlertFields { [key: string]: any; } export const getSources = (alert: TopAlert) => { + // when `kibana.alert.group` is not flattened (for alert detail pages) + if (alert.fields[ALERT_GROUP]) return alert.fields[ALERT_GROUP]; + + // when `kibana.alert.group` is flattened (for alert flyout) const groupsFromGroupFields = alert.fields[ALERT_GROUP_FIELD]?.map((field, index) => { const values = alert.fields[ALERT_GROUP_VALUE]; if (values?.length && values[index]) { diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/groups.tsx b/x-pack/plugins/observability_solution/observability/public/components/alert_sources/groups.tsx similarity index 76% rename from x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/groups.tsx rename to x-pack/plugins/observability_solution/observability/public/components/alert_sources/groups.tsx index f2644ac78b0c5..5a14f39a97811 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/groups.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/alert_sources/groups.tsx @@ -8,10 +8,10 @@ import { EuiLink } from '@elastic/eui'; import React, { useEffect, useState } from 'react'; import { SERVICE_NAME } from '@kbn/observability-shared-plugin/common'; -import { useKibana } from '../../../../utils/kibana_react'; -import { TimeRange, Group } from '../../../../../common/custom_threshold_rule/types'; -import { generateSourceLink } from '../../../../../common/custom_threshold_rule/helpers/get_alert_source_links'; -import { APM_APP_LOCATOR_ID } from '../../../../../common/custom_threshold_rule/get_apm_app_url'; +import { useKibana } from '../../utils/kibana_react'; +import { APM_APP_LOCATOR_ID } from './get_apm_app_url'; +import { Group, TimeRange } from '../../../common/typings'; +import { generateSourceLink } from './get_alert_source_links'; export function Groups({ groups, timeRange }: { groups: Group[]; timeRange: TimeRange }) { const { @@ -57,10 +57,7 @@ export function Groups({ groups, timeRange }: { groups: Group[]; timeRange: Time {group.field}:{' '} {sourceLinks[group.field] ? ( - + {group.value} ) : ( diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.test.tsx b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.test.tsx index a30511c891e7b..a98a519a1606a 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.test.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.test.tsx @@ -18,11 +18,9 @@ import { buildCustomThresholdRule, } from '../../mocks/custom_threshold_rule'; import { CustomThresholdAlertFields } from '../../types'; -import { RuleConditionChart } from '../rule_condition_chart/rule_condition_chart'; +import { RuleConditionChart } from '../../../rule_condition_chart/rule_condition_chart'; import { CustomThresholdAlert } from '../types'; import AlertDetailsAppSection from './alert_details_app_section'; -import { Groups } from './groups'; -import { Tags } from './tags'; const mockedChartStartContract = chartPluginMock.createStartContract(); @@ -49,7 +47,7 @@ jest.mock('@kbn/observability-get-padded-alert-time-range-util', () => ({ }), })); -jest.mock('../rule_condition_chart/rule_condition_chart', () => ({ +jest.mock('../../../rule_condition_chart/rule_condition_chart', () => ({ RuleConditionChart: jest.fn(() =>
), })); @@ -80,7 +78,6 @@ jest.mock('../../../../utils/kibana_react', () => ({ describe('AlertDetailsAppSection', () => { const queryClient = new QueryClient(); const mockedSetAlertSummaryFields = jest.fn(); - const ruleLink = 'ruleLink'; const renderComponent = ( alert: Partial = {}, @@ -92,7 +89,6 @@ describe('AlertDetailsAppSection', () => { @@ -111,65 +107,22 @@ describe('AlertDetailsAppSection', () => { expect(result.getByTestId('thresholdRule-2000-2500')).toBeTruthy(); }); - it('should render alert summary fields', async () => { + it('should render additional alert summary fields', async () => { renderComponent(); expect(mockedSetAlertSummaryFields).toBeCalledTimes(2); expect(mockedSetAlertSummaryFields).toBeCalledWith([ { - label: 'Source', + label: 'Related logs', value: ( - - - - - View related logs - - - - ), - }, - { - label: 'Tags', - value: , - }, - { - label: 'Rule', - value: ( - - Monitoring hosts - - ), - }, - ]); - }); - - it('should not render group and tag summary fields', async () => { - const alertFields = { tags: [], 'kibana.alert.group': undefined }; - renderComponent({}, alertFields); - - expect(mockedSetAlertSummaryFields).toBeCalledTimes(2); - expect(mockedSetAlertSummaryFields).toBeCalledWith([ - { - label: 'Rule', - value: ( - - Monitoring hosts - + + + View related logs + + ), }, ]); diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx index c0c399914acd9..83aa8cf2a35d3 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx @@ -21,13 +21,7 @@ import { } from '@elastic/eui'; import { RuleTypeParams } from '@kbn/alerting-plugin/common'; import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util'; -import { - ALERT_END, - ALERT_START, - ALERT_EVALUATION_VALUES, - ALERT_GROUP, - TAGS, -} from '@kbn/rule-data-utils'; +import { ALERT_END, ALERT_START, ALERT_EVALUATION_VALUES, ALERT_GROUP } from '@kbn/rule-data-utils'; import { DataView } from '@kbn/data-views-plugin/common'; import type { EventAnnotationConfig, @@ -37,18 +31,16 @@ import type { import moment from 'moment'; import { LOGS_EXPLORER_LOCATOR_ID, LogsExplorerLocatorParams } from '@kbn/deeplinks-observability'; import { TimeRange } from '@kbn/es-query'; +import { getGroupFilters } from '../../../../../common/custom_threshold_rule/helpers/get_group'; import { useLicense } from '../../../../hooks/use_license'; import { useKibana } from '../../../../utils/kibana_react'; -import { getGroupFilters } from '../../../../../common/custom_threshold_rule/helpers/get_group'; import { metricValueFormatter } from '../../../../../common/custom_threshold_rule/metric_value_formatter'; import { AlertSummaryField } from '../../../..'; import { AlertParams } from '../../types'; import { Threshold } from '../custom_threshold'; import { CustomThresholdRule, CustomThresholdAlert } from '../types'; import { LogRateAnalysis } from './log_rate_analysis'; -import { Groups } from './groups'; -import { Tags } from './tags'; -import { RuleConditionChart } from '../rule_condition_chart/rule_condition_chart'; +import { RuleConditionChart } from '../../../rule_condition_chart/rule_condition_chart'; import { getViewInAppUrl } from '../../../../../common/custom_threshold_rule/get_view_in_app_url'; import { SearchConfigurationWithExtractedReferenceType } from '../../../../../common/custom_threshold_rule/types'; import { generateChartTitleAndTooltip } from './helpers/generate_chart_title_and_tooltip'; @@ -56,7 +48,6 @@ import { generateChartTitleAndTooltip } from './helpers/generate_chart_title_and interface AppSectionProps { alert: CustomThresholdAlert; rule: CustomThresholdRule; - ruleLink: string; setAlertSummaryFields: React.Dispatch>; } @@ -64,7 +55,6 @@ interface AppSectionProps { export default function AlertDetailsAppSection({ alert, rule, - ruleLink, setAlertSummaryFields, }: AppSectionProps) { const services = useKibana().services; @@ -89,7 +79,6 @@ export default function AlertDetailsAppSection({ const alertStart = alert.fields[ALERT_START]; const alertEnd = alert.fields[ALERT_END]; const groups = alert.fields[ALERT_GROUP]; - const tags = alert.fields[TAGS]; const chartTitleAndTooltip: Array<{ title: string; tooltip: string }> = []; @@ -145,64 +134,30 @@ export default function AlertDetailsAppSection({ useEffect(() => { const alertSummaryFields = []; - if (groups) { - alertSummaryFields.push({ - label: i18n.translate( - 'xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.source', - { - defaultMessage: 'Source', - } - ), - value: ( - <> - - - - {i18n.translate( - 'xpack.observability.alertDetailsAppSection.a.viewRelatedLogsLabel', - { - defaultMessage: 'View related logs', - } - )} - - - - ), - }); - } - if (tags && tags.length > 0) { - alertSummaryFields.push({ - label: i18n.translate( - 'xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.tags', - { - defaultMessage: 'Tags', - } - ), - value: , - }); - } + alertSummaryFields.push({ label: i18n.translate( - 'xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.rule', + 'xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.relatedLogs', { - defaultMessage: 'Rule', + defaultMessage: 'Related logs', } ), value: ( - - {rule.name} - + + + {i18n.translate('xpack.observability.alertDetailsAppSection.a.viewRelatedLogsLabel', { + defaultMessage: 'View related logs', + })} + + ), }); setAlertSummaryFields(alertSummaryFields); - }, [groups, tags, rule, ruleLink, setAlertSummaryFields, timeRange, alertEnd, viewInAppUrl]); + }, [viewInAppUrl, setAlertSummaryFields]); useEffect(() => { const initDataView = async () => { diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/log_rate_analysis_query.ts b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/log_rate_analysis_query.ts index 2eb3186a46a9d..a23105f08cebf 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/log_rate_analysis_query.ts +++ b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/helpers/log_rate_analysis_query.ts @@ -10,12 +10,10 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { getGroupFilters } from '../../../../../../common/custom_threshold_rule/helpers/get_group'; import { Aggregators } from '../../../../../../common/custom_threshold_rule/types'; import { buildEsQuery } from '../../../../../utils/build_es_query'; -import type { - CustomThresholdExpressionMetric, - Group, -} from '../../../../../../common/custom_threshold_rule/types'; +import type { CustomThresholdExpressionMetric } from '../../../../../../common/custom_threshold_rule/types'; import type { TopAlert } from '../../../../../typings/alerts'; import type { CustomThresholdRuleTypeParams } from '../../../types'; +import { Group } from '../../../../../../common/typings'; const getKuery = (metrics: CustomThresholdExpressionMetric[], filter?: string) => { let query = ''; diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/tags.tsx b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/tags.tsx deleted file mode 100644 index b04d104695de8..0000000000000 --- a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/alert_details_app_section/tags.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import React, { useState } from 'react'; -import { EuiBadge, EuiPopover } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; - -export function Tags({ tags }: { tags: string[] }) { - const [isMoreTagsOpen, setIsMoreTagsOpen] = useState(false); - const onMoreTagsClick = () => setIsMoreTagsOpen((isPopoverOpen) => !isPopoverOpen); - const closePopover = () => setIsMoreTagsOpen(false); - const moreTags = tags.length > 3 && ( - - - - ); - - return ( - <> - {tags.slice(0, 3).map((tag) => ( - {tag} - ))} -
- - {tags.slice(3).map((tag) => ( - {tag} - ))} - - - ); -} diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/custom_threshold_rule_expression.test.tsx b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/custom_threshold_rule_expression.test.tsx index 02c428ccf3698..62580b3a89f82 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/custom_threshold_rule_expression.test.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/custom_threshold_rule_expression.test.tsx @@ -21,7 +21,7 @@ import Expressions from './custom_threshold_rule_expression'; import { AlertParams, CustomThresholdPrefillOptions } from './types'; jest.mock('../../utils/kibana_react'); -jest.mock('./components/rule_condition_chart/rule_condition_chart', () => ({ +jest.mock('../rule_condition_chart/rule_condition_chart', () => ({ RuleConditionChart: jest.fn(() =>
), })); diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx index fab9568b080a9..0db9c2f048e11 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx @@ -42,7 +42,7 @@ import { TimeUnitChar } from '../../../common/utils/formatters/duration'; import { AlertContextMeta, AlertParams, MetricExpression } from './types'; import { ExpressionRow } from './components/expression_row'; import { MetricsExplorerFields, GroupBy } from './components/group_by'; -import { RuleConditionChart as PreviewChart } from './components/rule_condition_chart/rule_condition_chart'; +import { RuleConditionChart as PreviewChart } from '../rule_condition_chart/rule_condition_chart'; import { getSearchConfiguration } from './helpers/get_search_configuration'; const FILTER_TYPING_DEBOUNCE_MS = 500; diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/rule_condition_chart/helpers.test.ts b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.test.ts similarity index 98% rename from x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/rule_condition_chart/helpers.test.ts rename to x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.test.ts index 4211907b5d4a0..044b57c64da28 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/rule_condition_chart/helpers.test.ts +++ b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.test.ts @@ -7,7 +7,7 @@ import { Aggregators, CustomThresholdExpressionMetric, -} from '../../../../../common/custom_threshold_rule/types'; +} from '../../../common/custom_threshold_rule/types'; import { getBufferThreshold, getLensOperationFromRuleMetric, lensFieldFormatter } from './helpers'; const useCases = [ [ diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/rule_condition_chart/helpers.ts b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.ts similarity index 85% rename from x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/rule_condition_chart/helpers.ts rename to x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.ts index 1875af6ceb93e..7cedf19d0b660 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/rule_condition_chart/helpers.ts +++ b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/helpers.ts @@ -5,12 +5,10 @@ * 2.0. */ -import { - Aggregators, - CustomThresholdExpressionMetric, -} from '../../../../../common/custom_threshold_rule/types'; +import { Aggregators } from '../../../common/custom_threshold_rule/types'; +import { GenericMetric } from './rule_condition_chart'; -export const getLensOperationFromRuleMetric = (metric: CustomThresholdExpressionMetric): string => { +export const getLensOperationFromRuleMetric = (metric: GenericMetric): string => { const { aggType, field, filter } = metric; let operation: string = aggType; const operationArgs: string[] = []; @@ -56,7 +54,7 @@ export const LensFieldFormat = { } as const; export const lensFieldFormatter = ( - metrics: CustomThresholdExpressionMetric[] + metrics: GenericMetric[] ): typeof LensFieldFormat[keyof typeof LensFieldFormat] => { if (metrics.length < 1 || !metrics[0].field) return LensFieldFormat.NUMBER; const firstMetricField = metrics[0].field; @@ -65,5 +63,5 @@ export const lensFieldFormatter = ( return LensFieldFormat.NUMBER; }; -export const isRate = (metrics: CustomThresholdExpressionMetric[]): boolean => +export const isRate = (metrics: GenericMetric[]): boolean => Boolean(metrics.length > 0 && metrics[0].aggType === Aggregators.RATE); diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/rule_condition_chart/painless_tinymath_parser.test.ts b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.test.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/rule_condition_chart/painless_tinymath_parser.test.ts rename to x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.test.ts diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/rule_condition_chart/painless_tinymath_parser.ts b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/rule_condition_chart/painless_tinymath_parser.ts rename to x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/painless_tinymath_parser.ts diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/rule_condition_chart/rule_condition_chart.test.tsx b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/rule_condition_chart.test.tsx similarity index 78% rename from x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/rule_condition_chart/rule_condition_chart.test.tsx rename to x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/rule_condition_chart.test.tsx index d164e6670b4a7..ac0624265be0b 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/rule_condition_chart/rule_condition_chart.test.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/rule_condition_chart.test.tsx @@ -13,13 +13,12 @@ import { COMPARATORS } from '@kbn/alerting-comparators'; import { Aggregators, CustomThresholdSearchSourceFields, -} from '../../../../../common/custom_threshold_rule/types'; -import { useKibana } from '../../../../utils/kibana_react'; -import { kibanaStartMock } from '../../../../utils/kibana_react.mock'; -import { MetricExpression } from '../../types'; -import { RuleConditionChart } from './rule_condition_chart'; +} from '../../../common/custom_threshold_rule/types'; +import { useKibana } from '../../utils/kibana_react'; +import { kibanaStartMock } from '../../utils/kibana_react.mock'; +import { RuleConditionChart, RuleConditionChartExpressions } from './rule_condition_chart'; -jest.mock('../../../../utils/kibana_react'); +jest.mock('../../utils/kibana_react'); const useKibanaMock = useKibana as jest.Mock; @@ -34,7 +33,7 @@ describe('Rule condition chart', () => { jest.clearAllMocks(); mockKibana(); }); - async function setup(expression: MetricExpression, dataView?: DataView) { + async function setup(expression: RuleConditionChartExpressions, dataView?: DataView) { const wrapper = mountWithIntl( { } it('should display no data message', async () => { - const expression: MetricExpression = { + const expression: RuleConditionChartExpressions = { metrics: [ { name: 'A', @@ -67,7 +66,6 @@ describe('Rule condition chart', () => { ], timeSize: 1, timeUnit: 'm', - sourceId: 'default', threshold: [1], comparator: COMPARATORS.GREATER_THAN_OR_EQUALS, }; diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/rule_condition_chart/rule_condition_chart.tsx b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/rule_condition_chart.tsx similarity index 71% rename from x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/rule_condition_chart/rule_condition_chart.tsx rename to x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/rule_condition_chart.tsx index 1e326e3fa5f6d..a8710004876ff 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/rule_condition_chart/rule_condition_chart.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/rule_condition_chart/rule_condition_chart.tsx @@ -26,10 +26,12 @@ import { i18n } from '@kbn/i18n'; import { TimeRange } from '@kbn/es-query'; import { EventAnnotationConfig } from '@kbn/event-annotation-common'; import { COMPARATORS } from '@kbn/alerting-comparators'; -import { EventsAsUnit } from '../../../../../common/constants'; -import { CustomThresholdSearchSourceFields } from '../../../../../common/custom_threshold_rule/types'; -import { useKibana } from '../../../../utils/kibana_react'; -import { MetricExpression } from '../../types'; +import { SerializedSearchSourceFields } from '@kbn/data-plugin/common'; +import { TimeUnitChar } from '../../../common'; +import { LEGACY_COMPARATORS } from '../../../common/utils/convert_legacy_outside_comparator'; +import { EventsAsUnit } from '../../../common/constants'; +import { Aggregators } from '../../../common/custom_threshold_rule/types'; +import { useKibana } from '../../utils/kibana_react'; import { AggMap, PainlessTinyMathParser } from './painless_tinymath_parser'; import { lensFieldFormatter, @@ -38,15 +40,38 @@ import { isRate, LensFieldFormat, } from './helpers'; - interface ChartOptions { seriesType?: SeriesType; interval?: string; } +interface GenericSearchSourceFields extends SerializedSearchSourceFields { + query?: Query; + filter?: Array>; +} + +export type GenericAggType = Aggregators | 'custom'; + +export interface GenericMetric { + aggType: GenericAggType; + name: string; + field?: string; + filter?: string; +} + +export interface RuleConditionChartExpressions { + metrics: GenericMetric[]; + threshold: number[]; + comparator: COMPARATORS | LEGACY_COMPARATORS; + warningThreshold?: number[]; + warningComparator?: COMPARATORS | LEGACY_COMPARATORS; + timeSize?: number; + timeUnit?: TimeUnitChar; + equation?: string; +} interface RuleConditionChartProps { - metricExpression: MetricExpression; - searchConfiguration: CustomThresholdSearchSourceFields; + metricExpression: RuleConditionChartExpressions; + searchConfiguration: GenericSearchSourceFields; dataView?: DataView; groupBy?: string | string[]; error?: IErrorObject; @@ -76,11 +101,22 @@ export function RuleConditionChart({ services: { lens }, } = useKibana(); const { euiTheme } = useEuiTheme(); - const { metrics, timeSize, timeUnit, threshold, comparator, equation } = metricExpression; + const { + metrics, + timeSize, + timeUnit, + threshold, + comparator, + equation, + warningComparator, + warningThreshold, + } = metricExpression; const [attributes, setAttributes] = useState(); const [aggMap, setAggMap] = useState(); const [formula, setFormula] = useState(''); const [thresholdReferenceLine, setThresholdReferenceLine] = useState(); + const [warningThresholdReferenceLine, setWarningThresholdReferenceLine] = + useState(); const [alertAnnotation, setAlertAnnotation] = useState(); const [chartLoading, setChartLoading] = useState(false); const filters = [...(searchConfiguration.filter || []), ...additionalFilters]; @@ -98,13 +134,13 @@ export function RuleConditionChart({ const paragraphElements = errorDiv.querySelectorAll('p'); if (!paragraphElements || paragraphElements.length < 2) return; paragraphElements[0].innerText = i18n.translate( - 'xpack.observability.customThreshold.rule..charts.error_equation.title', + 'xpack.observability.ruleCondition.chart.error_equation.title', { defaultMessage: 'An error occurred while rendering the chart', } ); paragraphElements[1].innerText = i18n.translate( - 'xpack.observability.customThreshold.rule..charts.error_equation.description', + 'xpack.observability.ruleCondition.chart.error_equation.description', { defaultMessage: 'Check the rule equation.', } @@ -113,6 +149,77 @@ export function RuleConditionChart({ }); }, [chartLoading, attributes]); + // Build the warning threshold reference line + useEffect(() => { + if (!warningThreshold) { + if (warningThresholdReferenceLine?.length) { + setWarningThresholdReferenceLine([]); + } + return; + } + const refLayers = []; + if ( + warningComparator === COMPARATORS.NOT_BETWEEN || + (warningComparator === COMPARATORS.BETWEEN && warningThreshold.length === 2) + ) { + const refLineStart = new XYReferenceLinesLayer({ + data: [ + { + value: (warningThreshold[0] || 0).toString(), + color: euiTheme.colors.warning, + fill: warningComparator === COMPARATORS.NOT_BETWEEN ? 'below' : 'none', + }, + ], + }); + const refLineEnd = new XYReferenceLinesLayer({ + data: [ + { + value: (warningThreshold[1] || 0).toString(), + color: euiTheme.colors.warning, + fill: warningComparator === COMPARATORS.NOT_BETWEEN ? 'above' : 'none', + }, + ], + }); + + refLayers.push(refLineStart, refLineEnd); + } else { + let fill: FillStyle = 'above'; + if ( + warningComparator === COMPARATORS.LESS_THAN || + warningComparator === COMPARATORS.LESS_THAN_OR_EQUALS + ) { + fill = 'below'; + } + const warningThresholdRefLine = new XYReferenceLinesLayer({ + data: [ + { + value: (warningThreshold[0] || 0).toString(), + color: euiTheme.colors.warning, + fill, + }, + ], + }); + // A transparent line to add extra buffer at the top of threshold + const bufferRefLine = new XYReferenceLinesLayer({ + data: [ + { + value: getBufferThreshold(warningThreshold[0]), + color: 'transparent', + fill, + }, + ], + }); + refLayers.push(warningThresholdRefLine, bufferRefLine); + } + setWarningThresholdReferenceLine(refLayers); + }, [ + warningThreshold, + warningComparator, + euiTheme.colors.warning, + metrics, + warningThresholdReferenceLine?.length, + ]); + // Build the threshold reference line useEffect(() => { if (!threshold) return; @@ -225,7 +332,7 @@ export function RuleConditionChart({ const baseLayer = { type: 'formula', value: formula, - label: 'Custom Threshold', + label: formula, groupBy, format: { id: formatId, @@ -272,6 +379,9 @@ export function RuleConditionChart({ const layers: Array = [ xyDataLayer, ]; + if (warningThresholdReferenceLine) { + layers.push(...warningThresholdReferenceLine); + } if (thresholdReferenceLine) { layers.push(...thresholdReferenceLine); } @@ -311,13 +421,14 @@ export function RuleConditionChart({ timeSize, timeUnit, seriesType, + warningThresholdReferenceLine, ]); if ( !dataView || !attributes || error?.equation || - Object.keys(error?.metrics || {}).length !== 0 || + Object.keys(error?.metrics || error?.metric || {}).length !== 0 || !timeSize || !timeRange ) { @@ -329,7 +440,7 @@ export function RuleConditionChart({ data-test-subj="thresholdRuleNoChartData" body={ ); } - return (
= ( prevState: Readonly, prevProps: Readonly @@ -34,6 +34,7 @@ interface AutocompleteFieldProps { autoFocus?: boolean; 'aria-label'?: string; compressed?: boolean; + theme?: EuiThemeComputed; } interface AutocompleteFieldState { @@ -64,12 +65,17 @@ export class AutocompleteField extends React.Component< disabled, 'aria-label': ariaLabel, compressed, + theme, } = this.props; const { areSuggestionsVisible, selectedIndex } = this.state; return ( - +
{areSuggestionsVisible && !isLoadingSuggestions && suggestions.length > 0 ? ( - + {suggestions.map((suggestion, suggestionIndex) => ( ))} - + ) : null} - +
); } @@ -310,20 +330,3 @@ const withUnfocused = (state: AutocompleteFieldState) => ({ ...state, isFocused: false, }); - -const AutocompleteContainer = euiStyled.div` - position: relative; -`; - -const SuggestionsPanel = euiStyled(EuiPanel).attrs(() => ({ - paddingSize: 'none', - hasShadow: true, -}))` - position: absolute; - width: 100%; - margin-top: 2px; - overflow-x: hidden; - overflow-y: scroll; - z-index: ${(props) => props.theme.eui.euiZLevel1}; - max-height: 322px; -`; diff --git a/x-pack/plugins/observability_solution/observability/public/components/rule_kql_filter/autocomplete_field/suggestion_item.tsx b/x-pack/plugins/observability_solution/observability/public/components/rule_kql_filter/autocomplete_field/suggestion_item.tsx index 67e4c879c7243..2eb5165b20e89 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/rule_kql_filter/autocomplete_field/suggestion_item.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/rule_kql_filter/autocomplete_field/suggestion_item.tsx @@ -6,29 +6,96 @@ */ import React from 'react'; -import { EuiIcon } from '@elastic/eui'; -import { euiStyled } from '@kbn/kibana-react-plugin/common'; +import { EuiIcon, euiPaletteColorBlind, EuiThemeComputed, useEuiTheme } from '@elastic/eui'; import { QuerySuggestion, QuerySuggestionTypes } from '@kbn/unified-search-plugin/public'; import { transparentize } from 'polished'; +import { css } from '@emotion/react'; interface Props { isSelected?: boolean; onClick?: React.MouseEventHandler; onMouseEnter?: React.MouseEventHandler; + onKeyDown?: React.KeyboardEventHandler; suggestion: QuerySuggestion; } export function SuggestionItem(props: Props) { - const { isSelected, onClick, onMouseEnter, suggestion } = props; + const { isSelected, onClick, onMouseEnter, onKeyDown, suggestion } = props; + const { euiTheme } = useEuiTheme(); + + const suggestionItemContainerCss = ` + display: flex; + flex-direction: row; + font-size: ${euiTheme.font.scale.s}; + height: ${euiTheme.size.xl}; + white-space: nowrap; + background-color: ${isSelected ? euiTheme.colors.lightestShade : 'transparent'}; + `; + + const suggestionItemFieldCss = ` + align-items: center; + cursor: pointer; + display: flex; + flex-direction: row; + height: ${euiTheme.size.xl}; + padding: ${euiTheme.size.xs}; + `; + + const suggestionItemIconFieldCss = ` + background-color: ${transparentize(0.9, getEuiIconColor(euiTheme, suggestion.type))}; + color: ${getEuiIconColor(euiTheme, suggestion.type)}; + flex: 0 0 auto; + justify-content: center; + width: ${euiTheme.size.xl}; + `; + + const suggestionItemTextFieldCss = ` + flex: 2 0 0; + font-family: ${euiTheme.font.familyCode}; + `; + + const suggestionItemDescriptionFieldCss = ` + flex: 3 0 0; + p { + display: inline; + span { + font-family: ${euiTheme.font.familyCode}; + } + } + `; return ( - - +
+
- - {suggestion.text} - {suggestion.description} - +
+
+ {suggestion.text} +
+
+ {suggestion.description} +
+
); } @@ -36,55 +103,6 @@ SuggestionItem.defaultProps = { isSelected: false, }; -const SuggestionItemContainer = euiStyled.div<{ - isSelected?: boolean; -}>` - display: flex; - flex-direction: row; - font-size: ${(props) => props.theme.eui.euiFontSizeS}; - height: ${(props) => props.theme.eui.euiSizeXL}; - white-space: nowrap; - background-color: ${(props) => - props.isSelected ? props.theme.eui.euiColorLightestShade : 'transparent'}; -`; - -const SuggestionItemField = euiStyled.div` - align-items: center; - cursor: pointer; - display: flex; - flex-direction: row; - height: ${(props) => props.theme.eui.euiSizeXL}; - padding: ${(props) => props.theme.eui.euiSizeXS}; -`; - -const SuggestionItemIconField = euiStyled(SuggestionItemField)<{ - suggestionType: QuerySuggestionTypes; -}>` - background-color: ${(props) => - transparentize(0.9, getEuiIconColor(props.theme, props.suggestionType))}; - color: ${(props) => getEuiIconColor(props.theme, props.suggestionType)}; - flex: 0 0 auto; - justify-content: center; - width: ${(props) => props.theme.eui.euiSizeXL}; -`; - -const SuggestionItemTextField = euiStyled(SuggestionItemField)` - flex: 2 0 0; - font-family: ${(props) => props.theme.eui.euiCodeFontFamily}; -`; - -const SuggestionItemDescriptionField = euiStyled(SuggestionItemField)` - flex: 3 0 0; - - p { - display: inline; - - span { - font-family: ${(props) => props.theme.eui.euiCodeFontFamily}; - } - } -`; - const getEuiIconType = (suggestionType: QuerySuggestionTypes) => { switch (suggestionType) { case QuerySuggestionTypes.Field: @@ -102,18 +120,23 @@ const getEuiIconType = (suggestionType: QuerySuggestionTypes) => { } }; -const getEuiIconColor = (theme: any, suggestionType: QuerySuggestionTypes): string => { +const getEuiIconColor = ( + euiTheme: EuiThemeComputed, + suggestionType: QuerySuggestionTypes +): string => { + const palette = euiPaletteColorBlind(); + switch (suggestionType) { case QuerySuggestionTypes.Field: - return theme?.eui.euiColorVis7; + return palette[7]; case QuerySuggestionTypes.Value: - return theme?.eui.euiColorVis0; + return palette[0]; case QuerySuggestionTypes.Operator: - return theme?.eui.euiColorVis1; + return palette[1]; case QuerySuggestionTypes.Conjunction: - return theme?.eui.euiColorVis2; + return palette[2]; case QuerySuggestionTypes.RecentSearch: default: - return theme?.eui.euiColorMediumShade; + return euiTheme.colors.mediumShade; } }; diff --git a/x-pack/plugins/observability_solution/observability/public/components/rule_kql_filter/kuery_bar.tsx b/x-pack/plugins/observability_solution/observability/public/components/rule_kql_filter/kuery_bar.tsx index 9c16d4d7b69af..26115ec331021 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/rule_kql_filter/kuery_bar.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/rule_kql_filter/kuery_bar.tsx @@ -10,6 +10,7 @@ import React, { useEffect, useState } from 'react'; import { DataViewBase } from '@kbn/es-query'; import { QuerySuggestion } from '@kbn/unified-search-plugin/public'; +import { useEuiTheme } from '@elastic/eui'; import { WithKueryAutocompletion } from './with_kuery_autocompletion'; import { AutocompleteField } from './autocomplete_field'; @@ -59,6 +60,8 @@ export function RuleFlyoutKueryBar({ } }, [value]); + const { euiTheme } = useEuiTheme(); + const handleChange = (query: string) => { setValidation(validateQuery(query)); setDraftQuery(query); @@ -86,6 +89,7 @@ export function RuleFlyoutKueryBar({ placeholder={placeholder} suggestions={suggestions} value={draftQuery} + theme={euiTheme} /> )} diff --git a/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/tags.tsx b/x-pack/plugins/observability_solution/observability/public/components/tags.tsx similarity index 89% rename from x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/tags.tsx rename to x-pack/plugins/observability_solution/observability/public/components/tags.tsx index 6d5cb11a63b67..e7059463ef7bd 100644 --- a/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/components/tags.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/tags.tsx @@ -19,14 +19,14 @@ export function Tags({ tags }: { tags: string[] }) { key="more" onClick={onMoreTagsClick} onClickAriaLabel={i18n.translate( - 'xpack.infra.metrics.alertDetailsAppSection.summaryField.moreTags.ariaLabel', + 'xpack.observability.alertDetails.alertSummaryField.moreTags.ariaLabel', { defaultMessage: 'more tags badge', } )} > diff --git a/x-pack/plugins/observability_solution/observability/public/index.ts b/x-pack/plugins/observability_solution/observability/public/index.ts index d655b872ca3e5..665bdeb0569b3 100644 --- a/x-pack/plugins/observability_solution/observability/public/index.ts +++ b/x-pack/plugins/observability_solution/observability/public/index.ts @@ -99,3 +99,6 @@ export { formatAlertEvaluationValue } from './utils/format_alert_evaluation_valu export { WithKueryAutocompletion } from './components/rule_kql_filter/with_kuery_autocompletion'; export { AutocompleteField } from './components/rule_kql_filter/autocomplete_field'; export { RuleFlyoutKueryBar } from './components/rule_kql_filter/kuery_bar'; +export { RuleConditionChart } from './components/rule_condition_chart/rule_condition_chart'; +export { getGroupFilters } from '../common/custom_threshold_rule/helpers/get_group'; +export type { GenericAggType } from './components/rule_condition_chart/rule_condition_chart'; diff --git a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details.tsx b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details.tsx index e2bbfe4edda9a..e17fc1666e061 100644 --- a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details.tsx +++ b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details.tsx @@ -174,7 +174,7 @@ export function AlertDetails() { isAlertDetailsEnabledPerApp(alertDetail.formatted, config) ? ( <> - + {rule && alertDetail.formatted && ( @@ -184,7 +184,6 @@ export function AlertDetails() { rule={rule} timeZone={timeZone} setAlertSummaryFields={setSummaryFields} - ruleLink={http.basePath.prepend(paths.observability.ruleDetails(rule.id))} /> ({ ...jest.requireActual('react-router-dom'), @@ -19,6 +29,21 @@ jest.mock('react-router-dom', () => ({ jest.mock('../../../utils/kibana_react'); +const useKibanaMock = useKibana as jest.Mock; + +const mockKibana = () => { + useKibanaMock.mockReturnValue({ + services: { + ...kibanaStartMock.startContract().services, + http: { + basePath: { + prepend: jest.fn(), + }, + }, + }, + }); +}; + describe('Alert summary', () => { jest .spyOn(useUiSettingHook, 'useUiSetting') @@ -26,14 +51,30 @@ describe('Alert summary', () => { beforeEach(() => { jest.clearAllMocks(); + mockKibana(); }); it('should show alert data', async () => { - const alertSummary = render(); + const alertSummary = render( + + ); + + const groups = alertWithGroupsAndTags.fields[ALERT_GROUP] as Group[]; + expect(alertSummary.queryByText('Source')).toBeInTheDocument(); + expect(alertSummary.queryByText(groups[0].field, { exact: false })).toBeInTheDocument(); + expect(alertSummary.queryByText(groups[0].value)).toBeInTheDocument(); + expect(alertSummary.queryByText(groups[1].field, { exact: false })).toBeInTheDocument(); + expect(alertSummary.queryByText(groups[1].value)).toBeInTheDocument(); + expect(alertSummary.queryByText('Tags')).toBeInTheDocument(); + expect(alertSummary.queryByText(alertWithGroupsAndTags.fields[TAGS]![0])).toBeInTheDocument(); + expect(alertSummary.queryByText('Rule')).toBeInTheDocument(); + expect( + alertSummary.queryByText(alertWithGroupsAndTags.fields[ALERT_RULE_NAME]) + ).toBeInTheDocument(); expect(alertSummary.queryByText('Actual value')).toBeInTheDocument(); - expect(alertSummary.queryByText(alertWithTags.fields['kibana.alert.evaluation.value']!)); + expect(alertSummary.queryByText(alertWithGroupsAndTags.fields[ALERT_EVALUATION_VALUE]!)); expect(alertSummary.queryByText('Expected value')).toBeInTheDocument(); - expect(alertSummary.queryByText(alertWithTags.fields['kibana.alert.evaluation.threshold']!)); + expect(alertSummary.queryByText(alertWithGroupsAndTags.fields[ALERT_EVALUATION_THRESHOLD]!)); }); }); diff --git a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/alert_summary.tsx b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/alert_summary.tsx index 20d0fb36f9f8a..82c22626aab85 100644 --- a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/alert_summary.tsx +++ b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/alert_summary.tsx @@ -5,24 +5,97 @@ * 2.0. */ -import React, { ReactNode } from 'react'; -import { EuiFlexItem, EuiFlexGroup, EuiText, EuiSpacer } from '@elastic/eui'; +import React, { useEffect, useState, ReactNode } from 'react'; +import { EuiFlexItem, EuiFlexGroup, EuiText, EuiSpacer, EuiLink } from '@elastic/eui'; +import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util'; +import { + TAGS, + ALERT_START, + ALERT_END, + ALERT_RULE_NAME, + ALERT_RULE_UUID, +} from '@kbn/rule-data-utils'; +import { i18n } from '@kbn/i18n'; +import { TimeRange } from '@kbn/es-query'; +import { TopAlert } from '../../..'; +import { Groups } from '../../../components/alert_sources/groups'; +import { Tags } from '../../../components/tags'; +import { getSources } from '../../../components/alert_sources/get_sources'; +import { useKibana } from '../../../utils/kibana_react'; +import { paths } from '../../../../common/locators/paths'; export interface AlertSummaryField { label: ReactNode | string; value: ReactNode | string | number; } interface AlertSummaryProps { + alert: TopAlert; alertSummaryFields?: AlertSummaryField[]; } -export function AlertSummary({ alertSummaryFields }: AlertSummaryProps) { +export function AlertSummary({ alert, alertSummaryFields }: AlertSummaryProps) { + const { http } = useKibana().services; + + const [timeRange, setTimeRange] = useState({ from: 'now-15m', to: 'now' }); + + const alertStart = alert.fields[ALERT_START]; + const alertEnd = alert.fields[ALERT_END]; + const ruleName = alert.fields[ALERT_RULE_NAME]; + const ruleId = alert.fields[ALERT_RULE_UUID]; + const tags = alert.fields[TAGS]; + + const ruleLink = http.basePath.prepend(paths.observability.ruleDetails(ruleId)); + const commonFieldsAtStart = []; + const commonFieldsAtEnd = []; + const groups = getSources(alert) as Array<{ field: string; value: string }>; + + useEffect(() => { + setTimeRange(getPaddedAlertTimeRange(alertStart!, alertEnd)); + }, [alertStart, alertEnd]); + + if (groups && groups.length > 0) { + commonFieldsAtStart.push({ + label: i18n.translate('xpack.observability.alertDetails.alertSummaryField.source', { + defaultMessage: 'Source', + }), + value: ( + + ), + }); + } + + if (tags && tags.length > 0) { + commonFieldsAtEnd.push({ + label: i18n.translate('xpack.observability.alertDetails.alertSummaryField.tags', { + defaultMessage: 'Tags', + }), + value: , + }); + } + + commonFieldsAtEnd.push({ + label: i18n.translate('xpack.observability.alertDetails.alertSummaryField.rule', { + defaultMessage: 'Rule', + }), + value: ( + + {ruleName} + + ), + }); + + const alertSummary = [ + ...commonFieldsAtStart, + ...(alertSummaryFields ?? []), + ...commonFieldsAtEnd, + ]; + return (
- {alertSummaryFields && alertSummaryFields.length > 0 && ( + {alertSummary && alertSummary.length > 0 && ( <> - {alertSummaryFields.map((field, idx) => { + {alertSummary.map((field, idx) => { return ( {field.label} diff --git a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/header_actions.test.tsx b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/header_actions.test.tsx index 6c07c54f60454..b3e28ba222c5a 100644 --- a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/header_actions.test.tsx +++ b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/header_actions.test.tsx @@ -13,7 +13,7 @@ import { casesPluginMock } from '@kbn/cases-plugin/public/mocks'; import { render } from '../../../utils/test_helper'; import { useKibana } from '../../../utils/kibana_react'; import { kibanaStartMock } from '../../../utils/kibana_react.mock'; -import { alertWithTags, mockAlertUuid, untrackedAlert } from '../mock/alert'; +import { alertWithGroupsAndTags, mockAlertUuid, untrackedAlert } from '../mock/alert'; import { useFetchRule } from '../../../hooks/use_fetch_rule'; import { HeaderActions } from './header_actions'; @@ -87,8 +87,8 @@ describe('Header Actions', () => { it('should display an actions button', () => { const { queryByTestId } = render( ); @@ -109,9 +109,9 @@ describe('Header Actions', () => { const { getByTestId, findByTestId } = render( ); @@ -136,8 +136,8 @@ describe('Header Actions', () => { it('should offer a "Edit rule" button which opens the edit rule flyout', async () => { const { getByTestId, findByTestId } = render( ); @@ -150,8 +150,8 @@ describe('Header Actions', () => { it('should offer a "Mark as untracked" button which is enabled', async () => { const { queryByTestId, findByTestId } = render( ); @@ -163,8 +163,8 @@ describe('Header Actions', () => { it('should offer a "Go to rule details" button which opens the rule details page in a new tab', async () => { const { queryByTestId, findByTestId } = render( ); @@ -188,8 +188,8 @@ describe('Header Actions', () => { it("should disable the 'Edit rule' when the rule is not available/deleted", async () => { const { queryByTestId, findByTestId } = render( ); @@ -214,8 +214,8 @@ describe('Header Actions', () => { it("should disable the 'View rule details' when the rule is not available/deleted", async () => { const { queryByTestId, findByTestId } = render( ); diff --git a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/mock/alert.ts b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/mock/alert.ts index 5f377aab47686..3474f9b40674b 100644 --- a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/mock/alert.ts +++ b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/mock/alert.ts @@ -9,6 +9,7 @@ import { ALERT_DURATION, ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, + ALERT_GROUP, ALERT_INSTANCE_ID, ALERT_REASON, ALERT_RULE_CATEGORY, @@ -27,6 +28,7 @@ import { EVENT_ACTION, EVENT_KIND, SPACE_IDS, + TAGS, TIMESTAMP, VERSION, } from '@kbn/rule-data-utils'; @@ -75,18 +77,23 @@ export const alertDetail: AlertData = { ) as unknown as AlertData['raw'], }; -export const alertWithTags: TopAlert = { +export const alertWithGroupsAndTags: TopAlert = { ...alert, fields: { ...alert.fields, + [ALERT_GROUP]: [ + { field: 'host.name', value: 'host-0' }, + { field: 'container.id', value: 'container-0' }, + ], [ALERT_RULE_TAGS]: tags, + [TAGS]: tags, }, }; export const untrackedAlert: TopAlert = { - ...alertWithTags, + ...alertWithGroupsAndTags, fields: { - ...alertWithTags.fields, + ...alertWithGroupsAndTags.fields, [ALERT_STATUS]: ALERT_STATUS_UNTRACKED, }, }; diff --git a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/mock/alert_summary_fields.ts b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/mock/alert_summary_fields.ts index 304f750009e8d..b80890c7f73af 100644 --- a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/mock/alert_summary_fields.ts +++ b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/mock/alert_summary_fields.ts @@ -5,20 +5,17 @@ * 2.0. */ -import { alertWithTags } from './alert'; +import { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE } from '@kbn/rule-data-utils'; +import { alertWithGroupsAndTags } from './alert'; import type { AlertSummaryField } from '../components/alert_summary'; export const alertSummaryFieldsMock: AlertSummaryField[] = [ { label: 'Actual value', - value: alertWithTags.fields['kibana.alert.evaluation.value']!, + value: alertWithGroupsAndTags.fields[ALERT_EVALUATION_VALUE]!, }, { label: 'Expected value', - value: alertWithTags.fields['kibana.alert.evaluation.threshold']!, - }, - { - label: 'Source', - value: '-', + value: alertWithGroupsAndTags.fields[ALERT_EVALUATION_THRESHOLD]!, }, ]; diff --git a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts index 94be15415abb8..baec846b71154 100644 --- a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts +++ b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts @@ -17,7 +17,6 @@ import { LocatorPublic } from '@kbn/share-plugin/common'; import { RecoveredActionGroup } from '@kbn/alerting-plugin/common'; import { IBasePath, Logger } from '@kbn/core/server'; import { AlertsClientError, RuleExecutorOptions } from '@kbn/alerting-plugin/server'; -import { Group } from '../../../../common/custom_threshold_rule/types'; import { getEvaluationValues, getThreshold } from './lib/get_values'; import { AlertsLocatorParams, getAlertDetailsUrl } from '../../../../common'; import { getViewInAppUrl } from '../../../../common/custom_threshold_rule/get_view_in_app_url'; @@ -46,6 +45,7 @@ import { import { formatAlertResult, getLabel } from './lib/format_alert_result'; import { EvaluatedRuleParams, evaluateRule } from './lib/evaluate_rule'; import { MissingGroupsRecord } from './lib/check_missing_group'; +import { Group } from '../../../../common/typings'; export interface CustomThresholdLocators { alertsLocator?: LocatorPublic; diff --git a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/types.ts b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/types.ts index 6f1e8cf72cc7d..052741c474f46 100644 --- a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/types.ts +++ b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/types.ts @@ -18,9 +18,9 @@ import { ALERT_EVALUATION_VALUES, ALERT_GROUP, } from '@kbn/rule-data-utils'; +import { Group } from '../../../../common/typings'; import { CustomMetricExpressionParams, - Group, SearchConfigurationWithExtractedReferenceType, } from '../../../../common/custom_threshold_rule/types'; import { FIRED_ACTIONS_ID, NO_DATA_ACTIONS_ID, FIRED_ACTION, NO_DATA_ACTION } from './constants'; diff --git a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/utils.ts b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/utils.ts index 88f1febca71c0..c80a003c050e0 100644 --- a/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/utils.ts +++ b/x-pack/plugins/observability_solution/observability/server/lib/rules/custom_threshold/utils.ts @@ -17,7 +17,7 @@ import { set } from '@kbn/safer-lodash-set'; import { ParsedExperimentalFields } from '@kbn/rule-registry-plugin/common/parse_experimental_fields'; import { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common'; import { Alert } from '@kbn/alerts-as-data-utils'; -import type { Group } from '../../../../common/custom_threshold_rule/types'; +import type { Group } from '../../../../common/typings'; import { ObservabilityConfig } from '../../..'; import { AlertExecutionDetails } from './types'; diff --git a/x-pack/plugins/observability_solution/observability/server/ui_settings.ts b/x-pack/plugins/observability_solution/observability/server/ui_settings.ts index 4653b2bb63242..f5d87286d6844 100644 --- a/x-pack/plugins/observability_solution/observability/server/ui_settings.ts +++ b/x-pack/plugins/observability_solution/observability/server/ui_settings.ts @@ -250,7 +250,7 @@ export const uiSettings: Record = { name: i18n.translate('xpack.observability.enableInfrastructureContainerAssetView', { defaultMessage: 'Container view', }), - value: false, + value: true, description: i18n.translate( 'xpack.observability.enableInfrastructureContainerAssetViewDescription', { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/common/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/common/index.ts index e29aa4c2e1bc9..cfb4987862535 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/common/index.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/common/index.ts @@ -8,7 +8,7 @@ export type { Message, Conversation, KnowledgeBaseEntry } from './types'; export type { ConversationCreateRequest } from './types'; export { KnowledgeBaseEntryRole, MessageRole } from './types'; -export type { FunctionDefinition } from './functions/types'; +export type { FunctionDefinition, CompatibleJSONSchema } from './functions/types'; export { FunctionVisibility } from './functions/function_visibility'; export { VISUALIZE_ESQL_USER_INTENTIONS, @@ -49,3 +49,5 @@ export { concatenateChatCompletionChunks } from './utils/concatenate_chat_comple export { DEFAULT_LANGUAGE_OPTION, LANGUAGE_OPTIONS } from './ui_settings/language_options'; export { isSupportedConnectorType } from './connectors'; + +export { ShortIdTable } from './utils/short_id_table'; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/common/types.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/common/types.ts index ea6c754193341..bd1a284b0d363 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/common/types.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/common/types.ts @@ -95,6 +95,7 @@ export interface KnowledgeBaseEntry { export interface UserInstruction { doc_id: string; text: string; + system?: boolean; } export type UserInstructionOrPlainText = string | UserInstruction; @@ -109,7 +110,7 @@ export interface ObservabilityAIAssistantScreenContextRequest { actions?: Array<{ name: string; description: string; parameters?: CompatibleJSONSchema }>; } -export type ScreenContextActionRespondFunction = ({}: { +export type ScreenContextActionRespondFunction = ({}: { args: TArguments; signal: AbortSignal; connectorId: string; @@ -117,7 +118,7 @@ export type ScreenContextActionRespondFunction = ({} messages: Message[]; }) => Promise; -export interface ScreenContextActionDefinition { +export interface ScreenContextActionDefinition { name: string; description: string; parameters?: CompatibleJSONSchema; @@ -137,6 +138,6 @@ export interface ObservabilityAIAssistantScreenContext { description: string; value: any; }>; - actions?: ScreenContextActionDefinition[]; + actions?: Array>; starterPrompts?: StarterPrompt[]; } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/concatenate_chat_completion_chunks.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/concatenate_chat_completion_chunks.ts index 8686ff93afb34..bead0974b91a3 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/concatenate_chat_completion_chunks.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/concatenate_chat_completion_chunks.ts @@ -31,6 +31,7 @@ export const concatenateChatCompletionChunks = acc.message.content += message.content ?? ''; acc.message.function_call.name += message.function_call?.name ?? ''; acc.message.function_call.arguments += message.function_call?.arguments ?? ''; + return cloneDeep(acc); }, { @@ -43,6 +44,6 @@ export const concatenateChatCompletionChunks = }, role: MessageRole.Assistant, }, - } + } as ConcatenatedMessage ) ); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/short_id_table.test.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/short_id_table.test.ts new file mode 100644 index 0000000000000..784cf67530652 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/short_id_table.test.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ShortIdTable } from './short_id_table'; + +describe('shortIdTable', () => { + it('generates at least 10k unique ids consistently', () => { + const ids = new Set(); + + const table = new ShortIdTable(); + + let i = 10_000; + while (i--) { + const id = table.take(String(i)); + ids.add(id); + } + + expect(ids.size).toBe(10_000); + }); + + it('returns the original id based on the generated id', () => { + const table = new ShortIdTable(); + + const idsByOriginal = new Map(); + + let i = 100; + while (i--) { + const id = table.take(String(i)); + idsByOriginal.set(String(i), id); + } + + expect(idsByOriginal.size).toBe(100); + + expect(() => { + Array.from(idsByOriginal.entries()).forEach(([originalId, shortId]) => { + const returnedOriginalId = table.lookup(shortId); + if (returnedOriginalId !== originalId) { + throw Error( + `Expected shortId ${shortId} to return ${originalId}, but ${returnedOriginalId} was returned instead` + ); + } + }); + }).not.toThrow(); + }); +}); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/short_id_table.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/short_id_table.ts new file mode 100644 index 0000000000000..30049452ddf51 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/short_id_table.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. + */ + +const ALPHABET = 'abcdefghijklmnopqrstuvwxyz'; + +function generateShortId(size: number): string { + let id = ''; + let i = size; + while (i--) { + const index = Math.floor(Math.random() * ALPHABET.length); + id += ALPHABET[index]; + } + return id; +} + +const MAX_ATTEMPTS_AT_LENGTH = 100; + +export class ShortIdTable { + private byShortId: Map = new Map(); + private byOriginalId: Map = new Map(); + + constructor() {} + + take(originalId: string) { + if (this.byOriginalId.has(originalId)) { + return this.byOriginalId.get(originalId)!; + } + + let uniqueId: string | undefined; + let attemptsAtLength = 0; + let length = 4; + while (!uniqueId) { + const nextId = generateShortId(length); + attemptsAtLength++; + if (!this.byShortId.has(nextId)) { + uniqueId = nextId; + } else if (attemptsAtLength >= MAX_ATTEMPTS_AT_LENGTH) { + attemptsAtLength = 0; + length++; + } + } + + this.byShortId.set(uniqueId, originalId); + this.byOriginalId.set(originalId, uniqueId); + + return uniqueId; + } + + lookup(shortId: string) { + return this.byShortId.get(shortId); + } +} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/throw_serialized_chat_completion_errors.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/throw_serialized_chat_completion_errors.ts index 2c23109a1bac0..e137a4cce1f75 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/throw_serialized_chat_completion_errors.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/throw_serialized_chat_completion_errors.ts @@ -21,7 +21,7 @@ export function throwSerializedChatCompletionErrors< return (source$) => source$.pipe( tap((event) => { - // de-serialise error + // de-serialize error if (event.type === StreamingChatResponseEventType.ChatCompletionError) { const code = event.error.code ?? ChatCompletionErrorCode.InternalError; const message = event.error.message; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/until_aborted.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/until_aborted.ts new file mode 100644 index 0000000000000..d5e3ff9e18bd4 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/common/utils/until_aborted.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 { Observable, OperatorFunction, takeUntil } from 'rxjs'; +import { AbortError } from '@kbn/kibana-utils-plugin/common'; + +export function untilAborted(signal: AbortSignal): OperatorFunction { + return (source$) => { + const signal$ = new Observable((subscriber) => { + if (signal.aborted) { + subscriber.error(new AbortError()); + } + signal.addEventListener('abort', () => { + subscriber.error(new AbortError()); + }); + }); + + return source$.pipe(takeUntil(signal$)); + }; +} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/analytics/schemas/chat_feedback.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/analytics/schemas/chat_feedback.ts index fa0c670fa2988..01df60e95a952 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/analytics/schemas/chat_feedback.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/analytics/schemas/chat_feedback.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { EventTypeOpts } from '@kbn/analytics-client'; +import type { EventTypeOpts } from '@kbn/core/public'; import type { Message, Conversation } from '../../../common'; import type { Feedback } from '../../components/buttons/feedback_buttons'; import { ObservabilityAIAssistantTelemetryEventType } from '../telemetry_event_type'; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/analytics/schemas/common.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/analytics/schemas/common.ts index 3c4d4ee0c75e0..b01a8e05a4ea5 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/analytics/schemas/common.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/analytics/schemas/common.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { RootSchema } from '@kbn/analytics-client'; +import type { RootSchema } from '@kbn/core/public'; import type { Message } from '../../../common'; export const messageSchema: RootSchema = { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/analytics/schemas/insight_feedback.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/analytics/schemas/insight_feedback.ts index 7f8a37cf95aee..67bd03f1fcd4f 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/analytics/schemas/insight_feedback.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/analytics/schemas/insight_feedback.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { EventTypeOpts } from '@kbn/analytics-client'; +import type { EventTypeOpts } from '@kbn/core/public'; import type { Message } from '../../../common'; import type { Feedback } from '../../components/buttons/feedback_buttons'; import { ObservabilityAIAssistantTelemetryEventType } from '../telemetry_event_type'; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/analytics/schemas/user_sent_prompt.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/analytics/schemas/user_sent_prompt.ts index b7ce5f2dacb38..9c6f6d45110b1 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/analytics/schemas/user_sent_prompt.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/analytics/schemas/user_sent_prompt.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { EventTypeOpts } from '@kbn/analytics-client'; +import type { EventTypeOpts } from '@kbn/core/public'; import type { Message } from '../../../common'; import { ObservabilityAIAssistantTelemetryEventType } from '../telemetry_event_type'; import { messageSchema } from './common'; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/components/assistant_avatar.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant/public/components/assistant_avatar.tsx index 64ac351bad0a4..c9b0b21e70bcd 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/components/assistant_avatar.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/components/assistant_avatar.tsx @@ -10,6 +10,7 @@ export interface AssistantAvatarProps { size?: keyof typeof sizeMap; children?: ReactNode; css?: React.SVGProps['css']; + className?: string; } export const sizeMap = { @@ -20,7 +21,7 @@ export const sizeMap = { xs: 16, }; -export function AssistantAvatar({ size = 's', css }: AssistantAvatarProps) { +export function AssistantAvatar({ size = 's', css, className }: AssistantAvatarProps) { const sizePx = sizeMap[size]; return ( diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_abortable_async.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_abortable_async.ts index afd776dc13990..433ca877b0f62 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_abortable_async.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_abortable_async.ts @@ -40,6 +40,7 @@ export function useAbortableAsync( if (clearValueOnNext) { setValue(undefined); + setError(undefined); } try { @@ -47,7 +48,10 @@ export function useAbortableAsync( if (isPromise(response)) { setLoading(true); response - .then(setValue) + .then((nextValue) => { + setError(undefined); + setValue(nextValue); + }) .catch((err) => { setValue(undefined); setError(err); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/index.ts index 7f0c1f8bb4c09..2e604b59fc7ab 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/index.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/index.ts @@ -5,7 +5,6 @@ * 2.0. */ import type { PluginInitializer, PluginInitializerContext } from '@kbn/core/public'; -export type { CompatibleJSONSchema } from '../common/functions/types'; import { ObservabilityAIAssistantPlugin } from './plugin'; import type { @@ -18,6 +17,7 @@ import type { ObservabilityAIAssistantChatService, RegisterRenderFunctionDefinition, RenderFunction, + DiscoveredDataset, } from './types'; export type { @@ -27,6 +27,7 @@ export type { ObservabilityAIAssistantChatService, RegisterRenderFunctionDefinition, RenderFunction, + DiscoveredDataset, }; export { aiAssistantCapabilities } from '../common/capabilities'; @@ -59,15 +60,27 @@ export { VISUALIZE_ESQL_USER_INTENTIONS, } from '../common/functions/visualize_esql'; -export { isSupportedConnectorType } from '../common'; -export { FunctionVisibility } from '../common'; +export { + isSupportedConnectorType, + FunctionVisibility, + MessageRole, + KnowledgeBaseEntryRole, + concatenateChatCompletionChunks, + StreamingChatResponseEventType, +} from '../common'; +export type { + CompatibleJSONSchema, + Conversation, + Message, + KnowledgeBaseEntry, + FunctionDefinition, + ChatCompletionChunkEvent, + ShortIdTable, +} from '../common'; export type { TelemetryEventTypeWithPayload } from './analytics'; export { ObservabilityAIAssistantTelemetryEventType } from './analytics/telemetry_event_type'; -export type { Conversation, Message, KnowledgeBaseEntry } from '../common'; -export { MessageRole, KnowledgeBaseEntryRole } from '../common'; - export { createFunctionRequestMessage } from '../common/utils/create_function_request_message'; export { createFunctionResponseMessage } from '../common/utils/create_function_response_message'; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/mock.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant/public/mock.tsx index 4775ad1b551b1..31907f54c49bf 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/mock.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/mock.tsx @@ -8,7 +8,10 @@ import { i18n } from '@kbn/i18n'; import { noop } from 'lodash'; import React from 'react'; import { Observable, of } from 'rxjs'; -import type { StreamingChatResponseEventWithoutError } from '../common/conversation_complete'; +import type { + ChatCompletionChunkEvent, + StreamingChatResponseEventWithoutError, +} from '../common/conversation_complete'; import { MessageRole, ScreenContextActionDefinition } from '../common/types'; import type { ObservabilityAIAssistantAPIClient } from './api'; import type { @@ -21,7 +24,7 @@ import { buildFunctionElasticsearch, buildFunctionServiceSummary } from './utils export const mockChatService: ObservabilityAIAssistantChatService = { sendAnalyticsEvent: noop, - chat: (options) => new Observable(), + chat: (options) => new Observable(), complete: (options) => new Observable(), getFunctions: () => [buildFunctionElasticsearch(), buildFunctionServiceSummary()], renderFunction: (name) => ( diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/complete.test.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/complete.test.ts index 421770cf415c7..4665f9b7b486b 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/complete.test.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/complete.test.ts @@ -15,6 +15,8 @@ import { ChatCompletionError, MessageAddEvent, createInternalServerError, + createConversationNotFoundError, + StreamingChatResponseEventWithoutError, } from '../../common'; import type { ObservabilityAIAssistantChatService } from '../types'; import { complete } from './complete'; @@ -45,7 +47,7 @@ const messages: Message[] = [ const createLlmResponse = ( chunks: Array<{ content: string; function_call?: { name: string; arguments: string } }> -): StreamingChatResponseEvent[] => { +): StreamingChatResponseEventWithoutError[] => { const id = v4(); const message = chunks.reduce( (prev, current) => { @@ -61,7 +63,7 @@ const createLlmResponse = ( } ); - const events: StreamingChatResponseEvent[] = [ + const events: StreamingChatResponseEventWithoutError[] = [ ...chunks.map((msg) => ({ id, message: msg, @@ -108,20 +110,12 @@ describe('complete', () => { describe('when an error is emitted', () => { beforeEach(() => { - requestCallback.mockImplementation(() => - of({ - type: StreamingChatResponseEventType.ChatCompletionError, - error: { - message: 'Not found', - code: ChatCompletionErrorCode.NotFoundError, - }, - }) - ); + requestCallback.mockImplementation(() => throwError(() => createConversationNotFoundError())); }); it('the observable errors out', async () => { await expect(async () => await lastValueFrom(callComplete())).rejects.toThrowError( - 'Not found' + 'Conversation not found' ); await expect(async () => await lastValueFrom(callComplete())).rejects.toBeInstanceOf( diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/complete.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/complete.ts index 8d9efd033bad7..90a8f16639ed6 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/complete.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/complete.ts @@ -20,19 +20,16 @@ import { import { MessageRole, StreamingChatResponseEventType, - type BufferFlushEvent, type ConversationCreateEvent, type ConversationUpdateEvent, type Message, type MessageAddEvent, - type StreamingChatResponseEvent, type StreamingChatResponseEventWithoutError, } from '../../common'; -import { ObservabilityAIAssistantScreenContext } from '../../common/types'; +import type { ObservabilityAIAssistantScreenContext } from '../../common/types'; import { createFunctionResponseMessage } from '../../common/utils/create_function_response_message'; -import { throwSerializedChatCompletionErrors } from '../../common/utils/throw_serialized_chat_completion_errors'; import type { ObservabilityAIAssistantAPIClientRequestParamsOf } from '../api'; -import { ObservabilityAIAssistantChatService } from '../types'; +import type { ObservabilityAIAssistantChatService } from '../types'; import { createPublicFunctionResponseError } from '../utils/create_function_response_error'; export function complete( @@ -46,20 +43,14 @@ export function complete( disableFunctions, signal, responseLanguage, + instructions, }: { client: Pick; getScreenContexts: () => ObservabilityAIAssistantScreenContext[]; - connectorId: string; - conversationId?: string; - messages: Message[]; - persist: boolean; - disableFunctions: boolean; - signal: AbortSignal; - responseLanguage: string; - }, + } & Parameters[0], requestCallback: ( params: ObservabilityAIAssistantAPIClientRequestParamsOf<'POST /internal/observability_ai_assistant/chat/complete'> - ) => Observable + ) => Observable ): Observable { return new Observable((subscriber) => { const screenContexts = getScreenContexts(); @@ -75,16 +66,10 @@ export function complete( screenContexts, conversationId, responseLanguage, + instructions, }, }, - }).pipe( - filter( - (event): event is StreamingChatResponseEvent => - event.type !== StreamingChatResponseEventType.BufferFlush - ), - throwSerializedChatCompletionErrors(), - shareReplay() - ); + }).pipe(shareReplay()); const messages$ = response$.pipe( filter( @@ -148,6 +133,7 @@ export function complete( persist, responseLanguage, disableFunctions, + instructions, }, requestCallback ).subscribe(subscriber); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_chat_service.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_chat_service.ts index 45fa95aa72a17..db3c8b1f5bbf3 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_chat_service.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/service/create_chat_service.ts @@ -6,10 +6,9 @@ */ import type { AnalyticsServiceStart, HttpResponse } from '@kbn/core/public'; -import { AbortError } from '@kbn/kibana-utils-plugin/common'; import type { IncomingMessage } from 'http'; -import { pick } from 'lodash'; import { + catchError, concatMap, delay, filter, @@ -17,27 +16,30 @@ import { map, Observable, of, + OperatorFunction, scan, shareReplay, switchMap, + throwError, timestamp, } from 'rxjs'; -import { Message, MessageRole } from '../../common'; +import { ChatCompletionChunkEvent, Message, MessageRole } from '../../common'; import { - type BufferFlushEvent, StreamingChatResponseEventType, - type StreamingChatResponseEventWithoutError, + type BufferFlushEvent, type StreamingChatResponseEvent, + type StreamingChatResponseEventWithoutError, } from '../../common/conversation_complete'; -import { - FunctionRegistry, - FunctionResponse, - FunctionVisibility, -} from '../../common/functions/types'; +import { FunctionRegistry, FunctionResponse } from '../../common/functions/types'; import { filterFunctionDefinitions } from '../../common/utils/filter_function_definitions'; import { throwSerializedChatCompletionErrors } from '../../common/utils/throw_serialized_chat_completion_errors'; +import { untilAborted } from '../../common/utils/until_aborted'; import { sendEvent } from '../analytics'; -import type { ObservabilityAIAssistantAPIClient } from '../api'; +import type { + ObservabilityAIAssistantAPIClient, + ObservabilityAIAssistantAPIClientRequestParamsOf, + ObservabilityAIAssistantAPIEndpoint, +} from '../api'; import type { ChatRegistrationRenderFunction, ObservabilityAIAssistantChatService, @@ -91,6 +93,45 @@ function toObservable(response: HttpResponse) { ); } +function serialize( + signal: AbortSignal +): OperatorFunction { + return (source$) => + source$.pipe( + catchError((error) => { + if ( + 'response' in error && + 'json' in error.response && + typeof error.response.json === 'function' + ) { + const responseBodyPromise = (error.response as HttpResponse['response'])!.json(); + + return from( + responseBodyPromise.then((body: { message?: string }) => { + if (body) { + error.body = body; + if (body.message) { + error.message = body.message; + } + } + throw error; + }) + ); + } + return throwError(() => error); + }), + switchMap((readable) => toObservable(readable as HttpResponse)), + map((line) => JSON.parse(line) as StreamingChatResponseEvent | BufferFlushEvent), + filter( + (line): line is Exclude => + line.type !== StreamingChatResponseEventType.BufferFlush + ), + throwSerializedChatCompletionErrors(), + untilAborted(signal), + shareReplay() + ); +} + export async function createChatService({ analytics, signal: setupAbortSignal, @@ -130,73 +171,39 @@ export async function createChatService({ }); }; - const client: Pick = { - chat(name: string, { connectorId, messages, function: callFunctions = 'auto', signal }) { - return new Observable((subscriber) => { - const functions = getFunctions().filter((fn) => { - const visibility = fn.visibility ?? FunctionVisibility.All; - - return ( - visibility === FunctionVisibility.All || visibility === FunctionVisibility.AssistantOnly - ); - }); + function callStreamingApi( + endpoint: TEndpoint, + options: { + signal: AbortSignal; + } & ObservabilityAIAssistantAPIClientRequestParamsOf + ): Observable { + return from( + apiClient(endpoint, { + ...options, + asResponse: true, + rawResponse: true, + }) + ).pipe(serialize(options.signal)); + } - apiClient('POST /internal/observability_ai_assistant/chat', { - params: { - body: { - name, - messages, - connectorId, - functions: - callFunctions === 'none' - ? [] - : functions.map((fn) => pick(fn, 'name', 'description', 'parameters')), - }, + const client: Pick = { + chat(name: string, { connectorId, messages, functionCall, functions, signal }) { + return callStreamingApi('POST /internal/observability_ai_assistant/chat', { + params: { + body: { + name, + messages, + connectorId, + functionCall, + functions: functions ?? [], }, - signal, - asResponse: true, - rawResponse: true, - }) - .then((_response) => { - const response = _response as unknown as HttpResponse; - - const subscription = toObservable(response) - .pipe( - map((line) => JSON.parse(line) as StreamingChatResponseEvent | BufferFlushEvent), - filter( - (line): line is StreamingChatResponseEvent => - line.type !== StreamingChatResponseEventType.BufferFlush && - line.type !== StreamingChatResponseEventType.TokenCount - ), - throwSerializedChatCompletionErrors() - ) - .subscribe(subscriber); - - // if the request is aborted, convert that into state as well - signal.addEventListener('abort', () => { - subscriber.error(new AbortError()); - subscription.unsubscribe(); - }); - }) - .catch(async (err) => { - if ('response' in err) { - const body = await (err.response as HttpResponse['response'])?.json(); - err.body = body; - if (body.message) { - err.message = body.message; - } - } - throw err; - }) - .catch((err) => { - subscriber.error(err); - }); - - return subscriber; + }, + signal, }).pipe( - // make sure the request is only triggered once, - // even with multiple subscribers - shareReplay() + filter( + (line): line is ChatCompletionChunkEvent => + line.type === StreamingChatResponseEventType.ChatCompletionChunk + ) ); }, complete({ @@ -208,6 +215,7 @@ export async function createChatService({ disableFunctions, signal, responseLanguage, + instructions, }) { return complete( { @@ -220,21 +228,13 @@ export async function createChatService({ signal, client, responseLanguage, + instructions, }, ({ params }) => { - return from( - apiClient('POST /internal/observability_ai_assistant/chat/complete', { - params, - signal, - asResponse: true, - rawResponse: true, - }) - ).pipe( - map((_response) => toObservable(_response as unknown as HttpResponse)), - switchMap((response$) => response$), - map((line) => JSON.parse(line) as StreamingChatResponseEvent | BufferFlushEvent), - shareReplay() - ); + return callStreamingApi('POST /internal/observability_ai_assistant/chat/complete', { + params, + signal, + }); } ); }, diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/storybook_mock.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant/public/storybook_mock.tsx index 6cad5a52ed2f8..d3b52f6803621 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/storybook_mock.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/storybook_mock.tsx @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { noop } from 'lodash'; import React from 'react'; import { Observable, of } from 'rxjs'; -import { MessageRole } from '.'; +import { ChatCompletionChunkEvent, MessageRole } from '.'; import type { StreamingChatResponseEventWithoutError } from '../common/conversation_complete'; import type { ObservabilityAIAssistantAPIClient } from './api'; import type { ObservabilityAIAssistantChatService, ObservabilityAIAssistantService } from './types'; @@ -16,7 +16,7 @@ import { buildFunctionElasticsearch, buildFunctionServiceSummary } from './utils export const createStorybookChatService = (): ObservabilityAIAssistantChatService => ({ sendAnalyticsEvent: () => {}, - chat: (options) => new Observable(), + chat: (options) => new Observable(), complete: (options) => new Observable(), getFunctions: () => [buildFunctionElasticsearch(), buildFunctionServiceSummary()], renderFunction: (name) => ( diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts index bfafbc4772462..8480af2e02327 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts @@ -9,6 +9,7 @@ import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public'; import type { Observable } from 'rxjs'; import type { + ChatCompletionChunkEvent, MessageAddEvent, StreamingChatResponseEventWithoutError, } from '../common/conversation_complete'; @@ -17,6 +18,7 @@ import type { Message, ObservabilityAIAssistantScreenContext, PendingMessage, + UserInstructionOrPlainText, } from '../common/types'; import type { TelemetryEventTypeWithPayload } from './analytics'; import type { ObservabilityAIAssistantAPIClient } from './api'; @@ -34,6 +36,13 @@ import { createScreenContextAction } from './utils/create_screen_context_action' export type { PendingMessage }; +export interface DiscoveredDataset { + title: string; + description: string; + indexPatterns: string[]; + columns: unknown[]; +} + export interface ObservabilityAIAssistantChatService { sendAnalyticsEvent: (event: TelemetryEventTypeWithPayload) => void; chat: ( @@ -41,19 +50,25 @@ export interface ObservabilityAIAssistantChatService { options: { messages: Message[]; connectorId: string; - function?: 'none' | 'auto'; + functions?: Array>; + functionCall?: string; signal: AbortSignal; } - ) => Observable; + ) => Observable; complete: (options: { getScreenContexts: () => ObservabilityAIAssistantScreenContext[]; conversationId?: string; connectorId: string; messages: Message[]; persist: boolean; - disableFunctions: boolean; + disableFunctions: + | boolean + | { + except: string[]; + }; signal: AbortSignal; - responseLanguage: string; + responseLanguage?: string; + instructions?: UserInstructionOrPlainText[]; }) => Observable; getFunctions: (options?: { contexts?: string[]; filter?: string }) => FunctionDefinition[]; hasFunction: (name: string) => boolean; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/utils/create_screen_context_action.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/utils/create_screen_context_action.ts index 3dbc4dbaf36f0..fcd6e8dd7bb80 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/utils/create_screen_context_action.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/utils/create_screen_context_action.ts @@ -18,11 +18,11 @@ type ReturnOf, - TResponse = ReturnOf + TRespondFunction extends ScreenContextActionRespondFunction> >( definition: TActionDefinition, - respond: ScreenContextActionRespondFunction -): ScreenContextActionDefinition { + respond: TRespondFunction +): ScreenContextActionDefinition> { return { ...definition, respond, diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/analytics/recall_ranking.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/analytics/recall_ranking.ts index 8a6f6a88d8538..ab6201780038a 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/analytics/recall_ranking.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/analytics/recall_ranking.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { RootSchema, EventTypeOpts } from '@kbn/analytics-client'; +import { RootSchema, EventTypeOpts } from '@kbn/core/public'; interface ScoredDocument { content: string; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/context.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/context.ts index baf006844c516..c7df07dc18d2f 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/context.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/context.ts @@ -5,24 +5,16 @@ * 2.0. */ -import { decodeOrThrow, jsonRt } from '@kbn/io-ts-utils'; -import { Logger } from '@kbn/logging'; import type { Serializable } from '@kbn/utility-types'; -import dedent from 'dedent'; import { encode } from 'gpt-tokenizer'; -import * as t from 'io-ts'; -import { compact, last, omit } from 'lodash'; -import { lastValueFrom, Observable } from 'rxjs'; +import { compact, last } from 'lodash'; +import { Observable } from 'rxjs'; import { FunctionRegistrationParameters } from '.'; import { MessageAddEvent } from '../../common/conversation_complete'; import { FunctionVisibility } from '../../common/functions/types'; -import { MessageRole, type Message } from '../../common/types'; -import { concatenateChatCompletionChunks } from '../../common/utils/concatenate_chat_completion_chunks'; +import { MessageRole } from '../../common/types'; import { createFunctionResponseMessage } from '../../common/utils/create_function_response_message'; -import { RecallRanking, RecallRankingEventType } from '../analytics/recall_ranking'; -import type { ObservabilityAIAssistantClient } from '../service/client'; -import { FunctionCallChatFunction } from '../service/types'; -import { parseSuggestionScores } from './parse_suggestion_scores'; +import { recallAndScore } from '../utils/recall/recall_and_score'; const MAX_TOKEN_COUNT_FOR_DATA_ON_SCREEN = 1000; @@ -70,55 +62,26 @@ export function registerContextFunction({ messages.filter((message) => message.message.role === MessageRole.User) ); - const userPrompt = userMessage?.message.content; - const queries = [{ text: userPrompt, boost: 3 }, { text: screenDescription }].filter( - ({ text }) => text - ) as Array<{ text: string; boost?: number }>; - - const suggestions = await retrieveSuggestions({ client, queries }); - if (suggestions.length === 0) { - return { content }; - } - - try { - const { relevantDocuments, scores } = await scoreSuggestions({ + const userPrompt = userMessage?.message.content!; + + const { scores, relevantDocuments, suggestions } = await recallAndScore({ + recall: client.recall, + chat, + logger: resources.logger, + userPrompt, + context: screenDescription, + messages, + signal, + analytics, + }); + + return { + content: { ...content, learnings: relevantDocuments as unknown as Serializable }, + data: { + scores, suggestions, - screenDescription, - userPrompt, - messages, - chat, - signal, - logger: resources.logger, - }); - - analytics.reportEvent(RecallRankingEventType, { - prompt: queries.map((query) => query.text).join('|'), - scoredDocuments: suggestions.map((suggestion) => { - const llmScore = scores.find((score) => score.id === suggestion.id); - return { - content: suggestion.text, - elserScore: suggestion.score ?? -1, - llmScore: llmScore ? llmScore.score : -1, - }; - }), - }); - - return { - content: { ...content, learnings: relevantDocuments as unknown as Serializable }, - data: { - scores, - suggestions, - }, - }; - } catch (error) { - return { - content: { ...content, learnings: suggestions.slice(0, 5) }, - data: { - error, - suggestions, - }, - }; - } + }, + }; } return new Observable((subscriber) => { @@ -141,146 +104,3 @@ export function registerContextFunction({ } ); } - -async function retrieveSuggestions({ - queries, - client, -}: { - queries: Array<{ text: string; boost?: number }>; - client: ObservabilityAIAssistantClient; -}) { - const recallResponse = await client.recall({ - queries, - }); - - return recallResponse.entries.map((entry) => omit(entry, 'labels', 'is_correction')); -} - -const scoreFunctionRequestRt = t.type({ - message: t.type({ - function_call: t.type({ - name: t.literal('score'), - arguments: t.string, - }), - }), -}); - -const scoreFunctionArgumentsRt = t.type({ - scores: t.string, -}); - -async function scoreSuggestions({ - suggestions, - messages, - userPrompt, - screenDescription, - chat, - signal, - logger, -}: { - suggestions: Awaited>; - messages: Message[]; - userPrompt: string | undefined; - screenDescription: string; - chat: FunctionCallChatFunction; - signal: AbortSignal; - logger: Logger; -}) { - const indexedSuggestions = suggestions.map((suggestion, index) => ({ - ...omit(suggestion, 'score'), // To not bias the LLM - id: index, - })); - - const newUserMessageContent = - dedent(`Given the following question, score the documents that are relevant to the question. on a scale from 0 to 7, - 0 being completely irrelevant, and 7 being extremely relevant. Information is relevant to the question if it helps in - answering the question. Judge it according to the following criteria: - - - The document is relevant to the question, and the rest of the conversation - - The document has information relevant to the question that is not mentioned, - or more detailed than what is available in the conversation - - The document has a high amount of information relevant to the question compared to other documents - - The document contains new information not mentioned before in the conversation - - Question: - ${userPrompt} - - Screen description: - ${screenDescription} - - Documents: - ${JSON.stringify(indexedSuggestions, null, 2)}`); - - const newUserMessage: Message = { - '@timestamp': new Date().toISOString(), - message: { - role: MessageRole.User, - content: newUserMessageContent, - }, - }; - - const scoreFunction = { - name: 'score', - description: - 'Use this function to score documents based on how relevant they are to the conversation.', - parameters: { - type: 'object', - properties: { - scores: { - description: `The document IDs and their scores, as CSV. Example: - - my_id,7 - my_other_id,3 - my_third_id,4 - `, - type: 'string', - }, - }, - required: ['score'], - } as const, - contexts: ['core'], - }; - - const response = await lastValueFrom( - chat('score_suggestions', { - messages: [...messages.slice(0, -2), newUserMessage], - functions: [scoreFunction], - functionCall: 'score', - signal, - }).pipe(concatenateChatCompletionChunks()) - ); - - const scoreFunctionRequest = decodeOrThrow(scoreFunctionRequestRt)(response); - const { scores: scoresAsString } = decodeOrThrow(jsonRt.pipe(scoreFunctionArgumentsRt))( - scoreFunctionRequest.message.function_call.arguments - ); - - const scores = parseSuggestionScores(scoresAsString).map(({ index, score }) => { - return { - id: suggestions[index].id, - score, - }; - }); - - if (scores.length === 0) { - // seemingly invalid or no scores, return all - return { relevantDocuments: suggestions, scores: [] }; - } - - const suggestionIds = suggestions.map((document) => document.id); - - const relevantDocumentIds = scores - .filter((document) => suggestionIds.includes(document.id)) // Remove hallucinated documents - .filter((document) => document.score > 4) - .sort((a, b) => b.score - a.score) - .slice(0, 5) - .map((document) => document.id); - - const relevantDocuments = suggestions.filter((suggestion) => - relevantDocumentIds.includes(suggestion.id) - ); - - logger.debug(`Relevant documents: ${JSON.stringify(relevantDocuments, null, 2)}`); - - return { relevantDocuments, scores }; -} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/get_dataset_info/get_relevant_field_names.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/get_dataset_info/get_relevant_field_names.ts index 2f32731ac3f2d..557f09784c7f9 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/get_dataset_info/get_relevant_field_names.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/get_dataset_info/get_relevant_field_names.ts @@ -9,7 +9,7 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/ import type { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server'; import { castArray, chunk, groupBy, uniq } from 'lodash'; import { lastValueFrom } from 'rxjs'; -import { MessageRole, type Message } from '../../../common'; +import { MessageRole, ShortIdTable, type Message } from '../../../common'; import { concatenateChatCompletionChunks } from '../../../common/utils/concatenate_chat_completion_chunks'; import { FunctionCallChatFunction } from '../../service/types'; @@ -87,8 +87,10 @@ export async function getRelevantFieldNames({ const groupedFields = groupBy(allFields, (field) => field.name); + const shortIdTable = new ShortIdTable(); + const relevantFields = await Promise.all( - chunk(fieldNames, 500).map(async (fieldsInChunk) => { + chunk(fieldNames, 250).map(async (fieldsInChunk) => { const chunkResponse$ = ( await chat('get_relevant_dataset_names', { signal, @@ -112,29 +114,31 @@ export async function getRelevantFieldNames({ role: MessageRole.User, content: `This is the list: - ${fieldsInChunk.join('\n')}`, + ${fieldsInChunk + .map((field) => JSON.stringify({ field, id: shortIdTable.take(field) })) + .join('\n')}`, }, }, ], functions: [ { - name: 'fields', - description: 'The fields you consider relevant to the conversation', + name: 'select_relevant_fields', + description: 'The IDs of the fields you consider relevant to the conversation', parameters: { type: 'object', properties: { - fields: { + fieldIds: { type: 'array', items: { type: 'string', }, }, }, - required: ['fields'], + required: ['fieldIds'], } as const, }, ], - functionCall: 'fields', + functionCall: 'select_relevant_fields', }) ).pipe(concatenateChatCompletionChunks()); @@ -143,10 +147,16 @@ export async function getRelevantFieldNames({ return chunkResponse.message?.function_call?.arguments ? ( JSON.parse(chunkResponse.message.function_call.arguments) as { - fields: string[]; + fieldIds: string[]; } - ).fields - .filter((field) => fieldsInChunk.includes(field)) + ).fieldIds + .map((fieldId) => { + const fieldName = shortIdTable.lookup(fieldId); + return fieldName ?? fieldId; + }) + .filter((fieldName) => { + return fieldsInChunk.includes(fieldName); + }) .map((field) => { const fieldDescriptors = groupedFields[field]; return `${field}:${fieldDescriptors.map((descriptor) => descriptor.type).join(',')}`; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/index.ts index 4cf8147d31c71..5b16b79bd9980 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/index.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/index.ts @@ -51,6 +51,9 @@ export const registerFunctions: RegistrationCallback = async ({ Note that ES|QL (the Elasticsearch Query Language which is a new piped language) is the preferred query language. + If you want to call a function or tool, only call it a single time per message. Wait until the function has been executed and its results + returned to you, before executing the same tool or another tool again if needed. + DO NOT UNDER ANY CIRCUMSTANCES USE ES|QL syntax (\`service.name == "foo"\`) with "kqlFilter" (\`service.name:"foo"\`). The user is able to change the language which they want you to reply in on the settings page of the AI Assistant for Observability, which can be found in the ${ @@ -63,7 +66,10 @@ export const registerFunctions: RegistrationCallback = async ({ functions.registerInstruction(({ availableFunctionNames }) => { const instructions: string[] = []; - if (availableFunctionNames.includes(GET_DATASET_INFO_FUNCTION_NAME)) { + if ( + availableFunctionNames.includes(QUERY_FUNCTION_NAME) && + availableFunctionNames.includes(GET_DATASET_INFO_FUNCTION_NAME) + ) { instructions.push(`You MUST use the "${GET_DATASET_INFO_FUNCTION_NAME}" ${ functions.hasFunction('get_apm_dataset_info') ? 'or the get_apm_dataset_info' : '' } function before calling the "${QUERY_FUNCTION_NAME}" or the "changes" functions. diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts index 41d3a6eaea5ce..f1758c1583f71 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts @@ -6,20 +6,22 @@ */ import { notImplemented } from '@hapi/boom'; import { toBooleanRt } from '@kbn/io-ts-utils'; +import { context as otelContext } from '@opentelemetry/api'; import * as t from 'io-ts'; +import { from, map } from 'rxjs'; import { Readable } from 'stream'; -import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; -import { KibanaRequest } from '@kbn/core/server'; -import { context as otelContext } from '@opentelemetry/api'; import { aiAssistantSimulatedFunctionCalling } from '../..'; +import { createFunctionResponseMessage } from '../../../common/utils/create_function_response_message'; +import { withoutTokenCountEvents } from '../../../common/utils/without_token_count_events'; +import { LangTracer } from '../../service/client/instrumentation/lang_tracer'; import { flushBuffer } from '../../service/util/flush_buffer'; import { observableIntoOpenAIStream } from '../../service/util/observable_into_openai_stream'; import { observableIntoStream } from '../../service/util/observable_into_stream'; +import { withAssistantSpan } from '../../service/util/with_assistant_span'; +import { recallAndScore } from '../../utils/recall/recall_and_score'; import { createObservabilityAIAssistantServerRoute } from '../create_observability_ai_assistant_server_route'; -import { screenContextRt, messageRt, functionRt } from '../runtime_types'; +import { functionRt, messageRt, screenContextRt } from '../runtime_types'; import { ObservabilityAIAssistantRouteHandlerResources } from '../types'; -import { withAssistantSpan } from '../../service/util/with_assistant_span'; -import { LangTracer } from '../../service/client/instrumentation/lang_tracer'; const chatCompleteBaseRt = t.type({ body: t.intersection([ @@ -32,14 +34,24 @@ const chatCompleteBaseRt = t.type({ conversationId: t.string, title: t.string, responseLanguage: t.string, - disableFunctions: toBooleanRt, + disableFunctions: t.union([ + toBooleanRt, + t.type({ + except: t.array(t.string), + }), + ]), instructions: t.array( t.union([ t.string, - t.type({ - doc_id: t.string, - text: t.string, - }), + t.intersection([ + t.type({ + doc_id: t.string, + text: t.string, + }), + t.partial({ + system: t.boolean, + }), + ]), ]) ), }), @@ -67,17 +79,17 @@ const chatCompletePublicRt = t.intersection([ }), ]); -async function guardAgainstInvalidConnector({ - actions, +async function initializeChatRequest({ + context, request, - connectorId, -}: { - actions: ActionsPluginStart; - request: KibanaRequest; - connectorId: string; -}) { - return withAssistantSpan('guard_against_invalid_connector', async () => { - const actionsClient = await actions.getActionsClientWithRequest(request); + plugins: { cloud, actions }, + params: { + body: { connectorId }, + }, + service, +}: ObservabilityAIAssistantRouteHandlerResources & { params: { body: { connectorId: string } } }) { + await withAssistantSpan('guard_against_invalid_connector', async () => { + const actionsClient = await (await actions.start()).getActionsClientWithRequest(request); const connector = await actionsClient.get({ id: connectorId, @@ -86,6 +98,29 @@ async function guardAgainstInvalidConnector({ return connector; }); + + const [client, cloudStart, simulateFunctionCalling] = await Promise.all([ + service.getClient({ request }), + cloud?.start(), + (await context.core).uiSettings.client.get(aiAssistantSimulatedFunctionCalling), + ]); + + if (!client) { + throw notImplemented(); + } + + const controller = new AbortController(); + + request.events.aborted$.subscribe(() => { + controller.abort(); + }); + + return { + client, + isCloudEnabled: Boolean(cloudStart?.isCloudEnabled), + simulateFunctionCalling, + signal: controller.signal, + }; } const chatRoute = createObservabilityAIAssistantServerRoute({ @@ -107,38 +142,20 @@ const chatRoute = createObservabilityAIAssistantServerRoute({ ]), }), handler: async (resources): Promise => { - const { request, params, service, context, plugins } = resources; + const { params } = resources; const { body: { name, messages, connectorId, functions, functionCall }, } = params; - await guardAgainstInvalidConnector({ - actions: await plugins.actions.start(), - request, - connectorId, - }); - - const [client, cloudStart, simulateFunctionCalling] = await Promise.all([ - service.getClient({ request }), - resources.plugins.cloud?.start(), - (await context.core).uiSettings.client.get(aiAssistantSimulatedFunctionCalling), - ]); - - if (!client) { - throw notImplemented(); - } - - const controller = new AbortController(); - - request.events.aborted$.subscribe(() => { - controller.abort(); - }); + const { client, simulateFunctionCalling, signal, isCloudEnabled } = await initializeChatRequest( + resources + ); const response$ = client.chat(name, { messages, connectorId, - signal: controller.signal, + signal, ...(functions.length ? { functions, @@ -149,7 +166,65 @@ const chatRoute = createObservabilityAIAssistantServerRoute({ tracer: new LangTracer(otelContext.active()), }); - return observableIntoStream(response$.pipe(flushBuffer(!!cloudStart?.isCloudEnabled))); + return observableIntoStream(response$.pipe(flushBuffer(isCloudEnabled))); + }, +}); + +const chatRecallRoute = createObservabilityAIAssistantServerRoute({ + endpoint: 'POST /internal/observability_ai_assistant/chat/recall', + options: { + tags: ['access:ai_assistant'], + }, + params: t.type({ + body: t.type({ + prompt: t.string, + context: t.string, + connectorId: t.string, + }), + }), + handler: async (resources): Promise => { + const { client, simulateFunctionCalling, signal, isCloudEnabled } = await initializeChatRequest( + resources + ); + + const { connectorId, prompt, context } = resources.params.body; + + const response$ = from( + recallAndScore({ + analytics: (await resources.context.core).coreStart.analytics, + chat: (name, params) => + client + .chat(name, { + ...params, + connectorId, + simulateFunctionCalling, + signal, + tracer: new LangTracer(otelContext.active()), + }) + .pipe(withoutTokenCountEvents()), + context, + logger: resources.logger, + messages: [], + userPrompt: prompt, + recall: client.recall, + signal, + }) + ).pipe( + map(({ scores, suggestions, relevantDocuments }) => { + return createFunctionResponseMessage({ + name: 'context', + data: { + suggestions, + scores, + }, + content: { + relevantDocuments, + }, + }); + }) + ); + + return observableIntoStream(response$.pipe(flushBuffer(isCloudEnabled))); }, }); @@ -158,7 +233,7 @@ async function chatComplete( params: t.TypeOf; } ) { - const { request, params, service, plugins } = resources; + const { params, service } = resources; const { body: { @@ -174,32 +249,12 @@ async function chatComplete( }, } = params; - await guardAgainstInvalidConnector({ - actions: await plugins.actions.start(), - request, - connectorId, - }); - - const [client, cloudStart, simulateFunctionCalling] = await Promise.all([ - service.getClient({ request }), - resources.plugins.cloud?.start() || Promise.resolve(undefined), - ( - await resources.context.core - ).uiSettings.client.get(aiAssistantSimulatedFunctionCalling), - ]); - - if (!client) { - throw notImplemented(); - } - - const controller = new AbortController(); - - request.events.aborted$.subscribe(() => { - controller.abort(); - }); + const { client, isCloudEnabled, signal, simulateFunctionCalling } = await initializeChatRequest( + resources + ); const functionClient = await service.getFunctionClient({ - signal: controller.signal, + signal, resources, client, screenContexts, @@ -211,7 +266,7 @@ async function chatComplete( conversationId, title, persist, - signal: controller.signal, + signal, functionClient, responseLanguage, instructions, @@ -219,7 +274,7 @@ async function chatComplete( disableFunctions, }); - return response$.pipe(flushBuffer(!!cloudStart?.isCloudEnabled)); + return response$.pipe(flushBuffer(isCloudEnabled)); } const chatCompleteRoute = createObservabilityAIAssistantServerRoute({ @@ -271,6 +326,7 @@ const publicChatCompleteRoute = createObservabilityAIAssistantServerRoute({ export const chatRoutes = { ...chatRoute, + ...chatRecallRoute, ...chatCompleteRoute, ...publicChatCompleteRoute, }; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/adapters/process_openai_stream.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/adapters/process_openai_stream.ts index 908042770ea2d..59dbd24451c09 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/adapters/process_openai_stream.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/adapters/process_openai_stream.ts @@ -5,7 +5,7 @@ * 2.0. */ import { encode } from 'gpt-tokenizer'; -import { first, sum } from 'lodash'; +import { first, memoize, sum } from 'lodash'; import OpenAI from 'openai'; import { filter, map, Observable, tap } from 'rxjs'; import { v4 } from 'uuid'; @@ -51,6 +51,14 @@ export function processOpenAiStream({ }); } + const warnForToolCall = memoize( + (toolCall: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta.ToolCall) => { + logger.warn(`More tools than 1 were called: ${JSON.stringify(toolCall)}`); + }, + (toolCall: OpenAI.Chat.Completions.ChatCompletionChunk.Choice.Delta.ToolCall) => + toolCall.index + ); + const parsed$ = source.pipe( filter((line) => !!line && line !== '[DONE]'), map( @@ -76,7 +84,16 @@ export function processOpenAiStream({ firstChoice?.delta.content, firstChoice?.delta.function_call?.name, firstChoice?.delta.function_call?.arguments, - ].map((val) => encode(val || '').length) || 0 + ...(firstChoice?.delta.tool_calls?.flatMap((toolCall) => { + return [ + toolCall.function?.name, + toolCall.function?.arguments, + toolCall.id, + toolCall.index, + toolCall.type, + ]; + }) ?? []), + ].map((val) => encode(val?.toString() ?? '').length) || 0 ); }), filter( @@ -85,8 +102,17 @@ export function processOpenAiStream({ ), map((chunk): ChatCompletionChunkEvent => { const delta = chunk.choices[0].delta; - if (delta.tool_calls && delta.tool_calls.length > 1) { - logger.warn(`More tools than 1 were called: ${JSON.stringify(delta.tool_calls)}`); + if (delta.tool_calls && (delta.tool_calls.length > 1 || delta.tool_calls[0].index > 0)) { + delta.tool_calls.forEach((toolCall) => { + warnForToolCall(toolCall); + }); + return { + id, + type: StreamingChatResponseEventType.ChatCompletionChunk, + message: { + content: delta.content ?? '', + }, + }; } const functionCall: Omit | undefined = diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.test.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.test.ts index 0349d597b7ba0..46d72d303f7e3 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.test.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.test.ts @@ -27,6 +27,7 @@ import { createFunctionResponseMessage } from '../../../common/utils/create_func import { CONTEXT_FUNCTION_NAME } from '../../functions/context'; import { ChatFunctionClient } from '../chat_function_client'; import type { KnowledgeBaseService } from '../knowledge_base_service'; +import { USER_INSTRUCTIONS_HEADER } from '../util/get_system_message_from_instructions'; import { observableIntoStream } from '../util/observable_into_stream'; import { CreateChatCompletionResponseChunk } from './adapters/process_openai_stream'; @@ -34,7 +35,7 @@ type ChunkDelta = CreateChatCompletionResponseChunk['choices'][number]['delta']; type LlmSimulator = ReturnType; -const EXPECTED_STORED_SYSTEM_MESSAGE = `system\n\nWhat follows is a set of instructions provided by the user, please abide by them as long as they don't conflict with anything you've been told so far:\n\nYou MUST respond in the users preferred language which is: English.`; +const EXPECTED_STORED_SYSTEM_MESSAGE = `system\n\n${USER_INSTRUCTIONS_HEADER}\n\nYou MUST respond in the users preferred language which is: English.`; const nextTick = () => { return new Promise(process.nextTick); @@ -368,8 +369,8 @@ describe('Observability AI Assistant client', () => { last_updated: expect.any(String), token_count: { completion: 1, - prompt: 78, - total: 79, + prompt: 84, + total: 85, }, }, type: StreamingChatResponseEventType.ConversationCreate, @@ -425,8 +426,8 @@ describe('Observability AI Assistant client', () => { last_updated: expect.any(String), token_count: { completion: 6, - prompt: 262, - total: 268, + prompt: 268, + total: 274, }, }, type: StreamingChatResponseEventType.ConversationCreate, @@ -443,8 +444,8 @@ describe('Observability AI Assistant client', () => { title: 'An auto-generated title', token_count: { completion: 6, - prompt: 262, - total: 268, + prompt: 268, + total: 274, }, }, labels: {}, @@ -574,8 +575,8 @@ describe('Observability AI Assistant client', () => { last_updated: expect.any(String), token_count: { completion: 2, - prompt: 156, - total: 158, + prompt: 162, + total: 164, }, }, type: StreamingChatResponseEventType.ConversationUpdate, @@ -593,8 +594,8 @@ describe('Observability AI Assistant client', () => { title: 'My stored conversation', token_count: { completion: 2, - prompt: 156, - total: 158, + prompt: 162, + total: 164, }, }, labels: {}, diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.ts index 9739a59125011..dacd52648a6b8 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/index.ts @@ -45,7 +45,7 @@ import { } from '../../../common/conversation_complete'; import { CompatibleJSONSchema } from '../../../common/functions/types'; import { - UserInstruction, + UserInstructionOrPlainText, type Conversation, type ConversationCreateRequest, type ConversationUpdateRequest, @@ -170,9 +170,13 @@ export class ObservabilityAIAssistantClient { title?: string; isPublic?: boolean; kibanaPublicUrl?: string; - instructions?: Array; + instructions?: UserInstructionOrPlainText[]; simulateFunctionCalling?: boolean; - disableFunctions?: boolean; + disableFunctions?: + | boolean + | { + except: string[]; + }; }): Observable> => { return new LangTracer(context.active()).startActiveSpan( 'complete', diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/operators/continue_conversation.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/operators/continue_conversation.ts index 2ab26cb4799ae..83d9bf37e7efb 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/operators/continue_conversation.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/operators/continue_conversation.ts @@ -133,13 +133,17 @@ function getFunctionDefinitions({ }: { functionClient: ChatFunctionClient; functionLimitExceeded: boolean; - disableFunctions: boolean; + disableFunctions: + | boolean + | { + except: string[]; + }; }) { - if (functionLimitExceeded || disableFunctions) { + if (functionLimitExceeded || disableFunctions === true) { return []; } - const systemFunctions = functionClient + let systemFunctions = functionClient .getFunctions() .map((fn) => fn.definition) .filter( @@ -148,6 +152,10 @@ function getFunctionDefinitions({ [FunctionVisibility.AssistantOnly, FunctionVisibility.All].includes(def.visibility) ); + if (typeof disableFunctions === 'object') { + systemFunctions = systemFunctions.filter((fn) => disableFunctions.except.includes(fn.name)); + } + const actions = functionClient.getActions(); const allDefinitions = systemFunctions @@ -177,7 +185,11 @@ export function continueConversation({ requestInstructions: Array; userInstructions: UserInstruction[]; logger: Logger; - disableFunctions: boolean; + disableFunctions: + | boolean + | { + except: string[]; + }; tracer: LangTracer; }): Observable { let nextFunctionCallsLeft = functionCallsLeft; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/knowledge_base_service/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/knowledge_base_service/index.ts index 7c504aa43c38c..67cf8bcd000a9 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/knowledge_base_service/index.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/knowledge_base_service/index.ts @@ -309,7 +309,7 @@ export class KnowledgeBaseService { user?: { name: string }; modelId: string; }): Promise { - const query = { + const esQuery = { bool: { should: queries.map(({ text, boost = 1 }) => ({ text_expansion: { @@ -334,7 +334,7 @@ export class KnowledgeBaseService { Pick >({ index: [this.dependencies.resources.aliases.kb], - query, + query: esQuery, size: 20, _source: { includes: ['text', 'is_correction', 'labels'], @@ -481,7 +481,9 @@ export class KnowledgeBaseService { }): Promise<{ entries: RecalledEntry[]; }> => { - this.dependencies.logger.debug(`Recalling entries from KB for queries: "${queries}"`); + this.dependencies.logger.debug( + `Recalling entries from KB for queries: "${JSON.stringify(queries)}"` + ); const modelId = await this.dependencies.getModelId(); const [documentsFromKb, documentsFromConnectors] = await Promise.all([ diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/util/get_system_message_from_instructions.test.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/util/get_system_message_from_instructions.test.ts index 99a2c34bc33d7..93594fc520998 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/util/get_system_message_from_instructions.test.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/util/get_system_message_from_instructions.test.ts @@ -4,7 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { getSystemMessageFromInstructions } from './get_system_message_from_instructions'; +import { + getSystemMessageFromInstructions, + USER_INSTRUCTIONS_HEADER, +} from './get_system_message_from_instructions'; describe('getSystemMessageFromInstructions', () => { it('handles plain instructions', () => { @@ -42,9 +45,7 @@ describe('getSystemMessageFromInstructions', () => { requestInstructions: [{ doc_id: 'second', text: 'second_request' }], availableFunctionNames: [], }) - ).toEqual( - `first\n\nWhat follows is a set of instructions provided by the user, please abide by them as long as they don't conflict with anything you've been told so far:\n\nsecond_request` - ); + ).toEqual(`first\n\n${USER_INSTRUCTIONS_HEADER}\n\nsecond_request`); }); it('includes kb instructions if there is no request instruction', () => { @@ -55,9 +56,7 @@ describe('getSystemMessageFromInstructions', () => { requestInstructions: [], availableFunctionNames: [], }) - ).toEqual( - `first\n\nWhat follows is a set of instructions provided by the user, please abide by them as long as they don't conflict with anything you've been told so far:\n\nsecond_kb` - ); + ).toEqual(`first\n\n${USER_INSTRUCTIONS_HEADER}\n\nsecond_kb`); }); it('handles undefined values', () => { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/util/get_system_message_from_instructions.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/util/get_system_message_from_instructions.ts index ece79b9f78485..759ff07125b95 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/util/get_system_message_from_instructions.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/util/get_system_message_from_instructions.ts @@ -5,12 +5,19 @@ * 2.0. */ -import { compact } from 'lodash'; +import { compact, partition } from 'lodash'; import { v4 } from 'uuid'; -import { UserInstruction } from '../../../common/types'; +import { UserInstruction, UserInstructionOrPlainText } from '../../../common/types'; import { withTokenBudget } from '../../../common/utils/with_token_budget'; import { RegisteredInstruction } from '../types'; +export const USER_INSTRUCTIONS_HEADER = `## User instructions + +What follows is a set of instructions provided by the user, please abide by them +as long as they don't conflict with anything you've been told so far: + +`; + export function getSystemMessageFromInstructions({ registeredInstructions, userInstructions, @@ -19,7 +26,7 @@ export function getSystemMessageFromInstructions({ }: { registeredInstructions: RegisteredInstruction[]; userInstructions: UserInstruction[]; - requestInstructions: Array; + requestInstructions: UserInstructionOrPlainText[]; availableFunctionNames: string[]; }): string { const allRegisteredInstructions = compact( @@ -32,10 +39,17 @@ export function getSystemMessageFromInstructions({ ); const requestInstructionsWithId = requestInstructions.map((instruction) => - typeof instruction === 'string' ? { doc_id: v4(), text: instruction } : instruction + typeof instruction === 'string' + ? { doc_id: v4(), text: instruction, system: false } + : instruction + ); + + const [requestSystemInstructions, requestUserInstructionsWithId] = partition( + requestInstructionsWithId, + (instruction) => instruction.system === true ); - const requestOverrideIds = requestInstructionsWithId.map((instruction) => instruction.doc_id); + const requestOverrideIds = requestUserInstructionsWithId.map((instruction) => instruction.doc_id); // all request instructions, and those from the KB that are not defined as a request instruction const allUserInstructions = requestInstructionsWithId.concat( @@ -45,12 +59,9 @@ export function getSystemMessageFromInstructions({ const instructionsWithinBudget = withTokenBudget(allUserInstructions, 1000); return [ - ...allRegisteredInstructions, + ...allRegisteredInstructions.concat(requestSystemInstructions), ...(instructionsWithinBudget.length - ? [ - `What follows is a set of instructions provided by the user, please abide by them as long as they don't conflict with anything you've been told so far:`, - ...instructionsWithinBudget, - ] + ? [USER_INSTRUCTIONS_HEADER, ...instructionsWithinBudget] : []), ] .map((instruction) => { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/parse_suggestion_scores.test.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/parse_suggestion_scores.test.ts similarity index 71% rename from x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/parse_suggestion_scores.test.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/parse_suggestion_scores.test.ts index 7b62cf21af65b..abeeda3c37657 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/parse_suggestion_scores.test.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/parse_suggestion_scores.test.ts @@ -12,56 +12,56 @@ describe('parseSuggestionScores', () => { expect( parseSuggestionScores( dedent( - `0,1 - 2,7 - 3,10` + `my-id,1 + my-other-id,7 + my-another-id,10` ) ) ).toEqual([ { - index: 0, + id: 'my-id', score: 1, }, { - index: 2, + id: 'my-other-id', score: 7, }, { - index: 3, + id: 'my-another-id', score: 10, }, ]); }); it('parses semi-colons as separators', () => { - expect(parseSuggestionScores(`0,1;2,7;3,10`)).toEqual([ + expect(parseSuggestionScores(`idone,1;idtwo,7;idthree,10`)).toEqual([ { - index: 0, + id: 'idone', score: 1, }, { - index: 2, + id: 'idtwo', score: 7, }, { - index: 3, + id: 'idthree', score: 10, }, ]); }); it('parses spaces as separators', () => { - expect(parseSuggestionScores(`0,1 2,7 3,10`)).toEqual([ + expect(parseSuggestionScores(`a,1 b,7 c,10`)).toEqual([ { - index: 0, + id: 'a', score: 1, }, { - index: 2, + id: 'b', score: 7, }, { - index: 3, + id: 'c', score: 10, }, ]); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/parse_suggestion_scores.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/parse_suggestion_scores.ts similarity index 77% rename from x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/parse_suggestion_scores.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/parse_suggestion_scores.ts index 9fa39bf1233b5..464504bed85a8 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/functions/parse_suggestion_scores.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/parse_suggestion_scores.ts @@ -8,15 +8,15 @@ export function parseSuggestionScores(scoresAsString: string) { // make sure that spaces, semi-colons etc work as separators as well const scores = scoresAsString - .replace(/[^0-9,]/g, ' ') + .replace(/[^0-9a-zA-Z\-_,]/g, ' ') .trim() .split(/\s+/) .map((pair) => { - const [index, score] = pair.split(',').map((str) => parseInt(str, 10)); + const [id, score] = pair.split(',').map((str) => str.trim()); return { - index, - score, + id, + score: parseInt(score, 10), }; }); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/recall_and_score.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/recall_and_score.ts new file mode 100644 index 0000000000000..8885ff7e1d7a2 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/recall_and_score.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from '@kbn/logging'; +import { AnalyticsServiceStart } from '@kbn/core/server'; +import type { Message } from '../../../common'; +import type { ObservabilityAIAssistantClient } from '../../service/client'; +import type { FunctionCallChatFunction } from '../../service/types'; +import { retrieveSuggestions } from './retrieve_suggestions'; +import { scoreSuggestions } from './score_suggestions'; +import type { RetrievedSuggestion } from './types'; +import { RecallRanking, RecallRankingEventType } from '../../analytics/recall_ranking'; + +export async function recallAndScore({ + recall, + chat, + analytics, + userPrompt, + context, + messages, + logger, + signal, +}: { + recall: ObservabilityAIAssistantClient['recall']; + chat: FunctionCallChatFunction; + analytics: AnalyticsServiceStart; + userPrompt: string; + context: string; + messages: Message[]; + logger: Logger; + signal: AbortSignal; +}): Promise<{ + relevantDocuments?: RetrievedSuggestion[]; + scores?: Array<{ id: string; score: number }>; + suggestions: RetrievedSuggestion[]; +}> { + const queries = [ + { text: userPrompt, boost: 3 }, + { text: context, boost: 1 }, + ].filter((query) => query.text.trim()); + + const suggestions = await retrieveSuggestions({ + recall, + queries, + }); + + if (!suggestions.length) { + return { + relevantDocuments: [], + scores: [], + suggestions: [], + }; + } + + try { + const { scores, relevantDocuments } = await scoreSuggestions({ + suggestions, + logger, + messages, + userPrompt, + context, + signal, + chat, + }); + + analytics.reportEvent(RecallRankingEventType, { + prompt: queries.map((query) => query.text).join('\n\n'), + scoredDocuments: suggestions.map((suggestion) => { + const llmScore = scores.find((score) => score.id === suggestion.id); + return { + content: suggestion.text, + elserScore: suggestion.score ?? -1, + llmScore: llmScore ? llmScore.score : -1, + }; + }), + }); + + return { scores, relevantDocuments, suggestions }; + } catch (error) { + logger.error(`Error scoring documents: ${error.message}`, { error }); + return { + suggestions: suggestions.slice(0, 5), + }; + } +} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/retrieve_suggestions.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/retrieve_suggestions.ts new file mode 100644 index 0000000000000..3c680229cd5d2 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/retrieve_suggestions.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 { omit } from 'lodash'; +import { ObservabilityAIAssistantClient } from '../../service/client'; +import { RetrievedSuggestion } from './types'; + +export async function retrieveSuggestions({ + queries, + recall, +}: { + queries: Array<{ text: string; boost?: number }>; + recall: ObservabilityAIAssistantClient['recall']; +}): Promise { + const recallResponse = await recall({ + queries, + }); + + return recallResponse.entries.map((entry) => omit(entry, 'labels', 'is_correction')); +} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/score_suggestions.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/score_suggestions.ts new file mode 100644 index 0000000000000..b6a16d6329aec --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/score_suggestions.ts @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import * as t from 'io-ts'; +import { omit } from 'lodash'; +import { Logger } from '@kbn/logging'; +import dedent from 'dedent'; +import { lastValueFrom } from 'rxjs'; +import { decodeOrThrow, jsonRt } from '@kbn/io-ts-utils'; +import { concatenateChatCompletionChunks, Message, MessageRole } from '../../../common'; +import type { FunctionCallChatFunction } from '../../service/types'; +import type { RetrievedSuggestion } from './types'; +import { parseSuggestionScores } from './parse_suggestion_scores'; +import { ShortIdTable } from '../../../common/utils/short_id_table'; + +const scoreFunctionRequestRt = t.type({ + message: t.type({ + function_call: t.type({ + name: t.literal('score'), + arguments: t.string, + }), + }), +}); + +const scoreFunctionArgumentsRt = t.type({ + scores: t.string, +}); + +export async function scoreSuggestions({ + suggestions, + messages, + userPrompt, + context, + chat, + signal, + logger, +}: { + suggestions: RetrievedSuggestion[]; + messages: Message[]; + userPrompt: string; + context: string; + chat: FunctionCallChatFunction; + signal: AbortSignal; + logger: Logger; +}): Promise<{ + relevantDocuments: RetrievedSuggestion[]; + scores: Array<{ id: string; score: number }>; +}> { + const shortIdTable = new ShortIdTable(); + + const suggestionsWithShortId = suggestions.map((suggestion) => ({ + ...omit(suggestion, 'score', 'id'), // To not bias the LLM + originalId: suggestion.id, + shortId: shortIdTable.take(suggestion.id), + })); + + const newUserMessageContent = + dedent(`Given the following question, score the documents that are relevant to the question. on a scale from 0 to 7, + 0 being completely irrelevant, and 7 being extremely relevant. Information is relevant to the question if it helps in + answering the question. Judge it according to the following criteria: + + - The document is relevant to the question, and the rest of the conversation + - The document has information relevant to the question that is not mentioned, + or more detailed than what is available in the conversation + - The document has a high amount of information relevant to the question compared to other documents + - The document contains new information not mentioned before in the conversation + + User prompt: + ${userPrompt} + + Context: + ${context} + + Documents: + ${JSON.stringify( + suggestionsWithShortId.map((suggestion) => ({ + id: suggestion.shortId, + content: suggestion.text, + })), + null, + 2 + )}`); + + const newUserMessage: Message = { + '@timestamp': new Date().toISOString(), + message: { + role: MessageRole.User, + content: newUserMessageContent, + }, + }; + + const scoreFunction = { + name: 'score', + description: + 'Use this function to score documents based on how relevant they are to the conversation.', + parameters: { + type: 'object', + properties: { + scores: { + description: `The document IDs and their scores, as CSV. Example: + + my_id,7 + my_other_id,3 + my_third_id,4 + `, + type: 'string', + }, + }, + required: ['score'], + } as const, + }; + + const response = await lastValueFrom( + chat('score_suggestions', { + messages: [...messages.slice(0, -2), newUserMessage], + functions: [scoreFunction], + functionCall: 'score', + signal, + }).pipe(concatenateChatCompletionChunks()) + ); + + const scoreFunctionRequest = decodeOrThrow(scoreFunctionRequestRt)(response); + const { scores: scoresAsString } = decodeOrThrow(jsonRt.pipe(scoreFunctionArgumentsRt))( + scoreFunctionRequest.message.function_call.arguments + ); + + const scores = parseSuggestionScores(scoresAsString).map(({ id, score }) => { + const originalSuggestion = suggestionsWithShortId.find( + (suggestion) => suggestion.shortId === id + ); + return { + originalId: originalSuggestion?.originalId, + score, + }; + }); + + if (scores.length === 0) { + // seemingly invalid or no scores, return all + return { relevantDocuments: suggestions, scores: [] }; + } + + const suggestionIds = suggestions.map((document) => document.id); + + const relevantDocumentIds = scores + .filter((document) => suggestionIds.includes(document.originalId ?? '')) // Remove hallucinated documents + .filter((document) => document.score > 4) + .sort((a, b) => b.score - a.score) + .slice(0, 5) + .map((document) => document.originalId); + + const relevantDocuments = suggestions.filter((suggestion) => + relevantDocumentIds.includes(suggestion.id) + ); + + logger.debug(`Relevant documents: ${JSON.stringify(relevantDocuments, null, 2)}`); + + return { + relevantDocuments, + scores: scores.map((score) => ({ id: score.originalId!, score: score.score })), + }; +} diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/delete_signals_migration/delete_signals_migration_route.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/types.ts similarity index 61% rename from x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/delete_signals_migration/delete_signals_migration_route.ts rename to x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/types.ts index 837f34f3ec14f..3774df64c1ee1 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/delete_signals_migration/delete_signals_migration_route.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/utils/recall/types.ts @@ -5,10 +5,6 @@ * 2.0. */ -import * as t from 'io-ts'; +import type { RecalledEntry } from '../../service/knowledge_base_service'; -export const deleteSignalsMigrationSchema = t.exact( - t.type({ - migration_ids: t.array(t.string), - }) -); +export type RetrievedSuggestion = Omit; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/tsconfig.json b/x-pack/plugins/observability_solution/observability_ai_assistant/tsconfig.json index 0db746a4ab80c..aa26acbb6154a 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/tsconfig.json @@ -15,7 +15,6 @@ "kbn_references": [ "@kbn/i18n", "@kbn/core-analytics-browser", - "@kbn/analytics-client", "@kbn/logging", "@kbn/core", "@kbn/server-route-repository", diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.test.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.test.tsx index 836b8f6ef7f93..620fbbc2ab166 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.test.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import type { DatatableColumn } from '@kbn/expressions-plugin/common'; import type { LensPublicStart } from '@kbn/lens-plugin/public'; @@ -142,7 +142,8 @@ describe('VisualizeESQL', () => { }), }; renderComponent({}, lensService, undefined, ['There is an error mate']); - await waitFor(() => expect(screen.findByTestId('observabilityAiAssistantErrorsList'))); + + expect(await screen.findByTestId('observabilityAiAssistantErrorsList')).toBeInTheDocument(); }); it('should not display the table on first render', async () => { @@ -153,15 +154,16 @@ describe('VisualizeESQL', () => { suggestions: jest.fn(), }), }; + renderComponent({}, lensService); - // the button to render a table should be present - await waitFor(() => - expect(screen.findByTestId('observabilityAiAssistantLensESQLDisplayTableButton')) - ); - await waitFor(() => - expect(screen.queryByTestId('observabilityAiAssistantESQLDataGrid')).not.toBeInTheDocument() - ); + expect( + await screen.findByTestId('observabilityAiAssistantLensESQLDisplayTableButton') + ).toBeInTheDocument(); + + expect( + await screen.queryByTestId('observabilityAiAssistantESQLDataGrid') + ).not.toBeInTheDocument(); }); it('should display the table when user clicks the table button', async () => { @@ -172,11 +174,16 @@ describe('VisualizeESQL', () => { suggestions: jest.fn(), }), }; + renderComponent({}, lensService); - await waitFor(() => { - userEvent.click(screen.getByTestId('observabilityAiAssistantLensESQLDisplayTableButton')); - expect(screen.findByTestId('observabilityAiAssistantESQLDataGrid')); + + await act(async () => { + userEvent.click( + await screen.findByTestId('observabilityAiAssistantLensESQLDisplayTableButton') + ); }); + + expect(await screen.findByTestId('observabilityAiAssistantESQLDataGrid')).toBeInTheDocument(); }); it('should render the ESQLDataGrid if Lens returns a table', async () => { @@ -195,8 +202,6 @@ describe('VisualizeESQL', () => { }, lensService ); - await waitFor(() => { - expect(screen.findByTestId('observabilityAiAssistantESQLDataGrid')); - }); + expect(await screen.findByTestId('observabilityAiAssistantESQLDataGrid')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx index 97c46a6a2d759..9442a73751628 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/functions/visualize_esql.tsx @@ -124,12 +124,7 @@ export function VisualizeESQL({ }, [lens]); const dataViewAsync = useAsync(() => { - return getESQLAdHocDataview(indexPattern, dataViews).then((dataView) => { - if (dataView.fields.getByName('@timestamp')?.type === 'date') { - dataView.timeFieldName = '@timestamp'; - } - return dataView; - }); + return getESQLAdHocDataview(indexPattern, dataViews); }, [indexPattern, dataViews]); const chatFlyoutSecondSlotHandler = useContext(ObservabilityAIAssistantMultipaneFlyoutContext); diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/common/telemetry_events.ts b/x-pack/plugins/observability_solution/observability_logs_explorer/common/telemetry_events.ts index 3d8b6ce58f3f3..8776d2fd44dbb 100644 --- a/x-pack/plugins/observability_solution/observability_logs_explorer/common/telemetry_events.ts +++ b/x-pack/plugins/observability_solution/observability_logs_explorer/common/telemetry_events.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { type EventTypeOpts } from '@kbn/analytics-client'; +import { type EventTypeOpts } from '@kbn/ebt/client'; export const DATA_RECEIVED_TELEMETRY_EVENT: EventTypeOpts<{ rowCount: number; diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json b/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json index a3b20757c0096..446c237e257eb 100644 --- a/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json @@ -47,10 +47,10 @@ "@kbn/shared-ux-prompt-not-found", "@kbn/slo-plugin", "@kbn/es-query", - "@kbn/analytics-client", "@kbn/core-analytics-browser", "@kbn/react-hooks", "@kbn/data-quality-plugin", + "@kbn/ebt", ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/observability_solution/observability_onboarding/common/telemetry_events.ts b/x-pack/plugins/observability_solution/observability_onboarding/common/telemetry_events.ts index 24a4167d257c7..998c3e9cc9122 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/common/telemetry_events.ts +++ b/x-pack/plugins/observability_solution/observability_onboarding/common/telemetry_events.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { type EventTypeOpts } from '@kbn/analytics-client'; +import { type EventTypeOpts } from '@kbn/ebt/client'; export const OBSERVABILITY_ONBOARDING_TELEMETRY_EVENT: EventTypeOpts<{ flow?: string; diff --git a/x-pack/plugins/observability_solution/observability_onboarding/e2e/cypress/e2e/logs/custom_logs/configure.cy.ts b/x-pack/plugins/observability_solution/observability_onboarding/e2e/cypress/e2e/logs/custom_logs/configure.cy.ts index 3a4ceba04f706..d427f65fe5b8e 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/e2e/cypress/e2e/logs/custom_logs/configure.cy.ts +++ b/x-pack/plugins/observability_solution/observability_onboarding/e2e/cypress/e2e/logs/custom_logs/configure.cy.ts @@ -5,7 +5,8 @@ * 2.0. */ -describe('[Logs onboarding] Custom logs - configure step', () => { +// Failing: See https://github.com/elastic/kibana/issues/186215 +describe.skip('[Logs onboarding] Custom logs - configure step', () => { describe('logFilePaths', () => { beforeEach(() => { cy.loginAsViewerUser(); diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/footer/footer.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/footer/footer.tsx index 0f62a71348759..4c91f77358875 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/footer/footer.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/footer/footer.tsx @@ -101,9 +101,9 @@ export const Footer: FunctionComponent = () => { ]; return ( - + {sections.map((section, index) => ( - + diff --git a/x-pack/plugins/observability_solution/observability_onboarding/public/application/observability_onboarding_flow.tsx b/x-pack/plugins/observability_solution/observability_onboarding/public/application/observability_onboarding_flow.tsx index 62cb0ead11a8c..1abcb3a1320dc 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/public/application/observability_onboarding_flow.tsx +++ b/x-pack/plugins/observability_solution/observability_onboarding/public/application/observability_onboarding_flow.tsx @@ -9,7 +9,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import React, { useEffect } from 'react'; import { Route, Routes } from '@kbn/shared-ux-router'; import { useLocation } from 'react-router-dom-v5-compat'; -import { EuiPageTemplate, EuiSpacer } from '@elastic/eui'; +import { EuiPageTemplate, EuiPanel, EuiSpacer } from '@elastic/eui'; import { css } from '@emotion/react'; import backgroundImageUrl from './header/background.svg'; import { Footer } from './footer/footer'; @@ -30,41 +30,62 @@ export function ObservabilityOnboardingFlow() { return ( - div { - background-image: url(${backgroundImageUrl}); - background-position: right center; - background-repeat: no-repeat; - } + padding-top: 0px !important; `} - grow={false} - restrictWidth > - -
-
- - - - - - - - - - - - - - - - - -
- -
+ div { + background-image: url(${backgroundImageUrl}); + background-position: right center; + background-repeat: no-repeat; + } + `} + grow={false} + restrictWidth + > + +
+
+ + + + + + + + + + + + + + + + + + +
+ +
+
+
); } diff --git a/x-pack/plugins/observability_solution/observability_onboarding/tsconfig.json b/x-pack/plugins/observability_solution/observability_onboarding/tsconfig.json index eb31601928b87..947a1230afd16 100644 --- a/x-pack/plugins/observability_solution/observability_onboarding/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_onboarding/tsconfig.json @@ -36,9 +36,9 @@ "@kbn/shared-ux-link-redirect-app", "@kbn/cloud-experiments-plugin", "@kbn/home-sample-data-tab", - "@kbn/analytics-client", "@kbn/react-kibana-context-render", - "@kbn/react-kibana-context-theme" + "@kbn/react-kibana-context-theme", + "@kbn/ebt" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_flamegraph.tsx b/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_flamegraph.tsx index 276bfa3e0ee29..dd85fabee454a 100644 --- a/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_flamegraph.tsx +++ b/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_flamegraph.tsx @@ -5,10 +5,11 @@ * 2.0. */ -import type { BaseFlameGraph } from '@kbn/profiling-utils'; import React from 'react'; -import { ProfilingEmbeddable } from './profiling_embeddable'; +import { css } from '@emotion/react'; +import type { BaseFlameGraph } from '@kbn/profiling-utils'; import { EMBEDDABLE_FLAMEGRAPH } from '.'; +import { getProfilingComponent } from '../helpers/component_registry'; interface Props { data?: BaseFlameGraph; @@ -16,6 +17,20 @@ interface Props { height?: string; } -export function EmbeddableFlamegraph(props: Props) { - return ; +export function EmbeddableFlamegraph({ height, ...props }: Props) { + const EmbeddableFlamegraphComponent = getProfilingComponent(EMBEDDABLE_FLAMEGRAPH); + return ( +
+ {EmbeddableFlamegraphComponent && } +
+ ); } diff --git a/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_functions.tsx b/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_functions.tsx index 064d2ce7859bb..813f7c299649e 100644 --- a/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_functions.tsx +++ b/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_functions.tsx @@ -5,10 +5,11 @@ * 2.0. */ -import type { TopNFunctions } from '@kbn/profiling-utils'; import React from 'react'; +import { css } from '@emotion/react'; +import type { TopNFunctions } from '@kbn/profiling-utils'; import { EMBEDDABLE_FUNCTIONS } from '.'; -import { ProfilingEmbeddable } from './profiling_embeddable'; +import { getProfilingComponent } from '../helpers/component_registry'; interface Props { data?: TopNFunctions; @@ -18,5 +19,18 @@ interface Props { } export function EmbeddableFunctions(props: Props) { - return ; + const EmbeddableFunctionsComponent = getProfilingComponent(EMBEDDABLE_FUNCTIONS); + return ( +
+ {EmbeddableFunctionsComponent && } +
+ ); } diff --git a/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_profiling_search_bar.tsx b/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_profiling_search_bar.tsx index 02908cdf67a0c..aee46946a484d 100644 --- a/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_profiling_search_bar.tsx +++ b/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_profiling_search_bar.tsx @@ -5,11 +5,10 @@ * 2.0. */ +import React from 'react'; import { css } from '@emotion/react'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { default as React, useEffect, useRef, useState } from 'react'; import { EMBEDDABLE_PROFILING_SEARCH_BAR } from '.'; -import { ObservabilitySharedStart } from '../../../plugin'; +import { getProfilingComponent } from '../helpers/component_registry'; export interface EmbeddableProfilingSearchBarProps { kuery: string; @@ -24,38 +23,8 @@ export interface EmbeddableProfilingSearchBarProps { } export function EmbeddableProfilingSearchBar(props: EmbeddableProfilingSearchBarProps) { - const { embeddable: embeddablePlugin } = useKibana().services; - const [embeddable, setEmbeddable] = useState(); - const embeddableRoot: React.RefObject = useRef(null); - - useEffect(() => { - async function createEmbeddable() { - const factory = embeddablePlugin?.getEmbeddableFactory(EMBEDDABLE_PROFILING_SEARCH_BAR); - const input = { - id: 'embeddable_profiling', - }; - const embeddableObject = await factory?.create(input); - setEmbeddable(embeddableObject); - } - createEmbeddable(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - if (embeddableRoot.current && embeddable) { - embeddable.render(embeddableRoot.current); - } - }, [embeddable, embeddableRoot]); - - useEffect(() => { - if (embeddable) { - embeddable.updateInput({ - ...props, - }); - embeddable.reload(); - } - }, [embeddable, props]); - + const EmbeddableProfilingSearchBarComponent = + getProfilingComponent(EMBEDDABLE_PROFILING_SEARCH_BAR); return (
+ > + {EmbeddableProfilingSearchBarComponent && ( + + )} +
); } diff --git a/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_stack_traces.tsx b/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_stack_traces.tsx index c07f08b517d90..8110410aff697 100644 --- a/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_stack_traces.tsx +++ b/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/embeddables/embeddable_stack_traces.tsx @@ -6,9 +6,10 @@ */ import React from 'react'; +import { css } from '@emotion/react'; import { TopNType } from '@kbn/profiling-utils'; import { EMBEDDABLE_STACK_TRACES } from '.'; -import { ProfilingEmbeddable } from './profiling_embeddable'; +import { getProfilingComponent } from '../helpers/component_registry'; interface Props { type: TopNType; @@ -20,5 +21,18 @@ interface Props { } export function EmbeddableStackTraces(props: Props) { - return ; + const EmbeddableStackTracesComponent = getProfilingComponent(EMBEDDABLE_STACK_TRACES); + return ( +
+ {EmbeddableStackTracesComponent && } +
+ ); } diff --git a/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/helpers/component_registry.ts b/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/helpers/component_registry.ts new file mode 100644 index 0000000000000..90af3dc789b93 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_shared/public/components/profiling/helpers/component_registry.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +const registry: { [key: string]: React.FC } = {}; + +export const registerProfilingComponent = (key: string, component: React.FC) => { + if (registry[key] !== undefined) { + throw new Error( + i18n.translate('xpack.observabilityShared.profilingComponentAlreadyExists.error', { + defaultMessage: `Component with key {key} already exists`, + values: { key }, + }) + ); + } + registry[key] = component; +}; + +export const getProfilingComponent = (key: string): React.FC => { + if (registry[key] === undefined) { + throw new Error( + i18n.translate('xpack.observabilityShared.profilingComponentNotFound.error', { + defaultMessage: `Component with key {key} not found`, + values: { key }, + }) + ); + } + return registry[key]; +}; diff --git a/x-pack/plugins/observability_solution/observability_shared/public/plugin.ts b/x-pack/plugins/observability_solution/observability_shared/public/plugin.ts index 97808e516f320..94c7d11ad7f69 100644 --- a/x-pack/plugins/observability_solution/observability_shared/public/plugin.ts +++ b/x-pack/plugins/observability_solution/observability_shared/public/plugin.ts @@ -18,6 +18,7 @@ import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import { BehaviorSubject } from 'rxjs'; import { createLazyObservabilityPageTemplate } from './components/page_template'; import { createNavigationRegistry } from './components/page_template/helpers/navigation_registry'; +import { registerProfilingComponent } from './components/profiling/helpers/component_registry'; import { type AssetDetailsFlyoutLocator, AssetDetailsFlyoutLocatorDefinition, @@ -102,6 +103,7 @@ export class ObservabilitySharedPlugin implements Plugin { }); return { + registerProfilingComponent, locators: this.createLocators(pluginsSetup.share.url), navigation: { registerSections: this.navigationRegistry.registerSections, diff --git a/x-pack/plugins/observability_solution/profiling/kibana.jsonc b/x-pack/plugins/observability_solution/profiling/kibana.jsonc index e5df7002f4a35..329da8be36f1a 100644 --- a/x-pack/plugins/observability_solution/profiling/kibana.jsonc +++ b/x-pack/plugins/observability_solution/profiling/kibana.jsonc @@ -26,7 +26,6 @@ "observabilityShared", "unifiedSearch", "share", - "embeddable", "profilingDataAccess" ], "requiredBundles": [ diff --git a/x-pack/plugins/observability_solution/profiling/public/embeddables/flamegraph/embeddable_flamegraph.tsx b/x-pack/plugins/observability_solution/profiling/public/embeddables/flamegraph/embeddable_flamegraph.tsx index b7266049b57c4..5eaed16b89953 100644 --- a/x-pack/plugins/observability_solution/profiling/public/embeddables/flamegraph/embeddable_flamegraph.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/embeddables/flamegraph/embeddable_flamegraph.tsx @@ -4,11 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { Embeddable, EmbeddableOutput, IContainer } from '@kbn/embeddable-plugin/public'; -import { EMBEDDABLE_FLAMEGRAPH } from '@kbn/observability-shared-plugin/public'; -import { createFlameGraph } from '@kbn/profiling-utils'; +import { BaseFlameGraph, createFlameGraph } from '@kbn/profiling-utils'; import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; import { profilingShowErrorFrames } from '@kbn/observability-plugin/common'; import { FlameGraph } from '../../components/flamegraph'; import { AsyncEmbeddableComponent } from '../async_embeddable_component'; @@ -16,48 +13,26 @@ import { ProfilingEmbeddableProvider, ProfilingEmbeddablesDependencies, } from '../profiling_embeddable_provider'; -import { EmbeddableFlamegraphEmbeddableInput } from './embeddable_flamegraph_factory'; import { useProfilingDependencies } from '../../components/contexts/profiling_dependencies/use_profiling_dependencies'; -export class EmbeddableFlamegraph extends Embeddable< - EmbeddableFlamegraphEmbeddableInput, - EmbeddableOutput -> { - readonly type = EMBEDDABLE_FLAMEGRAPH; - private _domNode?: HTMLElement; +export type EmbeddableFlamegraphProps = FlamegraphProps & ProfilingEmbeddablesDependencies; - constructor( - private deps: ProfilingEmbeddablesDependencies, - initialInput: EmbeddableFlamegraphEmbeddableInput, - parent?: IContainer - ) { - super(initialInput, {}, parent); - } +export type EmbeddableFlamegraphSharedComponent = React.FC; - render(domNode: HTMLElement) { - this._domNode = domNode; - render( - - - , - domNode - ); - } - - public destroy() { - if (this._domNode) { - unmountComponentAtNode(this._domNode); - } - } +export interface FlamegraphProps { + data?: BaseFlameGraph; + isLoading: boolean; +} - reload() { - if (this._domNode) { - this.render(this._domNode); - } - } +export function EmbeddableFlamegraph({ data, isLoading, ...deps }: EmbeddableFlamegraphProps) { + return ( + + + + ); } -function Flamegraph({ isLoading, data }: EmbeddableFlamegraphEmbeddableInput) { +function Flamegraph({ isLoading, data }: FlamegraphProps) { const { core } = useProfilingDependencies().start; const showErrorFrames = core.uiSettings.get(profilingShowErrorFrames); const flamegraph = !isLoading && data ? createFlameGraph(data, showErrorFrames) : undefined; diff --git a/x-pack/plugins/observability_solution/profiling/public/embeddables/flamegraph/embeddable_flamegraph_factory.ts b/x-pack/plugins/observability_solution/profiling/public/embeddables/flamegraph/embeddable_flamegraph_factory.ts deleted file mode 100644 index 259c61df525e0..0000000000000 --- a/x-pack/plugins/observability_solution/profiling/public/embeddables/flamegraph/embeddable_flamegraph_factory.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { - EmbeddableFactoryDefinition, - EmbeddableInput, - IContainer, -} from '@kbn/embeddable-plugin/public'; -import { EMBEDDABLE_FLAMEGRAPH } from '@kbn/observability-shared-plugin/public'; -import type { BaseFlameGraph } from '@kbn/profiling-utils'; -import type { GetProfilingEmbeddableDependencies } from '../profiling_embeddable_provider'; - -interface EmbeddableFlamegraphInput { - data?: BaseFlameGraph; - isLoading: boolean; -} - -export type EmbeddableFlamegraphEmbeddableInput = EmbeddableFlamegraphInput & EmbeddableInput; - -export class EmbeddableFlamegraphFactory - implements EmbeddableFactoryDefinition -{ - readonly type = EMBEDDABLE_FLAMEGRAPH; - - constructor(private getProfilingEmbeddableDependencies: GetProfilingEmbeddableDependencies) {} - - async isEditable() { - return false; - } - - async create(input: EmbeddableFlamegraphEmbeddableInput, parent?: IContainer) { - const { EmbeddableFlamegraph } = await import('./embeddable_flamegraph'); - const deps = await this.getProfilingEmbeddableDependencies(); - return new EmbeddableFlamegraph(deps, input, parent); - } - - getDisplayName() { - return 'Universal Profiling Flamegraph'; - } -} diff --git a/x-pack/plugins/observability_solution/profiling/public/embeddables/flamegraph/index.tsx b/x-pack/plugins/observability_solution/profiling/public/embeddables/flamegraph/index.tsx new file mode 100644 index 0000000000000..67efb183fd459 --- /dev/null +++ b/x-pack/plugins/observability_solution/profiling/public/embeddables/flamegraph/index.tsx @@ -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 React from 'react'; +import { dynamic } from '@kbn/shared-ux-utility'; +import type { EmbeddableFlamegraphSharedComponent, FlamegraphProps } from './embeddable_flamegraph'; +import { ProfilingEmbeddablesDependencies } from '../profiling_embeddable_provider'; + +const LazyEmbeddableFlamegraph = dynamic(async () => { + const Component = await import('./embeddable_flamegraph'); + return { default: Component.EmbeddableFlamegraph }; +}); + +export const getEmbeddableFlamegraphComponent = ( + profilingEmbeddableDependencies: ProfilingEmbeddablesDependencies +): EmbeddableFlamegraphSharedComponent => { + return (props: FlamegraphProps) => { + return ; + }; +}; diff --git a/x-pack/plugins/observability_solution/profiling/public/embeddables/functions/embeddable_functions.tsx b/x-pack/plugins/observability_solution/profiling/public/embeddables/functions/embeddable_functions.tsx index 9a198ae66e262..e281f12224c8f 100644 --- a/x-pack/plugins/observability_solution/profiling/public/embeddables/functions/embeddable_functions.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/embeddables/functions/embeddable_functions.tsx @@ -4,58 +4,42 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { Embeddable, EmbeddableOutput, IContainer } from '@kbn/embeddable-plugin/public'; -import { EMBEDDABLE_FUNCTIONS } from '@kbn/observability-shared-plugin/public'; -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; + +import React, { useMemo } from 'react'; +import { TopNFunctions } from '@kbn/profiling-utils'; import { AsyncEmbeddableComponent } from '../async_embeddable_component'; import { ProfilingEmbeddableProvider, ProfilingEmbeddablesDependencies, } from '../profiling_embeddable_provider'; -import { EmbeddableFunctionsEmbeddableInput } from './embeddable_functions_factory'; import { EmbeddableFunctionsGrid } from './embeddable_functions_grid'; -export class EmbeddableFunctions extends Embeddable< - EmbeddableFunctionsEmbeddableInput, - EmbeddableOutput -> { - readonly type = EMBEDDABLE_FUNCTIONS; - private _domNode?: HTMLElement; - - constructor( - private deps: ProfilingEmbeddablesDependencies, - initialInput: EmbeddableFunctionsEmbeddableInput, - parent?: IContainer - ) { - super(initialInput, {}, parent); - } +export type EmbeddableFunctionsProps = FunctionsProps & ProfilingEmbeddablesDependencies; - render(domNode: HTMLElement) { - this._domNode = domNode; - const { data, isLoading, rangeFrom, rangeTo } = this.input; - const totalSeconds = (rangeTo - rangeFrom) / 1000; - render( - - -
- -
-
-
, - domNode - ); - } +export type EmbeddableFunctionsSharedComponent = React.FC; - public destroy() { - if (this._domNode) { - unmountComponentAtNode(this._domNode); - } - } +export interface FunctionsProps { + data?: TopNFunctions; + isLoading: boolean; + rangeFrom: number; + rangeTo: number; +} - reload() { - if (this._domNode) { - this.render(this._domNode); - } - } +export function EmbeddableFunctions({ + data, + isLoading, + rangeFrom, + rangeTo, + ...deps +}: EmbeddableFunctionsProps) { + const totalSeconds = useMemo(() => (rangeTo - rangeFrom) / 1000, [rangeFrom, rangeTo]); + return ( + + +
+ +
+
+
+ ); } diff --git a/x-pack/plugins/observability_solution/profiling/public/embeddables/functions/embeddable_functions_factory.ts b/x-pack/plugins/observability_solution/profiling/public/embeddables/functions/embeddable_functions_factory.ts deleted file mode 100644 index 99e0d4baa4859..0000000000000 --- a/x-pack/plugins/observability_solution/profiling/public/embeddables/functions/embeddable_functions_factory.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { - EmbeddableFactoryDefinition, - EmbeddableInput, - IContainer, -} from '@kbn/embeddable-plugin/public'; -import { EMBEDDABLE_FUNCTIONS } from '@kbn/observability-shared-plugin/public'; -import type { TopNFunctions } from '@kbn/profiling-utils'; -import { GetProfilingEmbeddableDependencies } from '../profiling_embeddable_provider'; - -interface EmbeddableFunctionsInput { - data?: TopNFunctions; - isLoading: boolean; - rangeFrom: number; - rangeTo: number; -} - -export type EmbeddableFunctionsEmbeddableInput = EmbeddableFunctionsInput & EmbeddableInput; - -export class EmbeddableFunctionsFactory - implements EmbeddableFactoryDefinition -{ - readonly type = EMBEDDABLE_FUNCTIONS; - - constructor(private getProfilingEmbeddableDependencies: GetProfilingEmbeddableDependencies) {} - - async isEditable() { - return false; - } - - async create(input: EmbeddableFunctionsEmbeddableInput, parent?: IContainer) { - const { EmbeddableFunctions } = await import('./embeddable_functions'); - const deps = await this.getProfilingEmbeddableDependencies(); - return new EmbeddableFunctions(deps, input, parent); - } - - getDisplayName() { - return 'Universal Profiling Functions'; - } -} diff --git a/x-pack/plugins/observability_solution/profiling/public/embeddables/functions/index.tsx b/x-pack/plugins/observability_solution/profiling/public/embeddables/functions/index.tsx new file mode 100644 index 0000000000000..849907c57dab1 --- /dev/null +++ b/x-pack/plugins/observability_solution/profiling/public/embeddables/functions/index.tsx @@ -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 React from 'react'; +import { dynamic } from '@kbn/shared-ux-utility'; +import type { EmbeddableFunctionsSharedComponent, FunctionsProps } from './embeddable_functions'; +import { ProfilingEmbeddablesDependencies } from '../profiling_embeddable_provider'; + +const LazyEmbeddableFunctions = dynamic(async () => { + const Component = await import('./embeddable_functions'); + return { default: Component.EmbeddableFunctions }; +}); + +export const getEmbeddableFunctionsComponent = ( + profilingEmbeddableDependencies: ProfilingEmbeddablesDependencies +): EmbeddableFunctionsSharedComponent => { + return (props: FunctionsProps) => { + return ; + }; +}; diff --git a/x-pack/plugins/observability_solution/profiling/public/embeddables/profiling_embeddable_provider.tsx b/x-pack/plugins/observability_solution/profiling/public/embeddables/profiling_embeddable_provider.tsx index 52000e4783620..3a26ede4faac1 100644 --- a/x-pack/plugins/observability_solution/profiling/public/embeddables/profiling_embeddable_provider.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/embeddables/profiling_embeddable_provider.tsx @@ -15,7 +15,7 @@ import { Services } from '../services'; export interface ProfilingEmbeddablesDependencies { coreStart: CoreStart; - coreSetup: CoreSetup; + coreSetup: CoreSetup; pluginsStart: ProfilingPluginPublicStartDeps; pluginsSetup: ProfilingPluginPublicSetupDeps; profilingFetchServices: Services; diff --git a/x-pack/plugins/observability_solution/profiling/public/embeddables/register_embeddables.ts b/x-pack/plugins/observability_solution/profiling/public/embeddables/register_embeddables.ts index 850ab8998e893..f4a775f007739 100644 --- a/x-pack/plugins/observability_solution/profiling/public/embeddables/register_embeddables.ts +++ b/x-pack/plugins/observability_solution/profiling/public/embeddables/register_embeddables.ts @@ -5,37 +5,36 @@ * 2.0. */ -import { EmbeddableSetup } from '@kbn/embeddable-plugin/public'; import { EMBEDDABLE_FLAMEGRAPH, EMBEDDABLE_FUNCTIONS, EMBEDDABLE_PROFILING_SEARCH_BAR, EMBEDDABLE_STACK_TRACES, } from '@kbn/observability-shared-plugin/public'; -import { EmbeddableFlamegraphFactory } from './flamegraph/embeddable_flamegraph_factory'; -import { EmbeddableFunctionsFactory } from './functions/embeddable_functions_factory'; -import { GetProfilingEmbeddableDependencies } from './profiling_embeddable_provider'; -import { EmbeddableSearchBarFactory } from './search_bar/embeddable_search_bar_factory'; -import { EmbeddableStackTracesFactory } from './stack_traces/embeddable_stack_traces_factory'; +import { getEmbeddableFlamegraphComponent } from './flamegraph'; +import { getEmbeddableFunctionsComponent } from './functions'; +import { ProfilingEmbeddablesDependencies } from './profiling_embeddable_provider'; +import { getEmbeddableStackTracesComponent } from './stack_traces'; +import { getEmbeddableSearchBarComponent } from './search_bar'; -export function registerEmbeddables( - embeddable: EmbeddableSetup, - getProfilingEmbeddableDependencies: GetProfilingEmbeddableDependencies -) { - embeddable.registerEmbeddableFactory( +export function registerEmbeddables(deps: ProfilingEmbeddablesDependencies) { + const { + pluginsSetup: { observabilityShared }, + } = deps; + observabilityShared.registerProfilingComponent( EMBEDDABLE_FLAMEGRAPH, - new EmbeddableFlamegraphFactory(getProfilingEmbeddableDependencies) + getEmbeddableFlamegraphComponent(deps) ); - embeddable.registerEmbeddableFactory( + observabilityShared.registerProfilingComponent( EMBEDDABLE_FUNCTIONS, - new EmbeddableFunctionsFactory(getProfilingEmbeddableDependencies) + getEmbeddableFunctionsComponent(deps) ); - embeddable.registerEmbeddableFactory( + observabilityShared.registerProfilingComponent( EMBEDDABLE_PROFILING_SEARCH_BAR, - new EmbeddableSearchBarFactory(getProfilingEmbeddableDependencies) + getEmbeddableSearchBarComponent(deps) ); - embeddable.registerEmbeddableFactory( + observabilityShared.registerProfilingComponent( EMBEDDABLE_STACK_TRACES, - new EmbeddableStackTracesFactory(getProfilingEmbeddableDependencies) + getEmbeddableStackTracesComponent(deps) ); } diff --git a/x-pack/plugins/observability_solution/profiling/public/embeddables/search_bar/embeddable_search_bar.tsx b/x-pack/plugins/observability_solution/profiling/public/embeddables/search_bar/embeddable_search_bar.tsx index e32f514ff5ac5..1c370e96501d0 100644 --- a/x-pack/plugins/observability_solution/profiling/public/embeddables/search_bar/embeddable_search_bar.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/embeddables/search_bar/embeddable_search_bar.tsx @@ -5,74 +5,52 @@ * 2.0. */ import { css } from '@emotion/react'; -import { Embeddable, EmbeddableOutput, IContainer } from '@kbn/embeddable-plugin/public'; -import { EMBEDDABLE_PROFILING_SEARCH_BAR } from '@kbn/observability-shared-plugin/public'; +import { EmbeddableProfilingSearchBarProps } from '@kbn/observability-shared-plugin/public'; import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; import { ProfilingSearchBar } from '../../components/profiling_app_page_template/profiling_search_bar'; import { ProfilingEmbeddableProvider, ProfilingEmbeddablesDependencies, } from '../profiling_embeddable_provider'; -import { EmbeddableSearchBarEmbeddableInput } from './embeddable_search_bar_factory'; -export class EmbeddableSearchBar extends Embeddable< - EmbeddableSearchBarEmbeddableInput, - EmbeddableOutput -> { - readonly type = EMBEDDABLE_PROFILING_SEARCH_BAR; - private _domNode?: HTMLElement; +export type EmbeddableSearchBarProps = EmbeddableProfilingSearchBarProps & + ProfilingEmbeddablesDependencies; - constructor( - private deps: ProfilingEmbeddablesDependencies, - initialInput: EmbeddableSearchBarEmbeddableInput, - parent?: IContainer - ) { - super(initialInput, {}, parent); - } +export type EmbeddableSearchBarSharedComponent = React.FC; - render(domNode: HTMLElement) { - this._domNode = domNode; - const { showDatePicker, kuery, onQuerySubmit, onRefresh, rangeFrom, rangeTo } = this.input; - - render( - -
- { - onQuerySubmit({ - dateRange, - query: typeof query?.query === 'string' ? query.query : '', - }); - }} - onRefresh={onRefresh} - onRefreshClick={onRefresh} - showQueryMenu={false} - rangeFrom={rangeFrom} - rangeTo={rangeTo} - /> -
-
, - domNode - ); - } - - public destroy() { - if (this._domNode) { - unmountComponentAtNode(this._domNode); - } - } - - reload() { - if (this._domNode) { - this.render(this._domNode); - } - } +export function EmbeddableSearchBar({ + showDatePicker, + kuery, + onQuerySubmit, + onRefresh, + rangeFrom, + rangeTo, + ...deps +}: EmbeddableSearchBarProps) { + return ( + +
+ { + onQuerySubmit({ + dateRange, + query: typeof query?.query === 'string' ? query.query : '', + }); + }} + onRefresh={onRefresh} + onRefreshClick={onRefresh} + showQueryMenu={false} + rangeFrom={rangeFrom} + rangeTo={rangeTo} + /> +
+
+ ); } diff --git a/x-pack/plugins/observability_solution/profiling/public/embeddables/search_bar/embeddable_search_bar_factory.ts b/x-pack/plugins/observability_solution/profiling/public/embeddables/search_bar/embeddable_search_bar_factory.ts deleted file mode 100644 index cc7443976e1b1..0000000000000 --- a/x-pack/plugins/observability_solution/profiling/public/embeddables/search_bar/embeddable_search_bar_factory.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { - EmbeddableFactoryDefinition, - EmbeddableInput, - IContainer, -} from '@kbn/embeddable-plugin/public'; -import { - EmbeddableProfilingSearchBarProps, - EMBEDDABLE_PROFILING_SEARCH_BAR, -} from '@kbn/observability-shared-plugin/public'; -import type { GetProfilingEmbeddableDependencies } from '../profiling_embeddable_provider'; - -export type EmbeddableSearchBarEmbeddableInput = EmbeddableProfilingSearchBarProps & - EmbeddableInput; - -export class EmbeddableSearchBarFactory - implements EmbeddableFactoryDefinition -{ - readonly type = EMBEDDABLE_PROFILING_SEARCH_BAR; - - constructor(private getProfilingEmbeddableDependencies: GetProfilingEmbeddableDependencies) {} - - async isEditable() { - return false; - } - - async create(input: EmbeddableSearchBarEmbeddableInput, parent?: IContainer) { - const { EmbeddableSearchBar } = await import('./embeddable_search_bar'); - const deps = await this.getProfilingEmbeddableDependencies(); - return new EmbeddableSearchBar(deps, input, parent); - } - - getDisplayName() { - return 'Universal Profiling Search bar'; - } -} diff --git a/x-pack/plugins/observability_solution/profiling/public/embeddables/search_bar/index.tsx b/x-pack/plugins/observability_solution/profiling/public/embeddables/search_bar/index.tsx new file mode 100644 index 0000000000000..f010ade0f2480 --- /dev/null +++ b/x-pack/plugins/observability_solution/profiling/public/embeddables/search_bar/index.tsx @@ -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 React from 'react'; +import { dynamic } from '@kbn/shared-ux-utility'; +import type { EmbeddableSearchBarSharedComponent } from './embeddable_search_bar'; +import { ProfilingEmbeddablesDependencies } from '../profiling_embeddable_provider'; + +const LazyEmbeddableSearchBar = dynamic(async () => { + const Component = await import('./embeddable_search_bar'); + return { default: Component.EmbeddableSearchBar }; +}); + +export const getEmbeddableSearchBarComponent = ( + profilingEmbeddableDependencies: ProfilingEmbeddablesDependencies +): EmbeddableSearchBarSharedComponent => { + return (props) => { + return ; + }; +}; diff --git a/x-pack/plugins/observability_solution/profiling/public/embeddables/stack_traces/embeddable_stack_traces.tsx b/x-pack/plugins/observability_solution/profiling/public/embeddables/stack_traces/embeddable_stack_traces.tsx index 938a2c85810f4..efc9aeda9c6bd 100644 --- a/x-pack/plugins/observability_solution/profiling/public/embeddables/stack_traces/embeddable_stack_traces.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/embeddables/stack_traces/embeddable_stack_traces.tsx @@ -4,54 +4,39 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { Embeddable, EmbeddableOutput, IContainer } from '@kbn/embeddable-plugin/public'; -import { EMBEDDABLE_STACK_TRACES } from '@kbn/observability-shared-plugin/public'; + import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; import { ProfilingEmbeddableProvider, ProfilingEmbeddablesDependencies, } from '../profiling_embeddable_provider'; -import { EmbeddableStackTracesEmbeddableInput } from './embeddable_stack_traces_factory'; -import { StackTraces } from './stack_traces'; - -export class EmbeddableStackTraces extends Embeddable< - EmbeddableStackTracesEmbeddableInput, - EmbeddableOutput -> { - readonly type = EMBEDDABLE_STACK_TRACES; - private _domNode?: HTMLElement; - - constructor( - private deps: ProfilingEmbeddablesDependencies, - initialInput: EmbeddableStackTracesEmbeddableInput, - parent?: IContainer - ) { - super(initialInput, {}, parent); - } +import { StackTraces, StackTracesProps } from './stack_traces'; - render(domNode: HTMLElement) { - this._domNode = domNode; - const props = this.input; - render( - -
- -
-
, - domNode - ); - } +export type EmbeddableStackTracesProps = StackTracesProps & ProfilingEmbeddablesDependencies; - public destroy() { - if (this._domNode) { - unmountComponentAtNode(this._domNode); - } - } +export type EmbeddableStackTracesSharedComponent = React.FC; - reload() { - if (this._domNode) { - this.render(this._domNode); - } - } +export function EmbeddableStackTraces({ + type, + kuery, + rangeFrom, + rangeTo, + onClick, + onChartBrushEnd, + ...deps +}: EmbeddableStackTracesProps) { + return ( + +
+ +
+
+ ); } diff --git a/x-pack/plugins/observability_solution/profiling/public/embeddables/stack_traces/embeddable_stack_traces_factory.ts b/x-pack/plugins/observability_solution/profiling/public/embeddables/stack_traces/embeddable_stack_traces_factory.ts deleted file mode 100644 index e079f1a7c4754..0000000000000 --- a/x-pack/plugins/observability_solution/profiling/public/embeddables/stack_traces/embeddable_stack_traces_factory.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { - EmbeddableFactoryDefinition, - EmbeddableInput, - IContainer, -} from '@kbn/embeddable-plugin/public'; -import { EMBEDDABLE_STACK_TRACES } from '@kbn/observability-shared-plugin/public'; -import { TopNType } from '@kbn/profiling-utils'; -import { GetProfilingEmbeddableDependencies } from '../profiling_embeddable_provider'; - -interface EmbeddableStackTracesInput { - type: TopNType; - kuery: string; - rangeFrom: number; - rangeTo: number; - onClick: (category: string) => void; - onChartBrushEnd: (range: { rangeFrom: string; rangeTo: string }) => void; -} - -export type EmbeddableStackTracesEmbeddableInput = EmbeddableStackTracesInput & EmbeddableInput; - -export class EmbeddableStackTracesFactory - implements EmbeddableFactoryDefinition -{ - readonly type = EMBEDDABLE_STACK_TRACES; - - constructor(private getProfilingEmbeddableDependencies: GetProfilingEmbeddableDependencies) {} - - async isEditable() { - return false; - } - - async create(input: EmbeddableStackTracesEmbeddableInput, parent?: IContainer) { - const { EmbeddableStackTraces } = await import('./embeddable_stack_traces'); - const deps = await this.getProfilingEmbeddableDependencies(); - return new EmbeddableStackTraces(deps, input, parent); - } - - getDisplayName() { - return 'Universal Profiling Threads'; - } -} diff --git a/x-pack/plugins/observability_solution/profiling/public/embeddables/stack_traces/index.tsx b/x-pack/plugins/observability_solution/profiling/public/embeddables/stack_traces/index.tsx new file mode 100644 index 0000000000000..29631941cd30e --- /dev/null +++ b/x-pack/plugins/observability_solution/profiling/public/embeddables/stack_traces/index.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { dynamic } from '@kbn/shared-ux-utility'; +import type { EmbeddableStackTracesSharedComponent } from './embeddable_stack_traces'; +import { ProfilingEmbeddablesDependencies } from '../profiling_embeddable_provider'; +import type { StackTracesProps } from './stack_traces'; + +const LazyEmbeddableStackTraces = dynamic(async () => { + const Component = await import('./embeddable_stack_traces'); + return { default: Component.EmbeddableStackTraces }; +}); + +export const getEmbeddableStackTracesComponent = ( + profilingEmbeddableDependencies: ProfilingEmbeddablesDependencies +): EmbeddableStackTracesSharedComponent => { + return (props: StackTracesProps) => { + return ; + }; +}; diff --git a/x-pack/plugins/observability_solution/profiling/public/embeddables/stack_traces/stack_traces.tsx b/x-pack/plugins/observability_solution/profiling/public/embeddables/stack_traces/stack_traces.tsx index 3205f04120012..9aae8b0ecc309 100644 --- a/x-pack/plugins/observability_solution/profiling/public/embeddables/stack_traces/stack_traces.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/embeddables/stack_traces/stack_traces.tsx @@ -13,7 +13,7 @@ import { AsyncStatus, useAsync } from '../../hooks/use_async'; import { EmptyDataPrompt } from '../empty_data_prompt'; import { ErrorPrompt } from '../error_prompt'; -interface Props { +export interface StackTracesProps { type: TopNType; kuery: string; rangeFrom: number; @@ -22,7 +22,14 @@ interface Props { onChartBrushEnd: (range: { rangeFrom: string; rangeTo: string }) => void; } -export function StackTraces({ type, kuery, rangeFrom, rangeTo, onClick, onChartBrushEnd }: Props) { +export function StackTraces({ + type, + kuery, + rangeFrom, + rangeTo, + onClick, + onChartBrushEnd, +}: StackTracesProps) { const { services: { fetchTopN }, } = useProfilingDependencies(); diff --git a/x-pack/plugins/observability_solution/profiling/public/plugin.tsx b/x-pack/plugins/observability_solution/profiling/public/plugin.tsx index e241d47934730..fca4f8dcb4c6b 100644 --- a/x-pack/plugins/observability_solution/profiling/public/plugin.tsx +++ b/x-pack/plugins/observability_solution/profiling/public/plugin.tsx @@ -13,7 +13,7 @@ import { Plugin, } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; -import type { NavigationSection } from '@kbn/observability-shared-plugin/public'; +import { NavigationSection } from '@kbn/observability-shared-plugin/public'; import type { Location } from 'history'; import { BehaviorSubject, combineLatest, from, map } from 'rxjs'; import { OBLT_PROFILING_APP_ID } from '@kbn/deeplinks-observability'; @@ -25,8 +25,19 @@ import { ProfilingEmbeddablesDependencies } from './embeddables/profiling_embedd export type ProfilingPluginSetup = void; export type ProfilingPluginStart = void; -export class ProfilingPlugin implements Plugin { - public setup(coreSetup: CoreSetup, pluginsSetup: ProfilingPluginPublicSetupDeps) { +export class ProfilingPlugin + implements + Plugin< + ProfilingPluginSetup, + ProfilingPluginStart, + ProfilingPluginPublicSetupDeps, + ProfilingPluginPublicStartDeps + > +{ + public setup( + coreSetup: CoreSetup, + pluginsSetup: ProfilingPluginPublicSetupDeps + ) { // Register an application into the side navigation menu const links = [ { @@ -93,11 +104,7 @@ export class ProfilingPlugin implements Plugin { category: DEFAULT_APP_CATEGORIES.observability, deepLinks: links, async mount({ element, history, theme$, setHeaderActionMenu }: AppMountParameters) { - const [coreStart, pluginsStart] = (await coreSetup.getStartServices()) as [ - CoreStart, - ProfilingPluginPublicStartDeps, - unknown - ]; + const [coreStart, pluginsStart] = await coreSetup.getStartServices(); const { renderApp } = await import('./app'); @@ -133,11 +140,7 @@ export class ProfilingPlugin implements Plugin { const getProfilingEmbeddableDependencies = async (): Promise => { - const [coreStart, pluginsStart] = (await coreSetup.getStartServices()) as [ - CoreStart, - ProfilingPluginPublicStartDeps, - unknown - ]; + const [coreStart, pluginsStart] = await coreSetup.getStartServices(); return { coreStart, coreSetup, @@ -147,7 +150,9 @@ export class ProfilingPlugin implements Plugin { }; }; - registerEmbeddables(pluginsSetup.embeddable, getProfilingEmbeddableDependencies); + getProfilingEmbeddableDependencies().then((deps) => { + registerEmbeddables(deps); + }); return {}; } diff --git a/x-pack/plugins/observability_solution/profiling/public/types.ts b/x-pack/plugins/observability_solution/profiling/public/types.ts index 9a7f959b34bb3..89a8f999010ce 100644 --- a/x-pack/plugins/observability_solution/profiling/public/types.ts +++ b/x-pack/plugins/observability_solution/profiling/public/types.ts @@ -24,7 +24,6 @@ import { ObservabilityAIAssistantPublicSetup, ObservabilityAIAssistantPublicStart, } from '@kbn/observability-ai-assistant-plugin/public'; -import { EmbeddableSetup } from '@kbn/embeddable-plugin/public'; import type { UnifiedSearchPublicPluginStart, UnifiedSearchPluginSetup, @@ -39,7 +38,6 @@ export interface ProfilingPluginPublicSetupDeps { charts: ChartsPluginSetup; licensing: LicensingPluginSetup; share: SharePluginSetup; - embeddable: EmbeddableSetup; unifiedSearch: UnifiedSearchPluginSetup; } diff --git a/x-pack/plugins/observability_solution/profiling/tsconfig.json b/x-pack/plugins/observability_solution/profiling/tsconfig.json index 1f18459455e7e..937eee96641c8 100644 --- a/x-pack/plugins/observability_solution/profiling/tsconfig.json +++ b/x-pack/plugins/observability_solution/profiling/tsconfig.json @@ -48,7 +48,6 @@ "@kbn/usage-collection-plugin", "@kbn/observability-ai-assistant-plugin", "@kbn/profiling-data-access-plugin", - "@kbn/embeddable-plugin", "@kbn/profiling-utils", "@kbn/security-plugin", "@kbn/shared-ux-utility", diff --git a/x-pack/plugins/observability_solution/slo/common/constants.ts b/x-pack/plugins/observability_solution/slo/common/constants.ts index db0c91ab0ef4b..a70a5fe082730 100644 --- a/x-pack/plugins/observability_solution/slo/common/constants.ts +++ b/x-pack/plugins/observability_solution/slo/common/constants.ts @@ -50,7 +50,7 @@ export const SUPPRESSED_PRIORITY_ACTION = { }; export const SLO_MODEL_VERSION = 2; -export const SLO_RESOURCES_VERSION = 3.2; +export const SLO_RESOURCES_VERSION = 3.3; export const SLO_RESOURCES_VERSION_MAJOR = 3; export const SLO_COMPONENT_TEMPLATE_MAPPINGS_NAME = '.slo-observability.sli-mappings'; diff --git a/x-pack/plugins/observability_solution/slo/public/components/slo/burn_rate/alert_details/alert_details_app_section.tsx b/x-pack/plugins/observability_solution/slo/public/components/slo/burn_rate/alert_details/alert_details_app_section.tsx index 324c84c9d923f..be83b74a0cc19 100644 --- a/x-pack/plugins/observability_solution/slo/public/components/slo/burn_rate/alert_details/alert_details_app_section.tsx +++ b/x-pack/plugins/observability_solution/slo/public/components/slo/burn_rate/alert_details/alert_details_app_section.tsx @@ -7,10 +7,8 @@ import { EuiFlexGroup, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { AlertSummaryField } from '@kbn/observability-plugin/public'; -import { ALL_VALUE } from '@kbn/slo-schema'; import React, { useEffect } from 'react'; import { useFetchSloDetails } from '../../../../hooks/use_fetch_slo_details'; -import { SLOGroupings } from '../../../../pages/slos/components/common/slo_groupings'; import { useKibana } from '../../../../utils/kibana_react'; import { CustomAlertDetailsPanel } from './components/custom_panels/custom_panels'; import { ErrorRatePanel } from './components/error_rate/error_rate_panel'; @@ -19,7 +17,6 @@ import { BurnRateAlert, BurnRateRule } from './types'; interface AppSectionProps { alert: BurnRateAlert; rule: BurnRateRule; - ruleLink: string; setAlertSummaryFields: React.Dispatch>; } @@ -27,7 +24,6 @@ interface AppSectionProps { export default function AlertDetailsAppSection({ alert, rule, - ruleLink, setAlertSummaryFields, }: AppSectionProps) { const { @@ -45,7 +41,7 @@ export default function AlertDetailsAppSection({ const fields = [ { label: i18n.translate('xpack.slo.burnRateRule.alertDetailsAppSection.summaryField.slo', { - defaultMessage: 'Source SLO', + defaultMessage: 'SLO', }), value: ( @@ -53,30 +49,10 @@ export default function AlertDetailsAppSection({ ), }, - { - label: i18n.translate('xpack.slo.burnRateRule.alertDetailsAppSection.summaryField.rule', { - defaultMessage: 'Rule', - }), - value: ( - - {rule.name} - - ), - }, ]; - if (instanceId !== ALL_VALUE) { - fields.push({ - label: i18n.translate( - 'xpack.slo.burnRateRule.alertDetailsAppSection.summaryField.instanceId', - { defaultMessage: 'SLO Instance' } - ), - value: , - }); - } - setAlertSummaryFields(fields); - }, [alertLink, rule, ruleLink, setAlertSummaryFields, basePath, slo, instanceId]); + }, [alertLink, rule, setAlertSummaryFields, basePath, slo, instanceId]); return ( diff --git a/x-pack/plugins/observability_solution/slo/public/components/slo/slo_permissions_callout/index.tsx b/x-pack/plugins/observability_solution/slo/public/components/slo/slo_permissions_callout/index.tsx new file mode 100644 index 0000000000000..720f31ccea3fa --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/public/components/slo/slo_permissions_callout/index.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { EuiCallOut, EuiSpacer, EuiLink, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { usePermissions } from '../../../hooks/use_permissions'; + +export function SloPermissionsCallout() { + const { data: permissions, isLoading } = usePermissions(); + + if (isLoading) { + return null; + } + + if (permissions?.hasAllReadRequested || permissions?.hasAllWriteRequested) { + return null; + } + + return ( + <> + + + + + + + +
    +
  • + +
  • +
+
+ + + + +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+ + + {i18n.translate('xpack.slo.permissionsCallout.readDocumentation', { + defaultMessage: 'Read the documentation for more details', + })} + +
+
+ + + ); +} diff --git a/x-pack/plugins/observability_solution/slo/public/data/slo/common.ts b/x-pack/plugins/observability_solution/slo/public/data/slo/common.ts index ae25d150f350b..b9276486c8d8e 100644 --- a/x-pack/plugins/observability_solution/slo/public/data/slo/common.ts +++ b/x-pack/plugins/observability_solution/slo/public/data/slo/common.ts @@ -39,6 +39,9 @@ export const buildHealthySummary = ( remaining: 0.93623, isEstimated: false, }, + fiveMinuteBurnRate: 0, + oneHourBurnRate: 0, + oneDayBurnRate: 0, ...params, }; }; @@ -55,6 +58,9 @@ export const buildViolatedSummary = ( remaining: -3.1234, isEstimated: false, }, + fiveMinuteBurnRate: 0, + oneHourBurnRate: 0, + oneDayBurnRate: 0, ...params, }; }; @@ -71,6 +77,9 @@ export const buildNoDataSummary = ( remaining: 1, isEstimated: false, }, + fiveMinuteBurnRate: 0, + oneHourBurnRate: 0, + oneDayBurnRate: 0, ...params, }; }; @@ -87,6 +96,9 @@ export const buildDegradingSummary = ( remaining: 0.1244, isEstimated: true, }, + fiveMinuteBurnRate: 0, + oneHourBurnRate: 0, + oneDayBurnRate: 0, ...params, }; }; diff --git a/x-pack/plugins/observability_solution/slo/public/data/slo/slo.ts b/x-pack/plugins/observability_solution/slo/public/data/slo/slo.ts index 02541ac8a17c7..ce50190eb7adf 100644 --- a/x-pack/plugins/observability_solution/slo/public/data/slo/slo.ts +++ b/x-pack/plugins/observability_solution/slo/public/data/slo/slo.ts @@ -62,6 +62,9 @@ const baseSlo: Omit = { remaining: 0.936, isEstimated: false, }, + fiveMinuteBurnRate: 0, + oneHourBurnRate: 0, + oneDayBurnRate: 0, }, groupBy: ALL_VALUE, groupings: {}, diff --git a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/slo_alerts_embeddable_factory.tsx b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/slo_alerts_embeddable_factory.tsx index 7472c43253454..b70e1d8e4c40a 100644 --- a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/slo_alerts_embeddable_factory.tsx +++ b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/alerts/slo_alerts_embeddable_factory.tsx @@ -33,7 +33,11 @@ export const getAlertsPanelTitle = () => }); export function getAlertsEmbeddableFactory(deps: SloEmbeddableDeps, kibanaVersion: string) { - const factory: ReactEmbeddableFactory = { + const factory: ReactEmbeddableFactory< + SloAlertsEmbeddableState, + SloAlertsEmbeddableState, + SloAlertsApi + > = { type: SLO_ALERTS_EMBEDDABLE_ID, deserializeState: (state) => { return state.rawState as SloAlertsEmbeddableState; diff --git a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/error_budget/error_budget_react_embeddable_factory.tsx b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/error_budget/error_budget_react_embeddable_factory.tsx index 63ebb3fb37205..6d01995fb8191 100644 --- a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/error_budget/error_budget_react_embeddable_factory.tsx +++ b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/error_budget/error_budget_react_embeddable_factory.tsx @@ -28,7 +28,11 @@ export const getErrorBudgetPanelTitle = () => const queryClient = new QueryClient(); export const getErrorBudgetEmbeddableFactory = (deps: SloEmbeddableDeps) => { - const factory: ReactEmbeddableFactory = { + const factory: ReactEmbeddableFactory< + SloErrorBudgetEmbeddableState, + SloErrorBudgetEmbeddableState, + ErrorBudgetApi + > = { type: SLO_ERROR_BUDGET_ID, deserializeState: (state) => { return state.rawState as SloErrorBudgetEmbeddableState; diff --git a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/overview/slo_embeddable_factory.tsx b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/overview/slo_embeddable_factory.tsx index 33252386c0e81..861909b040e9a 100644 --- a/x-pack/plugins/observability_solution/slo/public/embeddable/slo/overview/slo_embeddable_factory.tsx +++ b/x-pack/plugins/observability_solution/slo/public/embeddable/slo/overview/slo_embeddable_factory.tsx @@ -36,7 +36,11 @@ export const getOverviewPanelTitle = () => defaultMessage: 'SLO Overview', }); export const getOverviewEmbeddableFactory = (deps: SloEmbeddableDeps) => { - const factory: ReactEmbeddableFactory = { + const factory: ReactEmbeddableFactory< + SloOverviewEmbeddableState, + SloOverviewEmbeddableState, + SloOverviewApi + > = { type: SLO_OVERVIEW_EMBEDDABLE_ID, deserializeState: (state) => { return state.rawState as SloOverviewEmbeddableState; diff --git a/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_global_diagnosis.ts b/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_global_diagnosis.ts index 22862110910da..df8ea83ed2aaa 100644 --- a/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_global_diagnosis.ts +++ b/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_global_diagnosis.ts @@ -19,11 +19,7 @@ interface SloGlobalDiagnosisResponse { } export interface UseFetchSloGlobalDiagnoseResponse { - isInitialLoading: boolean; isLoading: boolean; - isRefetching: boolean; - isSuccess: boolean; - isError: boolean; data: SloGlobalDiagnosisResponse | undefined; } @@ -33,7 +29,7 @@ export function useFetchSloGlobalDiagnosis(): UseFetchSloGlobalDiagnoseResponse notifications: { toasts }, } = useKibana().services; - const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data } = useQuery({ + const { isLoading, data } = useQuery({ queryKey: sloKeys.globalDiagnosis(), queryFn: async ({ signal }) => { try { @@ -65,9 +61,5 @@ export function useFetchSloGlobalDiagnosis(): UseFetchSloGlobalDiagnoseResponse return { data, isLoading, - isInitialLoading, - isRefetching, - isSuccess, - isError, }; } diff --git a/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_slo_list.ts b/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_slo_list.ts index 289c8e5cbd418..0e2bb39dab878 100644 --- a/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_slo_list.ts +++ b/x-pack/plugins/observability_solution/slo/public/hooks/use_fetch_slo_list.ts @@ -115,6 +115,7 @@ export function useFetchSloList({ if (String(error) === 'Error: Forbidden') { return false; } + return failureCount < 4; }, onSuccess: ({ results }: FindSLOResponse) => { diff --git a/x-pack/plugins/observability_solution/slo/public/hooks/use_permissions.test.ts b/x-pack/plugins/observability_solution/slo/public/hooks/use_permissions.test.ts new file mode 100644 index 0000000000000..c9646c070dbbb --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/public/hooks/use_permissions.test.ts @@ -0,0 +1,169 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { sloFeatureId } from '@kbn/observability-shared-plugin/common'; +import { useKibana } from '../utils/kibana_react'; +import { useFetchSloGlobalDiagnosis } from './use_fetch_global_diagnosis'; +import { usePermissions } from './use_permissions'; + +jest.mock('../utils/kibana_react'); +jest.mock('./use_fetch_global_diagnosis'); + +const useKibanaMock = useKibana as jest.Mock; +const useFetchSloGlobalDiagnosisMock = useFetchSloGlobalDiagnosis as jest.Mock; + +describe('usePermissions', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('is loading until diagnosis is done', () => { + useKibanaMock.mockReturnValue({ + services: { + application: { capabilities: { [sloFeatureId]: { read: true, write: true } } }, + }, + }); + useFetchSloGlobalDiagnosisMock.mockReturnValue({ + data: undefined, + isLoading: true, + }); + + const { data, isLoading } = usePermissions(); + + expect(isLoading).toBe(true); + expect(data).toBe(undefined); + }); + + describe('hasAllReadRequested', () => { + it('returns hasAllReadRequested = true when both write capabilities and read privileges are true', () => { + useKibanaMock.mockReturnValue({ + services: { + application: { capabilities: { [sloFeatureId]: { read: true, write: false } } }, + }, + }); + useFetchSloGlobalDiagnosisMock.mockReturnValue({ + data: { + userPrivileges: { + read: { has_all_requested: true }, + write: { has_all_requested: false }, + }, + }, + isLoading: false, + }); + + const { data } = usePermissions(); + + expect(data?.hasAllReadRequested).toBe(true); + }); + + it('returns hasAllReadRequested = false when read capabilities is false and read privileges is true', () => { + useKibanaMock.mockReturnValue({ + services: { + application: { capabilities: { [sloFeatureId]: { read: false, write: false } } }, + }, + }); + useFetchSloGlobalDiagnosisMock.mockReturnValue({ + data: { + userPrivileges: { + read: { has_all_requested: true }, + write: { has_all_requested: false }, + }, + }, + isLoading: false, + }); + + const { data } = usePermissions(); + + expect(data?.hasAllReadRequested).toBe(false); + }); + + it('returns hasAllReadRequested = false when read capabilities is true and read privileges is false', () => { + useKibanaMock.mockReturnValue({ + services: { + application: { capabilities: { [sloFeatureId]: { read: true, write: false } } }, + }, + }); + useFetchSloGlobalDiagnosisMock.mockReturnValue({ + data: { + userPrivileges: { + read: { has_all_requested: false }, + write: { has_all_requested: false }, + }, + }, + isLoading: false, + }); + + const { data } = usePermissions(); + + expect(data?.hasAllReadRequested).toBe(false); + }); + }); + + describe('hasAllWriteRequested', () => { + it('returns hasAllWriteRequested = true when both write capabilities and write privileges are true', () => { + useKibanaMock.mockReturnValue({ + services: { + application: { capabilities: { [sloFeatureId]: { read: false, write: true } } }, + }, + }); + useFetchSloGlobalDiagnosisMock.mockReturnValue({ + data: { + userPrivileges: { + read: { has_all_requested: false }, + write: { has_all_requested: true }, + }, + }, + isLoading: false, + }); + + const { data } = usePermissions(); + + expect(data?.hasAllWriteRequested).toBe(true); + }); + + it('returns hasAllWriteRequested = false when write capabilities is false and write privileges is true', () => { + useKibanaMock.mockReturnValue({ + services: { + application: { capabilities: { [sloFeatureId]: { read: false, write: false } } }, + }, + }); + useFetchSloGlobalDiagnosisMock.mockReturnValue({ + data: { + userPrivileges: { + read: { has_all_requested: false }, + write: { has_all_requested: true }, + }, + }, + isLoading: false, + }); + + const { data } = usePermissions(); + + expect(data?.hasAllWriteRequested).toBe(false); + }); + + it('returns hasAllWriteRequested = false when write capabilities is true and write privileges is false', () => { + useKibanaMock.mockReturnValue({ + services: { + application: { capabilities: { [sloFeatureId]: { read: false, write: true } } }, + }, + }); + useFetchSloGlobalDiagnosisMock.mockReturnValue({ + data: { + userPrivileges: { + read: { has_all_requested: false }, + write: { has_all_requested: false }, + }, + }, + isLoading: false, + }); + + const { data } = usePermissions(); + + expect(data?.hasAllWriteRequested).toBe(false); + }); + }); +}); diff --git a/x-pack/plugins/observability_solution/slo/public/hooks/use_permissions.ts b/x-pack/plugins/observability_solution/slo/public/hooks/use_permissions.ts new file mode 100644 index 0000000000000..6e380fbe1a33e --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/public/hooks/use_permissions.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { sloFeatureId } from '@kbn/observability-plugin/common'; +import { useKibana } from '../utils/kibana_react'; +import { useFetchSloGlobalDiagnosis } from './use_fetch_global_diagnosis'; + +export function usePermissions() { + const { + application: { capabilities }, + } = useKibana().services; + + const { data: globalDiagnosis, isLoading } = useFetchSloGlobalDiagnosis(); + + const hasRequiredReadCapabilities = !!capabilities[sloFeatureId].read ?? false; + const hasRequiredWriteCapabilities = !!capabilities[sloFeatureId].write ?? false; + + const hasRequiredReadPrivileges = + !!globalDiagnosis?.userPrivileges.read.has_all_requested ?? false; + const hasRequiredWritePrivileges = + !!globalDiagnosis?.userPrivileges.write.has_all_requested ?? false; + + return { + isLoading, + data: isLoading + ? undefined + : { + capabilities: { + read: hasRequiredReadCapabilities, + write: hasRequiredWriteCapabilities, + }, + privileges: { + read: hasRequiredReadPrivileges, + write: hasRequiredWritePrivileges, + }, + hasAllReadRequested: hasRequiredReadCapabilities && hasRequiredReadPrivileges, + hasAllWriteRequested: hasRequiredWriteCapabilities && hasRequiredWritePrivileges, + }, + }; +} diff --git a/x-pack/plugins/observability_solution/slo/public/locators/slo_edit.test.ts b/x-pack/plugins/observability_solution/slo/public/locators/slo_edit.test.ts index ea85e58479636..40fcae8c840ee 100644 --- a/x-pack/plugins/observability_solution/slo/public/locators/slo_edit.test.ts +++ b/x-pack/plugins/observability_solution/slo/public/locators/slo_edit.test.ts @@ -20,7 +20,7 @@ describe('SloEditLocator', () => { it('should return correct url when slo is provided', async () => { const location = await locator.getLocation(buildSlo({ id: 'foo' })); expect(location.path).toEqual( - "/edit/foo?_a=(budgetingMethod:occurrences,createdAt:'2022-12-29T10:11:12.000Z',description:'some%20description%20useful',enabled:!t,groupBy:'*',groupings:(),id:foo,indicator:(params:(filter:'baz:%20foo%20and%20bar%20%3E%202',good:'http_status:%202xx',index:some-index,timestampField:custom_timestamp,total:'a%20query'),type:sli.kql.custom),instanceId:'*',meta:(),name:'super%20important%20level%20service',objective:(target:0.98),revision:1,settings:(frequency:'1m',preventInitialBackfill:!f,syncDelay:'1m'),summary:(errorBudget:(consumed:0.064,initial:0.02,isEstimated:!f,remaining:0.936),sliValue:0.99872,status:HEALTHY),tags:!(k8s,production,critical),timeWindow:(duration:'30d',type:rolling),updatedAt:'2022-12-29T10:11:12.000Z',version:2)" + "/edit/foo?_a=(budgetingMethod:occurrences,createdAt:'2022-12-29T10:11:12.000Z',description:'some%20description%20useful',enabled:!t,groupBy:'*',groupings:(),id:foo,indicator:(params:(filter:'baz:%20foo%20and%20bar%20%3E%202',good:'http_status:%202xx',index:some-index,timestampField:custom_timestamp,total:'a%20query'),type:sli.kql.custom),instanceId:'*',meta:(),name:'super%20important%20level%20service',objective:(target:0.98),revision:1,settings:(frequency:'1m',preventInitialBackfill:!f,syncDelay:'1m'),summary:(errorBudget:(consumed:0.064,initial:0.02,isEstimated:!f,remaining:0.936),fiveMinuteBurnRate:0,oneDayBurnRate:0,oneHourBurnRate:0,sliValue:0.99872,status:HEALTHY),tags:!(k8s,production,critical),timeWindow:(duration:'30d',type:rolling),updatedAt:'2022-12-29T10:11:12.000Z',version:2)" ); }); }); diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/header_control.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/header_control.tsx index 6239984922406..fb1e524e3e170 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/header_control.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/components/header_control.tsx @@ -20,9 +20,9 @@ import React, { useCallback, useEffect, useState } from 'react'; import { paths } from '../../../../common/locators/paths'; import { SloDeleteModal } from '../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal'; import { SloResetConfirmationModal } from '../../../components/slo/reset_confirmation_modal/slo_reset_confirmation_modal'; -import { useCapabilities } from '../../../hooks/use_capabilities'; import { useCloneSlo } from '../../../hooks/use_clone_slo'; import { useFetchRulesForSlo } from '../../../hooks/use_fetch_rules_for_slo'; +import { usePermissions } from '../../../hooks/use_permissions'; import { useResetSlo } from '../../../hooks/use_reset_slo'; import { useKibana } from '../../../utils/kibana_react'; import { convertSliApmParamsToApmAppDeeplinkUrl } from '../../../utils/slo/convert_sli_apm_params_to_apm_app_deeplink_url'; @@ -44,7 +44,7 @@ export function HeaderControl({ isLoading, slo }: Props) { } = useKibana().services; const hasApmReadCapabilities = capabilities.apm.show; - const { hasWriteCapabilities } = useCapabilities(); + const { data: permissions } = usePermissions(); const { isDeletingSlo, isResettingSlo, removeDeleteQueryParam, removeResetQueryParam } = useGetQueryParams(); @@ -198,7 +198,7 @@ export function HeaderControl({ isLoading, slo }: Props) { items={[ , , ({ ...jest.requireActual('react-router-dom'), @@ -42,7 +42,7 @@ jest.mock('react-router-dom', () => ({ jest.mock('@kbn/observability-shared-plugin/public'); jest.mock('../../utils/kibana_react'); jest.mock('../../hooks/use_license'); -jest.mock('../../hooks/use_capabilities'); +jest.mock('../../hooks/use_permissions'); jest.mock('../../hooks/use_fetch_active_alerts'); jest.mock('../../hooks/use_fetch_slo_details'); jest.mock('../../hooks/use_fetch_historical_summary'); @@ -53,7 +53,7 @@ jest.mock('../../hooks/use_delete_slo_instance'); const useKibanaMock = useKibana as jest.Mock; const useLicenseMock = useLicense as jest.Mock; -const useCapabilitiesMock = useCapabilities as jest.Mock; +const usePermissionsMock = usePermissions as jest.Mock; const useFetchActiveAlertsMock = useFetchActiveAlerts as jest.Mock; const useFetchSloDetailsMock = useFetchSloDetails as jest.Mock; const useFetchHistoricalSummaryMock = useFetchHistoricalSummary as jest.Mock; @@ -134,7 +134,10 @@ describe('SLO Details Page', () => { beforeEach(() => { jest.clearAllMocks(); mockKibana(); - useCapabilitiesMock.mockReturnValue({ hasWriteCapabilities: true, hasReadCapabilities: true }); + usePermissionsMock.mockReturnValue({ + isLoading: false, + data: { hasAllReadRequested: true, hasAllWriteRequested: true }, + }); useCreateDataViewsMock.mockReturnValue({ dataView: { getName: () => 'dataview', getIndexPattern: () => '.dataview-index' }, }); @@ -151,7 +154,7 @@ describe('SLO Details Page', () => { }); describe('when the incorrect license is found', () => { - it('navigates to the SLO List page', async () => { + it('navigates to the SLO welcome page', async () => { const slo = buildSlo(); jest.spyOn(Router, 'useParams').mockReturnValue({ sloId: slo.id }); useFetchSloDetailsMock.mockReturnValue({ isLoading: false, data: slo }); @@ -159,7 +162,24 @@ describe('SLO Details Page', () => { render(); - expect(mockNavigate).toBeCalledWith(paths.slos); + expect(mockNavigate).toBeCalledWith(paths.slosWelcome); + }); + }); + + describe('when the user has not the requested read permissions ', () => { + it('navigates to the slos welcome page', async () => { + const slo = buildSlo(); + jest.spyOn(Router, 'useParams').mockReturnValue({ sloId: slo.id }); + useFetchSloDetailsMock.mockReturnValue({ isLoading: false, data: slo }); + useLicenseMock.mockReturnValue({ hasAtLeast: () => true }); + usePermissionsMock.mockReturnValue({ + isLoading: false, + data: { hasAllReadRequested: false, hasAllWriteRequested: false }, + }); + + render(); + + expect(mockNavigate).toBeCalledWith(paths.slosWelcome); }); }); diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/slo_details.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/slo_details.tsx index 2c3cf109fa89f..3da2e0f991109 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_details/slo_details.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_details/slo_details.tsx @@ -21,6 +21,7 @@ import { AutoRefreshButton } from '../../components/slo/auto_refresh_button'; import { useAutoRefreshStorage } from '../../components/slo/auto_refresh_button/hooks/use_auto_refresh_storage'; import { useFetchSloDetails } from '../../hooks/use_fetch_slo_details'; import { useLicense } from '../../hooks/use_license'; +import { usePermissions } from '../../hooks/use_permissions'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { useKibana } from '../../utils/kibana_react'; import PageNotFound from '../404'; @@ -41,6 +42,7 @@ export function SloDetailsPage() { const { ObservabilityPageTemplate } = usePluginContext(); const { hasAtLeast } = useLicense(); const hasRightLicense = hasAtLeast('platinum'); + const { data: permissions } = usePermissions(); const { sloId } = useParams(); const { instanceId: sloInstanceId, remoteName } = useGetQueryParams(); @@ -62,8 +64,6 @@ export function SloDetailsPage() { selectedTabId, }); - useBreadcrumbs(getBreadcrumbs(basePath, slo)); - useEffect(() => { if (!slo || !observabilityAIAssistant) { return; @@ -90,15 +90,19 @@ export function SloDetailsPage() { }); }, [observabilityAIAssistant, slo]); + useEffect(() => { + if (hasRightLicense === false || permissions?.hasAllReadRequested === false) { + navigateToUrl(basePath.prepend(paths.slosWelcome)); + } + }, [hasRightLicense, permissions, navigateToUrl, basePath]); + + useBreadcrumbs(getBreadcrumbs(basePath, slo)); + const isSloNotFound = !isLoading && slo === undefined; if (isSloNotFound) { return ; } - if (hasRightLicense === false) { - navigateToUrl(basePath.prepend(paths.slos)); - } - const isPerformingAction = isLoading || isDeleting; const handleToggleAutoRefresh = () => { diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/components/common/documents_table.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/components/common/documents_table.tsx index 7f0e9efb77acc..6d21c3331ed3e 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/components/common/documents_table.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/components/common/documents_table.tsx @@ -97,7 +97,10 @@ export function DocumentsTable({ > {loading && } { diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/slo_edit.test.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/slo_edit.test.tsx index 5f6916720902b..03838225d1618 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/slo_edit.test.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/slo_edit.test.tsx @@ -8,6 +8,8 @@ import { ILicense } from '@kbn/licensing-plugin/common/types'; import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; import { observabilityAIAssistantPluginMock } from '@kbn/observability-ai-assistant-plugin/public/mock'; +import { useCreateRule, useFetchDataViews } from '@kbn/observability-plugin/public'; +import { HeaderMenuPortal } from '@kbn/observability-shared-plugin/public'; import { cleanup, fireEvent, waitFor } from '@testing-library/react'; import { createBrowserHistory } from 'history'; import React from 'react'; @@ -15,20 +17,18 @@ import Router from 'react-router-dom'; import { BehaviorSubject } from 'rxjs'; import { paths } from '../../../common/locators/paths'; import { buildSlo } from '../../data/slo/slo'; -import { useCapabilities } from '../../hooks/use_capabilities'; +import { useCreateDataView } from '../../hooks/use_create_data_view'; import { useCreateSlo } from '../../hooks/use_create_slo'; import { useFetchApmSuggestions } from '../../hooks/use_fetch_apm_suggestions'; +import { useFetchIndices } from '../../hooks/use_fetch_indices'; import { useFetchSloDetails } from '../../hooks/use_fetch_slo_details'; +import { usePermissions } from '../../hooks/use_permissions'; import { useUpdateSlo } from '../../hooks/use_update_slo'; -import { useCreateRule, useFetchDataViews } from '@kbn/observability-plugin/public'; -import { useCreateDataView } from '../../hooks/use_create_data_view'; -import { useFetchIndices } from '../../hooks/use_fetch_indices'; import { useKibana } from '../../utils/kibana_react'; import { kibanaStartMock } from '../../utils/kibana_react.mock'; import { render } from '../../utils/test_helper'; import { SLO_EDIT_FORM_DEFAULT_VALUES } from './constants'; import { SloEditPage } from './slo_edit'; -import { HeaderMenuPortal } from '@kbn/observability-shared-plugin/public'; jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), @@ -36,14 +36,14 @@ jest.mock('react-router-dom', () => ({ })); jest.mock('@kbn/observability-shared-plugin/public'); +jest.mock('@kbn/observability-plugin/public'); jest.mock('../../hooks/use_fetch_indices'); jest.mock('../../hooks/use_create_data_view'); jest.mock('../../hooks/use_fetch_slo_details'); jest.mock('../../hooks/use_create_slo'); jest.mock('../../hooks/use_update_slo'); -jest.mock('@kbn/observability-plugin/public'); jest.mock('../../hooks/use_fetch_apm_suggestions'); -jest.mock('../../hooks/use_capabilities'); +jest.mock('../../hooks/use_permissions'); const mockUseKibanaReturnValue = kibanaStartMock.startContract(); @@ -54,13 +54,13 @@ jest.mock('../../utils/kibana_react', () => ({ const useKibanaMock = useKibana as jest.Mock; const useFetchIndicesMock = useFetchIndices as jest.Mock; const useFetchDataViewsMock = useFetchDataViews as jest.Mock; -const useCreateDataViewsMock = useCreateDataView as jest.Mock; +const useCreateDataViewMock = useCreateDataView as jest.Mock; const useFetchSloMock = useFetchSloDetails as jest.Mock; const useCreateSloMock = useCreateSlo as jest.Mock; const useUpdateSloMock = useUpdateSlo as jest.Mock; const useCreateRuleMock = useCreateRule as jest.Mock; const useFetchApmSuggestionsMock = useFetchApmSuggestions as jest.Mock; -const useCapabilitiesMock = useCapabilities as jest.Mock; +const usePermissionsMock = usePermissions as jest.Mock; const HeaderMenuPortalMock = HeaderMenuPortal as jest.Mock; HeaderMenuPortalMock.mockReturnValue(
Portal node
); @@ -77,12 +77,14 @@ const mockKibana = (license: ILicense | null = licenseMock) => { theme: {}, application: { navigateToUrl: mockNavigate, + capabilities: {}, }, charts: { theme: { useChartsBaseTheme: () => {}, }, }, + dataViewEditor: {}, data: { dataViews: { find: jest.fn().mockReturnValue([]), @@ -164,18 +166,20 @@ describe('SLO Edit Page', () => { data: [ { getName: () => 'dataview', - getIndexPattern: () => '.dataview-index', + getIndexPattern: () => 'some-index', getRuntimeMappings: jest.fn().mockReturnValue({}), }, ], }); - useCreateDataViewsMock.mockReturnValue({ + useCreateDataViewMock.mockReturnValue({ dataView: { getName: () => 'dataview', - getIndexPattern: () => '.dataview-index', + getIndexPattern: () => 'some-index', getRuntimeMappings: jest.fn().mockReturnValue({}), + fields: [{ name: 'custom_timestamp', type: 'date' }], }, + loading: false, }); useFetchIndicesMock.mockReturnValue({ @@ -203,16 +207,21 @@ describe('SLO Edit Page', () => { isError: false, mutateAsync: mockUpdate, }); + + usePermissionsMock.mockReturnValue({ + isLoading: false, + data: { + hasAllWriteRequested: true, + hasAllReadRequested: true, + }, + }); + licenseMock.hasAtLeast.mockReturnValue(true); }); afterEach(cleanup); describe('when the incorrect license is found', () => { beforeEach(() => { - useCapabilitiesMock.mockReturnValue({ - hasWriteCapabilities: true, - hasReadCapabilities: true, - }); licenseMock.hasAtLeast.mockReturnValue(false); }); @@ -232,10 +241,6 @@ describe('SLO Edit Page', () => { describe('when the license is null', () => { beforeEach(() => { - useCapabilitiesMock.mockReturnValue({ - hasWriteCapabilities: true, - hasReadCapabilities: true, - }); mockKibana(null); }); @@ -255,18 +260,17 @@ describe('SLO Edit Page', () => { describe('when the correct license is found', () => { beforeEach(() => { - useCapabilitiesMock.mockReturnValue({ - hasWriteCapabilities: true, - hasReadCapabilities: true, - }); licenseMock.hasAtLeast.mockReturnValue(true); }); describe('with no write permission', () => { beforeEach(() => { - useCapabilitiesMock.mockReturnValue({ - hasWriteCapabilities: false, - hasReadCapabilities: true, + usePermissionsMock.mockReturnValue({ + isLoading: false, + data: { + hasAllWriteRequested: false, + hasAllReadRequested: true, + }, }); }); @@ -284,6 +288,31 @@ describe('SLO Edit Page', () => { }); }); + describe('with no read permission', () => { + beforeEach(() => { + usePermissionsMock.mockReturnValue({ + isLoading: false, + data: { + hasAllWriteRequested: false, + hasAllReadRequested: false, + }, + }); + }); + + it('redirects to the slo welcome page', async () => { + jest.spyOn(Router, 'useParams').mockReturnValue({ sloId: '1234' }); + jest + .spyOn(Router, 'useLocation') + .mockReturnValue({ pathname: '/slos/1234/edit', search: '', state: '', hash: '' }); + + useFetchSloMock.mockReturnValue({ isLoading: false, data: undefined }); + + render(); + + expect(mockNavigate).toBeCalledWith(mockBasePathPrepend(paths.slosWelcome)); + }); + }); + describe('when no sloId route param is provided', () => { beforeEach(() => { useFetchSloMock.mockReturnValue({ isLoading: false, data: undefined }); @@ -345,7 +374,7 @@ describe('SLO Edit Page', () => { describe('when a sloId route param is provided', () => { it('prefills the form with the SLO values', async () => { const slo = buildSlo({ id: '123Foo' }); - useFetchSloMock.mockReturnValue({ isLoading: false, isInitialLoading: false, data: slo }); + useFetchSloMock.mockReturnValue({ isLoading: false, data: slo }); jest.spyOn(Router, 'useParams').mockReturnValue({ sloId: '123Foo' }); jest @@ -429,7 +458,6 @@ describe('SLO Edit Page', () => { describe('when submitting has completed successfully', () => { it('navigates to the SLO List page', async () => { const slo = buildSlo(); - jest.spyOn(Router, 'useParams').mockReturnValue({ sloId: '123' }); jest .spyOn(Router, 'useLocation') @@ -444,9 +472,8 @@ describe('SLO Edit Page', () => { await waitFor(() => { fireEvent.click(getByTestId('sloFormSubmitButton')); }); - await waitFor(() => { - expect(mockNavigate).toBeCalledWith(mockBasePathPrepend(paths.slos)); - }); + + expect(mockNavigate).toBeCalledWith(mockBasePathPrepend(paths.slos)); }); }); }); diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/slo_edit.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/slo_edit.tsx index 09f3b257e6542..aa008838a8b3c 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/slo_edit.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slo_edit/slo_edit.tsx @@ -7,16 +7,15 @@ import { i18n } from '@kbn/i18n'; import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; -import React from 'react'; +import React, { useEffect } from 'react'; import { useParams } from 'react-router-dom'; -import { HeaderMenu } from '../../components/header_menu/header_menu'; -import { useKibana } from '../../utils/kibana_react'; import { paths } from '../../../common/locators/paths'; -import { useCapabilities } from '../../hooks/use_capabilities'; -import { useFetchSloGlobalDiagnosis } from '../../hooks/use_fetch_global_diagnosis'; +import { HeaderMenu } from '../../components/header_menu/header_menu'; import { useFetchSloDetails } from '../../hooks/use_fetch_slo_details'; import { useLicense } from '../../hooks/use_license'; +import { usePermissions } from '../../hooks/use_permissions'; import { usePluginContext } from '../../hooks/use_plugin_context'; +import { useKibana } from '../../utils/kibana_react'; import { SloEditForm } from './components/slo_edit_form'; export function SloEditPage() { @@ -24,11 +23,11 @@ export function SloEditPage() { application: { navigateToUrl }, http: { basePath }, } = useKibana().services; - const { hasWriteCapabilities } = useCapabilities(); - const { isError: hasErrorInGlobalDiagnosis } = useFetchSloGlobalDiagnosis(); + const { sloId } = useParams<{ sloId: string | undefined }>(); + + const { data: permissions } = usePermissions(); const { ObservabilityPageTemplate } = usePluginContext(); - const { sloId } = useParams<{ sloId: string | undefined }>(); const { hasAtLeast } = useLicense(); const hasRightLicense = hasAtLeast('platinum'); const { data: slo } = useFetchSloDetails({ sloId }); @@ -60,9 +59,15 @@ export function SloEditPage() { }, ]); - if (hasRightLicense === false || !hasWriteCapabilities || hasErrorInGlobalDiagnosis) { - navigateToUrl(basePath.prepend(paths.slos)); - } + useEffect(() => { + if (hasRightLicense === false || permissions?.hasAllReadRequested === false) { + navigateToUrl(basePath.prepend(paths.slosWelcome)); + } + + if (permissions?.hasAllWriteRequested === false) { + navigateToUrl(basePath.prepend(paths.slos)); + } + }, [hasRightLicense, permissions, navigateToUrl, basePath]); return ( {i18n.translate('xpack.slo.slosOutdatedDefinitions.sloPermissionsError', { @@ -96,7 +89,7 @@ export function SlosOutdatedDefinitions() { > - {!hasSlosAndHasPermissions ? ( + {!!errors ? ( errors ) : ( <> diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/create_slo_btn.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/create_slo_btn.tsx index 14701ef9e4f60..7add8debe165b 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/create_slo_btn.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/create_slo_btn.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { useKibana } from '../../../../utils/kibana_react'; import { paths } from '../../../../../common/locators/paths'; -import { useCapabilities } from '../../../../hooks/use_capabilities'; +import { usePermissions } from '../../../../hooks/use_permissions'; export function CreateSloBtn() { const { @@ -18,7 +18,7 @@ export function CreateSloBtn() { http: { basePath }, } = useKibana().services; - const { hasWriteCapabilities } = useCapabilities(); + const { data: permissions } = usePermissions(); const handleClickCreateSlo = () => { navigateToUrl(basePath.prepend(paths.sloCreate)); @@ -27,7 +27,7 @@ export function CreateSloBtn() { diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/sort_by_select.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/sort_by_select.tsx index 3d977a19cb0a8..189dbee8b4f8c 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/sort_by_select.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/common/sort_by_select.tsx @@ -97,6 +97,51 @@ export function SLOSortBy({ state, onStateChange, loading }: Props) { }); }, }, + { + label: i18n.translate('xpack.slo.list.sortBy.fiveMinuteBurnRate', { + defaultMessage: '5m burn rate', + }), + checked: sortBy === 'burn_rate_5m', + value: 'burn_rate_5m', + onClick: () => { + handleChangeSortBy({ + value: 'burn_rate_5m', + label: i18n.translate('xpack.slo.list.sortBy.fiveMinuteBurnRate', { + defaultMessage: '5m burn rate', + }), + }); + }, + }, + { + label: i18n.translate('xpack.slo.list.sortBy.oneHourBurnRate', { + defaultMessage: '1h burn rate', + }), + checked: sortBy === 'burn_rate_1h', + value: 'burn_rate_1h', + onClick: () => { + handleChangeSortBy({ + value: 'burn_rate_1h', + label: i18n.translate('xpack.slo.list.sortBy.oneHourBurnRate', { + defaultMessage: '1h burn rate', + }), + }); + }, + }, + { + label: i18n.translate('xpack.slo.list.sortBy.oneDayBurnRate', { + defaultMessage: '1d burn rate', + }), + checked: sortBy === 'burn_rate_1d', + value: 'burn_rate_1d', + onClick: () => { + handleChangeSortBy({ + value: 'burn_rate_1d', + label: i18n.translate('xpack.slo.list.sortBy.oneDayBurnRate', { + defaultMessage: '1d burn rate', + }), + }); + }, + }, ]; const groupLabel = sortByOptions.find((option) => option.value === sortBy)?.label || 'Default'; diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/compact_view/slo_list_compact_view.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/compact_view/slo_list_compact_view.tsx index d773d56ac47f8..0d556c0546e1f 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/compact_view/slo_list_compact_view.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/compact_view/slo_list_compact_view.tsx @@ -28,12 +28,12 @@ import { SloResetConfirmationModal } from '../../../../components/slo/reset_conf import { SloStatusBadge } from '../../../../components/slo/slo_status_badge'; import { SloActiveAlertsBadge } from '../../../../components/slo/slo_status_badge/slo_active_alerts_badge'; import { sloKeys } from '../../../../hooks/query_key_factory'; -import { useCapabilities } from '../../../../hooks/use_capabilities'; import { useCloneSlo } from '../../../../hooks/use_clone_slo'; import { useFetchActiveAlerts } from '../../../../hooks/use_fetch_active_alerts'; import { useFetchHistoricalSummary } from '../../../../hooks/use_fetch_historical_summary'; import { useFetchRulesForSlo } from '../../../../hooks/use_fetch_rules_for_slo'; import { useGetFilteredRuleTypes } from '../../../../hooks/use_get_filtered_rule_types'; +import { usePermissions } from '../../../../hooks/use_permissions'; import { useResetSlo } from '../../../../hooks/use_reset_slo'; import { useSpace } from '../../../../hooks/use_space'; import { useKibana } from '../../../../utils/kibana_react'; @@ -74,7 +74,7 @@ export function SloListCompactView({ sloList, loading, error }: Props) { (slo) => [slo.id, slo.instanceId ?? ALL_VALUE] as [string, string] ); - const { hasWriteCapabilities } = useCapabilities(); + const { data: permissions } = usePermissions(); const filteredRuleTypes = useGetFilteredRuleTypes(); const queryClient = useQueryClient(); @@ -173,7 +173,8 @@ export function SloListCompactView({ sloList, loading, error }: Props) { defaultMessage: 'Edit', }), 'data-test-subj': 'sloActionsEdit', - enabled: (slo) => (hasWriteCapabilities && !isRemote(slo)) || hasRemoteKibanaUrl(slo), + enabled: (slo) => + (permissions?.hasAllWriteRequested && !isRemote(slo)) || hasRemoteKibanaUrl(slo), onClick: (slo: SLOWithSummaryResponse) => { const remoteEditUrl = createRemoteSloEditUrl(slo, spaceId); if (!!remoteEditUrl) { @@ -193,7 +194,8 @@ export function SloListCompactView({ sloList, loading, error }: Props) { defaultMessage: 'Create new alert rule', }), 'data-test-subj': 'sloActionsCreateRule', - enabled: (slo: SLOWithSummaryResponse) => hasWriteCapabilities && !isRemote(slo), + enabled: (slo: SLOWithSummaryResponse) => + !!permissions?.hasAllWriteRequested && !isRemote(slo), onClick: (slo: SLOWithSummaryResponse) => { setSloToAddRule(slo); }, @@ -208,7 +210,8 @@ export function SloListCompactView({ sloList, loading, error }: Props) { defaultMessage: 'Manage rules', }), 'data-test-subj': 'sloActionsManageRules', - enabled: (slo: SLOWithSummaryResponse) => hasWriteCapabilities && !isRemote(slo), + enabled: (slo: SLOWithSummaryResponse) => + !!permissions?.hasAllWriteRequested && !isRemote(slo), onClick: (slo: SLOWithSummaryResponse) => { const locator = locators.get(rulesLocatorID); locator?.navigate({ params: { sloId: slo.id } }, { replace: false }); @@ -227,7 +230,7 @@ export function SloListCompactView({ sloList, loading, error }: Props) { }), 'data-test-subj': 'sloActionsClone', enabled: (slo: SLOWithSummaryResponse) => - (hasWriteCapabilities && !isRemote(slo)) || hasRemoteKibanaUrl(slo), + (permissions?.hasAllWriteRequested && !isRemote(slo)) || hasRemoteKibanaUrl(slo), onClick: (slo: SLOWithSummaryResponse) => { navigateToClone(slo); }, @@ -245,7 +248,7 @@ export function SloListCompactView({ sloList, loading, error }: Props) { }), 'data-test-subj': 'sloActionsDelete', enabled: (slo: SLOWithSummaryResponse) => - (hasWriteCapabilities && !isRemote(slo)) || hasRemoteKibanaUrl(slo), + (permissions?.hasAllWriteRequested && !isRemote(slo)) || hasRemoteKibanaUrl(slo), onClick: (slo: SLOWithSummaryResponse) => { const remoteDeleteUrl = createRemoteSloDeleteUrl(slo, spaceId); if (!!remoteDeleteUrl) { @@ -268,7 +271,7 @@ export function SloListCompactView({ sloList, loading, error }: Props) { }), 'data-test-subj': 'sloActionsReset', enabled: (slo: SLOWithSummaryResponse) => - (hasWriteCapabilities && !isRemote(slo)) || hasRemoteKibanaUrl(slo), + (permissions?.hasAllWriteRequested && !isRemote(slo)) || hasRemoteKibanaUrl(slo), onClick: (slo: SLOWithSummaryResponse) => { const remoteResetUrl = createRemoteSloResetUrl(slo, spaceId); if (!!remoteResetUrl) { diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/grouped_slos/group_list_view.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/grouped_slos/group_list_view.tsx index eefe5c1ba1e87..cee082338d78c 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/grouped_slos/group_list_view.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/grouped_slos/group_list_view.tsx @@ -27,12 +27,12 @@ import React, { memo, useState } from 'react'; import { paths } from '../../../../../common/locators/paths'; import { useFetchSloList } from '../../../../hooks/use_fetch_slo_list'; import { useKibana } from '../../../../utils/kibana_react'; -import { SLI_OPTIONS } from '../../../slo_edit/constants'; import { useSloFormattedSLIValue } from '../../hooks/use_slo_summary'; import type { SortDirection, SortField } from '../../hooks/use_url_search_state'; import { SlosView } from '../slos_view'; import { GroupByField } from '../slo_list_group_by'; import { SLOView } from '../toggle_slo_view'; +import { useGroupName } from './hooks/use_group_name'; interface Props { group: string; @@ -57,26 +57,7 @@ export function GroupListView({ }: Props) { const groupQuery = `"${groupBy}": "${group}"`; const query = kqlQuery ? `${groupQuery} and ${kqlQuery}` : groupQuery; - let groupName = group.toLowerCase(); - if (groupBy === 'slo.indicator.type') { - groupName = SLI_OPTIONS.find((option) => option.value === group)?.text ?? group; - } - if (groupBy === '_index') { - // get remote cluster name from index name - if (groupName.includes(':.')) { - const [remoteClusterName] = groupName.split(':.'); - groupName = i18n.translate('xpack.slo.group.remoteCluster', { - defaultMessage: 'Remote Cluster: {remoteClusterName}', - values: { - remoteClusterName, - }, - }); - } else { - groupName = i18n.translate('xpack.slo.group.remoteCluster.localKibana', { - defaultMessage: 'Local Kibana', - }); - } - } + const groupName = useGroupName(groupBy, group, summary); const [page, setPage] = useState(0); const [accordionState, setAccordionState] = useState<'open' | 'closed'>('closed'); diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/grouped_slos/group_view.test.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/grouped_slos/group_view.test.tsx index 3c61534d479b7..775659f6be580 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/grouped_slos/group_view.test.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/grouped_slos/group_view.test.tsx @@ -105,17 +105,65 @@ describe('Group View', () => { { group: 'production', groupBy: 'slo.tags', - summary: { total: 3, worst: 0.95, healthy: 2, violated: 1, degrading: 0, noData: 0 }, + summary: { + total: 3, + worst: { + sliValue: 1, + status: 'healthy', + slo: { + id: 'irrelevant', + instanceId: 'irrelevant', + name: 'irrelevant', + groupings: {}, + }, + }, + healthy: 2, + violated: 1, + degrading: 0, + noData: 0, + }, }, { group: 'something', groupBy: 'slo.tags', - summary: { total: 1, worst: 0.9, healthy: 0, violated: 1, degrading: 0, noData: 0 }, + summary: { + total: 1, + worst: { + sliValue: 1, + status: 'healthy', + slo: { + id: 'irrelevant', + instanceId: 'irrelevant', + name: 'irrelevant', + groupings: {}, + }, + }, + healthy: 0, + violated: 1, + degrading: 0, + noData: 0, + }, }, { group: 'anything', groupBy: 'slo.tags', - summary: { total: 2, worst: 0.85, healthy: 1, violated: 0, degrading: 0, noData: 1 }, + summary: { + total: 2, + worst: { + sliValue: 1, + status: 'healthy', + slo: { + id: 'irrelevant', + instanceId: 'irrelevant', + name: 'irrelevant', + groupings: {}, + }, + }, + healthy: 1, + violated: 0, + degrading: 0, + noData: 1, + }, }, ], }, @@ -163,7 +211,7 @@ describe('Group View', () => { results: [ { group: 'production', - groupBy: 'tags', + groupBy: 'slo.tags', summary: { total: 3, worst: 0.95, healthy: 2, violated: 1, degrading: 0, noData: 0 }, }, ], diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/grouped_slos/hooks/use_group_name.test.ts b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/grouped_slos/hooks/use_group_name.test.ts new file mode 100644 index 0000000000000..fa2b5192665ea --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/grouped_slos/hooks/use_group_name.test.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SLO_SUMMARY_DESTINATION_INDEX_PATTERN } from '../../../../../../common/constants'; +import { GroupSummary } from '@kbn/slo-schema'; +import { useGroupName } from './use_group_name'; + +describe('useGroupName', () => { + it('returns the group name for ungrouped', () => { + const groupName = useGroupName('ungrouped', 'irrelevant', {} as GroupSummary); + expect(groupName).toBe('irrelevant'); + }); + + it('returns the group name for slo tags', () => { + const groupName = useGroupName('slo.tags', 'some-tag', {} as GroupSummary); + expect(groupName).toBe('some-tag'); + }); + + it('returns the group name for status', () => { + const groupName = useGroupName('status', 'HEALTHY', {} as GroupSummary); + expect(groupName).toBe('healthy'); + }); + + it('returns the group name for slo instanceId', () => { + const summary = { + total: 2, + violated: 0, + healthy: 2, + degrading: 0, + noData: 0, + worst: { + sliValue: 1, + status: 'healthy', + slo: { + id: 'slo-id', + name: 'slo-name', + instanceId: 'domain.com', + groupings: { + host: { + name: 'domain.com', + }, + }, + }, + }, + } as GroupSummary; + const groupName = useGroupName('slo.instanceId', 'domain.com', summary); + expect(groupName).toBe('host.name: domain.com'); + }); + + it('returns the group name for slo indicator type', () => { + const groupName = useGroupName('slo.indicator.type', 'sli.kql.custom', {} as GroupSummary); + expect(groupName).toBe('Custom Query'); + }); + + it('returns the group name for local index', () => { + const groupName = useGroupName( + '_index', + SLO_SUMMARY_DESTINATION_INDEX_PATTERN, + {} as GroupSummary + ); + expect(groupName).toBe('Local Kibana'); + }); + + it('returns the group name for remote index', () => { + const groupName = useGroupName( + '_index', + `my-remote-cluster:${SLO_SUMMARY_DESTINATION_INDEX_PATTERN}`, + {} as GroupSummary + ); + expect(groupName).toBe('Remote Cluster: my-remote-cluster'); + }); +}); diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/grouped_slos/hooks/use_group_name.ts b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/grouped_slos/hooks/use_group_name.ts new file mode 100644 index 0000000000000..ca6ddff88fb19 --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/grouped_slos/hooks/use_group_name.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { ALL_VALUE, GroupSummary } from '@kbn/slo-schema'; +import { assertNever } from '@kbn/std'; +import { SLI_OPTIONS } from '../../../../slo_edit/constants'; +import { GroupByField } from '../../slo_list_group_by'; + +export function useGroupName(groupBy: GroupByField, group: string, summary?: GroupSummary) { + const groupName = group.toLowerCase(); + + switch (groupBy) { + case 'ungrouped': + case 'slo.tags': + case 'status': + return groupName; + case 'slo.instanceId': + if (groupName === ALL_VALUE || !summary) { + return i18n.translate('xpack.slo.group.ungroupedInstanceId', { + defaultMessage: 'Ungrouped', + }); + } + + const groupNames = flattenObject(summary.worst.slo.groupings); + return Object.entries(groupNames) + .map(([key, value]) => `${key}: ${value}`) + .join(', '); + case 'slo.indicator.type': + return SLI_OPTIONS.find((option) => option.value === group)?.text ?? groupName; + case '_index': + if (groupName.includes(':.')) { + const [remoteClusterName] = groupName.split(':.'); + return i18n.translate('xpack.slo.group.remoteCluster', { + defaultMessage: 'Remote Cluster: {remoteClusterName}', + values: { + remoteClusterName, + }, + }); + } + + return i18n.translate('xpack.slo.group.remoteCluster.localKibana', { + defaultMessage: 'Local Kibana', + }); + + default: + assertNever(groupBy); + } +} + +function flattenObject(obj: Record, parentKey = '', result: Record = {}) { + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + const newKey = parentKey ? `${parentKey}.${key}` : key; + if (typeof obj[key] === 'object' && obj[key] !== null) { + flattenObject(obj[key], newKey, result); + } else { + result[newKey] = obj[key]; + } + } + } + return result; +} diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/slo_item_actions.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/slo_item_actions.tsx index a5de471bae883..b3b6dbc645a2c 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slos/components/slo_item_actions.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/components/slo_item_actions.tsx @@ -19,7 +19,7 @@ import { SLOWithSummaryResponse } from '@kbn/slo-schema'; import { Rule } from '@kbn/triggers-actions-ui-plugin/public'; import React from 'react'; import styled from 'styled-components'; -import { useCapabilities } from '../../../hooks/use_capabilities'; +import { usePermissions } from '../../../hooks/use_permissions'; import { useCloneSlo } from '../../../hooks/use_clone_slo'; import { BurnRateRuleParams } from '../../../typings'; import { useKibana } from '../../../utils/kibana_react'; @@ -76,7 +76,7 @@ export function SloItemActions({ } = useKibana().services; const executionContextName = executionContext.get().name; const isDashboardContext = executionContextName === 'dashboards'; - const { hasWriteCapabilities } = useCapabilities(); + const { data: permissions } = usePermissions(); const navigateToClone = useCloneSlo(); const { handleNavigateToRules, sloEditUrl, remoteDeleteUrl, remoteResetUrl, sloDetailsUrl } = @@ -182,7 +182,7 @@ export function SloItemActions({ , ) => void; state: SearchState; @@ -80,6 +86,16 @@ export function SloGroupBy({ onStateChange, state, loading }: Props) { handleChangeGroupBy('slo.indicator.type'); }, }, + { + label: i18n.translate('xpack.slo.list.groupBy.sloInstanceId', { + defaultMessage: 'SLO instance id', + }), + checked: groupBy === 'slo.instanceId', + value: 'slo.instanceId', + onClick: () => { + handleChangeGroupBy('slo.instanceId'); + }, + }, ]; if (hasRemoteEnabled) { diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/hooks/use_url_search_state.ts b/x-pack/plugins/observability_solution/slo/public/pages/slos/hooks/use_url_search_state.ts index a4b842c2ee19f..59284ee617919 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slos/hooks/use_url_search_state.ts +++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/hooks/use_url_search_state.ts @@ -15,7 +15,15 @@ import type { GroupByField } from '../components/slo_list_group_by'; import type { SLOView } from '../components/toggle_slo_view'; export const SLO_LIST_SEARCH_URL_STORAGE_KEY = 'search'; -export type SortField = 'sli_value' | 'error_budget_consumed' | 'error_budget_remaining' | 'status'; +export type SortField = + | 'sli_value' + | 'error_budget_consumed' + | 'error_budget_remaining' + | 'status' + | 'burn_rate_5m' + | 'burn_rate_1h' + | 'burn_rate_1d'; + export type SortDirection = 'asc' | 'desc'; export interface SearchState { diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/slos.test.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slos/slos.test.tsx index 1d6debec567b4..1510af2e6d194 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slos/slos.test.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/slos.test.tsx @@ -15,7 +15,7 @@ import Router from 'react-router-dom'; import { paths } from '../../../common/locators/paths'; import { historicalSummaryData } from '../../data/slo/historical_summary_data'; import { emptySloList, sloList } from '../../data/slo/slo'; -import { useCapabilities } from '../../hooks/use_capabilities'; +import { usePermissions } from '../../hooks/use_permissions'; import { useCreateSlo } from '../../hooks/use_create_slo'; import { useDeleteSlo } from '../../hooks/use_delete_slo'; import { useDeleteSloInstance } from '../../hooks/use_delete_slo_instance'; @@ -43,6 +43,7 @@ jest.mock('../slo_settings/use_get_settings'); jest.mock('../../hooks/use_delete_slo'); jest.mock('../../hooks/use_delete_slo_instance'); jest.mock('../../hooks/use_fetch_historical_summary'); +jest.mock('../../hooks/use_permissions'); jest.mock('../../hooks/use_capabilities'); jest.mock('../../hooks/use_create_data_view'); @@ -54,7 +55,7 @@ const useCreateSloMock = useCreateSlo as jest.Mock; const useDeleteSloMock = useDeleteSlo as jest.Mock; const useDeleteSloInstanceMock = useDeleteSloInstance as jest.Mock; const useFetchHistoricalSummaryMock = useFetchHistoricalSummary as jest.Mock; -const useCapabilitiesMock = useCapabilities as jest.Mock; +const usePermissionsMock = usePermissions as jest.Mock; const useCreateDataViewMock = useCreateDataView as jest.Mock; const TagsListMock = TagsList as jest.Mock; @@ -157,7 +158,10 @@ describe('SLOs Page', () => { selectedRemoteClusters: [], }, }); - useCapabilitiesMock.mockReturnValue({ hasWriteCapabilities: true, hasReadCapabilities: true }); + usePermissionsMock.mockReturnValue({ + isLoading: false, + data: { hasAllReadRequested: true, hasAllWriteRequested: true }, + }); jest .spyOn(Router, 'useLocation') .mockReturnValue({ pathname: '/slos', search: '', state: '', hash: '' }); @@ -175,6 +179,7 @@ describe('SLOs Page', () => { data: {}, }); }); + it('navigates to the SLOs Welcome Page', async () => { await act(async () => { render(); @@ -207,9 +212,28 @@ describe('SLOs Page', () => { }); }); - it('should have a create new SLO button', async () => { + it('navigates to the SLOs Welcome Page when the user has not the request read permissions', async () => { useFetchSloListMock.mockReturnValue({ isLoading: false, data: sloList }); + useFetchHistoricalSummaryMock.mockReturnValue({ + isLoading: false, + data: historicalSummaryData, + }); + usePermissionsMock.mockReturnValue({ + isLoading: false, + data: { hasAllReadRequested: false, hasAllWriteRequested: false }, + }); + await act(async () => { + render(); + }); + + await waitFor(() => { + expect(mockNavigate).toBeCalledWith(paths.slosWelcome); + }); + }); + + it('should have a create new SLO button', async () => { + useFetchSloListMock.mockReturnValue({ isLoading: false, data: sloList }); useFetchHistoricalSummaryMock.mockReturnValue({ isLoading: false, data: historicalSummaryData, diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos/slos.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slos/slos.tsx index fdb40e12ba4e2..30d519e61e38f 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slos/slos.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slos/slos.tsx @@ -15,6 +15,7 @@ import { HeaderMenu } from '../../components/header_menu/header_menu'; import { SloOutdatedCallout } from '../../components/slo/slo_outdated_callout'; import { useFetchSloList } from '../../hooks/use_fetch_slo_list'; import { useLicense } from '../../hooks/use_license'; +import { usePermissions } from '../../hooks/use_permissions'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { useKibana } from '../../utils/kibana_react'; import { CreateSloBtn } from './components/common/create_slo_btn'; @@ -31,14 +32,9 @@ export function SlosPage() { } = useKibana().services; const { ObservabilityPageTemplate } = usePluginContext(); const { hasAtLeast } = useLicense(); + const { data: permissions } = usePermissions(); - const { - isLoading, - isError, - data: sloList, - } = useFetchSloList({ - perPage: 0, - }); + const { isLoading, isError, data: sloList } = useFetchSloList({ perPage: 0 }); const { total } = sloList ?? { total: 0 }; useBreadcrumbs([ @@ -55,7 +51,11 @@ export function SlosPage() { if ((!isLoading && total === 0) || hasAtLeast('platinum') === false || isError) { navigateToUrl(basePath.prepend(paths.slosWelcome)); } - }, [basePath, hasAtLeast, isError, isLoading, navigateToUrl, total]); + + if (permissions?.hasAllReadRequested === false) { + navigateToUrl(basePath.prepend(paths.slosWelcome)); + } + }, [basePath, hasAtLeast, isError, isLoading, navigateToUrl, total, permissions]); return ( Portal node
); @@ -38,8 +36,7 @@ HeaderMenuPortalMock.mockReturnValue(
Portal node
); const useKibanaMock = useKibana as jest.Mock; const useLicenseMock = useLicense as jest.Mock; const useFetchSloListMock = useFetchSloList as jest.Mock; -const useCapabilitiesMock = useCapabilities as jest.Mock; -const useGlobalDiagnosisMock = useFetchSloGlobalDiagnosis as jest.Mock; +const usePermissionsMock = usePermissions as jest.Mock; const mockNavigate = jest.fn(); @@ -64,7 +61,6 @@ describe('SLOs Welcome Page', () => { beforeEach(() => { jest.clearAllMocks(); mockKibana(); - useCapabilitiesMock.mockReturnValue({ hasWriteCapabilities: true, hasReadCapabilities: true }); jest .spyOn(Router, 'useLocation') .mockReturnValue({ pathname: '/slos/welcome', search: '', state: '', hash: '' }); @@ -74,9 +70,11 @@ describe('SLOs Welcome Page', () => { it('renders the welcome message with subscription buttons', async () => { useFetchSloListMock.mockReturnValue({ isLoading: false, data: emptySloList }); useLicenseMock.mockReturnValue({ hasAtLeast: () => false }); - useGlobalDiagnosisMock.mockReturnValue({ + usePermissionsMock.mockReturnValue({ + isLoading: false, data: { - userPrivileges: { write: { has_all_requested: true }, read: { has_all_requested: true } }, + hasAllWriteRequested: true, + hasAllReadRequested: true, }, }); @@ -91,8 +89,12 @@ describe('SLOs Welcome Page', () => { describe('when the correct license is found', () => { beforeEach(() => { useLicenseMock.mockReturnValue({ hasAtLeast: () => true }); - useGlobalDiagnosisMock.mockReturnValue({ - isError: false, + usePermissionsMock.mockReturnValue({ + isLoading: false, + data: { + hasAllWriteRequested: true, + hasAllReadRequested: true, + }, }); }); @@ -102,9 +104,12 @@ describe('SLOs Welcome Page', () => { }); it('disables the create slo button when no write capabilities', async () => { - useCapabilitiesMock.mockReturnValue({ - hasWriteCapabilities: false, - hasReadCapabilities: true, + usePermissionsMock.mockReturnValue({ + isLoading: false, + data: { + hasAllWriteRequested: false, + hasAllReadRequested: true, + }, }); render(); @@ -117,16 +122,11 @@ describe('SLOs Welcome Page', () => { }); it('disables the create slo button when no cluster permissions capabilities', async () => { - useCapabilitiesMock.mockReturnValue({ - hasWriteCapabilities: true, - hasReadCapabilities: true, - }); - useGlobalDiagnosisMock.mockReturnValue({ + usePermissionsMock.mockReturnValue({ + isLoading: false, data: { - userPrivileges: { - write: { has_all_requested: false }, - read: { has_all_requested: true }, - }, + hasAllWriteRequested: false, + hasAllReadRequested: true, }, }); @@ -138,12 +138,11 @@ describe('SLOs Welcome Page', () => { }); it('should display the welcome message with a Create new SLO button which should navigate to the SLO Creation page', async () => { - useGlobalDiagnosisMock.mockReturnValue({ + usePermissionsMock.mockReturnValue({ + isLoading: false, data: { - userPrivileges: { - write: { has_all_requested: true }, - read: { has_all_requested: true }, - }, + hasAllWriteRequested: true, + hasAllReadRequested: true, }, }); @@ -163,12 +162,11 @@ describe('SLOs Welcome Page', () => { describe('when loading is done and results are found', () => { beforeEach(() => { useFetchSloListMock.mockReturnValue({ isLoading: false, data: sloList }); - useGlobalDiagnosisMock.mockReturnValue({ + usePermissionsMock.mockReturnValue({ + isLoading: false, data: { - userPrivileges: { - write: { has_all_requested: true }, - read: { has_all_requested: true }, - }, + hasAllWriteRequested: true, + hasAllReadRequested: true, }, }); }); diff --git a/x-pack/plugins/observability_solution/slo/public/pages/slos_welcome/slos_welcome.tsx b/x-pack/plugins/observability_solution/slo/public/pages/slos_welcome/slos_welcome.tsx index 53b08809ecc6a..fc03009beb72e 100644 --- a/x-pack/plugins/observability_solution/slo/public/pages/slos_welcome/slos_welcome.tsx +++ b/x-pack/plugins/observability_solution/slo/public/pages/slos_welcome/slos_welcome.tsx @@ -5,65 +5,61 @@ * 2.0. */ -import React, { useEffect } from 'react'; import { - EuiPageTemplate, EuiButton, - EuiTitle, - EuiLink, - EuiImage, - EuiSpacer, EuiFlexGroup, EuiFlexItem, + EuiImage, + EuiLink, + EuiPageTemplate, + EuiSpacer, + EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; - -import { useKibana } from '../../utils/kibana_react'; +import React, { useEffect } from 'react'; +import { paths } from '../../../common/locators/paths'; +import { HeaderMenu } from '../../components/header_menu/header_menu'; +import { SloOutdatedCallout } from '../../components/slo/slo_outdated_callout'; +import { SloPermissionsCallout } from '../../components/slo/slo_permissions_callout'; +import { useFetchSloList } from '../../hooks/use_fetch_slo_list'; import { useLicense } from '../../hooks/use_license'; +import { usePermissions } from '../../hooks/use_permissions'; import { usePluginContext } from '../../hooks/use_plugin_context'; -import { useCapabilities } from '../../hooks/use_capabilities'; -import { useFetchSloList } from '../../hooks/use_fetch_slo_list'; -import { paths } from '../../../common/locators/paths'; +import { useKibana } from '../../utils/kibana_react'; import illustration from './assets/illustration.svg'; -import { useFetchSloGlobalDiagnosis } from '../../hooks/use_fetch_global_diagnosis'; -import { SloOutdatedCallout } from '../../components/slo/slo_outdated_callout'; -import { HeaderMenu } from '../../components/header_menu/header_menu'; export function SlosWelcomePage() { const { application: { navigateToUrl }, http: { basePath }, } = useKibana().services; - const { hasWriteCapabilities } = useCapabilities(); - const { data: globalDiagnosis } = useFetchSloGlobalDiagnosis(); - const { ObservabilityPageTemplate } = usePluginContext(); + const { ObservabilityPageTemplate } = usePluginContext(); + const { data: permissions } = usePermissions(); const { hasAtLeast } = useLicense(); const hasRightLicense = hasAtLeast('platinum'); - const { isLoading, data: sloList } = useFetchSloList(); + const { data: sloList } = useFetchSloList(); const { total } = sloList ?? { total: 0 }; - const hasRequiredWritePrivileges = !!globalDiagnosis?.userPrivileges.write.has_all_requested; - const hasRequiredReadPrivileges = !!globalDiagnosis?.userPrivileges.read.has_all_requested; + const hasSlosAndPermissions = + total > 0 && hasRightLicense && permissions?.hasAllReadRequested === true; const handleClickCreateSlo = () => { navigateToUrl(basePath.prepend(paths.sloCreate)); }; - const hasSlosAndHasPermissions = - total > 0 && hasAtLeast('platinum') === true && hasRequiredReadPrivileges; - useEffect(() => { - if (hasSlosAndHasPermissions) { + if (hasSlosAndPermissions) { navigateToUrl(basePath.prepend(paths.slos)); } - }, [basePath, hasSlosAndHasPermissions, navigateToUrl]); + }, [basePath, navigateToUrl, hasSlosAndPermissions]); - return hasSlosAndHasPermissions || isLoading ? null : ( + return ( + @@ -117,7 +113,7 @@ export function SlosWelcomePage() { fill color="primary" onClick={handleClickCreateSlo} - disabled={!hasWriteCapabilities || !hasRequiredWritePrivileges} + disabled={!permissions?.hasAllWriteRequested} > {i18n.translate('xpack.slo.sloList.welcomePrompt.buttonLabel', { defaultMessage: 'Create SLO', diff --git a/x-pack/plugins/observability_solution/slo/public/utils/slo/remote_slo_urls.test.ts b/x-pack/plugins/observability_solution/slo/public/utils/slo/remote_slo_urls.test.ts index 2c8476b9c2e09..3c0495fd1bc9b 100644 --- a/x-pack/plugins/observability_solution/slo/public/utils/slo/remote_slo_urls.test.ts +++ b/x-pack/plugins/observability_solution/slo/public/utils/slo/remote_slo_urls.test.ts @@ -51,7 +51,7 @@ describe('remote SLO URLs Utils', () => { `"https://cloud.elast.co/app/slos/edit/fixed-id"` ); expect(createRemoteSloCloneUrl(remoteSlo)).toMatchInlineSnapshot( - `"https://cloud.elast.co/app/slos/create?_a=(budgetingMethod:occurrences,createdAt:%272022-12-29T10:11:12.000Z%27,description:%27some%20description%20useful%27,enabled:!t,groupBy:%27*%27,groupings:(),indicator:(params:(filter:%27baz:%20foo%20and%20bar%20%3E%202%27,good:%27http_status:%202xx%27,index:some-index,timestampField:custom_timestamp,total:%27a%20query%27),type:sli.kql.custom),instanceId:%27*%27,meta:(),name:%27[Copy]%20super%20important%20level%20service%27,objective:(target:0.98),remote:(kibanaUrl:%27https:/cloud.elast.co/kibana%27,remoteName:remote_cluster),revision:1,settings:(frequency:%271m%27,preventInitialBackfill:!f,syncDelay:%271m%27),summary:(errorBudget:(consumed:0.064,initial:0.02,isEstimated:!f,remaining:0.936),sliValue:0.99872,status:HEALTHY),tags:!(k8s,production,critical),timeWindow:(duration:%2730d%27,type:rolling),updatedAt:%272022-12-29T10:11:12.000Z%27,version:2)"` + `"https://cloud.elast.co/app/slos/create?_a=(budgetingMethod:occurrences,createdAt:%272022-12-29T10:11:12.000Z%27,description:%27some%20description%20useful%27,enabled:!t,groupBy:%27*%27,groupings:(),indicator:(params:(filter:%27baz:%20foo%20and%20bar%20%3E%202%27,good:%27http_status:%202xx%27,index:some-index,timestampField:custom_timestamp,total:%27a%20query%27),type:sli.kql.custom),instanceId:%27*%27,meta:(),name:%27[Copy]%20super%20important%20level%20service%27,objective:(target:0.98),remote:(kibanaUrl:%27https:/cloud.elast.co/kibana%27,remoteName:remote_cluster),revision:1,settings:(frequency:%271m%27,preventInitialBackfill:!f,syncDelay:%271m%27),summary:(errorBudget:(consumed:0.064,initial:0.02,isEstimated:!f,remaining:0.936),fiveMinuteBurnRate:0,oneDayBurnRate:0,oneHourBurnRate:0,sliValue:0.99872,status:HEALTHY),tags:!(k8s,production,critical),timeWindow:(duration:%2730d%27,type:rolling),updatedAt:%272022-12-29T10:11:12.000Z%27,version:2)"` ); }); @@ -71,7 +71,7 @@ describe('remote SLO URLs Utils', () => { `"https://cloud.elast.co/s/my-custom-space/app/slos/edit/fixed-id"` ); expect(createRemoteSloCloneUrl(remoteSlo, 'my-custom-space')).toMatchInlineSnapshot( - `"https://cloud.elast.co/s/my-custom-space/app/slos/create?_a=(budgetingMethod:occurrences,createdAt:%272022-12-29T10:11:12.000Z%27,description:%27some%20description%20useful%27,enabled:!t,groupBy:%27*%27,groupings:(),indicator:(params:(filter:%27baz:%20foo%20and%20bar%20%3E%202%27,good:%27http_status:%202xx%27,index:some-index,timestampField:custom_timestamp,total:%27a%20query%27),type:sli.kql.custom),instanceId:%27*%27,meta:(),name:%27[Copy]%20super%20important%20level%20service%27,objective:(target:0.98),remote:(kibanaUrl:%27https:/cloud.elast.co/kibana%27,remoteName:remote_cluster),revision:1,settings:(frequency:%271m%27,preventInitialBackfill:!f,syncDelay:%271m%27),summary:(errorBudget:(consumed:0.064,initial:0.02,isEstimated:!f,remaining:0.936),sliValue:0.99872,status:HEALTHY),tags:!(k8s,production,critical),timeWindow:(duration:%2730d%27,type:rolling),updatedAt:%272022-12-29T10:11:12.000Z%27,version:2)"` + `"https://cloud.elast.co/s/my-custom-space/app/slos/create?_a=(budgetingMethod:occurrences,createdAt:%272022-12-29T10:11:12.000Z%27,description:%27some%20description%20useful%27,enabled:!t,groupBy:%27*%27,groupings:(),indicator:(params:(filter:%27baz:%20foo%20and%20bar%20%3E%202%27,good:%27http_status:%202xx%27,index:some-index,timestampField:custom_timestamp,total:%27a%20query%27),type:sli.kql.custom),instanceId:%27*%27,meta:(),name:%27[Copy]%20super%20important%20level%20service%27,objective:(target:0.98),remote:(kibanaUrl:%27https:/cloud.elast.co/kibana%27,remoteName:remote_cluster),revision:1,settings:(frequency:%271m%27,preventInitialBackfill:!f,syncDelay:%271m%27),summary:(errorBudget:(consumed:0.064,initial:0.02,isEstimated:!f,remaining:0.936),fiveMinuteBurnRate:0,oneDayBurnRate:0,oneHourBurnRate:0,sliValue:0.99872,status:HEALTHY),tags:!(k8s,production,critical),timeWindow:(duration:%2730d%27,type:rolling),updatedAt:%272022-12-29T10:11:12.000Z%27,version:2)"` ); }); }); diff --git a/x-pack/plugins/observability_solution/slo/server/assets/component_templates/slo_summary_mappings_template.ts b/x-pack/plugins/observability_solution/slo/server/assets/component_templates/slo_summary_mappings_template.ts index b0509495e8e79..d25722e4f323d 100644 --- a/x-pack/plugins/observability_solution/slo/server/assets/component_templates/slo_summary_mappings_template.ts +++ b/x-pack/plugins/observability_solution/slo/server/assets/component_templates/slo_summary_mappings_template.ts @@ -178,6 +178,45 @@ export const getSLOSummaryMappingsTemplate = ( spaceId: { type: 'keyword', }, + fiveMinuteBurnRate: { + properties: { + goodEvents: { + type: 'long', + }, + totalEvents: { + type: 'long', + }, + value: { + type: 'double', + }, + }, + }, + oneHourBurnRate: { + properties: { + goodEvents: { + type: 'long', + }, + totalEvents: { + type: 'long', + }, + value: { + type: 'double', + }, + }, + }, + oneDayBurnRate: { + properties: { + goodEvents: { + type: 'long', + }, + totalEvents: { + type: 'long', + }, + value: { + type: 'double', + }, + }, + }, }, }, }, diff --git a/x-pack/plugins/observability_solution/slo/server/assets/ingest_templates/slo_summary_pipeline_template.ts b/x-pack/plugins/observability_solution/slo/server/assets/ingest_templates/slo_summary_pipeline_template.ts index d3b4aed69f1c0..7d51ffeddea4b 100644 --- a/x-pack/plugins/observability_solution/slo/server/assets/ingest_templates/slo_summary_pipeline_template.ts +++ b/x-pack/plugins/observability_solution/slo/server/assets/ingest_templates/slo_summary_pipeline_template.ts @@ -194,6 +194,46 @@ export const getSLOSummaryPipelineTemplate = ( ignore_failure: true, }, }, + // >= 8.15: + { + script: { + description: 'Computes the last five minute burn rate value', + lang: 'painless', + params: { + isTimeslice: timeslicesBudgetingMethodSchema.is(slo.budgetingMethod), + totalSlicesInRange: timeslicesBudgetingMethodSchema.is(slo.budgetingMethod) + ? Math.floor(5 / slo.objective.timesliceWindow!.asMinutes()) + : 0, + }, + source: getBurnRateSource('fiveMinuteBurnRate'), + }, + }, + { + script: { + description: 'Computes the last hour burn rate value', + lang: 'painless', + params: { + isTimeslice: timeslicesBudgetingMethodSchema.is(slo.budgetingMethod), + totalSlicesInRange: timeslicesBudgetingMethodSchema.is(slo.budgetingMethod) + ? Math.floor(60 / slo.objective.timesliceWindow!.asMinutes()) + : 0, + }, + source: getBurnRateSource('oneHourBurnRate'), + }, + }, + { + script: { + description: 'Computes the last day burn rate value', + lang: 'painless', + params: { + isTimeslice: timeslicesBudgetingMethodSchema.is(slo.budgetingMethod), + totalSlicesInRange: timeslicesBudgetingMethodSchema.is(slo.budgetingMethod) + ? Math.floor(1440 / slo.objective.timesliceWindow!.asMinutes()) + : 0, + }, + source: getBurnRateSource('oneDayBurnRate'), + }, + }, ], _meta: { description: `Ingest pipeline for SLO summary data [id: ${slo.id}, revision: ${slo.revision}]`, @@ -203,3 +243,27 @@ export const getSLOSummaryPipelineTemplate = ( }, }; }; + +function getBurnRateSource( + burnRateKey: 'oneDayBurnRate' | 'oneHourBurnRate' | 'fiveMinuteBurnRate' +): string { + return `def totalEvents = ctx["${burnRateKey}"]["totalEvents"]; + def goodEvents = ctx["${burnRateKey}"]["goodEvents"]; + def errorBudgetInitial = ctx["errorBudgetInitial"]; + + if (totalEvents == null || totalEvents == 0) { + ctx["${burnRateKey}"]["value"] = 0.0; + return; + } + + def totalSlicesInRange = params["totalSlicesInRange"]; + def isTimeslice = params["isTimeslice"]; + if (isTimeslice && totalSlicesInRange > 0) { + def badEvents = (double)totalEvents - (double)goodEvents; + def sliValue = 1.0 - (badEvents / (double)totalSlicesInRange); + ctx["${burnRateKey}"]["value"] = (1.0 - sliValue) / errorBudgetInitial; + } else { + def sliValue = (double)goodEvents / (double)totalEvents; + ctx["${burnRateKey}"]["value"] = (1.0 - sliValue) / errorBudgetInitial; + }`; +} diff --git a/x-pack/plugins/observability_solution/slo/server/assets/transform_templates/slo_transform_template.test.ts b/x-pack/plugins/observability_solution/slo/server/assets/transform_templates/slo_transform_template.test.ts index 535c8295516af..d3c0a9ea67b5d 100644 --- a/x-pack/plugins/observability_solution/slo/server/assets/transform_templates/slo_transform_template.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/assets/transform_templates/slo_transform_template.test.ts @@ -143,7 +143,7 @@ describe('slo transform template', () => { }, defer_validation: true, _meta: { - version: 3.2, + version: 3.3, managed: true, managed_by: 'observability', }, @@ -247,7 +247,7 @@ describe('slo transform template', () => { }, defer_validation: true, _meta: { - version: 3.2, + version: 3.3, managed: true, managed_by: 'observability', }, diff --git a/x-pack/plugins/observability_solution/slo/server/lib/rules/slo_burn_rate/executor.test.ts b/x-pack/plugins/observability_solution/slo/server/lib/rules/slo_burn_rate/executor.test.ts index 16ba03e60f346..1b3ddb32c56f8 100644 --- a/x-pack/plugins/observability_solution/slo/server/lib/rules/slo_burn_rate/executor.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/lib/rules/slo_burn_rate/executor.test.ts @@ -9,7 +9,6 @@ import { SanitizedRuleConfig } from '@kbn/alerting-plugin/common'; import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common/rules_settings'; import { RuleExecutorServices } from '@kbn/alerting-plugin/server'; import { publicAlertsClientMock } from '@kbn/alerting-plugin/server/alerts_client/alerts_client.mock'; -import { ObservabilitySloAlert } from '@kbn/alerts-as-data-utils'; import { IBasePath, IUiSettingsClient, @@ -47,6 +46,7 @@ import { import { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, + ALERT_GROUP, ALERT_REASON, SLO_BURN_RATE_RULE_TYPE_ID, } from '@kbn/rule-registry-plugin/common/technical_rule_data_field_names'; @@ -54,7 +54,7 @@ import { SLODefinition, StoredSLODefinition } from '../../../domain/models'; import { SLONotFound } from '../../../errors'; import { SO_SLO_TYPE } from '../../../saved_objects'; import { createSLO } from '../../../services/fixtures/slo'; -import { getRuleExecutor } from './executor'; +import { BurnRateAlert, getRuleExecutor } from './executor'; import { generateAboveThresholdKey, generateBurnRateKey, @@ -163,7 +163,7 @@ describe('BurnRateRuleExecutor', () => { BurnRateAlertState, BurnRateAlertContext, BurnRateAllowedActionGroups, - ObservabilitySloAlert + BurnRateAlert > >; @@ -173,7 +173,11 @@ describe('BurnRateRuleExecutor', () => { loggerMock = loggingSystemMock.createLogger(); servicesMock = { savedObjectsClient: soClientMock, - scopedClusterClient: { asCurrentUser: esClientMock, asInternalUser: esClientMock }, + scopedClusterClient: { + asCurrentUser: esClientMock, + asInternalUser: esClientMock, + asSecondaryAuthUser: esClientMock, + }, alertsClient: publicAlertsClientMock.create(), alertFactory: { create: jest.fn(), @@ -334,7 +338,7 @@ describe('BurnRateRuleExecutor', () => { }); it('schedules an alert when both windows of first window definition burn rate have reached the threshold', async () => { - const slo = createSLO({ objective: { target: 0.9 } }); + const slo = createSLO({ objective: { target: 0.9 }, groupBy: ['group.by.field'] }); const ruleParams = someRuleParamsWithWindows({ sloId: slo.id }); soClientMock.find.mockResolvedValueOnce(createFindResponse([slo])); const buckets = [ @@ -400,6 +404,12 @@ describe('BurnRateRuleExecutor', () => { [SLO_ID_FIELD]: slo.id, [SLO_REVISION_FIELD]: slo.revision, [SLO_INSTANCE_ID_FIELD]: 'foo', + [ALERT_GROUP]: [ + { + field: 'group.by.field', + value: 'foo', + }, + ], }, }); expect(servicesMock.alertsClient?.report).toBeCalledWith({ @@ -416,6 +426,12 @@ describe('BurnRateRuleExecutor', () => { [SLO_ID_FIELD]: slo.id, [SLO_REVISION_FIELD]: slo.revision, [SLO_INSTANCE_ID_FIELD]: 'bar', + [ALERT_GROUP]: [ + { + field: 'group.by.field', + value: 'bar', + }, + ], }, }); expect(servicesMock.alertsClient?.setAlertData).toHaveBeenNthCalledWith(1, { @@ -450,7 +466,7 @@ describe('BurnRateRuleExecutor', () => { }); it('schedules a suppressed alert when both windows of first window definition burn rate have reached the threshold but the dependency matches', async () => { - const slo = createSLO({ objective: { target: 0.9 } }); + const slo = createSLO({ objective: { target: 0.9 }, groupBy: ['group.by.field'] }); const dependencyRuleParams = someRuleParamsWithWindows({ sloId: slo.id }); const ruleParams = someRuleParamsWithWindows({ sloId: slo.id, @@ -534,6 +550,12 @@ describe('BurnRateRuleExecutor', () => { [SLO_ID_FIELD]: slo.id, [SLO_REVISION_FIELD]: slo.revision, [SLO_INSTANCE_ID_FIELD]: 'foo', + [ALERT_GROUP]: [ + { + field: 'group.by.field', + value: 'foo', + }, + ], }, }); expect(servicesMock.alertsClient?.report).toBeCalledWith({ @@ -550,6 +572,12 @@ describe('BurnRateRuleExecutor', () => { [SLO_ID_FIELD]: slo.id, [SLO_REVISION_FIELD]: slo.revision, [SLO_INSTANCE_ID_FIELD]: 'bar', + [ALERT_GROUP]: [ + { + field: 'group.by.field', + value: 'bar', + }, + ], }, }); expect(servicesMock.alertsClient?.setAlertData).toHaveBeenNthCalledWith(1, { @@ -584,7 +612,7 @@ describe('BurnRateRuleExecutor', () => { }); it('schedules an alert when both windows of second window definition burn rate have reached the threshold', async () => { - const slo = createSLO({ objective: { target: 0.9 } }); + const slo = createSLO({ objective: { target: 0.9 }, groupBy: ['group.by.field'] }); const ruleParams = someRuleParamsWithWindows({ sloId: slo.id }); soClientMock.find.mockResolvedValueOnce(createFindResponse([slo])); const buckets = [ @@ -649,6 +677,12 @@ describe('BurnRateRuleExecutor', () => { [SLO_ID_FIELD]: slo.id, [SLO_REVISION_FIELD]: slo.revision, [SLO_INSTANCE_ID_FIELD]: 'foo', + [ALERT_GROUP]: [ + { + field: 'group.by.field', + value: 'foo', + }, + ], }, }); expect(servicesMock.alertsClient!.report).toBeCalledWith({ @@ -665,6 +699,12 @@ describe('BurnRateRuleExecutor', () => { [SLO_ID_FIELD]: slo.id, [SLO_REVISION_FIELD]: slo.revision, [SLO_INSTANCE_ID_FIELD]: 'bar', + [ALERT_GROUP]: [ + { + field: 'group.by.field', + value: 'bar', + }, + ], }, }); diff --git a/x-pack/plugins/observability_solution/slo/server/lib/rules/slo_burn_rate/executor.ts b/x-pack/plugins/observability_solution/slo/server/lib/rules/slo_burn_rate/executor.ts index 0df4a07fa70c1..1e4be6e78b88e 100644 --- a/x-pack/plugins/observability_solution/slo/server/lib/rules/slo_burn_rate/executor.ts +++ b/x-pack/plugins/observability_solution/slo/server/lib/rules/slo_burn_rate/executor.ts @@ -10,6 +10,7 @@ import numeral from '@elastic/numeral'; import { ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, + ALERT_GROUP, ALERT_REASON, } from '@kbn/rule-data-utils'; import { AlertsClientError, RuleExecutorOptions } from '@kbn/alerting-plugin/server'; @@ -36,6 +37,7 @@ import { BurnRateAllowedActionGroups, BurnRateRuleParams, BurnRateRuleTypeState, + Group, WindowSchema, } from './types'; import { @@ -50,6 +52,10 @@ import { evaluateDependencies } from './lib/evaluate_dependencies'; import { shouldSuppressInstanceId } from './lib/should_suppress_instance_id'; import { getSloSummary } from './lib/summary_repository'; +export type BurnRateAlert = Omit & { + [ALERT_GROUP]?: Group[]; +}; + export const getRuleExecutor = ({ basePath, alertsLocator, @@ -64,7 +70,7 @@ export const getRuleExecutor = ({ BurnRateAlertState, BurnRateAlertContext, BurnRateAllowedActionGroups, - ObservabilitySloAlert + BurnRateAlert > ): ReturnType< ExecutorType< @@ -123,6 +129,15 @@ export const getRuleExecutor = ({ window: windowDef, } = result; + const instances = instanceId.split(','); + const groups = + instanceId !== ALL_VALUE + ? [slo.groupBy].flat().reduce((resultGroups, groupByItem, index) => { + resultGroups.push({ field: groupByItem, value: instances[index].trim() }); + return resultGroups; + }, []) + : undefined; + const urlQuery = instanceId === ALL_VALUE ? '' : `?instanceId=${instanceId}`; const viewInAppUrl = addSpaceIdToPath( basePath.publicBaseUrl, @@ -165,6 +180,7 @@ export const getRuleExecutor = ({ [ALERT_REASON]: reason, [ALERT_EVALUATION_THRESHOLD]: windowDef.burnRateThreshold, [ALERT_EVALUATION_VALUE]: Math.min(longWindowBurnRate, shortWindowBurnRate), + [ALERT_GROUP]: groups, [SLO_ID_FIELD]: slo.id, [SLO_REVISION_FIELD]: slo.revision, [SLO_INSTANCE_ID_FIELD]: instanceId, diff --git a/x-pack/plugins/observability_solution/slo/server/lib/rules/slo_burn_rate/types.ts b/x-pack/plugins/observability_solution/slo/server/lib/rules/slo_burn_rate/types.ts index be7442749d625..c63306fb0df26 100644 --- a/x-pack/plugins/observability_solution/slo/server/lib/rules/slo_burn_rate/types.ts +++ b/x-pack/plugins/observability_solution/slo/server/lib/rules/slo_burn_rate/types.ts @@ -52,3 +52,8 @@ export type BurnRateAllowedActionGroups = ActionGroupIdsOf< | typeof LOW_PRIORITY_ACTION | typeof SUPPRESSED_PRIORITY_ACTION >; + +export interface Group { + field: string; + value: string; +} diff --git a/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts b/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts index f5075abac0c0a..1f4c3ee2e3bb3 100644 --- a/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts +++ b/x-pack/plugins/observability_solution/slo/server/routes/slo/route.ts @@ -33,6 +33,7 @@ import { GetSLOsOverview } from '../../services/get_slos_overview'; import type { IndicatorTypes } from '../../domain/models'; import { CreateSLO, + DefaultBurnRatesClient, DefaultSummaryClient, DefaultSummaryTransformManager, DefaultTransformManager, @@ -295,7 +296,8 @@ const getSLORoute = createSloServerRoute({ const soClient = (await context.core).savedObjects.client; const esClient = (await context.core).elasticsearch.client.asCurrentUser; const repository = new KibanaSavedObjectsSLORepository(soClient, logger); - const summaryClient = new DefaultSummaryClient(esClient); + const burnRatesClient = new DefaultBurnRatesClient(esClient); + const summaryClient = new DefaultSummaryClient(esClient, burnRatesClient); const defintionClient = new SloDefinitionClient(repository, esClient, logger); const getSLO = new GetSLO(defintionClient, summaryClient); diff --git a/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/create_slo.test.ts.snap b/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/create_slo.test.ts.snap index 6303588a918a6..8416373c460bf 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/create_slo.test.ts.snap +++ b/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/create_slo.test.ts.snap @@ -7,7 +7,7 @@ Array [ "description": "Ingest pipeline for SLO summary data [id: unique-id, revision: 1]", "managed": true, "managed_by": "observability", - "version": 3.2, + "version": 3.3, }, "description": "Ingest pipeline for SLO summary data [id: unique-id, revision: 1]", "id": ".slo-observability.summary.pipeline-unique-id-1", @@ -166,6 +166,93 @@ Array [ "value": "http://myhost.com/mock-server-basepath", }, }, + Object { + "script": Object { + "description": "Computes the last five minute burn rate value", + "lang": "painless", + "params": Object { + "isTimeslice": false, + "totalSlicesInRange": 0, + }, + "source": "def totalEvents = ctx[\\"fiveMinuteBurnRate\\"][\\"totalEvents\\"]; + def goodEvents = ctx[\\"fiveMinuteBurnRate\\"][\\"goodEvents\\"]; + def errorBudgetInitial = ctx[\\"errorBudgetInitial\\"]; + + if (totalEvents == null || totalEvents == 0) { + ctx[\\"fiveMinuteBurnRate\\"][\\"value\\"] = 0.0; + return; + } + + def totalSlicesInRange = params[\\"totalSlicesInRange\\"]; + def isTimeslice = params[\\"isTimeslice\\"]; + if (isTimeslice && totalSlicesInRange > 0) { + def badEvents = (double)totalEvents - (double)goodEvents; + def sliValue = 1.0 - (badEvents / (double)totalSlicesInRange); + ctx[\\"fiveMinuteBurnRate\\"][\\"value\\"] = (1.0 - sliValue) / errorBudgetInitial; + } else { + def sliValue = (double)goodEvents / (double)totalEvents; + ctx[\\"fiveMinuteBurnRate\\"][\\"value\\"] = (1.0 - sliValue) / errorBudgetInitial; + }", + }, + }, + Object { + "script": Object { + "description": "Computes the last hour burn rate value", + "lang": "painless", + "params": Object { + "isTimeslice": false, + "totalSlicesInRange": 0, + }, + "source": "def totalEvents = ctx[\\"oneHourBurnRate\\"][\\"totalEvents\\"]; + def goodEvents = ctx[\\"oneHourBurnRate\\"][\\"goodEvents\\"]; + def errorBudgetInitial = ctx[\\"errorBudgetInitial\\"]; + + if (totalEvents == null || totalEvents == 0) { + ctx[\\"oneHourBurnRate\\"][\\"value\\"] = 0.0; + return; + } + + def totalSlicesInRange = params[\\"totalSlicesInRange\\"]; + def isTimeslice = params[\\"isTimeslice\\"]; + if (isTimeslice && totalSlicesInRange > 0) { + def badEvents = (double)totalEvents - (double)goodEvents; + def sliValue = 1.0 - (badEvents / (double)totalSlicesInRange); + ctx[\\"oneHourBurnRate\\"][\\"value\\"] = (1.0 - sliValue) / errorBudgetInitial; + } else { + def sliValue = (double)goodEvents / (double)totalEvents; + ctx[\\"oneHourBurnRate\\"][\\"value\\"] = (1.0 - sliValue) / errorBudgetInitial; + }", + }, + }, + Object { + "script": Object { + "description": "Computes the last day burn rate value", + "lang": "painless", + "params": Object { + "isTimeslice": false, + "totalSlicesInRange": 0, + }, + "source": "def totalEvents = ctx[\\"oneDayBurnRate\\"][\\"totalEvents\\"]; + def goodEvents = ctx[\\"oneDayBurnRate\\"][\\"goodEvents\\"]; + def errorBudgetInitial = ctx[\\"errorBudgetInitial\\"]; + + if (totalEvents == null || totalEvents == 0) { + ctx[\\"oneDayBurnRate\\"][\\"value\\"] = 0.0; + return; + } + + def totalSlicesInRange = params[\\"totalSlicesInRange\\"]; + def isTimeslice = params[\\"isTimeslice\\"]; + if (isTimeslice && totalSlicesInRange > 0) { + def badEvents = (double)totalEvents - (double)goodEvents; + def sliValue = 1.0 - (badEvents / (double)totalSlicesInRange); + ctx[\\"oneDayBurnRate\\"][\\"value\\"] = (1.0 - sliValue) / errorBudgetInitial; + } else { + def sliValue = (double)goodEvents / (double)totalEvents; + ctx[\\"oneDayBurnRate\\"][\\"value\\"] = (1.0 - sliValue) / errorBudgetInitial; + }", + }, + }, ], }, ] @@ -179,6 +266,11 @@ Array [ "errorBudgetEstimated": false, "errorBudgetInitial": 0.010000000000000009, "errorBudgetRemaining": 1, + "fiveMinuteBurnRate": Object { + "goodEvents": 0, + "totalEvents": 0, + "value": 0, + }, "goodEvents": 0, "isTempDoc": true, "kibanaUrl": "http://myhost.com/mock-server-basepath", @@ -193,6 +285,16 @@ Array [ }, "name": null, }, + "oneDayBurnRate": Object { + "goodEvents": 0, + "totalEvents": 0, + "value": 0, + }, + "oneHourBurnRate": Object { + "goodEvents": 0, + "totalEvents": 0, + "value": 0, + }, "service": Object { "environment": "irrelevant", "name": "irrelevant", @@ -241,7 +343,7 @@ Array [ }, }, "id": "slo-unique-id", - "index": ".slo-observability.summary-v3.2.temp", + "index": ".slo-observability.summary-v3.3.temp", "refresh": true, }, ] diff --git a/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/reset_slo.test.ts.snap b/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/reset_slo.test.ts.snap index 9d231462c03af..721402bc88961 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/reset_slo.test.ts.snap +++ b/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/reset_slo.test.ts.snap @@ -203,7 +203,7 @@ exports[`ResetSLO resets all associated resources 8`] = ` "description": "Ingest pipeline for SLO summary data [id: irrelevant, revision: 1]", "managed": true, "managed_by": "observability", - "version": 3.2, + "version": 3.3, }, "description": "Ingest pipeline for SLO summary data [id: irrelevant, revision: 1]", "id": ".slo-observability.summary.pipeline-irrelevant-1", @@ -366,6 +366,93 @@ exports[`ResetSLO resets all associated resources 8`] = ` "value": "http://myhost.com/mock-server-basepath", }, }, + Object { + "script": Object { + "description": "Computes the last five minute burn rate value", + "lang": "painless", + "params": Object { + "isTimeslice": false, + "totalSlicesInRange": 0, + }, + "source": "def totalEvents = ctx[\\"fiveMinuteBurnRate\\"][\\"totalEvents\\"]; + def goodEvents = ctx[\\"fiveMinuteBurnRate\\"][\\"goodEvents\\"]; + def errorBudgetInitial = ctx[\\"errorBudgetInitial\\"]; + + if (totalEvents == null || totalEvents == 0) { + ctx[\\"fiveMinuteBurnRate\\"][\\"value\\"] = 0.0; + return; + } + + def totalSlicesInRange = params[\\"totalSlicesInRange\\"]; + def isTimeslice = params[\\"isTimeslice\\"]; + if (isTimeslice && totalSlicesInRange > 0) { + def badEvents = (double)totalEvents - (double)goodEvents; + def sliValue = 1.0 - (badEvents / (double)totalSlicesInRange); + ctx[\\"fiveMinuteBurnRate\\"][\\"value\\"] = (1.0 - sliValue) / errorBudgetInitial; + } else { + def sliValue = (double)goodEvents / (double)totalEvents; + ctx[\\"fiveMinuteBurnRate\\"][\\"value\\"] = (1.0 - sliValue) / errorBudgetInitial; + }", + }, + }, + Object { + "script": Object { + "description": "Computes the last hour burn rate value", + "lang": "painless", + "params": Object { + "isTimeslice": false, + "totalSlicesInRange": 0, + }, + "source": "def totalEvents = ctx[\\"oneHourBurnRate\\"][\\"totalEvents\\"]; + def goodEvents = ctx[\\"oneHourBurnRate\\"][\\"goodEvents\\"]; + def errorBudgetInitial = ctx[\\"errorBudgetInitial\\"]; + + if (totalEvents == null || totalEvents == 0) { + ctx[\\"oneHourBurnRate\\"][\\"value\\"] = 0.0; + return; + } + + def totalSlicesInRange = params[\\"totalSlicesInRange\\"]; + def isTimeslice = params[\\"isTimeslice\\"]; + if (isTimeslice && totalSlicesInRange > 0) { + def badEvents = (double)totalEvents - (double)goodEvents; + def sliValue = 1.0 - (badEvents / (double)totalSlicesInRange); + ctx[\\"oneHourBurnRate\\"][\\"value\\"] = (1.0 - sliValue) / errorBudgetInitial; + } else { + def sliValue = (double)goodEvents / (double)totalEvents; + ctx[\\"oneHourBurnRate\\"][\\"value\\"] = (1.0 - sliValue) / errorBudgetInitial; + }", + }, + }, + Object { + "script": Object { + "description": "Computes the last day burn rate value", + "lang": "painless", + "params": Object { + "isTimeslice": false, + "totalSlicesInRange": 0, + }, + "source": "def totalEvents = ctx[\\"oneDayBurnRate\\"][\\"totalEvents\\"]; + def goodEvents = ctx[\\"oneDayBurnRate\\"][\\"goodEvents\\"]; + def errorBudgetInitial = ctx[\\"errorBudgetInitial\\"]; + + if (totalEvents == null || totalEvents == 0) { + ctx[\\"oneDayBurnRate\\"][\\"value\\"] = 0.0; + return; + } + + def totalSlicesInRange = params[\\"totalSlicesInRange\\"]; + def isTimeslice = params[\\"isTimeslice\\"]; + if (isTimeslice && totalSlicesInRange > 0) { + def badEvents = (double)totalEvents - (double)goodEvents; + def sliValue = 1.0 - (badEvents / (double)totalSlicesInRange); + ctx[\\"oneDayBurnRate\\"][\\"value\\"] = (1.0 - sliValue) / errorBudgetInitial; + } else { + def sliValue = (double)goodEvents / (double)totalEvents; + ctx[\\"oneDayBurnRate\\"][\\"value\\"] = (1.0 - sliValue) / errorBudgetInitial; + }", + }, + }, ], }, ], @@ -468,6 +555,11 @@ exports[`ResetSLO resets all associated resources 11`] = ` "errorBudgetEstimated": false, "errorBudgetInitial": 0.0010000000000000009, "errorBudgetRemaining": 1, + "fiveMinuteBurnRate": Object { + "goodEvents": 0, + "totalEvents": 0, + "value": 0, + }, "goodEvents": 0, "isTempDoc": true, "kibanaUrl": "http://myhost.com/mock-server-basepath", @@ -482,6 +574,16 @@ exports[`ResetSLO resets all associated resources 11`] = ` }, "name": null, }, + "oneDayBurnRate": Object { + "goodEvents": 0, + "totalEvents": 0, + "value": 0, + }, + "oneHourBurnRate": Object { + "goodEvents": 0, + "totalEvents": 0, + "value": 0, + }, "service": Object { "environment": "irrelevant", "name": "irrelevant", @@ -534,7 +636,7 @@ exports[`ResetSLO resets all associated resources 11`] = ` }, }, "id": "slo-irrelevant", - "index": ".slo-observability.summary-v3.2.temp", + "index": ".slo-observability.summary-v3.3.temp", "refresh": true, }, ], diff --git a/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/summary_client.test.ts.snap b/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/summary_client.test.ts.snap index 15ed30430fea7..83e4a7af010d5 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/summary_client.test.ts.snap +++ b/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/summary_client.test.ts.snap @@ -11,6 +11,9 @@ Object { "isEstimated": false, "remaining": 0.80158, }, + "fiveMinuteBurnRate": 0.5, + "oneDayBurnRate": 0.7, + "oneHourBurnRate": 0.6, "sliValue": 0.990079, "status": "HEALTHY", }, @@ -28,6 +31,9 @@ Object { "isEstimated": false, "remaining": -99, }, + "fiveMinuteBurnRate": 0.5, + "oneDayBurnRate": 0.7, + "oneHourBurnRate": 0.6, "sliValue": 0.9, "status": "VIOLATED", }, @@ -45,6 +51,9 @@ Object { "isEstimated": false, "remaining": 0.80158, }, + "fiveMinuteBurnRate": 0.5, + "oneDayBurnRate": 0.7, + "oneHourBurnRate": 0.6, "sliValue": 0.990079, "status": "HEALTHY", }, diff --git a/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/summary_search_client.test.ts.snap b/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/summary_search_client.test.ts.snap index 76a022867cfcf..3aee32ab1b546 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/summary_search_client.test.ts.snap +++ b/x-pack/plugins/observability_solution/slo/server/services/__snapshots__/summary_search_client.test.ts.snap @@ -1,6 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP - exports[`Summary Search Client returns the summary documents without duplicate temporary summary documents 1`] = ` Array [ Object { @@ -47,6 +46,9 @@ Object { "isEstimated": false, "remaining": 1, }, + "fiveMinuteBurnRate": 0, + "oneDayBurnRate": 0, + "oneHourBurnRate": 0, "sliValue": -1, "status": "NO_DATA", "summaryUpdatedAt": null, @@ -63,6 +65,9 @@ Object { "isEstimated": false, "remaining": 1, }, + "fiveMinuteBurnRate": 0, + "oneDayBurnRate": 0, + "oneHourBurnRate": 0, "sliValue": -1, "status": "NO_DATA", "summaryUpdatedAt": null, @@ -79,6 +84,9 @@ Object { "isEstimated": false, "remaining": 1, }, + "fiveMinuteBurnRate": 0, + "oneDayBurnRate": 0, + "oneHourBurnRate": 0, "sliValue": -1, "status": "NO_DATA", "summaryUpdatedAt": null, @@ -95,6 +103,9 @@ Object { "isEstimated": false, "remaining": 1, }, + "fiveMinuteBurnRate": 0, + "oneDayBurnRate": 0, + "oneHourBurnRate": 0, "sliValue": -1, "status": "NO_DATA", "summaryUpdatedAt": null, @@ -111,6 +122,9 @@ Object { "isEstimated": false, "remaining": 1, }, + "fiveMinuteBurnRate": 0, + "oneDayBurnRate": 0, + "oneHourBurnRate": 0, "sliValue": -1, "status": "NO_DATA", "summaryUpdatedAt": null, diff --git a/x-pack/plugins/observability_solution/slo/server/services/find_slo.test.ts b/x-pack/plugins/observability_solution/slo/server/services/find_slo.test.ts index 183d4beee19dd..bb26ab235c9f4 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/find_slo.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/find_slo.test.ts @@ -91,6 +91,9 @@ describe('FindSLO', () => { remaining: 0.9, isEstimated: false, }, + fiveMinuteBurnRate: 0, + oneHourBurnRate: 0, + oneDayBurnRate: 0, }, tags: ['critical', 'k8s'], createdAt: slo.createdAt.toISOString(), @@ -180,6 +183,9 @@ function summarySearchResult(slo: SLODefinition): Paginated { remaining: 0.9, isEstimated: false, }, + fiveMinuteBurnRate: 0, + oneHourBurnRate: 0, + oneDayBurnRate: 0, }, }, ], diff --git a/x-pack/plugins/observability_solution/slo/server/services/find_slo_groups.ts b/x-pack/plugins/observability_solution/slo/server/services/find_slo_groups.ts index c3fadc6f60ba0..7282f276ea572 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/find_slo_groups.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/find_slo_groups.ts @@ -87,7 +87,14 @@ export class FindSLOGroups { }, }, _source: { - includes: ['sliValue', 'status', 'slo.id', 'slo.instanceId', 'slo.name'], + includes: [ + 'sliValue', + 'status', + 'slo.id', + 'slo.instanceId', + 'slo.name', + 'slo.groupings', + ], }, size: 1, }, @@ -165,6 +172,7 @@ export class FindSLOGroups { id: sourceSummaryDoc.slo.id, instanceId: sourceSummaryDoc.slo.instanceId, name: sourceSummaryDoc.slo.name, + groupings: sourceSummaryDoc.slo.groupings, }, }, violated: bucket.violated?.doc_count, diff --git a/x-pack/plugins/observability_solution/slo/server/services/get_diagnosis.ts b/x-pack/plugins/observability_solution/slo/server/services/get_diagnosis.ts index 697d3ceb560df..38c183316a591 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/get_diagnosis.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/get_diagnosis.ts @@ -14,7 +14,7 @@ export async function getGlobalDiagnosis( ) { const licenseInfo = licensing.license.toJSON(); const userWritePrivileges = await esClient.security.hasPrivileges({ - cluster: ['manage_transform'], + cluster: ['manage_transform', 'manage_ingest_pipelines'], index: [{ names: '.slo-*', privileges: ['all'] }], }); const userReadPrivileges = await esClient.security.hasPrivileges({ diff --git a/x-pack/plugins/observability_solution/slo/server/services/get_slo.test.ts b/x-pack/plugins/observability_solution/slo/server/services/get_slo.test.ts index 49421887d3d8e..a3665294ab5c2 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/get_slo.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/get_slo.test.ts @@ -49,6 +49,9 @@ describe('GetSLO', () => { remaining: 0.9, isEstimated: false, }, + fiveMinuteBurnRate: 0, + oneHourBurnRate: 0, + oneDayBurnRate: 0, }, }); @@ -91,6 +94,9 @@ describe('GetSLO', () => { remaining: 0.9, isEstimated: false, }, + fiveMinuteBurnRate: 0, + oneHourBurnRate: 0, + oneDayBurnRate: 0, }, tags: ['critical', 'k8s'], createdAt: slo.createdAt.toISOString(), diff --git a/x-pack/plugins/observability_solution/slo/server/services/summary_client.test.ts b/x-pack/plugins/observability_solution/slo/server/services/summary_client.test.ts index 584aba75e22ff..120285f374f4d 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/summary_client.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/summary_client.test.ts @@ -9,8 +9,10 @@ import { ElasticsearchClientMock, elasticsearchServiceMock } from '@kbn/core/ser import moment from 'moment'; import { SLO_DESTINATION_INDEX_PATTERN } from '../../common/constants'; import { Duration, DurationUnit } from '../domain/models'; +import { BurnRatesClient } from './burn_rates_client'; import { createSLO } from './fixtures/slo'; import { sevenDaysRolling, weeklyCalendarAligned } from './fixtures/time_window'; +import { createBurnRatesClientMock } from './mocks'; import { DefaultSummaryClient } from './summary_client'; const createEsResponse = (good: number = 90, total: number = 100) => ({ @@ -33,9 +35,17 @@ const createEsResponse = (good: number = 90, total: number = 100) => ({ describe('SummaryClient', () => { let esClientMock: ElasticsearchClientMock; + let burnRatesClientMock: jest.Mocked; beforeEach(() => { esClientMock = elasticsearchServiceMock.createElasticsearchClient(); + burnRatesClientMock = createBurnRatesClientMock(); + + burnRatesClientMock.calculate.mockResolvedValueOnce([ + { name: '5m', burnRate: 0.5, sli: 0.9 }, + { name: '1h', burnRate: 0.6, sli: 0.9 }, + { name: '1d', burnRate: 0.7, sli: 0.9 }, + ]); }); describe('fetchSummary', () => { @@ -43,7 +53,7 @@ describe('SummaryClient', () => { it('returns the summary', async () => { const slo = createSLO({ timeWindow: sevenDaysRolling() }); esClientMock.search.mockResolvedValueOnce(createEsResponse()); - const summaryClient = new DefaultSummaryClient(esClientMock); + const summaryClient = new DefaultSummaryClient(esClientMock, burnRatesClientMock); const result = await summaryClient.computeSummary({ slo }); @@ -78,7 +88,7 @@ describe('SummaryClient', () => { timeWindow: weeklyCalendarAligned(), }); esClientMock.search.mockResolvedValueOnce(createEsResponse()); - const summaryClient = new DefaultSummaryClient(esClientMock); + const summaryClient = new DefaultSummaryClient(esClientMock, burnRatesClientMock); await summaryClient.computeSummary({ slo }); @@ -121,7 +131,7 @@ describe('SummaryClient', () => { timeWindow: sevenDaysRolling(), }); esClientMock.search.mockResolvedValueOnce(createEsResponse()); - const summaryClient = new DefaultSummaryClient(esClientMock); + const summaryClient = new DefaultSummaryClient(esClientMock, burnRatesClientMock); const result = await summaryClient.computeSummary({ slo }); @@ -143,16 +153,8 @@ describe('SummaryClient', () => { }, }, aggs: { - good: { - sum: { - field: 'slo.isGoodSlice', - }, - }, - total: { - value_count: { - field: 'slo.isGoodSlice', - }, - }, + good: { sum: { field: 'slo.isGoodSlice' } }, + total: { value_count: { field: 'slo.isGoodSlice' } }, }, }); }); @@ -170,7 +172,7 @@ describe('SummaryClient', () => { timeWindow: weeklyCalendarAligned(), }); esClientMock.search.mockResolvedValueOnce(createEsResponse()); - const summaryClient = new DefaultSummaryClient(esClientMock); + const summaryClient = new DefaultSummaryClient(esClientMock, burnRatesClientMock); const result = await summaryClient.computeSummary({ slo }); @@ -196,16 +198,8 @@ describe('SummaryClient', () => { }, }, aggs: { - good: { - sum: { - field: 'slo.isGoodSlice', - }, - }, - total: { - value_count: { - field: 'slo.isGoodSlice', - }, - }, + good: { sum: { field: 'slo.isGoodSlice' } }, + total: { value_count: { field: 'slo.isGoodSlice' } }, }, }); }); diff --git a/x-pack/plugins/observability_solution/slo/server/services/summary_client.ts b/x-pack/plugins/observability_solution/slo/server/services/summary_client.ts index 635dbdd654ead..0b9ee20062d1e 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/summary_client.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/summary_client.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { AggregationsValueCountAggregate } from '@elastic/elasticsearch/lib/api/types'; import { AggregationsSumAggregate, AggregationsTopHitsAggregate, @@ -13,13 +14,16 @@ import { ElasticsearchClient } from '@kbn/core/server'; import { ALL_VALUE, calendarAlignedTimeWindowSchema, + Duration, + DurationUnit, occurrencesBudgetingMethodSchema, timeslicesBudgetingMethodSchema, } from '@kbn/slo-schema'; import { SLO_DESTINATION_INDEX_PATTERN } from '../../common/constants'; -import { Groupings, Meta, SLODefinition, Summary } from '../domain/models'; +import { DateRange, Groupings, Meta, SLODefinition, Summary } from '../domain/models'; import { computeSLI, computeSummaryStatus, toErrorBudget } from '../domain/services'; import { toDateRange } from '../domain/services/date_range'; +import { BurnRatesClient } from './burn_rates_client'; import { getFlattenedGroupings } from './utils'; import { computeTotalSlicesFromDateRange } from './utils/compute_total_slices_from_date_range'; @@ -44,7 +48,7 @@ export interface SummaryClient { } export class DefaultSummaryClient implements SummaryClient { - constructor(private esClient: ElasticsearchClient) {} + constructor(private esClient: ElasticsearchClient, private burnRatesClient: BurnRatesClient) {} async computeSummary({ slo, instanceId, remoteName }: Params): Promise { const dateRange = toDateRange(slo.timeWindow); @@ -103,45 +107,25 @@ export class DefaultSummaryClient implements SummaryClient { }, }, }), - ...(timeslicesBudgetingMethodSchema.is(slo.budgetingMethod) && { - good: { - sum: { field: 'slo.isGoodSlice' }, - }, - total: { - value_count: { field: 'slo.isGoodSlice' }, - }, - }), - ...(occurrencesBudgetingMethodSchema.is(slo.budgetingMethod) && { - good: { sum: { field: 'slo.numerator' } }, - total: { sum: { field: 'slo.denominator' } }, - }), + ...buildAggs(slo), }, }); - const good = result.aggregations?.good?.value ?? 0; - const total = result.aggregations?.total?.value ?? 0; const source = result.aggregations?.last_doc?.hits?.hits?.[0]?._source; const groupings = source?.slo?.groupings; - let sliValue; - if (timeslicesBudgetingMethodSchema.is(slo.budgetingMethod)) { - const totalSlices = computeTotalSlicesFromDateRange( - dateRange, - slo.objective.timesliceWindow! - ); - - sliValue = computeSLI(good, total, totalSlices); - } else { - sliValue = computeSLI(good, total); - } - - const initialErrorBudget = 1 - slo.objective.target; - const consumedErrorBudget = sliValue < 0 ? 0 : (1 - sliValue) / initialErrorBudget; - const errorBudget = toErrorBudget( - initialErrorBudget, - consumedErrorBudget, - calendarAlignedTimeWindowSchema.is(slo.timeWindow) && - occurrencesBudgetingMethodSchema.is(slo.budgetingMethod) + const sliValue = computeSliValue(slo, dateRange, result.aggregations); + const errorBudget = computeErrorBudget(slo, sliValue); + + const burnRates = await this.burnRatesClient.calculate( + slo, + instanceId ?? ALL_VALUE, + [ + { name: '5m', duration: new Duration(5, DurationUnit.Minute) }, + { name: '1h', duration: new Duration(1, DurationUnit.Hour) }, + { name: '1d', duration: new Duration(1, DurationUnit.Day) }, + ], + remoteName ); return { @@ -149,6 +133,9 @@ export class DefaultSummaryClient implements SummaryClient { sliValue, errorBudget, status: computeSummaryStatus(slo.objective, sliValue, errorBudget), + fiveMinuteBurnRate: getBurnRate('5m', burnRates), + oneHourBurnRate: getBurnRate('1h', burnRates), + oneDayBurnRate: getBurnRate('1d', burnRates), }, groupings: groupings ? getFlattenedGroupings({ groupBy: slo.groupBy, groupings }) : {}, meta: getMetaFields(slo, source ?? {}), @@ -156,6 +143,18 @@ export class DefaultSummaryClient implements SummaryClient { } } +function buildAggs(slo: SLODefinition) { + return timeslicesBudgetingMethodSchema.is(slo.budgetingMethod) + ? { + good: { sum: { field: 'slo.isGoodSlice' } }, + total: { value_count: { field: 'slo.isGoodSlice' } }, + } + : { + good: { sum: { field: 'slo.numerator' } }, + total: { sum: { field: 'slo.denominator' } }, + }; +} + function getMetaFields( slo: SLODefinition, source: { monitor?: { id?: string }; config_id?: string; observer?: { name?: string } } @@ -176,3 +175,41 @@ function getMetaFields( return {}; } } + +interface BurnRateBucket { + good: AggregationsSumAggregate; + total: AggregationsSumAggregate | AggregationsValueCountAggregate; +} + +function computeSliValue( + slo: SLODefinition, + dateRange: DateRange, + bucket: BurnRateBucket | undefined +) { + const good = bucket?.good?.value ?? 0; + const total = bucket?.total?.value ?? 0; + + if (timeslicesBudgetingMethodSchema.is(slo.budgetingMethod)) { + const totalSlices = computeTotalSlicesFromDateRange(dateRange, slo.objective.timesliceWindow!); + + return computeSLI(good, total, totalSlices); + } + + return computeSLI(good, total); +} + +function computeErrorBudget(slo: SLODefinition, sliValue: number) { + const initialErrorBudget = 1 - slo.objective.target; + const consumedErrorBudget = sliValue < 0 ? 0 : (1 - sliValue) / initialErrorBudget; + + return toErrorBudget( + initialErrorBudget, + consumedErrorBudget, + calendarAlignedTimeWindowSchema.is(slo.timeWindow) && + occurrencesBudgetingMethodSchema.is(slo.budgetingMethod) + ); +} + +function getBurnRate(burnRateWindow: string, burnRates: Array<{ name: string; burnRate: number }>) { + return burnRates.find(({ name }) => name === burnRateWindow)?.burnRate ?? 0; +} diff --git a/x-pack/plugins/observability_solution/slo/server/services/summary_search_client.ts b/x-pack/plugins/observability_solution/slo/server/services/summary_search_client.ts index d99679fe01ac0..6493e613bbedc 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/summary_search_client.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/summary_search_client.ts @@ -33,7 +33,14 @@ export interface SummaryResult { }; } -type SortField = 'error_budget_consumed' | 'error_budget_remaining' | 'sli_value' | 'status'; +type SortField = + | 'error_budget_consumed' + | 'error_budget_remaining' + | 'sli_value' + | 'status' + | 'burn_rate_5m' + | 'burn_rate_1h' + | 'burn_rate_1d'; export interface Sort { field: SortField; @@ -158,6 +165,9 @@ export class DefaultSummarySearchClient implements SummarySearchClient { sliValue: toHighPrecision(doc._source.sliValue), status: summaryDoc.status, summaryUpdatedAt: summaryDoc.summaryUpdatedAt, + fiveMinuteBurnRate: toHighPrecision(summaryDoc.fiveMinuteBurnRate?.value ?? 0), + oneHourBurnRate: toHighPrecision(summaryDoc.oneHourBurnRate?.value ?? 0), + oneDayBurnRate: toHighPrecision(summaryDoc.oneDayBurnRate?.value ?? 0), }, groupings: getFlattenedGroupings({ groupings: summaryDoc.slo.groupings, @@ -230,6 +240,12 @@ function toDocumentSortField(field: SortField) { return 'status'; case 'sli_value': return 'sliValue'; + case 'burn_rate_5m': + return 'fiveMinuteBurnRate.value'; + case 'burn_rate_1h': + return 'oneHourBurnRate.value'; + case 'burn_rate_1d': + return 'oneDayBurnRate.value'; default: assertNever(field); } diff --git a/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/occurrences.ts b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/occurrences.ts index 88d83a486d6c0..1dd2c0758096c 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/occurrences.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/occurrences.ts @@ -6,7 +6,6 @@ */ import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types'; -import { SLODefinition } from '../../../domain/models'; import { getSLOSummaryPipelineId, getSLOSummaryTransformId, @@ -14,7 +13,9 @@ import { SLO_RESOURCES_VERSION, SLO_SUMMARY_DESTINATION_INDEX_NAME, } from '../../../../common/constants'; +import { SLODefinition } from '../../../domain/models'; import { getGroupBy } from './common'; +import { buildBurnRateAgg } from './utils'; export function generateSummaryTransformForOccurrences( slo: SLODefinition @@ -115,6 +116,9 @@ export function generateSummaryTransformForOccurrences( field: '@timestamp', }, }, + ...buildBurnRateAgg('fiveMinuteBurnRate', slo), + ...buildBurnRateAgg('oneHourBurnRate', slo), + ...buildBurnRateAgg('oneDayBurnRate', slo), }, }, description: `Summarise the rollup data of SLO: ${slo.name} [id: ${slo.id}, revision: ${slo.revision}].`, diff --git a/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/timeslices_calendar_aligned.ts b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/timeslices_calendar_aligned.ts index b0304316583b7..f5db77467c94d 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/timeslices_calendar_aligned.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/timeslices_calendar_aligned.ts @@ -6,7 +6,6 @@ */ import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types'; -import { DurationUnit, SLODefinition } from '../../../domain/models'; import { getSLOSummaryPipelineId, getSLOSummaryTransformId, @@ -14,7 +13,9 @@ import { SLO_RESOURCES_VERSION, SLO_SUMMARY_DESTINATION_INDEX_NAME, } from '../../../../common/constants'; +import { DurationUnit, SLODefinition } from '../../../domain/models'; import { getGroupBy } from './common'; +import { buildBurnRateAgg } from './utils'; export function generateSummaryTransformForTimeslicesAndCalendarAligned( slo: SLODefinition @@ -142,6 +143,9 @@ export function generateSummaryTransformForTimeslicesAndCalendarAligned( field: '@timestamp', }, }, + ...buildBurnRateAgg('fiveMinuteBurnRate', slo), + ...buildBurnRateAgg('oneHourBurnRate', slo), + ...buildBurnRateAgg('oneDayBurnRate', slo), }, }, description: `Summarise the rollup data of SLO: ${slo.name} [id: ${slo.id}, revision: ${slo.revision}].`, diff --git a/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/timeslices_rolling.ts b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/timeslices_rolling.ts index 24273dd5c037d..05724cb9e43d6 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/timeslices_rolling.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/timeslices_rolling.ts @@ -6,7 +6,6 @@ */ import { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types'; -import { SLODefinition } from '../../../domain/models'; import { getSLOSummaryPipelineId, getSLOSummaryTransformId, @@ -14,7 +13,9 @@ import { SLO_RESOURCES_VERSION, SLO_SUMMARY_DESTINATION_INDEX_NAME, } from '../../../../common/constants'; +import { SLODefinition } from '../../../domain/models'; import { getGroupBy } from './common'; +import { buildBurnRateAgg } from './utils'; export function generateSummaryTransformForTimeslicesAndRolling( slo: SLODefinition @@ -118,6 +119,10 @@ export function generateSummaryTransformForTimeslicesAndRolling( field: '@timestamp', }, }, + + ...buildBurnRateAgg('fiveMinuteBurnRate', slo), + ...buildBurnRateAgg('oneHourBurnRate', slo), + ...buildBurnRateAgg('oneDayBurnRate', slo), }, }, description: `Summarise the rollup data of SLO: ${slo.name} [id: ${slo.id}, revision: ${slo.revision}].`, diff --git a/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/utils.test.ts b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/utils.test.ts new file mode 100644 index 0000000000000..660c0bd18abff --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/utils.test.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { twoMinute } from '../../fixtures/duration'; +import { createSLOWithTimeslicesBudgetingMethod, createSLO } from '../../fixtures/slo'; +import { getFiveMinuteRange, getOneDayRange, getOneHourRange } from './utils'; + +describe('getFiveMinuteRange', () => { + it('should return range for 5 minutes including slo delay', () => { + const slo = createSLOWithTimeslicesBudgetingMethod({ + objective: { + target: 0.98, + timesliceTarget: 0.9, + timesliceWindow: twoMinute(), + }, + }); + + const range = getFiveMinuteRange(slo); + expect(range).toEqual({ + gte: `now-540s/m`, + lte: `now-240s/m`, + }); + }); +}); + +describe('getOneHourRange', () => { + it('should return range for 1 hour including slo delay', () => { + const slo = createSLOWithTimeslicesBudgetingMethod({ + objective: { + target: 0.98, + timesliceTarget: 0.9, + timesliceWindow: twoMinute(), + }, + }); + const range = getOneHourRange(slo); + expect(range).toEqual({ + gte: `now-3840s/m`, + lte: `now-240s/m`, + }); + }); +}); + +describe('getOneDayRange', () => { + it('should return range for 1 day including slo delay', () => { + const slo = createSLO(); + + const range = getOneDayRange(slo); + expect(range).toEqual({ + gte: `now-86580s/m`, + lte: `now-180s/m`, + }); + }); +}); diff --git a/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/utils.ts b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/utils.ts new file mode 100644 index 0000000000000..fb1ec8aa5eadc --- /dev/null +++ b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/generators/utils.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { occurrencesBudgetingMethodSchema, timeslicesBudgetingMethodSchema } from '@kbn/slo-schema'; +import { SLODefinition } from '../../../domain/models'; +import { getDelayInSecondsFromSLO } from '../../../domain/services/get_delay_in_seconds_from_slo'; + +const FIVE_MINUTES_IN_SECONDS = 300; +const ONE_HOUR_IN_SECONDS = 3600; +const ONE_DAY_IN_SECONDS = 86400; + +export function getFiveMinuteRange(slo: SLODefinition) { + const delayInSeconds = getDelayInSecondsFromSLO(slo); + return { + gte: `now-${FIVE_MINUTES_IN_SECONDS + delayInSeconds}s/m`, + lte: `now-${delayInSeconds}s/m`, + }; +} + +export function getOneHourRange(slo: SLODefinition) { + const delayInSeconds = getDelayInSecondsFromSLO(slo); + return { + gte: `now-${ONE_HOUR_IN_SECONDS + delayInSeconds}s/m`, + lte: `now-${delayInSeconds}s/m`, + }; +} + +export function getOneDayRange(slo: SLODefinition) { + const delayInSeconds = getDelayInSecondsFromSLO(slo); + return { + gte: `now-${ONE_DAY_IN_SECONDS + delayInSeconds}s/m`, + lte: `now-${delayInSeconds}s/m`, + }; +} + +export function buildBurnRateAgg( + aggKey: 'fiveMinuteBurnRate' | 'oneHourBurnRate' | 'oneDayBurnRate', + slo: SLODefinition +) { + const aggKeyToRangeMap = { + fiveMinuteBurnRate: getFiveMinuteRange, + oneHourBurnRate: getOneHourRange, + oneDayBurnRate: getOneDayRange, + }; + + return { + [aggKey]: { + filter: { + range: { + '@timestamp': aggKeyToRangeMap[aggKey](slo), + }, + }, + ...(occurrencesBudgetingMethodSchema.is(slo.budgetingMethod) && { + aggs: { + goodEvents: { + sum: { + field: 'slo.numerator', + }, + }, + totalEvents: { + sum: { + field: 'slo.denominator', + }, + }, + }, + }), + ...(timeslicesBudgetingMethodSchema.is(slo.budgetingMethod) && { + aggs: { + goodEvents: { + sum: { + field: 'slo.isGoodSlice', + }, + }, + totalEvents: { + value_count: { + field: 'slo.isGoodSlice', + }, + }, + }, + }), + }, + }; +} diff --git a/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/helpers/create_temp_summary.ts b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/helpers/create_temp_summary.ts index fc2fdc6f22f2e..c473ed184f40c 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/helpers/create_temp_summary.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/summary_transform_generator/helpers/create_temp_summary.ts @@ -63,6 +63,22 @@ export interface EsSummaryDocument { kibanaUrl?: string; // >= 8.14 summaryUpdatedAt: string | null; latestSliTimestamp: string | null; + // >= 8.15 + fiveMinuteBurnRate?: { + totalEvents: number; + goodEvents: number; + value: number; + }; + oneHourBurnRate?: { + totalEvents: number; + goodEvents: number; + value: number; + }; + oneDayBurnRate?: { + totalEvents: number; + goodEvents: number; + value: number; + }; } export function createTempSummaryDocument( @@ -131,6 +147,22 @@ export function createTempSummaryDocument( kibanaUrl: basePath.publicBaseUrl ?? '', // added in 8.14, i.e. might be undefined summaryUpdatedAt: null, latestSliTimestamp: null, + // Added in 8.15 + fiveMinuteBurnRate: { + totalEvents: 0, + goodEvents: 0, + value: 0, + }, + oneHourBurnRate: { + totalEvents: 0, + goodEvents: 0, + value: 0, + }, + oneDayBurnRate: { + totalEvents: 0, + goodEvents: 0, + value: 0, + }, }; return doc; diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap index e3d9de62d62ce..dcb36e3ac06dd 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap @@ -400,13 +400,13 @@ Object { "_meta": Object { "managed": true, "managed_by": "observability", - "version": 3.2, + "version": 3.3, }, "defer_validation": true, "description": "Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]", "dest": Object { - "index": ".slo-observability.sli-v3.2", - "pipeline": ".slo-observability.sli.pipeline-v3.2", + "index": ".slo-observability.sli-v3.3", + "pipeline": ".slo-observability.sli.pipeline-v3.3", }, "frequency": "1m", "pivot": Object { @@ -571,13 +571,13 @@ Object { "_meta": Object { "managed": true, "managed_by": "observability", - "version": 3.2, + "version": 3.3, }, "defer_validation": true, "description": "Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]", "dest": Object { - "index": ".slo-observability.sli-v3.2", - "pipeline": ".slo-observability.sli.pipeline-v3.2", + "index": ".slo-observability.sli-v3.3", + "pipeline": ".slo-observability.sli.pipeline-v3.3", }, "frequency": "1m", "pivot": Object { @@ -742,13 +742,13 @@ Object { "_meta": Object { "managed": true, "managed_by": "observability", - "version": 3.2, + "version": 3.3, }, "defer_validation": true, "description": "Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]", "dest": Object { - "index": ".slo-observability.sli-v3.2", - "pipeline": ".slo-observability.sli.pipeline-v3.2", + "index": ".slo-observability.sli-v3.3", + "pipeline": ".slo-observability.sli.pipeline-v3.3", }, "frequency": "1m", "pivot": Object { diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap index c5021f57229f0..53af7ad5777ce 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap @@ -376,13 +376,13 @@ Object { "_meta": Object { "managed": true, "managed_by": "observability", - "version": 3.2, + "version": 3.3, }, "defer_validation": true, "description": "Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]", "dest": Object { - "index": ".slo-observability.sli-v3.2", - "pipeline": ".slo-observability.sli.pipeline-v3.2", + "index": ".slo-observability.sli-v3.3", + "pipeline": ".slo-observability.sli.pipeline-v3.3", }, "frequency": "1m", "pivot": Object { @@ -534,13 +534,13 @@ Object { "_meta": Object { "managed": true, "managed_by": "observability", - "version": 3.2, + "version": 3.3, }, "defer_validation": true, "description": "Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]", "dest": Object { - "index": ".slo-observability.sli-v3.2", - "pipeline": ".slo-observability.sli.pipeline-v3.2", + "index": ".slo-observability.sli-v3.3", + "pipeline": ".slo-observability.sli.pipeline-v3.3", }, "frequency": "1m", "pivot": Object { @@ -692,13 +692,13 @@ Object { "_meta": Object { "managed": true, "managed_by": "observability", - "version": 3.2, + "version": 3.3, }, "defer_validation": true, "description": "Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]", "dest": Object { - "index": ".slo-observability.sli-v3.2", - "pipeline": ".slo-observability.sli.pipeline-v3.2", + "index": ".slo-observability.sli-v3.3", + "pipeline": ".slo-observability.sli.pipeline-v3.3", }, "frequency": "1m", "pivot": Object { diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/histogram.test.ts.snap b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/histogram.test.ts.snap index f9015e64984d9..c0dae33244b3d 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/histogram.test.ts.snap +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/histogram.test.ts.snap @@ -77,13 +77,13 @@ Object { "_meta": Object { "managed": true, "managed_by": "observability", - "version": 3.2, + "version": 3.3, }, "defer_validation": true, "description": "Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]", "dest": Object { - "index": ".slo-observability.sli-v3.2", - "pipeline": ".slo-observability.sli.pipeline-v3.2", + "index": ".slo-observability.sli-v3.3", + "pipeline": ".slo-observability.sli.pipeline-v3.3", }, "frequency": "1m", "pivot": Object { @@ -229,13 +229,13 @@ Object { "_meta": Object { "managed": true, "managed_by": "observability", - "version": 3.2, + "version": 3.3, }, "defer_validation": true, "description": "Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]", "dest": Object { - "index": ".slo-observability.sli-v3.2", - "pipeline": ".slo-observability.sli.pipeline-v3.2", + "index": ".slo-observability.sli-v3.3", + "pipeline": ".slo-observability.sli.pipeline-v3.3", }, "frequency": "1m", "pivot": Object { @@ -381,13 +381,13 @@ Object { "_meta": Object { "managed": true, "managed_by": "observability", - "version": 3.2, + "version": 3.3, }, "defer_validation": true, "description": "Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]", "dest": Object { - "index": ".slo-observability.sli-v3.2", - "pipeline": ".slo-observability.sli.pipeline-v3.2", + "index": ".slo-observability.sli-v3.3", + "pipeline": ".slo-observability.sli.pipeline-v3.3", }, "frequency": "1m", "pivot": Object { diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/kql_custom.test.ts.snap b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/kql_custom.test.ts.snap index b85642d631f26..4f5b239e982cf 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/kql_custom.test.ts.snap +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/kql_custom.test.ts.snap @@ -118,13 +118,13 @@ Object { "_meta": Object { "managed": true, "managed_by": "observability", - "version": 3.2, + "version": 3.3, }, "defer_validation": true, "description": "Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]", "dest": Object { - "index": ".slo-observability.sli-v3.2", - "pipeline": ".slo-observability.sli.pipeline-v3.2", + "index": ".slo-observability.sli-v3.3", + "pipeline": ".slo-observability.sli.pipeline-v3.3", }, "frequency": "1m", "pivot": Object { @@ -243,13 +243,13 @@ Object { "_meta": Object { "managed": true, "managed_by": "observability", - "version": 3.2, + "version": 3.3, }, "defer_validation": true, "description": "Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]", "dest": Object { - "index": ".slo-observability.sli-v3.2", - "pipeline": ".slo-observability.sli.pipeline-v3.2", + "index": ".slo-observability.sli-v3.3", + "pipeline": ".slo-observability.sli.pipeline-v3.3", }, "frequency": "1m", "pivot": Object { @@ -368,13 +368,13 @@ Object { "_meta": Object { "managed": true, "managed_by": "observability", - "version": 3.2, + "version": 3.3, }, "defer_validation": true, "description": "Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]", "dest": Object { - "index": ".slo-observability.sli-v3.2", - "pipeline": ".slo-observability.sli.pipeline-v3.2", + "index": ".slo-observability.sli-v3.3", + "pipeline": ".slo-observability.sli.pipeline-v3.3", }, "frequency": "1m", "pivot": Object { diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/metric_custom.test.ts.snap b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/metric_custom.test.ts.snap index e123f9cf28ba6..351c4d57b8c5c 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/metric_custom.test.ts.snap +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/metric_custom.test.ts.snap @@ -117,13 +117,13 @@ Object { "_meta": Object { "managed": true, "managed_by": "observability", - "version": 3.2, + "version": 3.3, }, "defer_validation": true, "description": "Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]", "dest": Object { - "index": ".slo-observability.sli-v3.2", - "pipeline": ".slo-observability.sli.pipeline-v3.2", + "index": ".slo-observability.sli-v3.3", + "pipeline": ".slo-observability.sli.pipeline-v3.3", }, "frequency": "1m", "pivot": Object { @@ -280,13 +280,13 @@ Object { "_meta": Object { "managed": true, "managed_by": "observability", - "version": 3.2, + "version": 3.3, }, "defer_validation": true, "description": "Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]", "dest": Object { - "index": ".slo-observability.sli-v3.2", - "pipeline": ".slo-observability.sli.pipeline-v3.2", + "index": ".slo-observability.sli-v3.3", + "pipeline": ".slo-observability.sli.pipeline-v3.3", }, "frequency": "1m", "pivot": Object { diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/timeslice_metric.test.ts.snap b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/timeslice_metric.test.ts.snap index 91c884611700f..6ee25d4927910 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/timeslice_metric.test.ts.snap +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/__snapshots__/timeslice_metric.test.ts.snap @@ -33,13 +33,13 @@ Object { "_meta": Object { "managed": true, "managed_by": "observability", - "version": 3.2, + "version": 3.3, }, "defer_validation": true, "description": "Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]", "dest": Object { - "index": ".slo-observability.sli-v3.2", - "pipeline": ".slo-observability.sli.pipeline-v3.2", + "index": ".slo-observability.sli-v3.3", + "pipeline": ".slo-observability.sli.pipeline-v3.3", }, "frequency": "1m", "pivot": Object { @@ -247,13 +247,13 @@ Object { "_meta": Object { "managed": true, "managed_by": "observability", - "version": 3.2, + "version": 3.3, }, "defer_validation": true, "description": "Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]", "dest": Object { - "index": ".slo-observability.sli-v3.2", - "pipeline": ".slo-observability.sli.pipeline-v3.2", + "index": ".slo-observability.sli-v3.3", + "pipeline": ".slo-observability.sli.pipeline-v3.3", }, "frequency": "1m", "pivot": Object { diff --git a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.test.ts b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.test.ts index b292b8d12f342..f59c1fd9a96e6 100644 --- a/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.test.ts +++ b/x-pack/plugins/observability_solution/slo/server/services/transform_generators/synthetics_availability.test.ts @@ -26,13 +26,13 @@ describe('Synthetics Availability Transform Generator', () => { _meta: { managed: true, managed_by: 'observability', - version: 3.2, + version: 3.3, }, defer_validation: true, description: 'Rolled-up SLI data for SLO: irrelevant [id: irrelevant, revision: 1]', dest: { - index: '.slo-observability.sli-v3.2', - pipeline: '.slo-observability.sli.pipeline-v3.2', + index: '.slo-observability.sli-v3.3', + pipeline: '.slo-observability.sli.pipeline-v3.3', }, frequency: '1m', pivot: { diff --git a/x-pack/plugins/observability_solution/synthetics/common/requests/get_certs_request_body.ts b/x-pack/plugins/observability_solution/synthetics/common/requests/get_certs_request_body.ts index f4535f2f85b72..fa1bd9eafad32 100644 --- a/x-pack/plugins/observability_solution/synthetics/common/requests/get_certs_request_body.ts +++ b/x-pack/plugins/observability_solution/synthetics/common/requests/get_certs_request_body.ts @@ -151,6 +151,7 @@ export const getCertsRequestBody = ({ collapse: { field: 'tls.server.hash.sha256', inner_hits: { + size: 100, _source: { includes: ['monitor.id', 'monitor.name', 'url.full', 'config_id'], }, diff --git a/x-pack/plugins/observability_solution/synthetics/common/rules/alert_actions.ts b/x-pack/plugins/observability_solution/synthetics/common/rules/alert_actions.ts index 3a11325d78b1a..e15bb41de7fb6 100644 --- a/x-pack/plugins/observability_solution/synthetics/common/rules/alert_actions.ts +++ b/x-pack/plugins/observability_solution/synthetics/common/rules/alert_actions.ts @@ -222,6 +222,7 @@ function getServiceNowActionParams({ defaultActionMessage }: Translations): Serv externalId: null, correlation_id: null, correlation_display: null, + additional_fields: null, }, comments: [], }, diff --git a/x-pack/plugins/observability_solution/synthetics/server/alert_rules/common.test.ts b/x-pack/plugins/observability_solution/synthetics/server/alert_rules/common.test.ts index 2edcd37a95927..58227c38cfb54 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/alert_rules/common.test.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/alert_rules/common.test.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { alertsMock } from '@kbn/alerting-plugin/server/mocks'; import { IBasePath } from '@kbn/core/server'; import { updateState, setRecoveredAlertsContext } from './common'; import { SyntheticsCommonState } from '../../common/runtime_types/alert_rules/common'; @@ -186,8 +185,6 @@ describe('updateState', () => { }); describe('setRecoveredAlertsContext', () => { - const { alertFactory } = alertsMock.createRuleExecutorServices(); - const { getRecoveredAlerts } = alertFactory.done(); const alertUuid = 'alert-id'; const location = 'US Central'; const configId = '12345'; @@ -195,7 +192,6 @@ describe('setRecoveredAlertsContext', () => { const basePath = { publicBaseUrl: 'https://localhost:5601', } as IBasePath; - const getAlertUuid = () => alertUuid; const upConfigs = { [idWithLocation]: { @@ -219,17 +215,26 @@ describe('setRecoveredAlertsContext', () => { }; it('sets context correctly when monitor is deleted', () => { - const setContext = jest.fn(); - getRecoveredAlerts.mockReturnValue([ - { - getId: () => alertUuid, - getState: () => ({ - idWithLocation, - monitorName: 'test-monitor', - }), - setContext, - }, - ]); + const alertsClientMock = { + report: jest.fn(), + getAlertLimitValue: jest.fn().mockReturnValue(10), + setAlertLimitReached: jest.fn(), + getRecoveredAlerts: jest.fn().mockReturnValue([ + { + alert: { + getId: () => alertUuid, + getState: () => ({ + idWithLocation, + monitorName: 'test-monitor', + }), + setContext: jest.fn(), + getUuid: () => alertUuid, + }, + }, + ]), + setAlertData: jest.fn(), + isTrackedAlert: jest.fn(), + }; const staleDownConfigs = { [idWithLocation]: { configId, @@ -250,45 +255,56 @@ describe('setRecoveredAlertsContext', () => { }, }; setRecoveredAlertsContext({ - alertFactory, + alertsClient: alertsClientMock, basePath, - getAlertUuid, spaceId: 'default', staleDownConfigs, upConfigs: {}, dateFormat, tz: 'UTC', }); - expect(setContext).toBeCalledWith({ - checkedAt: 'Feb 26, 2023 @ 00:00:00.000', - configId: '12345', - idWithLocation, - linkMessage: '', - alertDetailsUrl: 'https://localhost:5601/app/observability/alerts/alert-id', - monitorName: 'test-monitor', - recoveryReason: 'the monitor has been deleted', - recoveryStatus: 'has been deleted', - monitorUrl: '(unavailable)', - monitorUrlLabel: 'URL', - reason: - 'Monitor "test-monitor" from Unnamed-location is recovered. Checked at February 25, 2023 7:00 PM.', - stateId: '123456', - status: 'recovered', + expect(alertsClientMock.setAlertData).toBeCalledWith({ + id: 'alert-id', + context: { + checkedAt: 'Feb 26, 2023 @ 00:00:00.000', + configId: '12345', + idWithLocation, + linkMessage: '', + alertDetailsUrl: 'https://localhost:5601/app/observability/alerts/alert-id', + monitorName: 'test-monitor', + recoveryReason: 'the monitor has been deleted', + recoveryStatus: 'has been deleted', + monitorUrl: '(unavailable)', + monitorUrlLabel: 'URL', + reason: + 'Monitor "test-monitor" from Unnamed-location is recovered. Checked at February 25, 2023 7:00 PM.', + stateId: '123456', + status: 'recovered', + }, }); }); it('sets context correctly when location is removed', () => { - const setContext = jest.fn(); - getRecoveredAlerts.mockReturnValue([ - { - getId: () => alertUuid, - getState: () => ({ - idWithLocation, - monitorName: 'test-monitor', - }), - setContext, - }, - ]); + const alertsClientMock = { + report: jest.fn(), + getAlertLimitValue: jest.fn().mockReturnValue(10), + setAlertLimitReached: jest.fn(), + getRecoveredAlerts: jest.fn().mockReturnValue([ + { + alert: { + getId: () => alertUuid, + getState: () => ({ + idWithLocation, + monitorName: 'test-monitor', + }), + setContext: jest.fn(), + getUuid: () => alertUuid, + }, + }, + ]), + setAlertData: jest.fn(), + isTrackedAlert: jest.fn(), + }; const staleDownConfigs = { [idWithLocation]: { configId, @@ -309,47 +325,58 @@ describe('setRecoveredAlertsContext', () => { }, }; setRecoveredAlertsContext({ - alertFactory, + alertsClient: alertsClientMock, basePath, - getAlertUuid, spaceId: 'default', staleDownConfigs, upConfigs: {}, dateFormat, tz: 'UTC', }); - expect(setContext).toBeCalledWith({ - configId: '12345', - checkedAt: 'Feb 26, 2023 @ 00:00:00.000', - monitorUrl: '(unavailable)', - reason: - 'Monitor "test-monitor" from Unnamed-location is recovered. Checked at February 25, 2023 7:00 PM.', - idWithLocation, - linkMessage: '', - alertDetailsUrl: 'https://localhost:5601/app/observability/alerts/alert-id', - monitorName: 'test-monitor', - recoveryReason: 'this location has been removed from the monitor', - recoveryStatus: 'has recovered', - stateId: '123456', - status: 'recovered', - monitorUrlLabel: 'URL', + expect(alertsClientMock.setAlertData).toBeCalledWith({ + id: 'alert-id', + context: { + configId: '12345', + checkedAt: 'Feb 26, 2023 @ 00:00:00.000', + monitorUrl: '(unavailable)', + reason: + 'Monitor "test-monitor" from Unnamed-location is recovered. Checked at February 25, 2023 7:00 PM.', + idWithLocation, + linkMessage: '', + alertDetailsUrl: 'https://localhost:5601/app/observability/alerts/alert-id', + monitorName: 'test-monitor', + recoveryReason: 'this location has been removed from the monitor', + recoveryStatus: 'has recovered', + stateId: '123456', + status: 'recovered', + monitorUrlLabel: 'URL', + }, }); }); it('sets context correctly when monitor is up', () => { - const setContext = jest.fn(); - getRecoveredAlerts.mockReturnValue([ - { - getId: () => alertUuid, - getState: () => ({ - idWithLocation, - monitorName: 'test-monitor', - locationId: 'us_west', - configId: '12345-67891', - }), - setContext, - }, - ]); + const alertsClientMock = { + report: jest.fn(), + getAlertLimitValue: jest.fn().mockReturnValue(10), + setAlertLimitReached: jest.fn(), + getRecoveredAlerts: jest.fn().mockReturnValue([ + { + alert: { + getId: () => alertUuid, + getState: () => ({ + idWithLocation, + monitorName: 'test-monitor', + locationId: 'us_west', + configId: '12345-67891', + }), + setContext: jest.fn(), + getUuid: () => alertUuid, + }, + }, + ]), + setAlertData: jest.fn(), + isTrackedAlert: jest.fn(), + }; const staleDownConfigs = { [idWithLocation]: { configId, @@ -370,33 +397,35 @@ describe('setRecoveredAlertsContext', () => { }, }; setRecoveredAlertsContext({ - alertFactory, + alertsClient: alertsClientMock, basePath, - getAlertUuid, spaceId: 'default', staleDownConfigs, upConfigs, dateFormat, tz: 'UTC', }); - expect(setContext).toBeCalledWith({ - configId: '12345-67891', - idWithLocation, - alertDetailsUrl: 'https://localhost:5601/app/observability/alerts/alert-id', - monitorName: 'test-monitor', - status: 'up', - recoveryReason: - 'the monitor is now up again. It ran successfully at Feb 26, 2023 @ 00:00:00.000', - recoveryStatus: 'is now up', - locationId: 'us_west', - checkedAt: 'Feb 26, 2023 @ 00:00:00.000', - linkMessage: - '- Link: https://localhost:5601/app/synthetics/monitor/12345-67891/errors/123456?locationId=us_west', - monitorUrl: '(unavailable)', - monitorUrlLabel: 'URL', - reason: - 'Monitor "test-monitor" from Unnamed-location is recovered. Checked at February 25, 2023 7:00 PM.', - stateId: null, + expect(alertsClientMock.setAlertData).toBeCalledWith({ + id: 'alert-id', + context: { + configId: '12345-67891', + idWithLocation, + alertDetailsUrl: 'https://localhost:5601/app/observability/alerts/alert-id', + monitorName: 'test-monitor', + status: 'up', + recoveryReason: + 'the monitor is now up again. It ran successfully at Feb 26, 2023 @ 00:00:00.000', + recoveryStatus: 'is now up', + locationId: 'us_west', + checkedAt: 'Feb 26, 2023 @ 00:00:00.000', + linkMessage: + '- Link: https://localhost:5601/app/synthetics/monitor/12345-67891/errors/123456?locationId=us_west', + monitorUrl: '(unavailable)', + monitorUrlLabel: 'URL', + reason: + 'Monitor "test-monitor" from Unnamed-location is recovered. Checked at February 25, 2023 7:00 PM.', + stateId: null, + }, }); }); }); diff --git a/x-pack/plugins/observability_solution/synthetics/server/alert_rules/common.ts b/x-pack/plugins/observability_solution/synthetics/server/alert_rules/common.ts index dc8bb9c25afe2..5bb366001f0fb 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/alert_rules/common.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/alert_rules/common.ts @@ -8,13 +8,22 @@ import moment, { Moment } from 'moment'; import { isRight } from 'fp-ts/lib/Either'; import Mustache from 'mustache'; import { IBasePath } from '@kbn/core/server'; -import { IRuleTypeAlerts, RuleExecutorServices } from '@kbn/alerting-plugin/server'; +import { + IRuleTypeAlerts, + ActionGroupIdsOf, + AlertInstanceContext as AlertContext, + AlertInstanceState as AlertState, +} from '@kbn/alerting-plugin/server'; import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; import { i18n } from '@kbn/i18n'; import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; -import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils'; +import { legacyExperimentalFieldMap, ObservabilityUptimeAlert } from '@kbn/alerts-as-data-utils'; +import { PublicAlertsClient } from '@kbn/alerting-plugin/server/alerts_client/types'; import { combineFiltersAndUserSearch, stringifyKueries } from '../../common/lib'; -import { SYNTHETICS_RULE_TYPES_ALERT_CONTEXT } from '../../common/constants/synthetics_alerts'; +import { + MonitorStatusActionGroup, + SYNTHETICS_RULE_TYPES_ALERT_CONTEXT, +} from '../../common/constants/synthetics_alerts'; import { uptimeRuleFieldMap } from '../../common/rules/uptime_rule_field_map'; import { getUptimeIndexPattern, @@ -26,7 +35,6 @@ import { getMonitorSummary } from './status_rule/message_utils'; import { SyntheticsCommonState, SyntheticsCommonStateCodec, - SyntheticsMonitorStatusAlertState, } from '../../common/runtime_types/alert_rules/common'; import { getSyntheticsErrorRouteFromMonitorId } from '../../common/utils/get_synthetics_monitor_url'; import { ALERT_DETAILS_URL, RECOVERY_REASON } from './action_variables'; @@ -154,30 +162,33 @@ export const getErrorDuration = (startedAt: Moment, endsAt: Moment) => { }; export const setRecoveredAlertsContext = ({ - alertFactory, + alertsClient, basePath, - getAlertUuid, spaceId, staleDownConfigs, upConfigs, dateFormat, tz, }: { - alertFactory: RuleExecutorServices['alertFactory']; + alertsClient: PublicAlertsClient< + ObservabilityUptimeAlert, + AlertState, + AlertContext, + ActionGroupIdsOf + >; basePath?: IBasePath; - getAlertUuid?: (alertId: string) => string | null; spaceId?: string; staleDownConfigs: AlertOverviewStatus['staleDownConfigs']; upConfigs: AlertOverviewStatus['upConfigs']; dateFormat: string; tz: string; }) => { - const { getRecoveredAlerts } = alertFactory.done(); - for (const alert of getRecoveredAlerts()) { - const recoveredAlertId = alert.getId(); - const alertUuid = getAlertUuid?.(recoveredAlertId) || undefined; + const recoveredAlerts = alertsClient.getRecoveredAlerts() ?? []; + for (const recoveredAlert of recoveredAlerts) { + const recoveredAlertId = recoveredAlert.alert.getId(); + const alertUuid = recoveredAlert.alert.getUuid(); - const state = alert.getState() as SyntheticsCommonState & SyntheticsMonitorStatusAlertState; + const state = recoveredAlert.alert.getState(); let recoveryReason = ''; let recoveryStatus = i18n.translate( @@ -279,7 +290,7 @@ export const setRecoveredAlertsContext = ({ } } - alert.setContext({ + const context = { ...state, ...(monitorSummary ? monitorSummary : {}), lastErrorMessage, @@ -290,7 +301,8 @@ export const setRecoveredAlertsContext = ({ ...(basePath && spaceId && alertUuid ? { [ALERT_DETAILS_URL]: getAlertDetailsUrl(basePath, spaceId, alertUuid) } : {}), - }); + }; + alertsClient.setAlertData({ id: recoveredAlertId, context }); } }; diff --git a/x-pack/plugins/observability_solution/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts b/x-pack/plugins/observability_solution/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts index 2117e26e37cf7..f7a6b54ca1929 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/alert_rules/status_rule/monitor_status_rule.ts @@ -8,9 +8,16 @@ import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; import { isEmpty } from 'lodash'; import { ActionGroupIdsOf } from '@kbn/alerting-plugin/common'; -import { GetViewInAppRelativeUrlFnOpts } from '@kbn/alerting-plugin/server'; +import { PluginSetupContract } from '@kbn/alerting-plugin/server'; +import { + GetViewInAppRelativeUrlFnOpts, + AlertInstanceContext as AlertContext, + RuleExecutorOptions, + AlertsClientError, + IRuleTypeAlerts, +} from '@kbn/alerting-plugin/server'; import { observabilityPaths } from '@kbn/observability-plugin/common'; -import { createLifecycleRuleTypeFactory, IRuleDataClient } from '@kbn/rule-registry-plugin/server'; +import { ObservabilityUptimeAlert } from '@kbn/alerts-as-data-utils'; import { SyntheticsPluginsSetupDependencies, SyntheticsServerSetup } from '../../types'; import { DOWN_LABEL, getMonitorAlertDocument, getMonitorSummary } from './message_utils'; import { @@ -19,7 +26,7 @@ import { } from '../../../common/runtime_types/alert_rules/common'; import { OverviewStatus } from '../../../common/runtime_types'; import { StatusRuleExecutor } from './status_rule_executor'; -import { StatusRulePramsSchema } from '../../../common/rules/status_rule'; +import { StatusRulePramsSchema, StatusRuleParams } from '../../../common/rules/status_rule'; import { MONITOR_STATUS, SYNTHETICS_ALERT_RULE_TYPES, @@ -37,20 +44,26 @@ import { ALERT_DETAILS_URL, getActionVariables, VIEW_IN_APP_URL } from '../actio import { STATUS_RULE_NAME } from '../translations'; import { SyntheticsMonitorClient } from '../../synthetics_service/synthetics_monitor/synthetics_monitor_client'; -export type ActionGroupIds = ActionGroupIdsOf; +type MonitorStatusRuleTypeParams = StatusRuleParams; +type MonitorStatusActionGroups = ActionGroupIdsOf; +type MonitorStatusRuleTypeState = SyntheticsCommonState; +type MonitorStatusAlertState = SyntheticsMonitorStatusAlertState; +type MonitorStatusAlertContext = AlertContext; +type MonitorStatusAlert = ObservabilityUptimeAlert; export const registerSyntheticsStatusCheckRule = ( server: SyntheticsServerSetup, plugins: SyntheticsPluginsSetupDependencies, syntheticsMonitorClient: SyntheticsMonitorClient, - ruleDataClient: IRuleDataClient + alerting: PluginSetupContract ) => { - const createLifecycleRuleType = createLifecycleRuleTypeFactory({ - ruleDataClient, - logger: server.logger, - }); + if (!alerting) { + throw new Error( + 'Cannot register the synthetics monitor status rule type. The alerting plugin needs to be enabled.' + ); + } - return createLifecycleRuleType({ + alerting.registerType({ id: SYNTHETICS_ALERT_RULE_TYPES.MONITOR_STATUS, category: DEFAULT_APP_CATEGORIES.observability.id, producer: 'uptime', @@ -64,19 +77,22 @@ export const registerSyntheticsStatusCheckRule = ( isExportable: true, minimumLicenseRequired: 'basic', doesSetRecoveryContext: true, - async executor({ state, params, services, spaceId, previousStartedAt }) { - const ruleState = state as SyntheticsCommonState; - + executor: async ( + options: RuleExecutorOptions< + MonitorStatusRuleTypeParams, + MonitorStatusRuleTypeState, + MonitorStatusAlertState, + MonitorStatusAlertContext, + MonitorStatusActionGroups, + MonitorStatusAlert + > + ) => { + const { state: ruleState, params, services, spaceId, previousStartedAt, startedAt } = options; + const { alertsClient, savedObjectsClient, scopedClusterClient, uiSettingsClient } = services; + if (!alertsClient) { + throw new AlertsClientError(); + } const { basePath } = server; - const { - alertFactory, - getAlertUuid, - savedObjectsClient, - scopedClusterClient, - alertWithLifecycle, - uiSettingsClient, - } = services; - const dateFormat = await uiSettingsClient.get('dateFormat'); const timezone = await uiSettingsClient.get('dateFormat:tz'); const tz = timezone === 'Browser' ? 'UTC' : timezone; @@ -106,13 +122,11 @@ export const registerSyntheticsStatusCheckRule = ( tz ); - const alert = alertWithLifecycle({ + const { uuid, start } = alertsClient.report({ id: alertId, - fields: getMonitorAlertDocument(monitorSummary), + actionGroup: MONITOR_STATUS.id, }); - const alertUuid = getAlertUuid(alertId); - const alertState = alert.getState() as SyntheticsMonitorStatusAlertState; - const errorStartedAt: string = alertState.errorStartedAt || ping['@timestamp']; + const errorStartedAt = start ?? startedAt.toISOString(); let relativeViewInAppUrl = ''; if (monitorSummary.stateId) { @@ -123,31 +137,29 @@ export const registerSyntheticsStatusCheckRule = ( }); } + const payload = getMonitorAlertDocument(monitorSummary); + const context = { ...monitorSummary, + idWithLocation, errorStartedAt, linkMessage: monitorSummary.stateId ? getFullViewInAppMessage(basePath, spaceId, relativeViewInAppUrl) : '', [VIEW_IN_APP_URL]: getViewInAppUrl(basePath, spaceId, relativeViewInAppUrl), + [ALERT_DETAILS_URL]: getAlertDetailsUrl(basePath, spaceId, uuid), }; - alert.replaceState({ - ...updateState(ruleState, true), - ...context, - idWithLocation, - }); - - alert.scheduleActions(MONITOR_STATUS.id, { - ...context, - [ALERT_DETAILS_URL]: getAlertDetailsUrl(basePath, spaceId, alertUuid), + alertsClient.setAlertData({ + id: alertId, + payload, + context, }); }); setRecoveredAlertsContext({ - alertFactory, + alertsClient, basePath, - getAlertUuid, spaceId, staleDownConfigs, upConfigs, @@ -159,7 +171,10 @@ export const registerSyntheticsStatusCheckRule = ( state: updateState(ruleState, !isEmpty(downConfigs), { downConfigs }), }; }, - alerts: UptimeRuleTypeAlertDefinition, + alerts: { + ...UptimeRuleTypeAlertDefinition, + shouldWrite: true, + } as IRuleTypeAlerts, getViewInAppRelativeUrl: ({ rule }: GetViewInAppRelativeUrlFnOpts<{}>) => observabilityPaths.ruleDetails(rule.id), }); diff --git a/x-pack/plugins/observability_solution/synthetics/server/server.ts b/x-pack/plugins/observability_solution/synthetics/server/server.ts index 69a1aa79d2410..b394e4ffc6883 100644 --- a/x-pack/plugins/observability_solution/synthetics/server/server.ts +++ b/x-pack/plugins/observability_solution/synthetics/server/server.ts @@ -138,18 +138,9 @@ export const initSyntheticsServer = ( } }); - const { - alerting: { registerType }, - } = plugins; + const { alerting } = plugins; - const statusAlert = registerSyntheticsStatusCheckRule( - server, - plugins, - syntheticsMonitorClient, - ruleDataClient - ); - - registerType(statusAlert); + registerSyntheticsStatusCheckRule(server, plugins, syntheticsMonitorClient, alerting); const tlsRule = registerSyntheticsTLSCheckRule( server, @@ -158,5 +149,5 @@ export const initSyntheticsServer = ( ruleDataClient ); - registerType(tlsRule); + alerting.registerType(tlsRule); }; diff --git a/x-pack/plugins/observability_solution/uptime/common/rules/alert_actions.ts b/x-pack/plugins/observability_solution/uptime/common/rules/alert_actions.ts index 007a49be1ba81..767e9d208c56f 100644 --- a/x-pack/plugins/observability_solution/uptime/common/rules/alert_actions.ts +++ b/x-pack/plugins/observability_solution/uptime/common/rules/alert_actions.ts @@ -214,6 +214,7 @@ function getServiceNowActionParams({ defaultActionMessage }: Translations): Serv externalId: null, correlation_id: null, correlation_display: null, + additional_fields: null, }, comments: [], }, diff --git a/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_filter.tsx b/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_filter.tsx index bd844b9110dab..f8b0422c669b5 100644 --- a/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_filter.tsx +++ b/x-pack/plugins/observability_solution/uptime/public/legacy_uptime/components/monitor/synthetics/step_detail/waterfall/waterfall_filter.tsx @@ -174,7 +174,6 @@ export const WaterfallFilter = ({ 0)} id="onlyHighlighted" label={FILTER_COLLAPSE_REQUESTS_LABEL} diff --git a/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/routes/uptime_route_wrapper.ts b/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/routes/uptime_route_wrapper.ts index 5beaa563790e8..2590db8524105 100644 --- a/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/routes/uptime_route_wrapper.ts +++ b/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/routes/uptime_route_wrapper.ts @@ -12,7 +12,11 @@ import { UptimeEsClient } from '../lib/lib'; export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute, server) => ({ ...uptimeRoute, options: { - tags: ['access:uptime-read', ...(uptimeRoute?.writeAccess ? ['access:uptime-write'] : [])], + tags: [ + 'oas-tag:uptime', + 'access:uptime-read', + ...(uptimeRoute?.writeAccess ? ['access:uptime-write'] : []), + ], }, handler: async (context, request, response) => { const coreContext = await context.core; diff --git a/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/uptime_server.ts b/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/uptime_server.ts index 0c417a6e061e6..b8a9b56c2a909 100644 --- a/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/uptime_server.ts +++ b/x-pack/plugins/observability_solution/uptime/server/legacy_uptime/uptime_server.ts @@ -89,6 +89,7 @@ export const initUptimeServer = ( router.versioned .get({ access: 'public', + description: `Get uptime settings`, path: routeDefinition.path, options: { tags: options?.tags, @@ -115,6 +116,7 @@ export const initUptimeServer = ( router.versioned .put({ access: 'public', + description: `Update uptime settings`, path: routeDefinition.path, options: { tags: options?.tags, diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts index 1579bf0cf9377..fe069f7a5d6f5 100644 --- a/x-pack/plugins/reporting/public/plugin.ts +++ b/x-pack/plugins/reporting/public/plugin.ts @@ -148,6 +148,7 @@ export class ReportingPublicPlugin id: 'reporting', title: this.title, order: 3, + keywords: ['reports', 'report', 'reporting'], mount: async (params) => { params.setBreadcrumbs([{ text: this.breadcrumbText }]); const [[coreStart, startDeps], { mountManagementSection }] = await Promise.all([ diff --git a/x-pack/plugins/reporting/server/usage/register_event_types.ts b/x-pack/plugins/reporting/server/usage/register_event_types.ts index bf34d33cf37c7..6347443eee8f7 100644 --- a/x-pack/plugins/reporting/server/usage/register_event_types.ts +++ b/x-pack/plugins/reporting/server/usage/register_event_types.ts @@ -5,9 +5,7 @@ * 2.0. */ -import { RootSchema } from '@kbn/analytics-client'; -import { EventTypeOpts } from '@kbn/core/public'; -import type { CoreSetup } from '@kbn/core/server'; +import type { CoreSetup, EventTypeOpts, RootSchema } from '@kbn/core/server'; import { EventType, FieldType } from '@kbn/reporting-server'; const fields: Record>> = { diff --git a/x-pack/plugins/reporting/tsconfig.json b/x-pack/plugins/reporting/tsconfig.json index e0f781d28f62c..b52c02a72bed1 100644 --- a/x-pack/plugins/reporting/tsconfig.json +++ b/x-pack/plugins/reporting/tsconfig.json @@ -47,7 +47,6 @@ "@kbn/reporting-mocks-server", "@kbn/core-http-request-handler-context-server", "@kbn/reporting-public", - "@kbn/analytics-client", "@kbn/reporting-csv-share-panel", "@kbn/react-kibana-context-render", "@kbn/react-kibana-mount", diff --git a/x-pack/plugins/rollup/public/crud_app/sections/components/deprecation_callout/deprecation_callout.tsx b/x-pack/plugins/rollup/public/crud_app/sections/components/deprecation_callout/deprecation_callout.tsx new file mode 100644 index 0000000000000..4b36b9a086b69 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/components/deprecation_callout/deprecation_callout.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiCallOut, EuiLink } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { documentationLinks } from '../../../services/documentation_links'; + +/* +A component for displaying a deprecation warning. + */ +export const DeprecationCallout = () => { + return ( + + + {i18n.translate('xpack.rollupJobs.deprecationCallout.migrationGuideLink', { + defaultMessage: 'migration guide', + })} + + ), + downsamplingLink: ( + + {i18n.translate('xpack.rollupJobs.deprecationCallout.downsamplingLink', { + defaultMessage: 'downsampling', + })} + + ), + }} + /> + + ); +}; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/components/deprecation_callout/index.ts b/x-pack/plugins/rollup/public/crud_app/sections/components/deprecation_callout/index.ts new file mode 100644 index 0000000000000..cfa1379a95030 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/components/deprecation_callout/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 { DeprecationCallout } from './deprecation_callout'; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/components/index.js b/x-pack/plugins/rollup/public/crud_app/sections/components/index.js index ca2dbcfe23f9d..9b1576902b797 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/components/index.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/components/index.js @@ -21,3 +21,5 @@ export { } from './job_details'; export { JobStatus } from './job_status'; + +export { DeprecationCallout } from './deprecation_callout'; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.js index c782e8646b714..4983fbb10ae8f 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/job_create.js @@ -13,6 +13,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { withKibana } from '@kbn/kibana-react-plugin/public'; +import { DeprecationCallout } from '../components'; import { EuiCallOut, @@ -556,6 +557,10 @@ export class JobCreateUi extends Component { + + + + {saveErrorFeedback} diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/deprecated_prompt.tsx b/x-pack/plugins/rollup/public/crud_app/sections/job_list/deprecated_prompt.tsx new file mode 100644 index 0000000000000..ecdae95e90813 --- /dev/null +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/deprecated_prompt.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { Fragment } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiButton, EuiEmptyPrompt, EuiPageSection } from '@elastic/eui'; +import { documentationLinks } from '../../services/documentation_links'; + +export const DeprecatedPrompt = () => { + return ( + + + + + } + body={ + +

+ +

+
+ } + actions={ + + + + } + /> +
+ ); +}; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js index 83641d6483817..7e03ade8b0221 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.js @@ -11,7 +11,6 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { - EuiButton, EuiButtonEmpty, EuiEmptyPrompt, EuiPageHeader, @@ -22,11 +21,13 @@ import { import { withKibana } from '@kbn/kibana-react-plugin/public'; import { extractQueryParams, SectionLoading } from '../../../shared_imports'; -import { getRouterLinkProps, listBreadcrumb } from '../../services'; +import { listBreadcrumb } from '../../services'; import { documentationLinks } from '../../services/documentation_links'; import { JobTable } from './job_table'; import { DetailPanel } from './detail_panel'; +import { DeprecationCallout } from '../components'; +import { DeprecatedPrompt } from './deprecated_prompt'; const REFRESH_RATE_MS = 30000; @@ -132,50 +133,6 @@ export class JobListUi extends Component { ); } - renderEmpty() { - return ( - - - - - } - body={ - -

- -

-
- } - actions={ - - - - } - /> -
- ); - } - renderLoading() { return ( @@ -216,6 +173,10 @@ export class JobListUi extends Component { + + + + @@ -235,7 +196,7 @@ export class JobListUi extends Component { content = this.renderError(jobLoadError); } } else if (!isLoading && !hasJobs) { - content = this.renderEmpty(); + content = ; } else if (isLoading) { content = this.renderLoading(); } else { diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.test.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.test.js index d15376323c0d3..06c32a647e68d 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.test.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.test.js @@ -52,10 +52,10 @@ const Component = (props) => ( const initTestBed = registerTestBed(Component, { defaultProps, store: rollupJobsStore }); describe('', () => { - it('should render empty prompt when loading is complete and there are no jobs', () => { + it('should render deprecated prompt when loading is complete and there are no rollup jobs', () => { const { exists } = initTestBed(); - expect(exists('jobListEmptyPrompt')).toBeTruthy(); + expect(exists('jobListDeprecatedPrompt')).toBeTruthy(); }); it('should display a loading message when loading the jobs', () => { diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_date_histogram.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_date_histogram.test.js index 022575e686679..8804f53e65ac6 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_date_histogram.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_date_histogram.test.js @@ -63,6 +63,10 @@ describe('Create Rollup Job, step 2: Date histogram', () => { expect(exists('rollupJobCreateDateHistogramDocsButton')).toBe(true); }); + test('should have a deprecation callout', () => { + expect(exists('rollupDeprecationCallout')).toBe(true); + }); + it('should have the "next" and "back" button visible', () => { expect(exists('rollupJobBackButton')).toBe(true); expect(exists('rollupJobNextButton')).toBe(true); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_histogram.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_histogram.test.js index bf2b76f75b9ab..2ed942604842d 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_histogram.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_histogram.test.js @@ -70,6 +70,10 @@ describe('Create Rollup Job, step 4: Histogram', () => { expect(exists('rollupJobCreateHistogramDocsButton')).toBe(true); }); + test('should have a deprecation callout', () => { + expect(exists('rollupDeprecationCallout')).toBe(true); + }); + it('should have the "next" and "back" button visible', () => { expect(exists('rollupJobBackButton')).toBe(true); expect(exists('rollupJobNextButton')).toBe(true); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js index fdeccde8690a7..b073dc97b574a 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_logistics.test.js @@ -58,6 +58,10 @@ describe('Create Rollup Job, step 1: Logistics', () => { expect(exists('rollupJobCreateLogisticsDocsButton')).toBe(true); }); + test('should have a deprecation callout', () => { + expect(exists('rollupDeprecationCallout')).toBe(true); + }); + it('should only have the "next" button visible', () => { expect(exists('rollupJobBackButton')).toBe(false); expect(exists('rollupJobNextButton')).toBe(true); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js index 872e26727ba25..b63935b18240c 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_metrics.test.js @@ -71,6 +71,10 @@ describe('Create Rollup Job, step 5: Metrics', () => { expect(exists('rollupJobCreateMetricsDocsButton')).toBe(true); }); + test('should have a deprecation callout', () => { + expect(exists('rollupDeprecationCallout')).toBe(true); + }); + it('should have the "next" and "back" button visible', () => { expect(exists('rollupJobBackButton')).toBe(true); expect(exists('rollupJobNextButton')).toBe(true); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_review.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_review.test.js index 5c0ceb7c25f74..d1b42bfaafbcd 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_review.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_review.test.js @@ -70,6 +70,10 @@ describe('Create Rollup Job, step 6: Review', () => { expect(exists('rollupJobCreateReviewTitle')).toBe(true); }); + test('should have a deprecation callout', () => { + expect(exists('rollupDeprecationCallout')).toBe(true); + }); + it('should have the "next" and "save" button visible', () => { expect(exists('rollupJobBackButton')).toBe(true); expect(exists('rollupJobNextButton')).toBe(false); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_create_terms.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_create_terms.test.js index 6736f82279c44..7b55e39ca08f9 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_create_terms.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_create_terms.test.js @@ -68,6 +68,10 @@ describe('Create Rollup Job, step 3: Terms', () => { expect(exists('rollupJobCreateTermsDocsButton')).toBe(true); }); + test('should have a deprecation callout', () => { + expect(exists('rollupDeprecationCallout')).toBe(true); + }); + it('should have the "next" and "back" button visible', () => { expect(exists('rollupJobBackButton')).toBe(true); expect(exists('rollupJobNextButton')).toBe(true); diff --git a/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js b/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js index 553fc0712d6fc..b329fce20c7bd 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js +++ b/x-pack/plugins/rollup/public/test/client_integration/job_list.test.js @@ -62,6 +62,10 @@ describe('', () => { startMock.http.get.mockClear(); }); + test('should have a deprecation callout', () => { + expect(exists('rollupDeprecationCallout')).toBe(true); + }); + test('should open the detail panel when clicking on a job in the table', () => { const { rows } = table.getMetaData('rollupJobsListTable'); const button = rows[0].columns[1].reactWrapper.find('button'); diff --git a/x-pack/plugins/rollup/server/plugin.ts b/x-pack/plugins/rollup/server/plugin.ts index ff8ff7e809b13..cd0a6835cd857 100644 --- a/x-pack/plugins/rollup/server/plugin.ts +++ b/x-pack/plugins/rollup/server/plugin.ts @@ -90,6 +90,12 @@ export class RollupPlugin implements Plugin { category: ['rollups'], schema: schema.boolean(), requiresPageReload: true, + deprecation: { + message: i18n.translate('xpack.rollupJobs.rollupDataViewsDeprecation', { + defaultMessage: 'This setting is deprecated and will be removed in Kibana 9.0.', + }), + docLinksKey: 'rollupSettings', + }, }, }); diff --git a/x-pack/plugins/search_homepage/README.mdx b/x-pack/plugins/search_homepage/README.mdx new file mode 100644 index 0000000000000..00ba4f491c607 --- /dev/null +++ b/x-pack/plugins/search_homepage/README.mdx @@ -0,0 +1,3 @@ +# Search Homepage + +The Search Homepage is a shared homepage for elasticsearch users. diff --git a/x-pack/plugins/search_homepage/common/index.ts b/x-pack/plugins/search_homepage/common/index.ts new file mode 100644 index 0000000000000..d93d5c49f8cc5 --- /dev/null +++ b/x-pack/plugins/search_homepage/common/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 const PLUGIN_ID = 'searchHomepage'; +export const PLUGIN_NAME = 'searchHomepage'; + +/** + * UI Setting id for the Search Homepage feature flag + */ +export const HOMEPAGE_FEATURE_FLAG_ID = 'searchHomepage:homepageEnabled'; diff --git a/x-pack/plugins/search_homepage/jest.config.js b/x-pack/plugins/search_homepage/jest.config.js new file mode 100644 index 0000000000000..65cd8f1e34252 --- /dev/null +++ b/x-pack/plugins/search_homepage/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/search_homepage'], + coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/search_homepage', + coverageReporters: ['text', 'html'], + collectCoverageFrom: ['/x-pack/plugins/search_homepage/{public,server}/**/*.{ts,tsx}'], +}; diff --git a/x-pack/plugins/search_homepage/kibana.jsonc b/x-pack/plugins/search_homepage/kibana.jsonc new file mode 100644 index 0000000000000..0e345ab0d330a --- /dev/null +++ b/x-pack/plugins/search_homepage/kibana.jsonc @@ -0,0 +1,26 @@ +{ + "type": "plugin", + "id": "@kbn/search-homepage", + "owner": "@elastic/search-kibana", + "plugin": { + "id": "searchHomepage", + "server": true, + "browser": true, + "configPath": [ + "xpack", + "search", + "homepage" + ], + "requiredPlugins": [ + "share", + ], + "optionalPlugins": [ + "cloud", + "console", + "usageCollection", + ], + "requiredBundles": [ + "kibanaReact" + ] + } +} diff --git a/x-pack/plugins/search_homepage/public/application.tsx b/x-pack/plugins/search_homepage/public/application.tsx new file mode 100644 index 0000000000000..4af256de498ed --- /dev/null +++ b/x-pack/plugins/search_homepage/public/application.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { CoreStart } from '@kbn/core/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { I18nProvider } from '@kbn/i18n-react'; +import { Router } from '@kbn/shared-ux-router'; +import { SearchHomepageAppPluginStartDependencies } from './types'; +import { HomepageRouter } from './router'; + +export const renderApp = async ( + core: CoreStart, + services: SearchHomepageAppPluginStartDependencies, + element: HTMLElement +) => { + ReactDOM.render( + + + + + + + + + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/x-pack/plugins/search_homepage/public/components/search_homepage.tsx b/x-pack/plugins/search_homepage/public/components/search_homepage.tsx new file mode 100644 index 0000000000000..7af02cbcf3275 --- /dev/null +++ b/x-pack/plugins/search_homepage/public/components/search_homepage.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; +import { EuiPageTemplate } from '@elastic/eui'; + +import { useKibana } from '../hooks/use_kibana'; +import { SearchHomepageBody } from './search_homepage_body'; +import { SearchHomepageHeader } from './search_homepage_header'; + +export const SearchHomepagePage = () => { + const { + services: { console: consolePlugin }, + } = useKibana(); + + const embeddableConsole = useMemo( + () => (consolePlugin?.EmbeddableConsole ? : null), + [consolePlugin] + ); + + return ( + + + + {embeddableConsole} + + ); +}; diff --git a/x-pack/plugins/search_homepage/public/components/search_homepage_body.tsx b/x-pack/plugins/search_homepage/public/components/search_homepage_body.tsx new file mode 100644 index 0000000000000..808393594e7d8 --- /dev/null +++ b/x-pack/plugins/search_homepage/public/components/search_homepage_body.tsx @@ -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 React from 'react'; +import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; + +export const SearchHomepageBody = () => ( + +
+ +); diff --git a/x-pack/plugins/search_homepage/public/components/search_homepage_header.tsx b/x-pack/plugins/search_homepage/public/components/search_homepage_header.tsx new file mode 100644 index 0000000000000..941655d67cdab --- /dev/null +++ b/x-pack/plugins/search_homepage/public/components/search_homepage_header.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { EuiPageTemplate, EuiTitle } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +export const SearchHomepageHeader = () => ( + +

+ +

+ + } + data-test-subj="search-homepage-header" + rightSideItems={[]} + /> +); diff --git a/x-pack/plugins/search_homepage/public/components/stack_app.tsx b/x-pack/plugins/search_homepage/public/components/stack_app.tsx new file mode 100644 index 0000000000000..ca18ac7112c09 --- /dev/null +++ b/x-pack/plugins/search_homepage/public/components/stack_app.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { SearchHomepageBody } from './search_homepage_body'; +import { SearchHomepageHeader } from './search_homepage_header'; + +export const App: React.FC = () => { + return ( + <> + + + + ); +}; diff --git a/x-pack/plugins/search_homepage/public/embeddable.tsx b/x-pack/plugins/search_homepage/public/embeddable.tsx new file mode 100644 index 0000000000000..ee69062ea3fe5 --- /dev/null +++ b/x-pack/plugins/search_homepage/public/embeddable.tsx @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { dynamic } from '@kbn/shared-ux-utility'; + +export const SearchHomepage = dynamic(async () => ({ + default: (await import('./components/stack_app')).App, +})); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/finalize_signals_migration/finalize_signals_migration_route.ts b/x-pack/plugins/search_homepage/public/feature_flags.ts similarity index 50% rename from x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/finalize_signals_migration/finalize_signals_migration_route.ts rename to x-pack/plugins/search_homepage/public/feature_flags.ts index a4a48a7bc0c21..bea65a8e1548f 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/finalize_signals_migration/finalize_signals_migration_route.ts +++ b/x-pack/plugins/search_homepage/public/feature_flags.ts @@ -5,12 +5,9 @@ * 2.0. */ -import * as t from 'io-ts'; +import { IUiSettingsClient } from '@kbn/core/public'; +import { HOMEPAGE_FEATURE_FLAG_ID } from '../common'; -export const finalizeSignalsMigrationSchema = t.exact( - t.type({ - migration_ids: t.array(t.string), - }) -); - -export type FinalizeSignalsMigrationSchema = t.TypeOf; +export function isHomepageEnabled(uiSettings: IUiSettingsClient): boolean { + return uiSettings.get(HOMEPAGE_FEATURE_FLAG_ID, false); +} diff --git a/x-pack/plugins/search_homepage/public/hooks/use_kibana.ts b/x-pack/plugins/search_homepage/public/hooks/use_kibana.ts new file mode 100644 index 0000000000000..b22c7b4ed9d7f --- /dev/null +++ b/x-pack/plugins/search_homepage/public/hooks/use_kibana.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useKibana as _useKibana } from '@kbn/kibana-react-plugin/public'; +import { SearchHomepageServicesContext } from '../types'; + +export const useKibana = () => _useKibana(); diff --git a/x-pack/plugins/search_homepage/public/index.ts b/x-pack/plugins/search_homepage/public/index.ts new file mode 100644 index 0000000000000..b5133bb506406 --- /dev/null +++ b/x-pack/plugins/search_homepage/public/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PluginInitializerContext } from '@kbn/core/public'; + +import { SearchHomepagePlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new SearchHomepagePlugin(initializerContext); +} + +export type { + SearchHomepagePluginSetup, + SearchHomepagePluginStart, + SearchHomepageAppInfo, +} from './types'; diff --git a/x-pack/plugins/search_homepage/public/plugin.ts b/x-pack/plugins/search_homepage/public/plugin.ts new file mode 100644 index 0000000000000..ebb3ef8ed822c --- /dev/null +++ b/x-pack/plugins/search_homepage/public/plugin.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + CoreSetup, + Plugin, + CoreStart, + AppMountParameters, + PluginInitializerContext, +} from '@kbn/core/public'; +import { i18n } from '@kbn/i18n'; +import { PLUGIN_ID } from '../common'; + +import { SearchHomepage } from './embeddable'; +import { isHomepageEnabled } from './feature_flags'; +import { + SearchHomepageConfigType, + SearchHomepagePluginSetup, + SearchHomepagePluginStart, + SearchHomepageAppPluginStartDependencies, + SearchHomepageAppInfo, +} from './types'; + +const appInfo: SearchHomepageAppInfo = { + id: PLUGIN_ID, + appRoute: '/app/elasticsearch/home', + title: i18n.translate('xpack.searchHomepage.appTitle', { defaultMessage: 'Home' }), +}; + +export class SearchHomepagePlugin + implements Plugin +{ + private readonly config: SearchHomepageConfigType; + constructor(initializerContext: PluginInitializerContext) { + this.config = initializerContext.config.get(); + } + + public setup( + core: CoreSetup + ) { + const result: SearchHomepagePluginSetup = { + app: appInfo, + isHomepageFeatureEnabled() { + return isHomepageEnabled(core.uiSettings); + }, + }; + if (!this.config.ui?.enabled) return result; + if (!isHomepageEnabled(core.uiSettings)) return result; + + core.application.register({ + ...result.app, + async mount({ element, history }: AppMountParameters) { + const { renderApp } = await import('./application'); + const [coreStart, depsStart] = await core.getStartServices(); + const startDeps: SearchHomepageAppPluginStartDependencies = { + ...depsStart, + history, + }; + + return renderApp(coreStart, startDeps, element); + }, + }); + + return result; + } + + public start(core: CoreStart) { + return { + app: appInfo, + isHomepageFeatureEnabled() { + return isHomepageEnabled(core.uiSettings); + }, + SearchHomepage, + }; + } +} diff --git a/x-pack/plugins/search_homepage/public/router.tsx b/x-pack/plugins/search_homepage/public/router.tsx new file mode 100644 index 0000000000000..e4db94ebde4ae --- /dev/null +++ b/x-pack/plugins/search_homepage/public/router.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { Route, Routes } from '@kbn/shared-ux-router'; + +import { SearchHomepagePage } from './components/search_homepage'; + +export const HomepageRouter = () => ( + + + + + +); diff --git a/x-pack/plugins/search_homepage/public/types.ts b/x-pack/plugins/search_homepage/public/types.ts new file mode 100644 index 0000000000000..de5283abfc61d --- /dev/null +++ b/x-pack/plugins/search_homepage/public/types.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ComponentProps, FC } from 'react'; +import type { CloudSetup } from '@kbn/cloud-plugin/public'; +import type { ConsolePluginStart } from '@kbn/console-plugin/public'; +import type { AppMountParameters, HttpStart } from '@kbn/core/public'; +import type { SharePluginStart } from '@kbn/share-plugin/public'; +import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import type { App } from './components/stack_app'; + +export interface SearchHomepageConfigType { + ui: { + enabled: boolean; + }; +} + +export interface SearchHomepageAppInfo { + appRoute: string; + id: string; + title: string; +} + +export interface SearchHomepagePluginSetup { + /** + * Search Homepage shared information for the Kibana application. + * Used to ensure the stack and serverless apps have the same route + * and deep links. + */ + app: SearchHomepageAppInfo; + /** + * Checks if the Search Homepage feature flag is currently enabled. + * @returns true if Search Homepage feature is enabled + */ + isHomepageFeatureEnabled: () => boolean; +} + +export interface SearchHomepagePluginStart { + /** + * Search Homepage shared information for the Kibana application. + * Used to ensure the stack and serverless apps have the same route + * and deep links. + */ + app: SearchHomepageAppInfo; + /** + * Checks if the Search Homepage feature flag is currently enabled. + * @returns true if Search Homepage feature is enabled + */ + isHomepageFeatureEnabled: () => boolean; + /** + * SearchHomepage shared component, used to render the search homepage in + * the Stack search plugin + */ + SearchHomepage: FC>; +} + +export interface SearchHomepageAppPluginStartDependencies { + history: AppMountParameters['history']; + usageCollection?: UsageCollectionStart; + share: SharePluginStart; + console?: ConsolePluginStart; +} + +export interface SearchHomepageServicesContext { + http: HttpStart; + share: SharePluginStart; + cloud?: CloudSetup; + usageCollection?: UsageCollectionStart; + console?: ConsolePluginStart; +} diff --git a/x-pack/plugins/search_homepage/server/config.ts b/x-pack/plugins/search_homepage/server/config.ts new file mode 100644 index 0000000000000..3e068a719f046 --- /dev/null +++ b/x-pack/plugins/search_homepage/server/config.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { PluginConfigDescriptor } from '@kbn/core/server'; + +export * from './types'; + +const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), + ui: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + }), +}); + +export type SearchHomepageConfig = TypeOf; + +export const config: PluginConfigDescriptor = { + exposeToBrowser: { + ui: true, + }, + schema: configSchema, +}; diff --git a/x-pack/plugins/search_homepage/server/index.ts b/x-pack/plugins/search_homepage/server/index.ts new file mode 100644 index 0000000000000..864af85c0a2fb --- /dev/null +++ b/x-pack/plugins/search_homepage/server/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PluginInitializerContext } from '@kbn/core/server'; + +export { config } from './config'; + +export async function plugin(initializerContext: PluginInitializerContext) { + const { SearchHomepagePlugin } = await import('./plugin'); + return new SearchHomepagePlugin(initializerContext); +} + +export type { SearchHomepagePluginSetup, SearchHomepagePluginStart } from './types'; diff --git a/x-pack/plugins/search_homepage/server/plugin.ts b/x-pack/plugins/search_homepage/server/plugin.ts new file mode 100644 index 0000000000000..f446ba4e41fd3 --- /dev/null +++ b/x-pack/plugins/search_homepage/server/plugin.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from '@kbn/core/server'; + +import { SearchHomepagePluginSetup, SearchHomepagePluginStart } from './types'; + +export class SearchHomepagePlugin + implements Plugin +{ + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup(core: CoreSetup<{}, SearchHomepagePluginStart>) { + this.logger.debug('searchHomepage: Setup'); + return {}; + } + + public start(core: CoreStart) { + return {}; + } +} diff --git a/x-pack/plugins/search_homepage/server/types.ts b/x-pack/plugins/search_homepage/server/types.ts new file mode 100644 index 0000000000000..c4e5d46959422 --- /dev/null +++ b/x-pack/plugins/search_homepage/server/types.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. + */ + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SearchHomepagePluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SearchHomepagePluginStart {} diff --git a/x-pack/plugins/search_homepage/tsconfig.json b/x-pack/plugins/search_homepage/tsconfig.json new file mode 100644 index 0000000000000..9f084b32f7d3b --- /dev/null +++ b/x-pack/plugins/search_homepage/tsconfig.json @@ -0,0 +1,30 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + }, + "include": [ + "__mocks__/**/*", + "common/**/*", + "public/**/*", + "server/**/*", + ], + "kbn_references": [ + "@kbn/core", + "@kbn/react-kibana-context-render", + "@kbn/kibana-react-plugin", + "@kbn/i18n-react", + "@kbn/shared-ux-router", + "@kbn/shared-ux-page-kibana-template", + "@kbn/shared-ux-utility", + "@kbn/i18n", + "@kbn/cloud-plugin", + "@kbn/console-plugin", + "@kbn/share-plugin", + "@kbn/usage-collection-plugin", + "@kbn/config-schema", + ], + "exclude": [ + "target/**/*", + ] +} diff --git a/x-pack/plugins/search_inference_endpoints/README.md b/x-pack/plugins/search_inference_endpoints/README.md new file mode 100644 index 0000000000000..3c28f965e7bf5 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/README.md @@ -0,0 +1,3 @@ +# Inference Endpoints + +The Inference Endpoints is a tool used to manage inference endpoints diff --git a/x-pack/plugins/lists/common/api/values/patch_list/patch_list_route.ts b/x-pack/plugins/search_inference_endpoints/common/constants.ts similarity index 59% rename from x-pack/plugins/lists/common/api/values/patch_list/patch_list_route.ts rename to x-pack/plugins/search_inference_endpoints/common/constants.ts index 8821658c5b23a..cdba227626a55 100644 --- a/x-pack/plugins/lists/common/api/values/patch_list/patch_list_route.ts +++ b/x-pack/plugins/search_inference_endpoints/common/constants.ts @@ -5,6 +5,7 @@ * 2.0. */ -import { listSchema, patchListSchema } from '@kbn/securitysolution-io-ts-list-types'; +export const PLUGIN_ID = 'searchInferenceEndpoints'; +export const PLUGIN_NAME = 'InferenceEndpoints'; -export { patchListSchema as patchListRequest, listSchema as patchListResponse }; +export const INFERENCE_ENDPOINTS_QUERY_KEY = 'inferenceEndpointsQueryKey'; diff --git a/x-pack/plugins/search_inference_endpoints/common/doc_links.ts b/x-pack/plugins/search_inference_endpoints/common/doc_links.ts new file mode 100644 index 0000000000000..c3d8d31dffa6d --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/common/doc_links.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 { DocLinks } from '@kbn/doc-links'; + +class InferenceEndpointsDocLinks { + public nlpImportModel: string = ''; + public supportedNlpModels: string = ''; + + constructor() {} + + setDocLinks(newDocLinks: DocLinks) { + this.nlpImportModel = newDocLinks.ml.nlpImportModel; + this.supportedNlpModels = newDocLinks.ml.supportedNlpModels; + } +} + +export const docLinks = new InferenceEndpointsDocLinks(); diff --git a/x-pack/plugins/search_inference_endpoints/common/translations.ts b/x-pack/plugins/search_inference_endpoints/common/translations.ts new file mode 100644 index 0000000000000..e58829812829b --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/common/translations.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const INFERENCE_ENDPOINT_LABEL = i18n.translate( + 'xpack.searchInferenceEndpoints.inferenceEndpointsLabel', + { + defaultMessage: 'Inference Endpoints', + } +); + +export const MANAGE_INFERENCE_ENDPOINTS_LABEL = i18n.translate( + 'xpack.searchInferenceEndpoints.allInferenceEndpoints.description', + { + defaultMessage: 'Manage your inference endpoints.', + } +); + +export const ADD_ENDPOINT_LABEL = i18n.translate( + 'xpack.searchInferenceEndpoints.newInferenceEndpointButtonLabel', + { + defaultMessage: 'Add endpoint', + } +); + +export const CREATE_FIRST_INFERENCE_ENDPOINT_DESCRIPTION = i18n.translate( + 'xpack.searchInferenceEndpoints.addEmptyPrompt.createFirstInferenceEndpointDescription', + { + defaultMessage: + 'Connect to your third-party model provider to create an inference endpoint for semantic search.', + } +); + +export const START_WITH_PREPARED_ENDPOINTS_LABEL = i18n.translate( + 'xpack.searchInferenceEndpoints.addEmptyPrompt.startWithPreparedEndpointsLabel', + { + defaultMessage: 'Get started quickly with our prepared endpoints:', + } +); + +export const ELSER_TITLE = i18n.translate( + 'xpack.searchInferenceEndpoints.addEmptyPrompt.elserTitle', + { + defaultMessage: 'ELSER', + } +); + +export const ELSER_DESCRIPTION = i18n.translate( + 'xpack.searchInferenceEndpoints.addEmptyPrompt.elserDescription', + { + defaultMessage: + 'ELSER is a sparse vector NLP model trained by Elastic for semantic search. Recommended for English language.', + } +); + +export const E5_TITLE = i18n.translate('xpack.searchInferenceEndpoints.addEmptyPrompt.e5Title', { + defaultMessage: 'Multilingual E5', +}); + +export const E5_DESCRIPTION = i18n.translate( + 'xpack.searchInferenceEndpoints.addEmptyPrompt.e5Description', + { + defaultMessage: + 'E5 is a dense vector NLP model that enables you to perform multi-lingual semantic search.', + } +); + +export const ERROR_TITLE = i18n.translate('xpack.searchInferenceEndpoints.inferenceId.errorTitle', { + defaultMessage: 'Error adding inference endpoint', +}); + +export const UNABLE_TO_CREATE_INFERENCE_ENDPOINT = i18n.translate( + 'xpack.searchInferenceEndpoints.inferenceFlyoutWrapperComponent.unableTocreateInferenceEndpointError', + { + defaultMessage: 'Unable to create an inference endpoint.', + } +); + +export const INFERENCE_ENDPOINT_ALREADY_EXISTS = i18n.translate( + 'xpack.searchInferenceEndpoints.inferenceFlyoutWrapperComponent.inferenceEndpointAlreadyExistsError', + { + defaultMessage: 'Inference Endpoint id already exists', + } +); + +export const FORBIDDEN_TO_ACCESS_TRAINED_MODELS = i18n.translate( + 'xpack.searchInferenceEndpoints.inferenceFlyoutWrapperComponent.forbiddenToAccessTrainedModelsError', + { + defaultMessage: 'Forbidden to access trained models', + } +); diff --git a/x-pack/plugins/search_inference_endpoints/common/types.ts b/x-pack/plugins/search_inference_endpoints/common/types.ts new file mode 100644 index 0000000000000..ef29d987309f8 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/common/types.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export enum APIRoutes { + GET_INFERENCE_ENDPOINTS = '/internal/inference_endpoints/endpoints', +} + +export interface SearchInferenceEndpointsConfigType { + ui: { + enabled: boolean; + }; +} diff --git a/x-pack/plugins/search_inference_endpoints/jest.config.js b/x-pack/plugins/search_inference_endpoints/jest.config.js new file mode 100644 index 0000000000000..8c19afbe9fb98 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/jest.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/search_inference_endpoints'], + coverageDirectory: + '/target/kibana-coverage/jest/x-pack/plugins/search_inference_endpoints', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/x-pack/plugins/search_inference_endpoints/{public,server}/**/*.{ts,tsx}', + ], +}; diff --git a/x-pack/plugins/search_inference_endpoints/kibana.jsonc b/x-pack/plugins/search_inference_endpoints/kibana.jsonc new file mode 100644 index 0000000000000..6cdca96205588 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/kibana.jsonc @@ -0,0 +1,26 @@ +{ + "type": "plugin", + "id": "@kbn/search-inference-endpoints", + "owner": "@elastic/search-kibana", + "plugin": { + "id": "searchInferenceEndpoints", + "server": true, + "browser": true, + "configPath": [ + "xpack", + "searchInferenceEndpoints" + ], + "requiredPlugins": [ + "actions", + "share", + ], + "optionalPlugins": [ + "cloud", + "console", + "ml" + ], + "requiredBundles": [ + "kibanaReact" + ] + } +} diff --git a/x-pack/plugins/search_inference_endpoints/public/application.tsx b/x-pack/plugins/search_inference_endpoints/public/application.tsx new file mode 100644 index 0000000000000..c14f24a678281 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/application.tsx @@ -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 React from 'react'; +import ReactDOM from 'react-dom'; +import { CoreStart } from '@kbn/core/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { I18nProvider } from '@kbn/i18n-react'; +import { Router } from '@kbn/shared-ux-router'; +import { AppPluginStartDependencies } from './types'; + +export const renderApp = async ( + core: CoreStart, + services: AppPluginStartDependencies, + element: HTMLElement +) => { + const { InferenceEndpointsRouter } = await import('./inference_endpoints_router'); + + ReactDOM.render( + + + + + + + + + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/x-pack/plugins/search_inference_endpoints/public/assets/images/inference_endpoint.svg b/x-pack/plugins/search_inference_endpoints/public/assets/images/inference_endpoint.svg new file mode 100644 index 0000000000000..927eccf9106a6 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/assets/images/inference_endpoint.svg @@ -0,0 +1,4338 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/constants.ts b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/constants.ts new file mode 100644 index 0000000000000..1b7e72149fd43 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/constants.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + SortFieldInferenceEndpoint, + QueryParams, + AlInferenceEndpointsTableState, + SortOrder, +} from './types'; + +export const DEFAULT_TABLE_ACTIVE_PAGE = 1; +export const DEFAULT_TABLE_LIMIT = 10; + +export const DEFAULT_QUERY_PARAMS: QueryParams = { + page: DEFAULT_TABLE_ACTIVE_PAGE, + perPage: DEFAULT_TABLE_LIMIT, + sortField: SortFieldInferenceEndpoint.endpoint, + sortOrder: SortOrder.asc, +}; + +export const DEFAULT_INFERENCE_ENDPOINTS_TABLE_STATE: AlInferenceEndpointsTableState = { + queryParams: DEFAULT_QUERY_PARAMS, +}; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/endpoints_table.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/endpoints_table.tsx new file mode 100644 index 0000000000000..af4755c72fc11 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/endpoints_table.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import type { EuiBasicTableProps, Pagination } from '@elastic/eui'; +import { EuiBasicTable } from '@elastic/eui'; + +import type { InferenceEndpointUI } from './types'; + +interface EndpointsTableProps { + columns: EuiBasicTableProps['columns']; + data: InferenceEndpointUI[]; + onChange: EuiBasicTableProps['onChange']; + pagination: Pagination; + sorting: EuiBasicTableProps['sorting']; +} + +export const EndpointsTable: React.FC = ({ + columns, + data, + onChange, + pagination, + sorting, +}) => { + return ( + + ); +}; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/table_columns.ts b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/table_columns.ts new file mode 100644 index 0000000000000..39e957f908684 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/table_columns.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 { i18n } from '@kbn/i18n'; + +export const TABLE_COLUMNS = [ + { + field: 'endpoint', + name: i18n.translate('xpack.searchInferenceEndpoints.inferenceEndpoints.table.endpoint', { + defaultMessage: 'Endpoint', + }), + sortable: true, + width: '50%', + }, + { + field: 'provider', + name: i18n.translate('xpack.searchInferenceEndpoints.inferenceEndpoints.table.provider', { + defaultMessage: 'Provider', + }), + sortable: false, + width: '110px', + }, + { + field: 'type', + name: i18n.translate('xpack.searchInferenceEndpoints.inferenceEndpoints.table.type', { + defaultMessage: 'Type', + }), + sortable: false, + width: '90px', + }, +]; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.test.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.test.tsx new file mode 100644 index 0000000000000..091ff8270dd63 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.test.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { screen } from '@testing-library/react'; +import { render } from '@testing-library/react'; +import { TabularPage } from './tabular_page'; +import { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils'; + +const inferenceEndpoints = [ + { + model_id: 'my-elser-model-05', + task_type: 'sparse_embedding', + service: 'elser', + service_settings: { + num_allocations: 1, + num_threads: 1, + model_id: '.elser_model_2', + }, + task_settings: {}, + }, + { + model_id: 'my-elser-model-04', + task_type: 'sparse_embedding', + service: 'elser', + service_settings: { + num_allocations: 1, + num_threads: 1, + model_id: '.elser_model_2', + }, + task_settings: {}, + }, +] as InferenceAPIConfigResponse[]; + +describe('When the tabular page is loaded', () => { + beforeEach(() => { + render(); + }); + + it('should display all model_ids in the table', () => { + const rows = screen.getAllByRole('row'); + expect(rows[1]).toHaveTextContent('my-elser-model-04'); + expect(rows[2]).toHaveTextContent('my-elser-model-05'); + }); +}); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.tsx new file mode 100644 index 0000000000000..2fb84dc4b99de --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.tsx @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback } from 'react'; + +import { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils'; + +import { useTableData } from '../../hooks/use_table_data'; + +import { useAllInferenceEndpointsState } from '../../hooks/use_all_inference_endpoints_state'; +import { EndpointsTable } from './endpoints_table'; +import { TABLE_COLUMNS } from './table_columns'; + +interface TabularPageProps { + inferenceEndpoints: InferenceAPIConfigResponse[]; +} + +export const TabularPage: React.FC = ({ inferenceEndpoints }) => { + const { queryParams, setQueryParams } = useAllInferenceEndpointsState(); + + const { paginatedSortedTableData, pagination, sorting } = useTableData( + inferenceEndpoints, + queryParams + ); + + const handleTableChange = useCallback( + ({ page, sort }) => { + const newQueryParams = { + ...queryParams, + ...(sort && { + sortField: sort.field, + sortOrder: sort.direction, + }), + ...(page && { + page: page.index + 1, + perPage: page.size, + }), + }; + setQueryParams(newQueryParams); + }, + [queryParams, setQueryParams] + ); + + return ( + + ); +}; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/types.ts b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/types.ts new file mode 100644 index 0000000000000..4afba1dda110a --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/types.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const INFERENCE_ENDPOINTS_TABLE_PER_PAGE_VALUES = [10, 25, 50, 100]; + +export enum SortFieldInferenceEndpoint { + endpoint = 'endpoint', +} +export enum SortOrder { + asc = 'asc', + desc = 'desc', +} + +export interface SortingParams { + sortField: SortFieldInferenceEndpoint; + sortOrder: SortOrder; +} + +export interface QueryParams extends SortingParams { + page: number; + perPage: number; +} + +export interface AlInferenceEndpointsTableState { + queryParams: QueryParams; +} + +export interface EuiBasicTableSortTypes { + direction: SortOrder; + field: string; +} + +export interface InferenceEndpointUI { + endpoint: string; + provider: string; + type: string; +} diff --git a/x-pack/plugins/search_inference_endpoints/public/components/app.tsx b/x-pack/plugins/search_inference_endpoints/public/components/app.tsx new file mode 100644 index 0000000000000..30c74898680a0 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/app.tsx @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { InferenceEndpoints } from './inference_endpoints'; + +export const App: React.FC = () => { + return ; +}; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.scss b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.scss new file mode 100644 index 0000000000000..b85859948b0be --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.scss @@ -0,0 +1,3 @@ +.addEmptyPrompt { + max-width: 860px; +} \ No newline at end of file diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.test.tsx b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.test.tsx new file mode 100644 index 0000000000000..f9b7e11e48d94 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.test.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { fireEvent, screen } from '@testing-library/react'; +import { AddEmptyPrompt } from './add_empty_prompt'; + +import { renderReactTestingLibraryWithI18n as render } from '@kbn/test-jest-helpers'; +import '@testing-library/jest-dom'; +const setIsInferenceFlyoutVisibleMock = jest.fn(); + +describe('When empty prompt is loaded', () => { + beforeEach(() => { + render(); + }); + + it('should display the description for creation of the first inference endpoint', () => { + expect( + screen.getByText( + /Connect to your third-party model provider to create an inference endpoint for semantic search./ + ) + ).toBeInTheDocument(); + }); + + it('calls setIsInferenceFlyoutVisible when the addInferenceEndpoint button is clicked', async () => { + fireEvent.click(screen.getByTestId('addEndpointButtonForEmptyPrompt')); + expect(setIsInferenceFlyoutVisibleMock).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.tsx b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.tsx new file mode 100644 index 0000000000000..69d74724016d6 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { + EuiButton, + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + EuiImage, + EuiSpacer, +} from '@elastic/eui'; + +import * as i18n from '../../../common/translations'; + +import inferenceEndpoint from '../../assets/images/inference_endpoint.svg'; + +import { ElserPrompt } from './elser_prompt'; +import { MultilingualE5Prompt } from './multilingual_e5_prompt'; + +import './add_empty_prompt.scss'; + +interface AddEmptyPromptProps { + setIsInferenceFlyoutVisible: (value: boolean) => void; +} + +export const AddEmptyPrompt: React.FC = ({ setIsInferenceFlyoutVisible }) => { + return ( + {i18n.INFERENCE_ENDPOINT_LABEL}} + body={ + + + {i18n.CREATE_FIRST_INFERENCE_ENDPOINT_DESCRIPTION} + + +
+ setIsInferenceFlyoutVisible(true)} + > + {i18n.ADD_ENDPOINT_LABEL} + +
+
+
+ } + footer={ + + + {i18n.START_WITH_PREPARED_ENDPOINTS_LABEL} + + + + + + + + + + + + } + color="plain" + hasBorder + icon={} + /> + ); +}; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/elser_prompt.tsx b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/elser_prompt.tsx new file mode 100644 index 0000000000000..db38b649fd66e --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/elser_prompt.tsx @@ -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 React from 'react'; + +import { EuiButton, EuiCard } from '@elastic/eui'; + +import * as i18n from '../../../common/translations'; + +interface ElserPromptProps { + setIsInferenceFlyoutVisible: (value: boolean) => void; +} +export const ElserPrompt: React.FC = ({ setIsInferenceFlyoutVisible }) => ( + setIsInferenceFlyoutVisible(true)}> + {i18n.ADD_ENDPOINT_LABEL} + + } + /> +); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/index.ts b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/index.ts new file mode 100644 index 0000000000000..dcb2d12f6c986 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/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 { AddEmptyPrompt } from './add_empty_prompt'; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/multilingual_e5_prompt.tsx b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/multilingual_e5_prompt.tsx new file mode 100644 index 0000000000000..69133909efcb2 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/multilingual_e5_prompt.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { EuiButton, EuiCard } from '@elastic/eui'; + +import * as i18n from '../../../common/translations'; + +interface MultilingualE5PromptProps { + setIsInferenceFlyoutVisible: (value: boolean) => void; +} + +export const MultilingualE5Prompt: React.FC = ({ + setIsInferenceFlyoutVisible, +}) => ( + setIsInferenceFlyoutVisible(true)}> + {i18n.ADD_ENDPOINT_LABEL} + + } + /> +); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt_page.tsx b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt_page.tsx new file mode 100644 index 0000000000000..ec958266e4e1f --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt_page.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { AddEmptyPrompt } from './empty_prompt/add_empty_prompt'; + +interface EmptyPromptPageProps { + setIsInferenceFlyoutVisible: (value: boolean) => void; +} + +export const EmptyPromptPage: React.FC = ({ + setIsInferenceFlyoutVisible, +}) => ; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/endpoints_router.tsx b/x-pack/plugins/search_inference_endpoints/public/components/endpoints_router.tsx new file mode 100644 index 0000000000000..775a4efdbc15c --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/endpoints_router.tsx @@ -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 React from 'react'; + +import { Route, Routes } from '@kbn/shared-ux-router'; + +import { INFERENCE_ENDPOINTS_PATH } from './routes'; + +import { InferenceEndpoints } from './inference_endpoints'; + +export const EndpointsRouter: React.FC = () => { + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints.tsx b/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints.tsx new file mode 100644 index 0000000000000..0fa200991b8d1 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; + +import { EuiPageTemplate } from '@elastic/eui'; + +import { useQueryInferenceEndpoints } from '../hooks/use_inference_endpoints'; +import { TabularPage } from './all_inference_endpoints/tabular_page'; +import { EmptyPromptPage } from './empty_prompt_page'; +import { InferenceEndpointsHeader } from './inference_endpoints_header'; +import { InferenceFlyoutWrapperComponent } from './inference_flyout_wrapper_component'; + +export const InferenceEndpoints: React.FC = () => { + const { inferenceEndpoints } = useQueryInferenceEndpoints(); + const [isInferenceFlyoutVisible, setIsInferenceFlyoutVisible] = useState(false); + + return ( + <> + {inferenceEndpoints.length > 0 && ( + + )} + + {inferenceEndpoints.length === 0 ? ( + + ) : ( + + )} + + {isInferenceFlyoutVisible && ( + + )} + + ); +}; diff --git a/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints_header.tsx b/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints_header.tsx new file mode 100644 index 0000000000000..8b551af28bd4c --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints_header.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiPageTemplate, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; +import * as i18n from '../../common/translations'; + +interface InferenceEndpointsHeaderProps { + setIsInferenceFlyoutVisible: (isVisible: boolean) => void; +} + +export const InferenceEndpointsHeader: React.FC = ({ + setIsInferenceFlyoutVisible, +}) => ( + .euiFlexGroup': { flexWrap: 'wrap' } }} + data-test-subj="allInferenceEndpointsPage" + pageTitle={i18n.INFERENCE_ENDPOINT_LABEL} + description={i18n.MANAGE_INFERENCE_ENDPOINTS_LABEL} + rightSideItems={[ + + + setIsInferenceFlyoutVisible(true)} + > + {i18n.ADD_ENDPOINT_LABEL} + + + , + ]} + /> +); diff --git a/x-pack/plugins/search_inference_endpoints/public/components/inference_flyout_wrapper_component.tsx b/x-pack/plugins/search_inference_endpoints/public/components/inference_flyout_wrapper_component.tsx new file mode 100644 index 0000000000000..bf1343fe3db8b --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/components/inference_flyout_wrapper_component.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexItem, EuiCallOut, EuiText, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { InferenceFlyoutWrapper } from '@kbn/inference_integration_flyout/components/inference_flyout_wrapper'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; + +import { InferenceTaskType } from '@elastic/elasticsearch/lib/api/types'; + +import { ModelConfig } from '@kbn/inference_integration_flyout/types'; +import { extractErrorProperties } from '@kbn/ml-error-utils'; +import { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_models'; +import { SUPPORTED_PYTORCH_TASKS, TRAINED_MODEL_TYPE } from '@kbn/ml-trained-models-utils'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils'; +import * as i18n from '../../common/translations'; +import { INFERENCE_ENDPOINTS_QUERY_KEY } from '../../common/constants'; +import { useKibana } from '../hooks/use_kibana'; +import { docLinks } from '../../common/doc_links'; + +interface InferenceFlyoutWrapperComponentProps { + inferenceEndpoints: InferenceAPIConfigResponse[]; + isInferenceFlyoutVisible: boolean; + setIsInferenceFlyoutVisible: (isVisible: boolean) => void; +} + +export const InferenceFlyoutWrapperComponent: React.FC = ({ + inferenceEndpoints, + isInferenceFlyoutVisible, + setIsInferenceFlyoutVisible, +}) => { + const [inferenceAddError, setInferenceAddError] = useState(undefined); + const [isCreateInferenceApiLoading, setIsCreateInferenceApiLoading] = useState(false); + const [availableTrainedModels, setAvailableTrainedModels] = useState< + TrainedModelConfigResponse[] + >([]); + + const [inferenceEndpointError, setInferenceEndpointError] = useState( + undefined + ); + + const queryClient = useQueryClient(); + + const { + services: { ml }, + } = useKibana(); + + const createInferenceEndpointMutation = useMutation( + async ({ + inferenceId, + taskType, + modelConfig, + }: { + inferenceId: string; + taskType: InferenceTaskType; + modelConfig: ModelConfig; + }) => { + if (!ml) { + throw new Error(i18n.UNABLE_TO_CREATE_INFERENCE_ENDPOINT); + } + await ml?.mlApi?.inferenceModels?.createInferenceEndpoint(inferenceId, taskType, modelConfig); + }, + { + onSuccess: () => { + queryClient.invalidateQueries([INFERENCE_ENDPOINTS_QUERY_KEY]); + }, + } + ); + + const onInferenceEndpointChange = useCallback( + async (inferenceId: string) => { + const modelsExist = inferenceEndpoints.some((i) => i.model_id === inferenceId); + if (modelsExist) { + setInferenceEndpointError(i18n.INFERENCE_ENDPOINT_ALREADY_EXISTS); + } else { + setInferenceEndpointError(undefined); + } + }, + [inferenceEndpoints] + ); + + const onSaveInferenceCallback = useCallback( + async (inferenceId: string, taskType: InferenceTaskType, modelConfig: ModelConfig) => { + setIsCreateInferenceApiLoading(true); + try { + await createInferenceEndpointMutation.mutateAsync({ inferenceId, taskType, modelConfig }); + setIsInferenceFlyoutVisible(!isInferenceFlyoutVisible); + } catch (error) { + const errorObj = extractErrorProperties(error); + setInferenceAddError(errorObj.message); + } finally { + setIsCreateInferenceApiLoading(false); + } + }, + [createInferenceEndpointMutation, isInferenceFlyoutVisible, setIsInferenceFlyoutVisible] + ); + + const onFlyoutClose = useCallback(() => { + setInferenceAddError(undefined); + setIsInferenceFlyoutVisible(!isInferenceFlyoutVisible); + }, [isInferenceFlyoutVisible, setIsInferenceFlyoutVisible]); + + useEffect(() => { + const fetchAvailableTrainedModels = async () => { + let models; + try { + models = await ml?.mlApi?.trainedModels?.getTrainedModels(); + } catch (error) { + const errorObj = extractErrorProperties(error); + if (errorObj.statusCode === 403) { + setInferenceAddError(i18n.FORBIDDEN_TO_ACCESS_TRAINED_MODELS); + } else { + setInferenceAddError(errorObj.message); + } + } finally { + setAvailableTrainedModels(models || []); + } + }; + + fetchAvailableTrainedModels(); + }, [ml]); + + const trainedModels = useMemo(() => { + const availableTrainedModelsList = availableTrainedModels + .filter( + (model: TrainedModelConfigResponse) => + model.model_type === TRAINED_MODEL_TYPE.PYTORCH && + (model?.inference_config + ? Object.keys(model.inference_config).includes(SUPPORTED_PYTORCH_TASKS.TEXT_EMBEDDING) + : {}) + ) + .map((model: TrainedModelConfigResponse) => model.model_id); + + return availableTrainedModelsList; + }, [availableTrainedModels]); + + return ( + + + + + + + + + ) + } + onInferenceEndpointChange={onInferenceEndpointChange} + inferenceEndpointError={inferenceEndpointError} + trainedModels={trainedModels} + onSaveInferenceEndpoint={onSaveInferenceCallback} + onFlyoutClose={onFlyoutClose} + isInferenceFlyoutVisible={isInferenceFlyoutVisible} + supportedNlpModels={docLinks.supportedNlpModels} + nlpImportModel={docLinks.nlpImportModel} + isCreateInferenceApiLoading={isCreateInferenceApiLoading} + setInferenceEndpointError={setInferenceEndpointError} + /> + ); +}; diff --git a/x-pack/plugins/lists/common/api/values/create_list_item/create_list_item_route.ts b/x-pack/plugins/search_inference_endpoints/public/components/routes.ts similarity index 55% rename from x-pack/plugins/lists/common/api/values/create_list_item/create_list_item_route.ts rename to x-pack/plugins/search_inference_endpoints/public/components/routes.ts index 70cef948d6cf1..032b8c96e43d0 100644 --- a/x-pack/plugins/lists/common/api/values/create_list_item/create_list_item_route.ts +++ b/x-pack/plugins/search_inference_endpoints/public/components/routes.ts @@ -5,6 +5,7 @@ * 2.0. */ -import { createListItemSchema, listItemSchema } from '@kbn/securitysolution-io-ts-list-types'; - -export { createListItemSchema as createListItemRequest, listItemSchema as createListItemResponse }; +export const ROOT_PATH = '/'; +export const SETUP_GUIDE_PATH = '/setup_guide'; +export const ERROR_STATE_PATH = '/error_state'; +export const INFERENCE_ENDPOINTS_PATH = `${ROOT_PATH}inference_endpoints`; diff --git a/x-pack/plugins/search_inference_endpoints/public/embeddable.tsx b/x-pack/plugins/search_inference_endpoints/public/embeddable.tsx new file mode 100644 index 0000000000000..fb733ec7ff5e6 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/embeddable.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { dynamic } from '@kbn/shared-ux-utility'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { CoreStart } from '@kbn/core-lifecycle-browser'; +import { AppPluginStartDependencies } from './types'; + +export const InferenceEndpoints = dynamic(async () => ({ + default: (await import('./components/app')).App, +})); + +export const InferenceEndpointsProvider = dynamic(async () => ({ + default: (await import('./providers/inference_endpoints_provider')).InferenceEndpointsProvider, +})); + +export const getInferenceEndpointsProvider = + (core: CoreStart, services: AppPluginStartDependencies) => + (props: React.ComponentProps) => + ( + + + + ); diff --git a/x-pack/plugins/search_inference_endpoints/public/hooks/use_all_inference_endpoints_state.tsx b/x-pack/plugins/search_inference_endpoints/public/hooks/use_all_inference_endpoints_state.tsx new file mode 100644 index 0000000000000..4979cdf7994fc --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/hooks/use_all_inference_endpoints_state.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 { useCallback, useState } from 'react'; + +import type { + QueryParams, + AlInferenceEndpointsTableState, +} from '../components/all_inference_endpoints/types'; + +import { DEFAULT_INFERENCE_ENDPOINTS_TABLE_STATE } from '../components/all_inference_endpoints/constants'; + +interface UseAllInferenceEndpointsStateReturn { + queryParams: QueryParams; + setQueryParams: (queryParam: Partial) => void; +} + +export function useAllInferenceEndpointsState(): UseAllInferenceEndpointsStateReturn { + const [tableState, setTableState] = useState( + DEFAULT_INFERENCE_ENDPOINTS_TABLE_STATE + ); + const setState = useCallback((state: AlInferenceEndpointsTableState) => { + setTableState(state); + }, []); + + return { + queryParams: { + ...DEFAULT_INFERENCE_ENDPOINTS_TABLE_STATE.queryParams, + ...tableState.queryParams, + }, + setQueryParams: (newQueryParams: Partial) => { + setState({ + queryParams: { ...tableState.queryParams, ...newQueryParams }, + }); + }, + }; +} diff --git a/x-pack/plugins/search_inference_endpoints/public/hooks/use_inference_endpoints.ts b/x-pack/plugins/search_inference_endpoints/public/hooks/use_inference_endpoints.ts new file mode 100644 index 0000000000000..f400429bec250 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/hooks/use_inference_endpoints.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 { useQuery } from '@tanstack/react-query'; +import { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils'; +import { APIRoutes } from '../types'; +import { useKibana } from './use_kibana'; +import { INFERENCE_ENDPOINTS_QUERY_KEY } from '../../common/constants'; + +export const useQueryInferenceEndpoints = (): { + inferenceEndpoints: InferenceAPIConfigResponse[]; + isLoading: boolean; +} => { + const { services } = useKibana(); + + const { data, isLoading } = useQuery({ + queryKey: [INFERENCE_ENDPOINTS_QUERY_KEY], + queryFn: async () => { + const response = await services.http.get<{ + inference_endpoints: InferenceAPIConfigResponse[]; + }>(APIRoutes.GET_INFERENCE_ENDPOINTS, {}); + + return response.inference_endpoints; + }, + }); + + return { inferenceEndpoints: data || [], isLoading }; +}; diff --git a/x-pack/plugins/search_inference_endpoints/public/hooks/use_kibana.ts b/x-pack/plugins/search_inference_endpoints/public/hooks/use_kibana.ts new file mode 100644 index 0000000000000..90fa7597e17dc --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/hooks/use_kibana.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useKibana as _useKibana } from '@kbn/kibana-react-plugin/public'; +import { AppServicesContext } from '../types'; + +export const useKibana = () => _useKibana(); diff --git a/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.test.tsx b/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.test.tsx new file mode 100644 index 0000000000000..e0026cbffff98 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.test.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils'; +import { renderHook } from '@testing-library/react-hooks'; +import { QueryParams } from '../components/all_inference_endpoints/types'; +import { useTableData } from './use_table_data'; +import { INFERENCE_ENDPOINTS_TABLE_PER_PAGE_VALUES } from '../components/all_inference_endpoints/types'; + +const inferenceEndpoints = [ + { + model_id: 'my-elser-model-04', + task_type: 'sparse_embedding', + service: 'elser', + service_settings: { + num_allocations: 1, + num_threads: 1, + model_id: '.elser_model_2', + }, + task_settings: {}, + }, + { + model_id: 'my-elser-model-01', + task_type: 'sparse_embedding', + service: 'elser', + service_settings: { + num_allocations: 1, + num_threads: 1, + model_id: '.elser_model_2', + }, + task_settings: {}, + }, + { + model_id: 'my-elser-model-05', + task_type: 'sparse_embedding', + service: 'elser', + service_settings: { + num_allocations: 1, + num_threads: 1, + model_id: '.elser_model_2', + }, + task_settings: {}, + }, +] as InferenceAPIConfigResponse[]; + +const queryParams = { + page: 1, + perPage: 10, + sortField: 'endpoint', + sortOrder: 'desc', +} as QueryParams; + +describe('useTableData', () => { + it('should return correct pagination', () => { + const { result } = renderHook(() => useTableData(inferenceEndpoints, queryParams)); + + expect(result.current.pagination).toEqual({ + pageIndex: 0, + pageSize: 10, + pageSizeOptions: INFERENCE_ENDPOINTS_TABLE_PER_PAGE_VALUES, + totalItemCount: 3, + }); + }); + + it('should return correct sorting', () => { + const { result } = renderHook(() => useTableData(inferenceEndpoints, queryParams)); + + expect(result.current.sorting).toEqual({ + sort: { + direction: 'desc', + field: 'endpoint', + }, + }); + }); + + it('should return correctly sorted data', () => { + const { result } = renderHook(() => useTableData(inferenceEndpoints, queryParams)); + + const expectedSortedData = [...inferenceEndpoints].sort((a, b) => + b.model_id.localeCompare(a.model_id) + ); + + const sortedEndpoints = result.current.sortedTableData.map((item) => item.endpoint); + const expectedModelIds = expectedSortedData.map((item) => item.model_id); + + expect(sortedEndpoints).toEqual(expectedModelIds); + }); +}); diff --git a/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.tsx b/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.tsx new file mode 100644 index 0000000000000..304c469e06093 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EuiTableSortingType } from '@elastic/eui'; +import { Pagination } from '@elastic/eui'; +import { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils'; +import { useMemo } from 'react'; +import { DEFAULT_TABLE_LIMIT } from '../components/all_inference_endpoints/constants'; +import { + InferenceEndpointUI, + INFERENCE_ENDPOINTS_TABLE_PER_PAGE_VALUES, + QueryParams, + SortOrder, +} from '../components/all_inference_endpoints/types'; + +interface UseTableDataReturn { + tableData: InferenceEndpointUI[]; + sortedTableData: InferenceEndpointUI[]; + paginatedSortedTableData: InferenceEndpointUI[]; + pagination: Pagination; + sorting: EuiTableSortingType; +} + +export const useTableData = ( + inferenceEndpoints: InferenceAPIConfigResponse[], + queryParams: QueryParams +): UseTableDataReturn => { + const tableData: InferenceEndpointUI[] = useMemo(() => { + return inferenceEndpoints.map((endpoint) => ({ + endpoint: endpoint.model_id, + provider: endpoint.service, + type: endpoint.task_type, + })); + }, [inferenceEndpoints]); + + const sortedTableData: InferenceEndpointUI[] = useMemo(() => { + return [...tableData].sort((a, b) => { + const aValue = a[queryParams.sortField]; + const bValue = b[queryParams.sortField]; + + if (queryParams.sortOrder === SortOrder.asc) { + return aValue.localeCompare(bValue); + } else { + return bValue.localeCompare(aValue); + } + }); + }, [tableData, queryParams]); + + const pagination: Pagination = useMemo( + () => ({ + pageIndex: queryParams.page - 1, + pageSize: queryParams.perPage, + pageSizeOptions: INFERENCE_ENDPOINTS_TABLE_PER_PAGE_VALUES, + totalItemCount: inferenceEndpoints.length ?? 0, + }), + [inferenceEndpoints, queryParams] + ); + + const paginatedSortedTableData: InferenceEndpointUI[] = useMemo(() => { + const pageSize = pagination.pageSize || DEFAULT_TABLE_LIMIT; + const startIndex = pagination.pageIndex * pageSize; + const endIndex = startIndex + pageSize; + return sortedTableData.slice(startIndex, endIndex); + }, [sortedTableData, pagination]); + + const sorting = useMemo( + () => ({ + sort: { + direction: queryParams.sortOrder, + field: queryParams.sortField, + }, + }), + [queryParams.sortField, queryParams.sortOrder] + ); + + return { tableData, sortedTableData, paginatedSortedTableData, pagination, sorting }; +}; diff --git a/x-pack/plugins/search_inference_endpoints/public/index.ts b/x-pack/plugins/search_inference_endpoints/public/index.ts new file mode 100644 index 0000000000000..b06f1f64b909d --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/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 { PluginInitializerContext } from '@kbn/core/public'; + +import { SearchInferenceEndpointsPlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new SearchInferenceEndpointsPlugin(initializerContext); +} + +export type { + SearchInferenceEndpointsPluginSetup, + SearchInferenceEndpointsPluginStart, +} from './types'; diff --git a/x-pack/plugins/search_inference_endpoints/public/inference_endpoints_overview.tsx b/x-pack/plugins/search_inference_endpoints/public/inference_endpoints_overview.tsx new file mode 100644 index 0000000000000..5dd017f263caa --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/inference_endpoints_overview.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; + +import { EuiPageTemplate } from '@elastic/eui'; + +import { App } from './components/app'; +import { useKibana } from './hooks/use_kibana'; +import { InferenceEndpointsProvider } from './providers/inference_endpoints_provider'; + +export const InferenceEndpointsOverview: React.FC = () => { + const { + services: { console: consolePlugin }, + } = useKibana(); + + const embeddableConsole = useMemo( + () => (consolePlugin?.EmbeddableConsole ? : null), + [consolePlugin] + ); + + return ( + + + + {embeddableConsole} + + + ); +}; diff --git a/x-pack/plugins/search_inference_endpoints/public/inference_endpoints_router.tsx b/x-pack/plugins/search_inference_endpoints/public/inference_endpoints_router.tsx new file mode 100644 index 0000000000000..7165b01993232 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/inference_endpoints_router.tsx @@ -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 { Route, Routes } from '@kbn/shared-ux-router'; +import React from 'react'; +import { Redirect } from 'react-router-dom'; +import { InferenceEndpointsOverview } from './inference_endpoints_overview'; + +import { ROOT_PATH, SEARCH_INFERENCE_ENDPOINTS_PATH } from './routes'; + +export const InferenceEndpointsRouter: React.FC = () => { + return ( + + + + + + + ); +}; diff --git a/x-pack/plugins/search_inference_endpoints/public/plugin.ts b/x-pack/plugins/search_inference_endpoints/public/plugin.ts new file mode 100644 index 0000000000000..ab49281f82e4c --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/plugin.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 { + AppMountParameters, + CoreSetup, + CoreStart, + Plugin, + PluginInitializerContext, +} from '@kbn/core/public'; +import { PLUGIN_ID, PLUGIN_NAME } from '../common/constants'; +import { docLinks } from '../common/doc_links'; +import { InferenceEndpoints, getInferenceEndpointsProvider } from './embeddable'; +import { + AppPluginStartDependencies, + SearchInferenceEndpointsConfigType, + SearchInferenceEndpointsPluginSetup, + SearchInferenceEndpointsPluginStart, +} from './types'; + +export class SearchInferenceEndpointsPlugin + implements Plugin +{ + private config: SearchInferenceEndpointsConfigType; + + constructor(initializerContext: PluginInitializerContext) { + this.config = initializerContext.config.get(); + } + + public setup( + core: CoreSetup + ): SearchInferenceEndpointsPluginSetup { + if (!this.config.ui?.enabled) return {}; + + core.application.register({ + id: PLUGIN_ID, + appRoute: '/app/search_inference_endpoints', + title: PLUGIN_NAME, + async mount({ element, history }: AppMountParameters) { + const { renderApp } = await import('./application'); + const [coreStart, depsStart] = await core.getStartServices(); + const startDeps: AppPluginStartDependencies = { + ...depsStart, + history, + }; + + return renderApp(coreStart, startDeps, element); + }, + }); + + return {}; + } + + public start( + core: CoreStart, + deps: AppPluginStartDependencies + ): SearchInferenceEndpointsPluginStart { + docLinks.setDocLinks(core.docLinks.links); + + return { + InferenceEdnpointsProvider: getInferenceEndpointsProvider(core, deps), + InferenceEndpoints, + }; + } + + public stop() {} +} diff --git a/x-pack/plugins/search_inference_endpoints/public/providers/inference_endpoints_provider.tsx b/x-pack/plugins/search_inference_endpoints/public/providers/inference_endpoints_provider.tsx new file mode 100644 index 0000000000000..1813fbae2901e --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/providers/inference_endpoints_provider.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import React, { FC, PropsWithChildren } from 'react'; + +const queryClient = new QueryClient({}); + +export interface InferenceEndpointsProviderProps { + children: React.ReactNode; +} + +export const InferenceEndpointsProvider: FC> = ({ + children, +}) => { + return {children}; +}; diff --git a/x-pack/plugins/lists/common/api/values/delete_list_index/delete_list_index_route.ts b/x-pack/plugins/search_inference_endpoints/public/routes.ts similarity index 65% rename from x-pack/plugins/lists/common/api/values/delete_list_index/delete_list_index_route.ts rename to x-pack/plugins/search_inference_endpoints/public/routes.ts index 2423ebd6bea60..d2904473400ee 100644 --- a/x-pack/plugins/lists/common/api/values/delete_list_index/delete_list_index_route.ts +++ b/x-pack/plugins/search_inference_endpoints/public/routes.ts @@ -5,6 +5,5 @@ * 2.0. */ -import { acknowledgeSchema } from '@kbn/securitysolution-io-ts-list-types'; - -export { acknowledgeSchema as deleteListIndexResponse }; +export const ROOT_PATH = '/'; +export const SEARCH_INFERENCE_ENDPOINTS_PATH = `${ROOT_PATH}inference_endpoints`; diff --git a/x-pack/plugins/search_inference_endpoints/public/types.ts b/x-pack/plugins/search_inference_endpoints/public/types.ts new file mode 100644 index 0000000000000..6ac241fbe87b0 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/public/types.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ConsolePluginStart } from '@kbn/console-plugin/public'; +import { HttpStart } from '@kbn/core-http-browser'; +import { AppMountParameters } from '@kbn/core/public'; +import { MlPluginStart } from '@kbn/ml-plugin/public'; +import { SharePluginStart } from '@kbn/share-plugin/public'; +import React from 'react'; +import type { App } from './components/app'; +import type { InferenceEndpointsProvider } from './providers/inference_endpoints_provider'; + +export * from '../common/types'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SearchInferenceEndpointsPluginSetup {} +export interface SearchInferenceEndpointsPluginStart { + InferenceEdnpointsProvider: React.FC>; + InferenceEndpoints: React.FC>; +} + +export interface AppPluginStartDependencies { + history: AppMountParameters['history']; + share: SharePluginStart; + console?: ConsolePluginStart; +} + +export interface AppServicesContext { + http: HttpStart; + ml?: MlPluginStart; + console?: ConsolePluginStart; +} diff --git a/x-pack/plugins/search_inference_endpoints/server/config.ts b/x-pack/plugins/search_inference_endpoints/server/config.ts new file mode 100644 index 0000000000000..5593fb2251aba --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/server/config.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { PluginConfigDescriptor } from '@kbn/core/server'; + +export * from './types'; + +const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), + ui: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + }), +}); + +export type SearchInferenceEndpointsConfig = TypeOf; + +export const config: PluginConfigDescriptor = { + exposeToBrowser: { + ui: true, + }, + schema: configSchema, +}; diff --git a/x-pack/plugins/search_inference_endpoints/server/index.ts b/x-pack/plugins/search_inference_endpoints/server/index.ts new file mode 100644 index 0000000000000..10fa965c40457 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/server/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PluginInitializerContext } from '@kbn/core/server'; + +export { config } from './config'; + +export async function plugin(initializerContext: PluginInitializerContext) { + const { SearchInferenceEndpointsPlugin } = await import('./plugin'); + return new SearchInferenceEndpointsPlugin(initializerContext); +} + +export type { + SearchInferenceEndpointsPluginSetup, + SearchInferenceEndpointsPluginStart, +} from './types'; diff --git a/x-pack/plugins/search_inference_endpoints/server/lib/fetch_inference_endpoints.test.ts b/x-pack/plugins/search_inference_endpoints/server/lib/fetch_inference_endpoints.test.ts new file mode 100644 index 0000000000000..ee282f3056caa --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/server/lib/fetch_inference_endpoints.test.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; + +import { fetchInferenceEndpoints } from './fetch_inference_endpoints'; + +describe('fetch indices', () => { + const mockInferenceEndpointsResponse = [ + { + model_id: 'my-elser-model-03', + task_type: 'sparse_embedding', + service: 'elser', + service_settings: { num_allocations: 1, num_threads: 1, model_id: '.elser_model_2' }, + task_settings: {}, + }, + { + model_id: 'my-elser-model-04', + task_type: 'sparse_embedding', + service: 'elser', + service_settings: { num_allocations: 1, num_threads: 1, model_id: '.elser_model_2' }, + task_settings: {}, + }, + { + model_id: 'my-elser-model-05', + task_type: 'sparse_embedding', + service: 'elser', + service_settings: { num_allocations: 1, num_threads: 1, model_id: '.elser_model_2' }, + task_settings: {}, + }, + { + model_id: 'my-elser-model-06', + task_type: 'sparse_embedding', + service: 'elser', + service_settings: { num_allocations: 1, num_threads: 1, model_id: '.elser_model_2' }, + task_settings: {}, + }, + ]; + beforeEach(() => { + jest.clearAllMocks(); + }); + + const mockClient = { + asCurrentUser: { + transport: { + request: jest.fn(), + }, + }, + }; + + it('returns all inference endpoints', async () => { + mockClient.asCurrentUser.transport.request.mockImplementationOnce(() => { + return Promise.resolve({ endpoints: mockInferenceEndpointsResponse }); + }); + + const indexData = await fetchInferenceEndpoints( + mockClient.asCurrentUser as unknown as ElasticsearchClient + ); + + expect(indexData).toEqual({ + inferenceEndpoints: mockInferenceEndpointsResponse, + }); + }); +}); diff --git a/x-pack/plugins/search_inference_endpoints/server/lib/fetch_inference_endpoints.ts b/x-pack/plugins/search_inference_endpoints/server/lib/fetch_inference_endpoints.ts new file mode 100644 index 0000000000000..69b38f0d3bdb3 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/server/lib/fetch_inference_endpoints.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; +import { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils'; + +export const fetchInferenceEndpoints = async ( + client: ElasticsearchClient +): Promise<{ + inferenceEndpoints: InferenceAPIConfigResponse[]; +}> => { + const { endpoints } = await client.transport.request<{ + endpoints: any; + }>({ + method: 'GET', + path: `/_inference/_all`, + }); + + return { + inferenceEndpoints: endpoints, + }; +}; diff --git a/x-pack/plugins/search_inference_endpoints/server/plugin.ts b/x-pack/plugins/search_inference_endpoints/server/plugin.ts new file mode 100644 index 0000000000000..74a877824956f --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/server/plugin.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CoreSetup, CoreStart, Logger, Plugin, PluginInitializerContext } from '@kbn/core/server'; +import { defineRoutes } from './routes'; +import { + SearchInferenceEndpointsPluginSetup, + SearchInferenceEndpointsPluginStart, + SearchInferenceEndpointsPluginStartDependencies, +} from './types'; + +export class SearchInferenceEndpointsPlugin + implements + Plugin< + SearchInferenceEndpointsPluginSetup, + SearchInferenceEndpointsPluginStart, + {}, + SearchInferenceEndpointsPluginStartDependencies + > +{ + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup( + core: CoreSetup< + SearchInferenceEndpointsPluginStartDependencies, + SearchInferenceEndpointsPluginStart + > + ) { + this.logger.debug('searchInferenceEndpoints: Setup'); + const router = core.http.createRouter(); + + defineRoutes({ logger: this.logger, router }); + + return {}; + } + + public start(core: CoreStart) { + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/search_inference_endpoints/server/routes.ts b/x-pack/plugins/search_inference_endpoints/server/routes.ts new file mode 100644 index 0000000000000..d5f010b902c52 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/server/routes.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 { IRouter } from '@kbn/core/server'; +import type { Logger } from '@kbn/logging'; +import { fetchInferenceEndpoints } from './lib/fetch_inference_endpoints'; +import { APIRoutes } from './types'; +import { errorHandler } from './utils/error_handler'; + +export function defineRoutes({ logger, router }: { logger: Logger; router: IRouter }) { + router.get( + { + path: APIRoutes.GET_INFERENCE_ENDPOINTS, + validate: {}, + }, + errorHandler(logger)(async (context, request, response) => { + const { + client: { asCurrentUser }, + } = (await context.core).elasticsearch; + + const { inferenceEndpoints } = await fetchInferenceEndpoints(asCurrentUser); + + return response.ok({ + body: { + inference_endpoints: inferenceEndpoints, + }, + headers: { 'content-type': 'application/json' }, + }); + }) + ); +} diff --git a/x-pack/plugins/search_inference_endpoints/server/types.ts b/x-pack/plugins/search_inference_endpoints/server/types.ts new file mode 100644 index 0000000000000..fcc6ffa55ec0f --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/server/types.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 { PluginStartContract as ActionsPluginStartContract } from '@kbn/actions-plugin/server'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SearchInferenceEndpointsPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SearchInferenceEndpointsPluginStart {} + +export interface SearchInferenceEndpointsPluginStartDependencies { + actions: ActionsPluginStartContract; +} + +export * from '../common/types'; diff --git a/x-pack/plugins/search_inference_endpoints/server/utils/error_handler.ts b/x-pack/plugins/search_inference_endpoints/server/utils/error_handler.ts new file mode 100644 index 0000000000000..3772786335fa8 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/server/utils/error_handler.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { RequestHandlerWrapper } from '@kbn/core-http-server'; +import type { Logger } from '@kbn/logging'; + +export const errorHandler: (logger: Logger) => RequestHandlerWrapper = (logger) => (handler) => { + return async (context, request, response) => { + try { + return await handler(context, request, response); + } catch (e) { + logger.error(e); + throw e; + } + }; +}; diff --git a/x-pack/plugins/search_inference_endpoints/tsconfig.json b/x-pack/plugins/search_inference_endpoints/tsconfig.json new file mode 100644 index 0000000000000..a61af6c752250 --- /dev/null +++ b/x-pack/plugins/search_inference_endpoints/tsconfig.json @@ -0,0 +1,38 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + }, + "include": [ + "__mocks__/**/*", + "common/**/*", + "public/**/*", + "server/**/*" + ], + "kbn_references": [ + "@kbn/config-schema", + "@kbn/core", + "@kbn/core-http-browser", + "@kbn/i18n", + "@kbn/i18n-react", + "@kbn/kibana-react-plugin", + "@kbn/inference_integration_flyout", + "@kbn/ml-plugin", + "@kbn/ml-trained-models-utils", + "@kbn/shared-ux-router", + "@kbn/core-http-server", + "@kbn/share-plugin", + "@kbn/actions-plugin", + "@kbn/shared-ux-utility", + "@kbn/core-lifecycle-browser", + "@kbn/logging", + "@kbn/react-kibana-context-render", + "@kbn/doc-links", + "@kbn/console-plugin", + "@kbn/test-jest-helpers", + "@kbn/ml-error-utils" + ], + "exclude": [ + "target/**/*", + ] +} diff --git a/x-pack/plugins/search_playground/__mocks__/fetch_query_source_fields.mock.ts b/x-pack/plugins/search_playground/__mocks__/fetch_query_source_fields.mock.ts index b25ca903c7a4b..d421ad6c8c9b7 100644 --- a/x-pack/plugins/search_playground/__mocks__/fetch_query_source_fields.mock.ts +++ b/x-pack/plugins/search_playground/__mocks__/fetch_query_source_fields.mock.ts @@ -5,7 +5,177 @@ * 2.0. */ -import { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; +import { IndicesGetMappingResponse, SearchResponse } from '@elastic/elasticsearch/lib/api/types'; + +export const SPARSE_SEMANTIC_FIELD_FIELD_CAPS = { + indices: ['test-index2'], + fields: { + infer_field: { + semantic_text: { + type: 'semantic_text', + metadata_field: false, + searchable: false, + aggregatable: false, + }, + }, + 'infer_field.inference.chunks.embeddings': { + sparse_vector: { + type: 'sparse_vector', + metadata_field: false, + searchable: true, + aggregatable: false, + }, + }, + non_infer_field: { + text: { + type: 'text', + metadata_field: false, + searchable: true, + aggregatable: false, + }, + }, + 'infer_field.inference.chunks.text': { + keyword: { + type: 'keyword', + metadata_field: false, + searchable: false, + aggregatable: false, + }, + }, + 'infer_field.inference': { + object: { + type: 'object', + metadata_field: false, + searchable: false, + aggregatable: false, + }, + }, + 'infer_field.inference.chunks': { + nested: { + type: 'nested', + metadata_field: false, + searchable: false, + aggregatable: false, + }, + }, + }, +}; + +export const SPARSE_SEMANTIC_FIELD_MAPPINGS = { + 'test-index2': { + mappings: { + properties: { + infer_field: { + type: 'semantic_text', + inference_id: 'elser-endpoint', + model_settings: { + task_type: 'sparse_embedding', + }, + }, + non_infer_field: { + type: 'text', + }, + }, + }, + }, +} as any as IndicesGetMappingResponse; + +export const DENSE_SEMANTIC_FIELD_MAPPINGS = { + 'test-index2': { + mappings: { + properties: { + infer_field: { + type: 'semantic_text', + inference_id: 'cohere', + model_settings: { + task_type: 'text_embedding', + dimensions: 1536, + similarity: 'dot_product', + }, + }, + non_infer_field: { + type: 'text', + }, + }, + }, + }, +} as any as IndicesGetMappingResponse; + +// for when semantic_text field hasn't been mapped with task_type +// when theres no data / no inference has been performed in the field +export const DENSE_SEMANTIC_FIELD_MAPPINGS_MISSING_TASK_TYPE = { + 'test-index2': { + mappings: { + properties: { + infer_field: { + type: 'semantic_text', + inference_id: 'cohere', + model_settings: { + dimensions: 1536, + similarity: 'dot_product', + }, + }, + non_infer_field: { + type: 'text', + }, + }, + }, + }, +} as any as IndicesGetMappingResponse; + +export const DENSE_SEMANTIC_FIELD_FIELD_CAPS = { + indices: ['test-index2'], + fields: { + infer_field: { + semantic_text: { + type: 'semantic_text', + metadata_field: false, + searchable: false, + aggregatable: false, + }, + }, + 'infer_field.inference.chunks.embeddings': { + sparse_vector: { + type: 'dense_vector', + metadata_field: false, + searchable: true, + aggregatable: false, + }, + }, + non_infer_field: { + text: { + type: 'text', + metadata_field: false, + searchable: true, + aggregatable: false, + }, + }, + 'infer_field.inference.chunks.text': { + keyword: { + type: 'keyword', + metadata_field: false, + searchable: false, + aggregatable: false, + }, + }, + 'infer_field.inference': { + object: { + type: 'object', + metadata_field: false, + searchable: false, + aggregatable: false, + }, + }, + 'infer_field.inference.chunks': { + nested: { + type: 'nested', + metadata_field: false, + searchable: false, + aggregatable: false, + }, + }, + }, +}; export const DENSE_SPARSE_SAME_FIELD_NAME_CAPS = { indices: ['cohere-embeddings', 'elser_index'], diff --git a/x-pack/plugins/search_playground/common/types.ts b/x-pack/plugins/search_playground/common/types.ts index c788b2df1896f..00bfde68ec2ea 100644 --- a/x-pack/plugins/search_playground/common/types.ts +++ b/x-pack/plugins/search_playground/common/types.ts @@ -7,18 +7,25 @@ export type IndicesQuerySourceFields = Record; -interface ModelFields { +interface ModelField { field: string; model_id: string; - nested: boolean; + indices: string[]; +} + +interface SemanticField { + field: string; + inferenceId: string; + embeddingType: 'sparse_vector' | 'dense_vector'; indices: string[]; } export interface QuerySourceFields { - elser_query_fields: ModelFields[]; - dense_vector_query_fields: ModelFields[]; + elser_query_fields: ModelField[]; + dense_vector_query_fields: ModelField[]; bm25_query_fields: string[]; source_fields: string[]; + semantic_fields: SemanticField[]; skipped_fields: number; } diff --git a/x-pack/plugins/search_playground/public/components/edit_context/edit_context_flyout.test.tsx b/x-pack/plugins/search_playground/public/components/edit_context/edit_context_flyout.test.tsx index 4e5bb7f807900..de82892b167be 100644 --- a/x-pack/plugins/search_playground/public/components/edit_context/edit_context_flyout.test.tsx +++ b/x-pack/plugins/search_playground/public/components/edit_context/edit_context_flyout.test.tsx @@ -19,12 +19,14 @@ jest.mock('../../hooks/use_indices_fields', () => ({ dense_vector_query_fields: [], bm25_query_fields: ['field1', 'field2'], source_fields: ['context_field1', 'context_field2'], + semantic_fields: [], }, index2: { elser_query_fields: [], dense_vector_query_fields: [], bm25_query_fields: ['field1', 'field2'], source_fields: ['context_field1', 'context_field2'], + semantic_fields: [], }, }, }), diff --git a/x-pack/plugins/search_playground/public/components/view_query/view_query_flyout.test.tsx b/x-pack/plugins/search_playground/public/components/view_query/view_query_flyout.test.tsx index 410989eaf52ad..39136e2557296 100644 --- a/x-pack/plugins/search_playground/public/components/view_query/view_query_flyout.test.tsx +++ b/x-pack/plugins/search_playground/public/components/view_query/view_query_flyout.test.tsx @@ -10,6 +10,7 @@ import { render, fireEvent, screen } from '@testing-library/react'; import { ViewQueryFlyout } from './view_query_flyout'; import { FormProvider, useForm } from 'react-hook-form'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import { ChatFormFields } from '../../types'; jest.mock('../../hooks/use_indices_fields', () => ({ useIndicesFields: () => ({ @@ -19,12 +20,14 @@ jest.mock('../../hooks/use_indices_fields', () => ({ dense_vector_query_fields: [], bm25_query_fields: ['field1', 'field2'], skipped_fields: 1, + semantic_fields: [], }, index2: { elser_query_fields: [], dense_vector_query_fields: [], bm25_query_fields: ['field1', 'field2'], skipped_fields: 0, + semantic_fields: [], }, }, }), @@ -41,7 +44,11 @@ jest.mock('../../hooks/use_usage_tracker', () => ({ const MockFormProvider = ({ children }: { children: React.ReactElement }) => { const methods = useForm({ values: { - indices: ['index1', 'index2'], + [ChatFormFields.indices]: ['index1', 'index2'], + [ChatFormFields.sourceFields]: { + index1: ['field1'], + index2: ['field1'], + }, }, }); return {children}; diff --git a/x-pack/plugins/search_playground/public/components/view_query/view_query_flyout.tsx b/x-pack/plugins/search_playground/public/components/view_query/view_query_flyout.tsx index 64139307de4ba..2fd64f073eac7 100644 --- a/x-pack/plugins/search_playground/public/components/view_query/view_query_flyout.tsx +++ b/x-pack/plugins/search_playground/public/components/view_query/view_query_flyout.tsx @@ -65,6 +65,12 @@ const groupTypeQueryFields = ( typeQueryFields += (typeQueryFields ? '_' : '') + 'SPARSE'; } + if ( + selectedFields.some((field) => indexFields.semantic_fields.find((f) => f.field === field)) + ) { + typeQueryFields += (typeQueryFields ? '_' : '') + 'SEMANTIC'; + } + return typeQueryFields; }); @@ -76,6 +82,7 @@ export const ViewQueryFlyout: React.FC = ({ onClose }) => const usageTracker = useUsageTracker(); const { getValues } = useFormContext(); const selectedIndices: string[] = getValues(ChatFormFields.indices); + const sourceFields = getValues(ChatFormFields.sourceFields); const { fields } = useIndicesFields(selectedIndices); const defaultFields = getDefaultQueryFields(fields); @@ -111,7 +118,7 @@ export const ViewQueryFlyout: React.FC = ({ onClose }) => const saveQuery = () => { queryFieldsOnChange(tempQueryFields); - elasticsearchQueryChange(createQuery(tempQueryFields, fields)); + elasticsearchQueryChange(createQuery(tempQueryFields, sourceFields, fields)); onClose(); const groupedQueryFields = groupTypeQueryFields(fields, tempQueryFields); @@ -168,7 +175,7 @@ export const ViewQueryFlyout: React.FC = ({ onClose }) => lineNumbers data-test-subj="ViewElasticsearchQueryResult" > - {JSON.stringify(createQuery(tempQueryFields, fields), null, 2)} + {JSON.stringify(createQuery(tempQueryFields, sourceFields, fields), null, 2)} @@ -198,6 +205,7 @@ export const ViewQueryFlyout: React.FC = ({ onClose }) => aria-label="Select query fields" data-test-subj={`queryFieldsSelectable_${index}`} options={[ + ...group.semantic_fields, ...group.elser_query_fields, ...group.dense_vector_query_fields, ...group.bm25_query_fields, diff --git a/x-pack/plugins/search_playground/public/hooks/use_source_indices_field.ts b/x-pack/plugins/search_playground/public/hooks/use_source_indices_field.ts index 342be7c191778..bc9a37060fb6f 100644 --- a/x-pack/plugins/search_playground/public/hooks/use_source_indices_field.ts +++ b/x-pack/plugins/search_playground/public/hooks/use_source_indices_field.ts @@ -89,7 +89,7 @@ export const useSourceIndicesFields = () => { setNoFieldsIndicesWarning(null); } - onElasticsearchQueryChange(createQuery(defaultFields, fields)); + onElasticsearchQueryChange(createQuery(defaultFields, defaultSourceFields, fields)); onSourceFieldsChange(defaultSourceFields); usageTracker?.count( AnalyticsEvents.sourceFieldsLoaded, diff --git a/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx b/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx index f3b19e8d4360e..7dd1a43d6fc01 100644 --- a/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx +++ b/x-pack/plugins/search_playground/public/hooks/use_source_indices_fields.test.tsx @@ -36,7 +36,6 @@ describe.skip('useSourceIndicesFields Hook', () => { { field: 'field1', model_id: 'model1', - nested: false, indices: ['newIndex'], }, ], @@ -44,6 +43,7 @@ describe.skip('useSourceIndicesFields Hook', () => { bm25_query_fields: [], source_fields: ['field1'], skipped_fields: 0, + semantic_fields: [], }, }; @@ -87,11 +87,11 @@ describe.skip('useSourceIndicesFields Hook', () => { expect(result.current.indices).toEqual([]); expect(getValues()).toMatchInlineSnapshot(` Object { - "doc_size": 5, + "doc_size": 3, "elasticsearch_query": Object {}, "indices": Array [], "prompt": "You are an assistant for question-answering tasks.", - "source_fields": Array [], + "source_fields": Object {}, } `); result.current.addIndex('newIndex'); @@ -109,16 +109,15 @@ describe.skip('useSourceIndicesFields Hook', () => { expect(result.current.loading).toBe(false); expect(getValues()).toMatchInlineSnapshot(` Object { - "doc_size": 5, + "doc_size": 3, "elasticsearch_query": Object { "retriever": Object { "standard": Object { "query": Object { - "text_expansion": Object { - "field1": Object { - "model_id": "model1", - "model_text": "{query}", - }, + "sparse_vector": Object { + "field": "field1", + "inference_id": "model1", + "query": "{query}", }, }, }, @@ -146,6 +145,7 @@ describe.skip('useSourceIndicesFields Hook', () => { bm25_query_fields: [], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, }; @@ -169,7 +169,7 @@ describe.skip('useSourceIndicesFields Hook', () => { expect(result.current.loading).toBe(false); expect(getValues()).toMatchInlineSnapshot(` Object { - "doc_size": 5, + "doc_size": 3, "elasticsearch_query": Object { "retriever": Object { "standard": Object { @@ -199,6 +199,7 @@ describe.skip('useSourceIndicesFields Hook', () => { bm25_query_fields: [], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, }; @@ -222,7 +223,7 @@ describe.skip('useSourceIndicesFields Hook', () => { expect(result.current.loading).toBe(false); expect(getValues()).toMatchInlineSnapshot(` Object { - "doc_size": 5, + "doc_size": 3, "elasticsearch_query": Object { "retriever": Object { "standard": Object { diff --git a/x-pack/plugins/search_playground/public/utils/create_query.test.ts b/x-pack/plugins/search_playground/public/utils/create_query.test.ts index 282326f0991d2..164f79618d74c 100644 --- a/x-pack/plugins/search_playground/public/utils/create_query.test.ts +++ b/x-pack/plugins/search_playground/public/utils/create_query.test.ts @@ -9,6 +9,8 @@ import { IndicesQuerySourceFields } from '../types'; import { createQuery, getDefaultQueryFields, getDefaultSourceFields } from './create_query'; describe('create_query', () => { + const sourceFields = { index1: [], index2: [] }; + describe('createQuery', () => { it('should return a sparse single query', () => { const fields = { @@ -17,25 +19,23 @@ describe('create_query', () => { const fieldDescriptors: IndicesQuerySourceFields = { index1: { - elser_query_fields: [ - { field: 'field1', model_id: 'model1', nested: false, indices: ['index1'] }, - ], + elser_query_fields: [{ field: 'field1', model_id: 'model1', indices: ['index1'] }], dense_vector_query_fields: [], bm25_query_fields: [], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, }; - expect(createQuery(fields, fieldDescriptors)).toEqual({ + expect(createQuery(fields, sourceFields, fieldDescriptors)).toEqual({ retriever: { standard: { query: { - text_expansion: { - field1: { - model_id: 'model1', - model_text: '{query}', - }, + sparse_vector: { + field: 'field1', + inference_id: 'model1', + query: '{query}', }, }, }, @@ -51,16 +51,15 @@ describe('create_query', () => { const fieldDescriptors: IndicesQuerySourceFields = { index1: { elser_query_fields: [], - dense_vector_query_fields: [ - { field: 'field1', model_id: 'model1', nested: false, indices: ['index1'] }, - ], + dense_vector_query_fields: [{ field: 'field1', model_id: 'model1', indices: ['index1'] }], bm25_query_fields: [], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, }; - expect(createQuery(fields, fieldDescriptors)).toEqual({ + expect(createQuery(fields, sourceFields, fieldDescriptors)).toEqual({ retriever: { standard: { query: { @@ -89,33 +88,34 @@ describe('create_query', () => { const fieldDescriptors: IndicesQuerySourceFields = { index1: { elser_query_fields: [ - { field: 'field1', model_id: 'model1', nested: false, indices: ['index1', 'index2'] }, + { field: 'field1', model_id: 'model1', indices: ['index1', 'index2'] }, ], dense_vector_query_fields: [], bm25_query_fields: [], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, index2: { elser_query_fields: [ - { field: 'field1', model_id: 'model1', nested: false, indices: ['index1', 'index2'] }, + { field: 'field1', model_id: 'model1', indices: ['index1', 'index2'] }, ], dense_vector_query_fields: [], bm25_query_fields: [], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, }; - expect(createQuery(fields, fieldDescriptors)).toEqual({ + expect(createQuery(fields, sourceFields, fieldDescriptors)).toEqual({ retriever: { standard: { query: { - text_expansion: { - field1: { - model_id: 'model1', - model_text: '{query}', - }, + sparse_vector: { + field: 'field1', + inference_id: 'model1', + query: '{query}', }, }, }, @@ -131,37 +131,34 @@ describe('create_query', () => { const fieldDescriptors: IndicesQuerySourceFields = { index1: { - elser_query_fields: [ - { field: 'field1', model_id: 'model1', nested: false, indices: ['index1'] }, - ], + elser_query_fields: [{ field: 'field1', model_id: 'model1', indices: ['index1'] }], dense_vector_query_fields: [], bm25_query_fields: [], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, index2: { - elser_query_fields: [ - { field: 'field2', model_id: 'model1', nested: false, indices: ['index2'] }, - ], + elser_query_fields: [{ field: 'field2', model_id: 'model1', indices: ['index2'] }], dense_vector_query_fields: [], bm25_query_fields: [], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, }; - expect(createQuery(fields, fieldDescriptors)).toEqual({ + expect(createQuery(fields, sourceFields, fieldDescriptors)).toEqual({ retriever: { rrf: { retrievers: [ { standard: { query: { - text_expansion: { - field1: { - model_id: 'model1', - model_text: '{query}', - }, + sparse_vector: { + field: 'field1', + inference_id: 'model1', + query: '{query}', }, }, }, @@ -169,11 +166,10 @@ describe('create_query', () => { { standard: { query: { - text_expansion: { - field2: { - model_id: 'model1', - model_text: '{query}', - }, + sparse_vector: { + field: 'field2', + inference_id: 'model1', + query: '{query}', }, }, }, @@ -184,72 +180,6 @@ describe('create_query', () => { }); }); - it('should return empty for nested dense query', () => { - const fields = { - index1: ['passages.field1.predicted_value'], - }; - - const fieldDescriptors: IndicesQuerySourceFields = { - index1: { - elser_query_fields: [], - dense_vector_query_fields: [ - { - field: 'passages.field1.predicted_value', - model_id: 'model1', - nested: true, - indices: ['index1'], - }, - ], - bm25_query_fields: [], - source_fields: [], - skipped_fields: 0, - }, - }; - - expect(createQuery(fields, fieldDescriptors)).toEqual({ - retriever: { - standard: { - query: { - match_all: {}, - }, - }, - }, - }); - }); - - it('should return empty for nested sparse query', () => { - const fields = { - index1: ['passages.field1.tokens'], - }; - - const fieldDescriptors: IndicesQuerySourceFields = { - index1: { - elser_query_fields: [ - { - field: 'passages.field1.tokens', - model_id: 'model1', - nested: true, - indices: ['index1'], - }, - ], - dense_vector_query_fields: [], - bm25_query_fields: [], - source_fields: [], - skipped_fields: 0, - }, - }; - - expect(createQuery(fields, fieldDescriptors)).toEqual({ - retriever: { - standard: { - query: { - match_all: {}, - }, - }, - }, - }); - }); - describe('hybrid without RRF', () => { it('should return a hybrid query', () => { const fields = { @@ -259,37 +189,34 @@ describe('create_query', () => { const fieldDescriptors: IndicesQuerySourceFields = { index1: { - elser_query_fields: [ - { field: 'field1', model_id: 'model1', nested: false, indices: ['index1'] }, - ], + elser_query_fields: [{ field: 'field1', model_id: 'model1', indices: ['index1'] }], dense_vector_query_fields: [], bm25_query_fields: ['content', 'title'], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, index2: { - elser_query_fields: [ - { field: 'field2', model_id: 'model1', nested: false, indices: ['index2'] }, - ], + elser_query_fields: [{ field: 'field2', model_id: 'model1', indices: ['index2'] }], dense_vector_query_fields: [], bm25_query_fields: [], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, }; - expect(createQuery(fields, fieldDescriptors, { rrf: false })).toEqual({ + expect(createQuery(fields, sourceFields, fieldDescriptors, { rrf: false })).toEqual({ retriever: { standard: { query: { bool: { should: [ { - text_expansion: { - field1: { - model_id: 'model1', - model_text: '{query}', - }, + sparse_vector: { + field: 'field1', + inference_id: 'model1', + query: '{query}', }, }, { @@ -299,11 +226,10 @@ describe('create_query', () => { }, }, { - text_expansion: { - field2: { - model_id: 'model1', - model_text: '{query}', - }, + sparse_vector: { + field: 'field2', + inference_id: 'model1', + query: '{query}', }, }, ], @@ -325,37 +251,34 @@ describe('create_query', () => { const fieldDescriptors: IndicesQuerySourceFields = { index1: { - elser_query_fields: [ - { field: 'field1', model_id: 'model1', nested: false, indices: ['index1'] }, - ], + elser_query_fields: [{ field: 'field1', model_id: 'model1', indices: ['index1'] }], dense_vector_query_fields: [], bm25_query_fields: ['content', 'title'], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, index2: { - elser_query_fields: [ - { field: 'field2', model_id: 'model1', nested: false, indices: ['index2'] }, - ], + elser_query_fields: [{ field: 'field2', model_id: 'model1', indices: ['index2'] }], dense_vector_query_fields: [], bm25_query_fields: [], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, }; - expect(createQuery(fields, fieldDescriptors)).toEqual({ + expect(createQuery(fields, sourceFields, fieldDescriptors)).toEqual({ retriever: { rrf: { retrievers: [ { standard: { query: { - text_expansion: { - field1: { - model_id: 'model1', - model_text: '{query}', - }, + sparse_vector: { + field: 'field1', + inference_id: 'model1', + query: '{query}', }, }, }, @@ -373,11 +296,10 @@ describe('create_query', () => { { standard: { query: { - text_expansion: { - field2: { - model_id: 'model1', - model_text: '{query}', - }, + sparse_vector: { + field: 'field2', + inference_id: 'model1', + query: '{query}', }, }, }, @@ -397,25 +319,23 @@ describe('create_query', () => { const fieldDescriptors: IndicesQuerySourceFields = { index1: { elser_query_fields: [], - dense_vector_query_fields: [ - { field: 'field1', model_id: 'model1', nested: false, indices: ['index1'] }, - ], + dense_vector_query_fields: [{ field: 'field1', model_id: 'model1', indices: ['index1'] }], bm25_query_fields: ['content', 'title'], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, index2: { - elser_query_fields: [ - { field: 'field2', model_id: 'model1', nested: false, indices: ['index2'] }, - ], + elser_query_fields: [{ field: 'field2', model_id: 'model1', indices: ['index2'] }], dense_vector_query_fields: [], bm25_query_fields: [], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, }; - expect(createQuery(fields, fieldDescriptors)).toEqual({ + expect(createQuery(fields, sourceFields, fieldDescriptors)).toEqual({ retriever: { standard: { query: { @@ -444,16 +364,15 @@ describe('create_query', () => { const fieldDescriptors: IndicesQuerySourceFields = { index1: { elser_query_fields: [], - dense_vector_query_fields: [ - { field: 'field1', model_id: 'model1', nested: false, indices: ['index1'] }, - ], + dense_vector_query_fields: [{ field: 'field1', model_id: 'model1', indices: ['index1'] }], bm25_query_fields: ['content', 'title'], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, }; - expect(createQuery(fields, fieldDescriptors)).toEqual({ + expect(createQuery(fields, sourceFields, fieldDescriptors)).toEqual({ retriever: { rrf: { retrievers: [ @@ -488,21 +407,262 @@ describe('create_query', () => { }, }); }); + + describe('semantic fields', () => { + describe('sparse_vector embedding', () => { + it('should return a query with semantic field, specified as a source field', () => { + // as the field is specified as a source field, it should use the nested query and manually calling the sparse_vector query + const fields = { + index1: ['field2', 'title', 'content'], + }; + + const fieldDescriptors: IndicesQuerySourceFields = { + index1: { + elser_query_fields: [], + dense_vector_query_fields: [ + { field: 'field1', model_id: 'model1', indices: ['index1'] }, + ], + bm25_query_fields: ['content', 'title'], + source_fields: [], + skipped_fields: 0, + semantic_fields: [ + { + field: 'field2', + inferenceId: 'model2', + indices: ['index1'], + embeddingType: 'sparse_vector', + }, + ], + }, + }; + + expect( + createQuery( + fields, + { + index1: ['field2'], + }, + fieldDescriptors + ) + ).toEqual({ + retriever: { + rrf: { + retrievers: [ + { + standard: { + query: { + nested: { + inner_hits: { _source: ['field2.inference.chunks.text'], size: 2 }, + path: 'field2.inference.chunks', + query: { + sparse_vector: { + field: 'field2.inference.chunks.embeddings', + inference_id: 'model2', + query: '{query}', + }, + }, + }, + }, + }, + }, + { + standard: { + query: { multi_match: { fields: ['title', 'content'], query: '{query}' } }, + }, + }, + ], + }, + }, + }); + }); + + it('should return a query with semantic field, specified not as a source field', () => { + // this should fallback to using the semantic field for querying + const fields = { + index1: ['field2', 'title', 'content'], + }; + + const fieldDescriptors: IndicesQuerySourceFields = { + index1: { + elser_query_fields: [], + dense_vector_query_fields: [ + { field: 'field1', model_id: 'model1', indices: ['index1'] }, + ], + bm25_query_fields: ['content', 'title'], + source_fields: [], + skipped_fields: 0, + semantic_fields: [ + { + field: 'field2', + inferenceId: 'model2', + indices: ['index1'], + embeddingType: 'sparse_vector', + }, + ], + }, + }; + + expect( + createQuery( + fields, + { + index1: ['content'], + }, + fieldDescriptors + ) + ).toEqual({ + retriever: { + rrf: { + retrievers: [ + { standard: { query: { semantic: { field: 'field2', query: '{query}' } } } }, + { + standard: { + query: { multi_match: { fields: ['title', 'content'], query: '{query}' } }, + }, + }, + ], + }, + }, + }); + }); + }); + + describe('dense embedding', () => { + it('should return a query with semantic field, specified as a source field', () => { + // as the field is specified as a source field, it should use the nested query and manually calling the knn query + const fields = { + index1: ['field2', 'title', 'content'], + }; + + const fieldDescriptors: IndicesQuerySourceFields = { + index1: { + elser_query_fields: [], + dense_vector_query_fields: [ + { field: 'field1', model_id: 'model1', indices: ['index1'] }, + ], + bm25_query_fields: ['content', 'title'], + source_fields: [], + skipped_fields: 0, + semantic_fields: [ + { + field: 'field2', + inferenceId: 'model2', + indices: ['index1'], + embeddingType: 'dense_vector', + }, + ], + }, + }; + + expect( + createQuery( + fields, + { + index1: ['field2'], + }, + fieldDescriptors + ) + ).toEqual({ + retriever: { + rrf: { + retrievers: [ + { + standard: { + query: { + nested: { + inner_hits: { _source: ['field2.inference.chunks.text'], size: 2 }, + path: 'field2.inference.chunks', + query: { + knn: { + field: 'field2.inference.chunks.embeddings', + query_vector_builder: { + text_embedding: { + model_id: 'model2', + model_text: '{query}', + }, + }, + }, + }, + }, + }, + }, + }, + { + standard: { + query: { multi_match: { fields: ['title', 'content'], query: '{query}' } }, + }, + }, + ], + }, + }, + }); + }); + + it('should return a query with semantic field, specified not as a source field', () => { + // this should fallback to using the semantic field for querying + const fields = { + index1: ['field2', 'title', 'content'], + }; + + const fieldDescriptors: IndicesQuerySourceFields = { + index1: { + elser_query_fields: [], + dense_vector_query_fields: [ + { field: 'field1', model_id: 'model1', indices: ['index1'] }, + ], + bm25_query_fields: ['content', 'title'], + source_fields: [], + skipped_fields: 0, + semantic_fields: [ + { + field: 'field2', + inferenceId: 'model2', + indices: ['index1'], + embeddingType: 'dense_vector', + }, + ], + }, + }; + + expect( + createQuery( + fields, + { + index1: ['content'], + }, + fieldDescriptors + ) + ).toEqual({ + retriever: { + rrf: { + retrievers: [ + { standard: { query: { semantic: { field: 'field2', query: '{query}' } } } }, + { + standard: { + query: { multi_match: { fields: ['title', 'content'], query: '{query}' } }, + }, + }, + ], + }, + }, + }); + }); + }); + }); }); describe('getDefaultQueryFields', () => { it('should return default ELSER query fields', () => { const fieldDescriptors: IndicesQuerySourceFields = { index1: { - elser_query_fields: [ - { field: 'field1', model_id: 'model1', nested: false, indices: ['index1'] }, - ], + elser_query_fields: [{ field: 'field1', model_id: 'model1', indices: ['index1'] }], dense_vector_query_fields: [ - { field: 'field1', model_id: 'dense_model', nested: false, indices: ['index1'] }, + { field: 'field1', model_id: 'dense_model', indices: ['index1'] }, ], bm25_query_fields: [], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, }; @@ -512,36 +672,34 @@ describe('create_query', () => { it('should return default elser query fields for multiple indices', () => { const fieldDescriptors: IndicesQuerySourceFields = { index1: { - elser_query_fields: [ - { field: 'field1', model_id: 'model1', nested: false, indices: ['index1'] }, - ], + elser_query_fields: [{ field: 'field1', model_id: 'model1', indices: ['index1'] }], dense_vector_query_fields: [ { field: 'dv_field1', model_id: 'dense_model', - nested: false, + indices: ['index1', 'index2'], }, ], bm25_query_fields: [], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, index2: { - elser_query_fields: [ - { field: 'vector', model_id: 'model1', nested: false, indices: ['index2'] }, - ], + elser_query_fields: [{ field: 'vector', model_id: 'model1', indices: ['index2'] }], dense_vector_query_fields: [ { field: 'dv_field1', model_id: 'dense_model', - nested: false, + indices: ['index1', 'index2'], }, ], bm25_query_fields: [], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, }; @@ -554,36 +712,34 @@ describe('create_query', () => { it('should return elser query fields for default fields', () => { const fieldDescriptors: IndicesQuerySourceFields = { index1: { - elser_query_fields: [ - { field: 'field1', model_id: 'model1', nested: false, indices: ['index1'] }, - ], + elser_query_fields: [{ field: 'field1', model_id: 'model1', indices: ['index1'] }], dense_vector_query_fields: [ { field: 'dv_field1', model_id: 'dense_model', - nested: false, + indices: ['index1', 'index2'], }, ], bm25_query_fields: [], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, index2: { - elser_query_fields: [ - { field: 'vector', model_id: 'model1', nested: false, indices: ['index2'] }, - ], + elser_query_fields: [{ field: 'vector', model_id: 'model1', indices: ['index2'] }], dense_vector_query_fields: [ { field: 'dv_field1', model_id: 'dense_model', - nested: false, + indices: ['index1', 'index2'], }, ], bm25_query_fields: [], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, }; @@ -598,11 +754,12 @@ describe('create_query', () => { index1: { elser_query_fields: [], dense_vector_query_fields: [ - { field: 'dv_field1', model_id: 'dense_model', nested: false, indices: ['index1'] }, + { field: 'dv_field1', model_id: 'dense_model', indices: ['index1'] }, ], bm25_query_fields: [], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, }; @@ -617,6 +774,7 @@ describe('create_query', () => { bm25_query_fields: ['title', 'text', 'content'], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, }; @@ -633,6 +791,7 @@ describe('create_query', () => { bm25_query_fields: ['unknown1', 'unknown2'], source_fields: [], skipped_fields: 0, + semantic_fields: [], }, }; @@ -648,6 +807,7 @@ describe('create_query', () => { 'search-search-labs': { elser_query_fields: [], dense_vector_query_fields: [], + semantic_fields: [], bm25_query_fields: [ 'additional_urls', 'title', @@ -695,6 +855,7 @@ describe('create_query', () => { const fieldDescriptors: IndicesQuerySourceFields = { 'search-search-labs': { elser_query_fields: [], + semantic_fields: [], dense_vector_query_fields: [], bm25_query_fields: [], source_fields: [], @@ -713,6 +874,7 @@ describe('create_query', () => { const fieldDescriptors: IndicesQuerySourceFields = { 'search-search-labs': { elser_query_fields: [], + semantic_fields: [], dense_vector_query_fields: [], bm25_query_fields: [], source_fields: ['non_suggested_field'], diff --git a/x-pack/plugins/search_playground/public/utils/create_query.ts b/x-pack/plugins/search_playground/public/utils/create_query.ts index b2602a45707a8..0f65bea734d5c 100644 --- a/x-pack/plugins/search_playground/public/utils/create_query.ts +++ b/x-pack/plugins/search_playground/public/utils/create_query.ts @@ -46,6 +46,7 @@ interface ReRankOptions { export function createQuery( fields: IndexFields, + sourceFields: IndexFields, fieldDescriptors: IndicesQuerySourceFields, rerankOptions: ReRankOptions = { rrf: true, @@ -57,19 +58,83 @@ export function createQuery( const indexFields: string[] = fields[index]; const indexFieldDescriptors: QuerySourceFields = fieldDescriptors[index]; + const semanticMatches = indexFields.map((field) => { + const semanticField = indexFieldDescriptors.semantic_fields.find((x) => x.field === field); + const isSourceField = sourceFields[index].includes(field); + + // this is needed to get the inner_hits for the source field + // we cant rely on only the semantic field + // in future inner_hits option will be added to semantic + if (semanticField && isSourceField) { + if (semanticField.embeddingType === 'dense_vector') { + const filter = + semanticField.indices.length < indices.length + ? { filter: { terms: { _index: semanticField.indices } } } + : {}; + + return { + nested: { + path: `${semanticField.field}.inference.chunks`, + query: { + knn: { + field: `${semanticField.field}.inference.chunks.embeddings`, + ...filter, + query_vector_builder: { + text_embedding: { + model_id: semanticField.inferenceId, + model_text: '{query}', + }, + }, + }, + }, + inner_hits: { + size: 2, + _source: [`${semanticField.field}.inference.chunks.text`], + }, + }, + }; + } else if (semanticField.embeddingType === 'sparse_vector') { + return { + nested: { + path: `${semanticField.field}.inference.chunks`, + query: { + sparse_vector: { + inference_id: semanticField.inferenceId, + field: `${semanticField.field}.inference.chunks.embeddings`, + query: '{query}', + }, + }, + inner_hits: { + size: 2, + _source: [`${semanticField.field}.inference.chunks.text`], + }, + }, + }; + } + } else if (semanticField) { + return { + semantic: { + field: semanticField.field, + query: '{query}', + }, + }; + } else { + return null; + } + }); + const sparseMatches = indexFields.map((field) => { const elserField = indexFieldDescriptors.elser_query_fields.find( (x) => x.field === field ); - // not supporting nested fields for now - if (elserField && !elserField.nested) { + if (elserField) { // when another index has the same field, we don't want to duplicate the match rule const hasExistingSparseMatch = acc.queryMatches.find( - (x: any) => - x?.text_expansion?.[field] && - x?.text_expansion?.[field].model_id === elserField?.model_id + (x) => + x?.sparse_vector?.field === field && + x?.sparse_vector?.inference_id === elserField?.model_id ); if (hasExistingSparseMatch) { @@ -77,11 +142,10 @@ export function createQuery( } return { - text_expansion: { - [elserField.field]: { - model_id: elserField.model_id, - model_text: '{query}', - }, + sparse_vector: { + field: elserField.field, + inference_id: elserField.model_id, + query: '{query}', }, }; } @@ -108,8 +172,7 @@ export function createQuery( (x) => x.field === field ); - // not supporting nested fields for now - if (denseVectorField && !denseVectorField.nested) { + if (denseVectorField) { // when the knn field isn't found in all indices, we need a filter to ensure we only use the field from the correct index const filter = denseVectorField.indices.length < indices.length @@ -134,7 +197,7 @@ export function createQuery( }) .filter((x) => !!x); - const matches = [...sparseMatches, bm25Match].filter((x) => !!x); + const matches = [...sparseMatches, ...semanticMatches, bm25Match].filter((x) => !!x); return { queryMatches: [...acc.queryMatches, ...matches], @@ -222,6 +285,14 @@ export function getDefaultSourceFields(fieldDescriptors: IndicesQuerySourceField (acc: IndexFields, index: string) => { const indexFieldDescriptors = fieldDescriptors[index]; + // semantic_text fields are prioritized + if (indexFieldDescriptors.semantic_fields.length > 0) { + return { + ...acc, + [index]: indexFieldDescriptors.semantic_fields.map((x) => x.field), + }; + } + // if there are no source fields, we don't need to suggest anything if (indexFieldDescriptors.source_fields.length === 0) { return { @@ -253,7 +324,9 @@ export function getDefaultQueryFields(fieldDescriptors: IndicesQuerySourceFields const indexFieldDescriptors = fieldDescriptors[index]; const fields: string[] = []; - if (indexFieldDescriptors.elser_query_fields.length > 0) { + if (indexFieldDescriptors.semantic_fields.length > 0) { + fields.push(...indexFieldDescriptors.semantic_fields.map((x) => x.field)); + } else if (indexFieldDescriptors.elser_query_fields.length > 0) { const suggested = indexFieldDescriptors.elser_query_fields.filter((x) => SUGGESTED_SPARSE_FIELDS.includes(x.field) ); diff --git a/x-pack/plugins/search_playground/server/analytics/events.ts b/x-pack/plugins/search_playground/server/analytics/events.ts index 4330cb1a9e795..0db146a2f45a5 100644 --- a/x-pack/plugins/search_playground/server/analytics/events.ts +++ b/x-pack/plugins/search_playground/server/analytics/events.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { EventTypeOpts } from '@kbn/analytics-client'; +import { EventTypeOpts } from '@kbn/core/server'; export interface SendMessageEventData { connectorType: string; diff --git a/x-pack/plugins/search_playground/server/lib/conversational_chain.test.ts b/x-pack/plugins/search_playground/server/lib/conversational_chain.test.ts index 12b3a6f62c32c..f7ad46d3fdfda 100644 --- a/x-pack/plugins/search_playground/server/lib/conversational_chain.test.ts +++ b/x-pack/plugins/search_playground/server/lib/conversational_chain.test.ts @@ -6,13 +6,12 @@ */ import type { Client } from '@elastic/elasticsearch'; -import { createAssist as Assist } from '../utils/assist'; -import { clipContext, ConversationalChain } from './conversational_chain'; -import { FakeListChatModel } from '@langchain/core/utils/testing'; -import { FakeListLLM } from 'langchain/llms/fake'; import { BaseChatModel } from '@langchain/core/language_models/chat_models'; -import { experimental_StreamData, Message } from 'ai'; import { ChatPromptTemplate } from '@langchain/core/prompts'; +import { FakeListChatModel, FakeStreamingLLM } from '@langchain/core/utils/testing'; +import { Message, experimental_StreamData } from 'ai'; +import { createAssist as Assist } from '../utils/assist'; +import { ConversationalChain, clipContext } from './conversational_chain'; describe('conversational chain', () => { const createTestChain = async ({ @@ -76,7 +75,7 @@ describe('conversational chain', () => { ? new FakeListChatModel({ responses, }) - : new FakeListLLM({ responses }); + : new FakeStreamingLLM({ responses }); const aiClient = Assist({ es_client: mockElasticsearchClient as unknown as Client, @@ -209,6 +208,57 @@ describe('conversational chain', () => { }); }, 10000); + it('should be able to create a conversational chain with inner hit field', async () => { + await createTestChain({ + responses: ['the final answer'], + chat: [ + { + id: '1', + role: 'user', + content: 'what is the work from home policy?', + }, + ], + expectedFinalAnswer: 'the final answer', + docs: [ + { + _index: 'index', + _id: '1', + inner_hits: { + 'field.inference.chunks': { + hits: { + hits: [ + { + _source: { + text: 'value', + }, + }, + ], + }, + }, + }, + }, + ], + expectedDocs: [ + { + documents: [{ metadata: { _id: '1', _index: 'index' }, pageContent: 'value' }], + type: 'retrieved_docs', + }, + ], + expectedTokens: [ + { type: 'context_token_count', count: 7 }, + { type: 'prompt_token_count', count: 20 }, + ], + expectedSearchRequest: [ + { + method: 'POST', + path: '/index,website/_search', + body: { query: { match: { field: 'what is the work from home policy?' } }, size: 3 }, + }, + ], + contentField: { index: 'field' }, + }); + }, 10000); + it('asking with chat history should re-write the question', async () => { await createTestChain({ responses: ['rewrite the question', 'the final answer'], diff --git a/x-pack/plugins/search_playground/server/lib/elasticsearch_retriever.ts b/x-pack/plugins/search_playground/server/lib/elasticsearch_retriever.ts index 4a2c3344b2934..8504651bb6398 100644 --- a/x-pack/plugins/search_playground/server/lib/elasticsearch_retriever.ts +++ b/x-pack/plugins/search_playground/server/lib/elasticsearch_retriever.ts @@ -89,7 +89,7 @@ export class ElasticsearchRetriever extends BaseRetriever { : this.content_field[hit._index as string]; // we need to iterate over the _source object to get the value of complex key definition such as metadata.source - const valueForSelectedField = getValueForSelectedField(hit._source, pageContentFieldKey); + const valueForSelectedField = getValueForSelectedField(hit, pageContentFieldKey); return new Document({ pageContent: valueForSelectedField, diff --git a/x-pack/plugins/search_playground/server/lib/fetch_query_source_fields.test.ts b/x-pack/plugins/search_playground/server/lib/fetch_query_source_fields.test.ts index 539a3deaa7c03..8991cb9924480 100644 --- a/x-pack/plugins/search_playground/server/lib/fetch_query_source_fields.test.ts +++ b/x-pack/plugins/search_playground/server/lib/fetch_query_source_fields.test.ts @@ -19,8 +19,13 @@ import { SPARSE_INPUT_OUTPUT_ONE_INDEX, SPARSE_INPUT_OUTPUT_ONE_INDEX_FIELD_CAPS, SPARSE_INPUT_OUTPUT_ONE_INDEX_FIELD_CAPS_MODEL_ID_KEYWORD, + SPARSE_SEMANTIC_FIELD_FIELD_CAPS, + SPARSE_SEMANTIC_FIELD_MAPPINGS, DENSE_SPARSE_SAME_FIELD_NAME_CAPS, DENSE_SPARSE_SAME_FIELD_NAME_DOCS, + DENSE_SEMANTIC_FIELD_MAPPINGS, + DENSE_SEMANTIC_FIELD_FIELD_CAPS, + DENSE_SEMANTIC_FIELD_MAPPINGS_MISSING_TASK_TYPE, } from '../../__mocks__/fetch_query_source_fields.mock'; import { fetchFields, @@ -36,10 +41,20 @@ describe('fetch_query_source_fields', () => { { index: 'workplace_index', doc: ELSER_PASSAGE_CHUNKED_TWO_INDICES_DOCS[0], + mapping: { + workplace_index: { + mappings: {}, + }, + }, }, { index: 'workplace_index2', doc: ELSER_PASSAGE_CHUNKED_TWO_INDICES_DOCS[1], + mapping: { + workplace_index2: { + mappings: {}, + }, + }, }, ]) ).toEqual({ @@ -55,14 +70,15 @@ describe('fetch_query_source_fields', () => { { field: 'vector.tokens', model_id: '.elser_model_2', - nested: false, indices: ['workplace_index'], }, ], skipped_fields: 8, source_fields: ['metadata.summary', 'metadata.rolePermissions', 'text', 'metadata.name'], + semantic_fields: [], }, workplace_index2: { + semantic_fields: [], bm25_query_fields: [ 'metadata.summary', 'content', @@ -75,7 +91,6 @@ describe('fetch_query_source_fields', () => { { field: 'content_vector.tokens', model_id: '.elser_model_2', - nested: false, indices: ['workplace_index2'], }, ], @@ -95,10 +110,16 @@ describe('fetch_query_source_fields', () => { { index: 'search-example-main', doc: DENSE_PASSAGE_FIRST_SINGLE_INDEX_DOC, + mapping: { + 'search-example-main': { + mappings: {}, + }, + }, }, ]) ).toEqual({ 'search-example-main': { + semantic_fields: [], bm25_query_fields: [ 'page_content_key', 'title', @@ -118,7 +139,6 @@ describe('fetch_query_source_fields', () => { { field: 'page_content_e5_embbeding.predicted_value', model_id: '.multilingual-e5-small_linux-x86_64', - nested: false, indices: ['search-example-main'], }, ], @@ -149,18 +169,23 @@ describe('fetch_query_source_fields', () => { { index: 'search-nethys', doc: SPARSE_DOC_SINGLE_INDEX, + mapping: { + 'search-nethys': { + mappings: {}, + }, + }, }, ]) ).toEqual({ 'search-nethys': { bm25_query_fields: ['body_content', 'headings', 'title'], dense_vector_query_fields: [], + semantic_fields: [], elser_query_fields: [ { field: 'ml.inference.body_content_expanded.predicted_value', indices: ['search-nethys'], model_id: '.elser_model_2_linux-x86_64', - nested: false, }, ], source_fields: ['body_content', 'headings', 'title'], @@ -176,6 +201,11 @@ describe('fetch_query_source_fields', () => { { index: 'workplace_index_nested', doc: DENSE_VECTOR_DOCUMENT_FIRST[0], + mapping: { + workplace_index_nested: { + mappings: {}, + }, + }, }, ]) ).toEqual({ @@ -192,6 +222,7 @@ describe('fetch_query_source_fields', () => { ], dense_vector_query_fields: [], elser_query_fields: [], + semantic_fields: [], source_fields: [ 'metadata.category', 'content', @@ -213,6 +244,11 @@ describe('fetch_query_source_fields', () => { { index: 'index2', doc: DENSE_INPUT_OUTPUT_ONE_INDEX[0], + mapping: { + index2: { + mappings: {}, + }, + }, }, ]) ).toEqual({ @@ -223,10 +259,10 @@ describe('fetch_query_source_fields', () => { field: 'text_embedding', indices: ['index2'], model_id: '.multilingual-e5-small', - nested: false, }, ], elser_query_fields: [], + semantic_fields: [], source_fields: ['text'], skipped_fields: 2, }, @@ -239,6 +275,11 @@ describe('fetch_query_source_fields', () => { { index: 'index', doc: SPARSE_INPUT_OUTPUT_ONE_INDEX[0], + mapping: { + index: { + mappings: {}, + }, + }, }, ]) ).toEqual({ @@ -249,10 +290,10 @@ describe('fetch_query_source_fields', () => { field: 'text_embedding', indices: ['index'], model_id: '.elser_model_2', - nested: false, }, ], dense_vector_query_fields: [], + semantic_fields: [], source_fields: ['text'], skipped_fields: 2, }, @@ -265,10 +306,20 @@ describe('fetch_query_source_fields', () => { { index: 'cohere-embeddings', doc: DENSE_SPARSE_SAME_FIELD_NAME_DOCS[0], + mapping: { + 'cohere-embeddings': { + mappings: {}, + }, + }, }, { index: 'elser_index', doc: DENSE_SPARSE_SAME_FIELD_NAME_DOCS[1], + mapping: { + elser_index: { + mappings: {}, + }, + }, }, ]) ).toEqual({ @@ -279,12 +330,12 @@ describe('fetch_query_source_fields', () => { field: 'text_embedding', indices: ['cohere-embeddings'], model_id: 'cohere_embeddings', - nested: false, }, ], elser_query_fields: [], skipped_fields: 2, source_fields: ['text'], + semantic_fields: [], }, elser_index: { bm25_query_fields: ['text'], @@ -294,14 +345,96 @@ describe('fetch_query_source_fields', () => { field: 'text_embedding', indices: ['elser_index'], model_id: 'my-elser-model', - nested: false, }, ], skipped_fields: 2, source_fields: ['text'], + semantic_fields: [], }, }); }); + + describe('semantic text support', () => { + it('should return the correct fields for semantic text - sparse', () => { + expect( + parseFieldsCapabilities(SPARSE_SEMANTIC_FIELD_FIELD_CAPS, [ + { + index: 'test-index2', + // unused + doc: SPARSE_INPUT_OUTPUT_ONE_INDEX[0], + mapping: SPARSE_SEMANTIC_FIELD_MAPPINGS, + }, + ]) + ).toEqual({ + 'test-index2': { + bm25_query_fields: ['non_infer_field'], + dense_vector_query_fields: [], + elser_query_fields: [], + semantic_fields: [ + { + embeddingType: 'sparse_vector', + field: 'infer_field', + inferenceId: 'elser-endpoint', + indices: ['test-index2'], + }, + ], + skipped_fields: 4, + source_fields: ['infer_field', 'non_infer_field'], + }, + }); + }); + + it('should return the correct fields for semantic text - dense', () => { + expect( + parseFieldsCapabilities(DENSE_SEMANTIC_FIELD_FIELD_CAPS, [ + { + index: 'test-index2', + // unused + doc: DENSE_INPUT_OUTPUT_ONE_INDEX[0], + mapping: DENSE_SEMANTIC_FIELD_MAPPINGS, + }, + ]) + ).toEqual({ + 'test-index2': { + bm25_query_fields: ['non_infer_field'], + dense_vector_query_fields: [], + elser_query_fields: [], + semantic_fields: [ + { + embeddingType: 'dense_vector', + field: 'infer_field', + inferenceId: 'cohere', + indices: ['test-index2'], + }, + ], + skipped_fields: 4, + source_fields: ['infer_field', 'non_infer_field'], + }, + }); + }); + + it('skips if the semantic_text field not setup correctly', () => { + expect( + parseFieldsCapabilities(DENSE_SEMANTIC_FIELD_FIELD_CAPS, [ + { + index: 'test-index2', + // unused + doc: DENSE_INPUT_OUTPUT_ONE_INDEX[0], + mapping: DENSE_SEMANTIC_FIELD_MAPPINGS_MISSING_TASK_TYPE, + }, + ]) + ).toEqual({ + 'test-index2': { + bm25_query_fields: ['non_infer_field'], + dense_vector_query_fields: [], + elser_query_fields: [], + semantic_fields: [], + skipped_fields: 5, // increat by 1 for the semantic field + source_fields: ['non_infer_field'], + }, + }); + }); + }); }); describe('getModelIdFields', () => { @@ -340,6 +473,13 @@ describe('fetch_query_source_fields', () => { asCurrentUser: { fieldCaps: jest.fn().mockResolvedValue(DENSE_PASSAGE_FIRST_SINGLE_INDEX_FIELD_CAPS), search: jest.fn().mockResolvedValue(DENSE_PASSAGE_FIRST_SINGLE_INDEX_DOC), + indices: { + getMapping: jest.fn().mockResolvedValue({ + 'search-example-main': { + mappings: {}, + }, + }), + }, }, } as any; const indices = ['search-example-main']; @@ -371,6 +511,13 @@ describe('fetch_query_source_fields', () => { asCurrentUser: { fieldCaps: jest.fn().mockResolvedValue(SPARSE_INPUT_OUTPUT_ONE_INDEX_FIELD_CAPS), search: jest.fn().mockResolvedValue(SPARSE_INPUT_OUTPUT_ONE_INDEX), + indices: { + getMapping: jest.fn().mockResolvedValue({ + index: { + mappings: {}, + }, + }), + }, }, } as any; const indices = ['index']; diff --git a/x-pack/plugins/search_playground/server/lib/fetch_query_source_fields.ts b/x-pack/plugins/search_playground/server/lib/fetch_query_source_fields.ts index 82db1b8de6df4..15e1ead0bf037 100644 --- a/x-pack/plugins/search_playground/server/lib/fetch_query_source_fields.ts +++ b/x-pack/plugins/search_playground/server/lib/fetch_query_source_fields.ts @@ -5,8 +5,14 @@ * 2.0. */ -import { SearchResponse, FieldCapsResponse } from '@elastic/elasticsearch/lib/api/types'; -import { FieldCapsFieldCapability } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { + SearchResponse, + FieldCapsResponse, + IndicesGetMappingResponse, + FieldCapsFieldCapability, + MappingPropertyBase, +} from '@elastic/elasticsearch/lib/api/types'; + import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; import { IndicesQuerySourceFields } from '../types'; @@ -15,11 +21,35 @@ interface FieldModelId { modelId: string | undefined; } +type SemanticEmbeddingType = 'sparse_vector' | 'dense_vector'; + +interface SemanticField { + field: string; + inferenceId: string; + embeddingType?: SemanticEmbeddingType; +} + interface IndexFieldModel { index: string; fields: FieldModelId[]; + semanticTextFields: SemanticField[]; } +type TaskType = 'sparse_embedding' | 'text_embedding'; + +interface MappingSemanticTextProperty extends MappingPropertyBase { + type: 'semantic_text'; + inference_id: string; + model_settings?: { + task_type: TaskType; + }; +} + +const EMBEDDING_TYPE: Record = { + sparse_embedding: 'sparse_vector', + text_embedding: 'dense_vector', +}; + export const getModelIdFields = (fieldCapsResponse: FieldCapsResponse) => { const { fields } = fieldCapsResponse; return Object.keys(fields).reduce>((acc, fieldKey) => { @@ -64,7 +94,7 @@ export const fetchFields = async ( const modelIdFields = getModelIdFields(fieldCapabilities); - const indicesAggs = await Promise.all( + const indicesAggsMappings = await Promise.all( indices.map(async (index) => ({ index, doc: await client.asCurrentUser.search({ @@ -85,14 +115,19 @@ export const fetchFields = async ( ), }, }), + mapping: await client.asCurrentUser.indices.getMapping({ index }), })) ); - return parseFieldsCapabilities(fieldCapabilities, indicesAggs); + return parseFieldsCapabilities(fieldCapabilities, indicesAggsMappings); }; const INFERENCE_MODEL_FIELD_REGEXP = /\.predicted_value|\.tokens/; +const getSemanticField = (field: string, semanticFields: SemanticField[]) => { + return semanticFields.find((sf) => sf.field === field); +}; + const getModelField = (field: string, modelIdFields: FieldModelId[]) => { // For input_output inferred fields, the model_id is at the top level const topLevelModelField = modelIdFields.find( @@ -141,12 +176,12 @@ const isFieldInIndex = ( export const parseFieldsCapabilities = ( fieldCapsResponse: FieldCapsResponse, - aggDocs: Array<{ index: string; doc: SearchResponse }> + aggMappingDocs: Array<{ index: string; doc: SearchResponse; mapping: IndicesGetMappingResponse }> ): IndicesQuerySourceFields => { const { fields, indices: indexOrIndices } = fieldCapsResponse; const indices = Array.isArray(indexOrIndices) ? indexOrIndices : [indexOrIndices]; - const indexModelIdFields = aggDocs.map((aggDoc) => { + const indexModelIdFields = aggMappingDocs.map((aggDoc) => { const modelIdFields = Object.keys(aggDoc.doc.aggregations || {}).map((field) => { return { field, @@ -154,9 +189,28 @@ export const parseFieldsCapabilities = ( }; }); + const mappingProperties = aggDoc.mapping[aggDoc.index].mappings.properties || {}; + + const semanticTextFields: SemanticField[] = Object.keys(mappingProperties || {}) + .filter( + // @ts-ignore + (field) => mappingProperties[field].type === 'semantic_text' + ) + .map((field) => { + const mapping = mappingProperties[field] as unknown as MappingSemanticTextProperty; + return { + field, + inferenceId: mapping?.inference_id, + embeddingType: mapping?.model_settings?.task_type + ? EMBEDDING_TYPE[mapping.model_settings.task_type] + : undefined, + }; + }); + return { index: aggDoc.index, fields: modelIdFields, + semanticTextFields, }; }); @@ -167,6 +221,7 @@ export const parseFieldsCapabilities = ( bm25_query_fields: [], source_fields: [], skipped_fields: 0, + semantic_fields: [], }; return acc; }, {}); @@ -186,15 +241,38 @@ export const parseFieldsCapabilities = ( : (indices as unknown as string[]); for (const index of indicesPresentIn) { - const modelIdFields = indexModelIdFields.find( + const { fields: modelIdFields, semanticTextFields } = indexModelIdFields.find( (indexModelIdField) => indexModelIdField.index === index - )!.fields; + )!; + const nestedField = isFieldNested(fieldKey, fieldCapsResponse); + + if (isFieldInIndex(field, 'semantic_text', index)) { + const semanticFieldMapping = getSemanticField(fieldKey, semanticTextFields); - if ( + // only use this when embeddingType and inferenceId is defined + // this requires semantic_text field to be set up correctly and ingested + if ( + semanticFieldMapping && + semanticFieldMapping.embeddingType && + semanticFieldMapping.inferenceId && + !nestedField + ) { + const semanticField = { + field: fieldKey, + inferenceId: semanticFieldMapping.inferenceId, + embeddingType: semanticFieldMapping.embeddingType, + indices: (field.semantic_text.indices as string[]) || indicesPresentIn, + }; + + acc[index].semantic_fields.push(semanticField); + acc[index].source_fields.push(fieldKey); + } else { + acc[index].skipped_fields++; + } + } else if ( isFieldInIndex(field, 'rank_features', index) || isFieldInIndex(field, 'sparse_vector', index) ) { - const nestedField = isFieldNested(fieldKey, fieldCapsResponse); const modelId = getModelField(fieldKey, modelIdFields); const fieldCapabilities = field.rank_features || field.sparse_vector; @@ -205,7 +283,6 @@ export const parseFieldsCapabilities = ( const elserModelField = { field: fieldKey, model_id: modelId, - nested: !!isFieldNested(fieldKey, fieldCapsResponse), indices: (fieldCapabilities.indices as string[]) || indicesPresentIn, }; acc[index].elser_query_fields.push(elserModelField); @@ -213,7 +290,6 @@ export const parseFieldsCapabilities = ( acc[index].skipped_fields++; } } else if (isFieldInIndex(field, 'dense_vector', index)) { - const nestedField = isFieldNested(fieldKey, fieldCapsResponse); const modelId = getModelField(fieldKey, modelIdFields); const fieldCapabilities = field.dense_vector; @@ -224,7 +300,6 @@ export const parseFieldsCapabilities = ( const denseVectorField = { field: fieldKey, model_id: modelId, - nested: !!nestedField, indices: (fieldCapabilities.indices as string[]) || indicesPresentIn, }; acc[index].dense_vector_query_fields.push(denseVectorField); diff --git a/x-pack/plugins/search_playground/server/utils/get_value_for_selected_field.test.ts b/x-pack/plugins/search_playground/server/utils/get_value_for_selected_field.test.ts index a13e51603cc7b..bf02f6620d38c 100644 --- a/x-pack/plugins/search_playground/server/utils/get_value_for_selected_field.test.ts +++ b/x-pack/plugins/search_playground/server/utils/get_value_for_selected_field.test.ts @@ -22,7 +22,7 @@ describe('getValueForSelectedField', () => { }, }; - expect(getValueForSelectedField(hit._source, 'test')).toEqual('The Shawshank Redemption'); + expect(getValueForSelectedField(hit, 'test')).toEqual('The Shawshank Redemption'); }); test('should return for combined key', () => { @@ -39,7 +39,7 @@ describe('getValueForSelectedField', () => { }, }; - expect(getValueForSelectedField(hit._source, 'metadata.source')).toEqual( + expect(getValueForSelectedField(hit, 'metadata.source')).toEqual( 'Over the course of several years, two convicts form a friendship, seeking consolation and, eventually, redemption through basic compassion' ); }); @@ -58,7 +58,7 @@ describe('getValueForSelectedField', () => { }, }; - expect(getValueForSelectedField(hit._source, 'metadata.sources')).toBe(''); + expect(getValueForSelectedField(hit, 'metadata.sources')).toBe(''); }); test('should return empty string for nested key', () => { @@ -75,6 +75,52 @@ describe('getValueForSelectedField', () => { }, }; - expect(getValueForSelectedField(hit._source, 'bla.sources')).toBe(''); + expect(getValueForSelectedField(hit, 'bla.sources')).toBe(''); + }); + + test('should return when its a chunked passage', () => { + const hit = { + _index: 'sample-index', + _id: '8jSNY48B6iHEi98DL1C-', + _score: 0.7789394, + _source: { + test: 'The Shawshank Redemption', + metadata: { + source: + 'Over the course of several years, two convicts form a friendship, seeking consolation and, eventually, redemption through basic compassion', + }, + }, + inner_hits: { + 'test.inference.chunks': { + hits: { + hits: [ + { + _source: { + text: 'Over the course of several years', + }, + }, + { + _source: { + text: 'two convicts form a friendship', + }, + }, + { + _source: { + text: 'seeking consolation and, eventually, redemption through basic compassion', + }, + }, + ], + }, + }, + }, + }; + + expect(getValueForSelectedField(hit as any, 'test')).toMatchInlineSnapshot(` + "Over the course of several years + --- + two convicts form a friendship + --- + seeking consolation and, eventually, redemption through basic compassion" + `); }); }); diff --git a/x-pack/plugins/search_playground/server/utils/get_value_for_selected_field.ts b/x-pack/plugins/search_playground/server/utils/get_value_for_selected_field.ts index 6a2044f2943e4..68bd600d62143 100644 --- a/x-pack/plugins/search_playground/server/utils/get_value_for_selected_field.ts +++ b/x-pack/plugins/search_playground/server/utils/get_value_for_selected_field.ts @@ -5,8 +5,20 @@ * 2.0. */ +import { SearchHit } from '@elastic/elasticsearch/lib/api/types'; import { get } from 'lodash'; -export const getValueForSelectedField = (source: unknown, path: string): string => { - return get(source, path, ''); +export const getValueForSelectedField = (hit: SearchHit, path: string): string => { + if (!hit) { + return ''; + } + + // for semantic_text matches + if (!!hit.inner_hits?.[`${path}.inference.chunks`]) { + return hit.inner_hits[`${path}.inference.chunks`].hits.hits + .map((innerHit) => innerHit._source.text) + .join('\n --- \n'); + } + + return get(hit._source, path, ''); }; diff --git a/x-pack/plugins/search_playground/tsconfig.json b/x-pack/plugins/search_playground/tsconfig.json index c75c9213103bf..7e7217975cc85 100644 --- a/x-pack/plugins/search_playground/tsconfig.json +++ b/x-pack/plugins/search_playground/tsconfig.json @@ -39,7 +39,6 @@ "@kbn/core-logging-server-mocks", "@kbn/analytics", "@kbn/usage-collection-plugin", - "@kbn/analytics-client", "@kbn/console-plugin" ], "exclude": [ diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx index 03ed9597fcb2d..4f3c1eb103a75 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.tsx @@ -130,7 +130,6 @@ export const SubFeatureForm = (props: Props) => { }} checked={isGranted} disabled={props.disabled} - compressed={true} /> ); })} diff --git a/x-pack/plugins/security/server/analytics/analytics_service.ts b/x-pack/plugins/security/server/analytics/analytics_service.ts index 98d316d8a8d5f..b5bbbf0079cf9 100644 --- a/x-pack/plugins/security/server/analytics/analytics_service.ts +++ b/x-pack/plugins/security/server/analytics/analytics_service.ts @@ -5,8 +5,11 @@ * 2.0. */ -import type { EventTypeOpts } from '@kbn/analytics-client'; -import type { AnalyticsServiceSetup as CoreAnalyticsServiceSetup, Logger } from '@kbn/core/server'; +import type { + AnalyticsServiceSetup as CoreAnalyticsServiceSetup, + EventTypeOpts, + Logger, +} from '@kbn/core/server'; import type { CSPViolationReport, diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 791d784c36a0d..cf362926bdd04 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -8,7 +8,7 @@ import type { Subscription } from 'rxjs'; import { map } from 'rxjs'; -import type { CloudStart } from '@kbn/cloud-plugin/server'; +import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/server'; import type { TypeOf } from '@kbn/config-schema'; import type { CoreSetup, @@ -88,6 +88,7 @@ export interface PluginSetupDependencies { taskManager: TaskManagerSetupContract; usageCollection?: UsageCollectionSetup; spaces?: SpacesPluginSetup; + cloud?: CloudSetup; } export interface PluginStartDependencies { diff --git a/x-pack/plugins/security/server/routes/feature_check/feature_check.test.ts b/x-pack/plugins/security/server/routes/feature_check/feature_check.test.ts index 953f65afa9a7c..a1745ec0930b4 100644 --- a/x-pack/plugins/security/server/routes/feature_check/feature_check.test.ts +++ b/x-pack/plugins/security/server/routes/feature_check/feature_check.test.ts @@ -86,6 +86,15 @@ describe('GET role mappings feature check', () => { expect(response.payload).toEqual(asserts.result); expect(mockLicensingContext.license.check).toHaveBeenCalledWith('security', 'basic'); + + if (canReadSecurity) { + expect( + mockCoreContext.elasticsearch.client.asInternalUser.transport.request + ).toHaveBeenCalledWith({ + method: 'GET', + path: '/_xpack/usage?filter_path=remote_clusters.*,security.realms.*', + }); + } }); }; diff --git a/x-pack/plugins/security/server/routes/feature_check/feature_check.ts b/x-pack/plugins/security/server/routes/feature_check/feature_check.ts index 442f040398962..46276957b6c83 100644 --- a/x-pack/plugins/security/server/routes/feature_check/feature_check.ts +++ b/x-pack/plugins/security/server/routes/feature_check/feature_check.ts @@ -88,7 +88,10 @@ async function getEnabledSecurityFeatures(esClient: ElasticsearchClient, logger: // `transport.request` is potentially unsafe when combined with untrusted user input. // Do not augment with such input. const xpackUsagePromise = esClient.transport - .request({ method: 'GET', path: '/_xpack/usage' }) + .request({ + method: 'GET', + path: '/_xpack/usage?filter_path=remote_clusters.*,security.realms.*', + }) .then((body) => body as XPackUsageResponse) .catch((error) => { // fall back to no external realms configured. diff --git a/x-pack/plugins/security/tsconfig.json b/x-pack/plugins/security/tsconfig.json index f3ccbabffff97..a555c58c5804e 100644 --- a/x-pack/plugins/security/tsconfig.json +++ b/x-pack/plugins/security/tsconfig.json @@ -65,7 +65,6 @@ "@kbn/core-http-server", "@kbn/core-http-server-mocks", "@kbn/remote-clusters-plugin", - "@kbn/analytics-client", "@kbn/security-plugin-types-common", "@kbn/security-plugin-types-public", "@kbn/security-plugin-types-server", diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/set_alert_assignees_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/set_alert_assignees_route.schema.yaml index 77fd51fe99494..739386d637abd 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/set_alert_assignees_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/alert_assignees/set_alert_assignees_route.schema.yaml @@ -6,8 +6,9 @@ paths: /api/detection_engine/signals/assignees: summary: Assigns users to alerts post: - operationId: SetAlertAssignees + x-labels: [ess, serverless] x-codegen-enabled: true + operationId: SetAlertAssignees description: Assigns users to alerts. requestBody: required: true diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/index.ts index 7adc565a09c8e..0e4f6a7ae6609 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/index.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export * from './set_alert_tags/set_alert_tags_route'; +export * from './set_alert_tags/set_alert_tags.gen'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags.gen.ts new file mode 100644 index 0000000000000..3640d78cd33b2 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags.gen.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Manage alert tags API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { AlertIds, AlertTags } from '../../../model/alert.gen'; + +export type ManageAlertTags = z.infer; +export const ManageAlertTags = z.object({ + tags_to_add: AlertTags, + tags_to_remove: AlertTags, +}); + +export type ManageAlertTagsRequestBody = z.infer; +export const ManageAlertTagsRequestBody = z.object({ + ids: AlertIds, + tags: ManageAlertTags, +}); +export type ManageAlertTagsRequestBodyInput = z.input; + +/** + * Elasticsearch update by query response + */ +export type ManageAlertTagsResponse = z.infer; +export const ManageAlertTagsResponse = z.object({}).catchall(z.unknown()); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags.schema.yaml new file mode 100644 index 0000000000000..ca2c93b88b25e --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags.schema.yaml @@ -0,0 +1,70 @@ +openapi: 3.0.0 +info: + title: Manage alert tags API endpoint + version: '2023-10-31' +paths: + /api/detection_engine/signals/tags: + post: + x-labels: [serverless, ess] + operationId: ManageAlertTags + x-codegen-enabled: true + summary: Manage alert tags for a one or more alerts + tags: + - Alerts API + requestBody: + description: An object containing tags to add or remove and alert ids the changes will be applied + required: true + content: + application/json: + schema: + type: object + properties: + ids: + $ref: '../../../model/alert.schema.yaml#/components/schemas/AlertIds' + tags: + $ref: '#/components/schemas/ManageAlertTags' + required: + - ids + - tags + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + additionalProperties: true + description: Elasticsearch update by query response + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../model/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../model/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + +components: + schemas: + ManageAlertTags: + type: object + properties: + tags_to_add: + $ref: '../../../model/alert.schema.yaml#/components/schemas/AlertTags' + tags_to_remove: + $ref: '../../../model/alert.schema.yaml#/components/schemas/AlertTags' + required: + - tags_to_add + - tags_to_remove diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags_route.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags_route.mock.ts index f1e05ad7f125a..a14b36fb7b2c5 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags_route.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags_route.mock.ts @@ -5,13 +5,13 @@ * 2.0. */ -import type { SetAlertTagsRequestBody } from './set_alert_tags_route'; +import type { ManageAlertTagsRequestBody } from './set_alert_tags.gen'; export const getSetAlertTagsRequestMock = ( tagsToAdd: string[] = [], tagsToRemove: string[] = [], ids: string[] = [] -): SetAlertTagsRequestBody => ({ +): ManageAlertTagsRequestBody => ({ tags: { tags_to_add: tagsToAdd, tags_to_remove: tagsToRemove }, ids, }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags_route.ts deleted file mode 100644 index 8a6f93c20b770..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags_route.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { alert_tag_ids, alert_tags } from '../../model'; - -export const setAlertTagsRequestBody = t.exact( - t.type({ - tags: alert_tags, - ids: alert_tag_ids, - }) -); - -export type SetAlertTagsRequestBody = t.TypeOf; -export type SetAlertTagsRequestBodyDecoded = SetAlertTagsRequestBody; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.schema.yaml index 3a7a19611a144..92b82e9d1e849 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/get_prebuilt_rules_and_timelines_status/get_prebuilt_rules_and_timelines_status_route.schema.yaml @@ -5,8 +5,9 @@ info: paths: /api/detection_engine/rules/prepackaged/_status: get: - operationId: GetPrebuiltRulesAndTimelinesStatus + x-labels: [ess] x-codegen-enabled: true + operationId: GetPrebuiltRulesAndTimelinesStatus summary: Get the status of Elastic prebuilt rules tags: - Prebuilt Rules API diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route.schema.yaml index 3aeb1b04317f9..ab27c71c4ef33 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/prebuilt_rules/install_prebuilt_rules_and_timelines/install_prebuilt_rules_and_timelines_route.schema.yaml @@ -5,8 +5,9 @@ info: paths: /api/detection_engine/rules/prepackaged: put: - operationId: InstallPrebuiltRulesAndTimelines + x-labels: [ess] x-codegen-enabled: true + operationId: InstallPrebuiltRulesAndTimelines summary: Installs all Elastic prebuilt rules and timelines tags: - Prebuilt Rules API 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 6b5a3aa1c6982..a2e75b8ae4fb6 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 @@ -5,8 +5,9 @@ info: paths: /api/detection_engine/rules/_bulk_action: post: - operationId: PerformBulkAction + x-labels: [ess, serverless] x-codegen-enabled: true + operationId: PerformBulkAction 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: diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_create_rules/bulk_create_rules_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_create_rules/bulk_create_rules_route.schema.yaml index 002fa5613eed9..127ad9784988d 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_create_rules/bulk_create_rules_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_create_rules/bulk_create_rules_route.schema.yaml @@ -6,8 +6,8 @@ paths: /api/detection_engine/rules/_bulk_create: post: x-labels: [ess] - operationId: BulkCreateRules x-codegen-enabled: true + operationId: BulkCreateRules deprecated: true description: Creates new detection rules in bulk. tags: diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_delete_rules/bulk_delete_rules_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_delete_rules/bulk_delete_rules_route.schema.yaml index 8438bb5b60052..02f78a65fee7c 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_delete_rules/bulk_delete_rules_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_delete_rules/bulk_delete_rules_route.schema.yaml @@ -5,8 +5,9 @@ info: paths: /api/detection_engine/rules/_bulk_delete: delete: - operationId: BulkDeleteRules + x-labels: [ess] x-codegen-enabled: true + operationId: BulkDeleteRules deprecated: true description: Deletes multiple rules. tags: diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_patch_rules/bulk_patch_rules_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_patch_rules/bulk_patch_rules_route.schema.yaml index 7ba82e4ad3673..65bd0e1a4ac36 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_patch_rules/bulk_patch_rules_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_patch_rules/bulk_patch_rules_route.schema.yaml @@ -5,8 +5,9 @@ info: paths: /api/detection_engine/rules/_bulk_update: patch: - operationId: BulkPatchRules + x-labels: [ess] x-codegen-enabled: true + operationId: BulkPatchRules deprecated: true description: Updates multiple rules using the `PATCH` method. tags: diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_update_rules/bulk_update_rules_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_update_rules/bulk_update_rules_route.schema.yaml index 6f85e51c6a01e..37241035439d3 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_update_rules/bulk_update_rules_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_crud/bulk_update_rules/bulk_update_rules_route.schema.yaml @@ -5,8 +5,9 @@ info: paths: /api/detection_engine/rules/_bulk_update: put: - operationId: BulkUpdateRules + x-labels: [ess] x-codegen-enabled: true + operationId: BulkUpdateRules deprecated: true description: Updates multiple rules using the `PUT` method. tags: diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/create_rule_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/create_rule_route.schema.yaml index 464a2df0641e3..a5071837af2cf 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/create_rule_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/create_rule/create_rule_route.schema.yaml @@ -6,8 +6,8 @@ paths: /api/detection_engine/rules: post: x-labels: [ess, serverless] - operationId: CreateRule x-codegen-enabled: true + operationId: CreateRule description: Create a single detection rule tags: - Rules API diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/delete_rule/delete_rule_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/delete_rule/delete_rule_route.schema.yaml index be55d0add8322..b6ef8a444eb55 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/delete_rule/delete_rule_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/delete_rule/delete_rule_route.schema.yaml @@ -5,8 +5,9 @@ info: paths: /api/detection_engine/rules: delete: - operationId: DeleteRule + x-labels: [ess, serverless] x-codegen-enabled: true + operationId: DeleteRule description: Deletes a single rule using the `rule_id` or `id` field. tags: - Rules API diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.schema.yaml index df2bdb114c2e0..aec02102bcca4 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.schema.yaml @@ -5,8 +5,9 @@ info: paths: /api/detection_engine/rules: patch: - operationId: PatchRule + x-labels: [ess, serverless] x-codegen-enabled: true + operationId: PatchRule description: Patch a single rule tags: - Rules API diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/read_rule/read_rule_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/read_rule/read_rule_route.schema.yaml index bcb4cc83381df..817579eb8c27e 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/read_rule/read_rule_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/read_rule/read_rule_route.schema.yaml @@ -5,8 +5,9 @@ info: paths: /api/detection_engine/rules: get: - operationId: ReadRule + x-labels: [ess, serverless] x-codegen-enabled: true + operationId: ReadRule description: Read a single rule tags: - Rules API diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/update_rule_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/update_rule_route.schema.yaml index e32a3cd52e688..de82265ca3379 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/update_rule_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/crud/update_rule/update_rule_route.schema.yaml @@ -5,8 +5,9 @@ info: paths: /api/detection_engine/rules: put: - operationId: UpdateRule + x-labels: [ess, serverless] x-codegen-enabled: true + operationId: UpdateRule description: Update a single rule tags: - Rules API diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.schema.yaml index 73c60f76e19a8..0a88075abb158 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/export_rules/export_rules_route.schema.yaml @@ -6,8 +6,9 @@ paths: /api/detection_engine/rules/_export: summary: Exports rules to an `.ndjson` file post: - operationId: ExportRules + x-labels: [ess, serverless] x-codegen-enabled: true + operationId: ExportRules summary: Export rules description: Exports rules to an `.ndjson` file. The following configuration items are also included in the `.ndjson` file - Actions, Exception lists. Prebuilt rules cannot be exported. tags: 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 index 4a37d1f9f5bc9..4f27662e37bfd 100644 --- 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 @@ -5,8 +5,9 @@ info: paths: /api/detection_engine/rules/_find: get: - operationId: FindRules + x-labels: [ess, serverless] x-codegen-enabled: true + operationId: FindRules description: Finds rules that match the given query. tags: - Rules API diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/import_rules_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/import_rules_route.schema.yaml index ddc0f063747ec..9056fcea04bca 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/import_rules_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/import_rules/import_rules_route.schema.yaml @@ -6,8 +6,9 @@ paths: /api/detection_engine/rules/_import: summary: Imports rules from an `.ndjson` file post: - operationId: ImportRules + x-labels: [ess, serverless] x-codegen-enabled: true + operationId: ImportRules summary: Import rules description: Imports rules from an `.ndjson` file, including actions and exception lists. tags: diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/read_tags/read_tags_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/read_tags/read_tags_route.schema.yaml index ae4ef41a9ff32..0a9d622dd2d4a 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/read_tags/read_tags_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/read_tags/read_tags_route.schema.yaml @@ -6,8 +6,9 @@ paths: /api/detection_engine/tags: summary: Aggregates and returns rule tags get: - operationId: ReadTags + x-labels: [ess, serverless] x-codegen-enabled: true + operationId: ReadTags summary: Aggregates and returns all unique tags from all rules tags: - Tags API 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 990ea4ef64876..c1f9a7cdd5096 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 @@ -5,8 +5,10 @@ info: paths: /internal/detection_engine/rules/{ruleId}/execution/events: put: - operationId: GetRuleExecutionEvents + x-labels: [ess, serverless] + x-internal: true x-codegen-enabled: true + operationId: GetRuleExecutionEvents summary: Returns execution events of a given rule (aggregated by execution UUID) from Event Log. tags: - Rule Execution Log API 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.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.schema.yaml index 42f8d54a2e616..a9a4eb2aca7e1 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.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.schema.yaml @@ -5,8 +5,10 @@ info: paths: /internal/detection_engine/rules/{ruleId}/execution/results: put: - operationId: GetRuleExecutionResults + x-labels: [ess, serverless] + x-internal: true x-codegen-enabled: true + operationId: GetRuleExecutionResults summary: Returns execution results of a given rule (aggregated by execution UUID) from Event Log. tags: - Rule Execution Log API diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals/index.ts index 636728ee2525d..97f441030bb48 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals/index.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals/index.ts @@ -5,6 +5,5 @@ * 2.0. */ -export * from './query_signals/query_signals_route'; -export * from './set_signal_status/set_signal_status_route'; -export * from './set_signal_status/set_signal_status_type_dependents'; +export * from './query_signals/query_signals_route.gen'; +export * from './set_signal_status/set_signals_status_route.gen'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals/query_signals/query_signals_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals/query_signals/query_signals_route.gen.ts new file mode 100644 index 0000000000000..67c5a695d1949 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals/query_signals/query_signals_route.gen.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Alerts search API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +export type AlertsSortCombinations = z.infer; +export const AlertsSortCombinations = z.union([z.string(), z.object({}).catchall(z.unknown())]); + +export type AlertsSort = z.infer; +export const AlertsSort = z.union([AlertsSortCombinations, z.array(AlertsSortCombinations)]); + +/** + * Elasticsearch query and aggregation request + */ +export type SearchAlertsRequestBody = z.infer; +export const SearchAlertsRequestBody = z.object({ + query: z.object({}).catchall(z.unknown()).optional(), + aggs: z.object({}).catchall(z.unknown()).optional(), + size: z.number().int().min(0).optional(), + track_total_hits: z.boolean().optional(), + _source: z.union([z.boolean(), z.string(), z.array(z.string())]).optional(), + fields: z.array(z.string()).optional(), + runtime_mappings: z.object({}).catchall(z.unknown()).optional(), + sort: AlertsSort.optional(), +}); +export type SearchAlertsRequestBodyInput = z.input; + +/** + * Elasticsearch search response + */ +export type SearchAlertsResponse = z.infer; +export const SearchAlertsResponse = z.object({}).catchall(z.unknown()); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals/query_signals/query_signals_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/signals/query_signals/query_signals_route.schema.yaml new file mode 100644 index 0000000000000..cd70e4b0c4071 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals/query_signals/query_signals_route.schema.yaml @@ -0,0 +1,93 @@ +openapi: 3.0.0 +info: + title: Alerts search API endpoint + version: '2023-10-31' +paths: + /api/detection_engine/signals/search: + post: + x-labels: [serverless, ess] + operationId: SearchAlerts + x-codegen-enabled: true + summary: Find and/or aggregate detection alerts that match the given query + tags: + - Alerts API + requestBody: + description: Search and/or aggregation query + required: true + content: + application/json: + schema: + type: object + properties: + query: + type: object + additionalProperties: true + aggs: + type: object + additionalProperties: true + size: + type: integer + minimum: 0 + track_total_hits: + type: boolean + _source: + oneOf: + - type: boolean + - type: string + - type: array + items: + type: string + fields: + type: array + items: + type: string + runtime_mappings: + type: object + additionalProperties: true + sort: + $ref: '#/components/schemas/AlertsSort' + description: Elasticsearch query and aggregation request + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + additionalProperties: true + description: Elasticsearch search response + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../model/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../model/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + +components: + schemas: + AlertsSortCombinations: + anyOf: + - type: string + - type: object + additionalProperties: true + + AlertsSort: + oneOf: + - $ref: '#/components/schemas/AlertsSortCombinations' + - type: array + items: + $ref: '#/components/schemas/AlertsSortCombinations' diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals/query_signals/query_signals_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals/query_signals/query_signals_route.test.ts deleted file mode 100644 index 6afdc9a07e8ab..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals/query_signals/query_signals_route.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license 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 { QuerySignalsSchema } from './query_signals_route'; -import { querySignalsSchema } from './query_signals_route'; -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -describe('query, aggs, size, _source and track_total_hits on signals index', () => { - test('query, aggs, size, _source and track_total_hits simultaneously', () => { - const payload: QuerySignalsSchema = { - query: {}, - aggs: {}, - size: 1, - track_total_hits: true, - _source: ['field'], - }; - - const decoded = querySignalsSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('query, only', () => { - const payload: QuerySignalsSchema = { - query: {}, - }; - - const decoded = querySignalsSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('aggs only', () => { - const payload: QuerySignalsSchema = { - aggs: {}, - }; - - const decoded = querySignalsSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('size only', () => { - const payload: QuerySignalsSchema = { - size: 1, - }; - - const decoded = querySignalsSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('track_total_hits only', () => { - const payload: QuerySignalsSchema = { - track_total_hits: true, - }; - - const decoded = querySignalsSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('_source only (as string)', () => { - const payload: QuerySignalsSchema = { - _source: 'field', - }; - - const decoded = querySignalsSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('_source only (as string[])', () => { - const payload: QuerySignalsSchema = { - _source: ['field'], - }; - - const decoded = querySignalsSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('_source only (as boolean)', () => { - const payload: QuerySignalsSchema = { - _source: false, - }; - - const decoded = querySignalsSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('fields only', () => { - const payload: QuerySignalsSchema = { - fields: ['test*'], - }; - - const decoded = querySignalsSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('sort only', () => { - const payload: QuerySignalsSchema = { - sort: { - '@payload': 'desc', - }, - }; - - const decoded = querySignalsSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); -}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals/query_signals/query_signals_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals/query_signals/query_signals_route.ts deleted file mode 100644 index 7c0283f3f2a80..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals/query_signals/query_signals_route.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; -import { PositiveInteger } from '@kbn/securitysolution-io-ts-types'; - -export const querySignalsSchema = t.exact( - t.partial({ - query: t.object, - aggs: t.object, - size: PositiveInteger, - track_total_hits: t.boolean, - _source: t.union([t.boolean, t.string, t.array(t.string)]), - fields: t.array(t.string), - runtime_mappings: t.unknown, - sort: t.object, - }) -); - -export type QuerySignalsSchema = t.TypeOf; -export type QuerySignalsSchemaDecoded = QuerySignalsSchema; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals/set_signal_status/set_signal_status_route.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals/set_signal_status/set_signal_status_route.test.ts deleted file mode 100644 index 3f9e432fbc4f9..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals/set_signal_status/set_signal_status_route.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { SetSignalsStatusSchema } from './set_signal_status_route'; -import { setSignalsStatusSchema } from './set_signal_status_route'; -import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; - -describe('set signal status schema', () => { - test('signal_ids and status is valid', () => { - const payload: SetSignalsStatusSchema = { - signal_ids: ['somefakeid'], - status: 'open', - }; - - const decoded = setSignalsStatusSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('query and status is valid', () => { - const payload: SetSignalsStatusSchema = { - query: {}, - status: 'open', - }; - - const decoded = setSignalsStatusSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(payload); - }); - - test('signal_ids and missing status is invalid', () => { - const payload: Omit = { - signal_ids: ['somefakeid'], - }; - - const decoded = setSignalsStatusSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "status"', - ]); - expect(message.schema).toEqual({}); - }); - - test('query and missing status is invalid', () => { - const payload: Omit = { - query: {}, - }; - - const decoded = setSignalsStatusSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "status"', - ]); - expect(message.schema).toEqual({}); - }); - - test('signal_ids is present but status has wrong value', () => { - const payload: Omit & { status: 'fakeVal' } = { - query: {}, - status: 'fakeVal', - }; - - const decoded = setSignalsStatusSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "fakeVal" supplied to "status"', - ]); - expect(message.schema).toEqual({}); - }); -}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals/set_signal_status/set_signal_status_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals/set_signal_status/set_signal_status_route.ts deleted file mode 100644 index 78d62031d5eb3..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals/set_signal_status/set_signal_status_route.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. - */ - -import * as t from 'io-ts'; - -import { conflicts, signal_ids, signal_status_query, status } from '../../model'; - -export const setSignalsStatusSchema = t.intersection([ - t.type({ - status, - }), - t.partial({ - conflicts, - signal_ids, - query: signal_status_query, - }), -]); - -export type SetSignalsStatusSchema = t.TypeOf; -export type SetSignalsStatusSchemaDecoded = SetSignalsStatusSchema; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals/set_signal_status/set_signal_status_type_dependents.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals/set_signal_status/set_signal_status_type_dependents.test.ts deleted file mode 100644 index 97cf74435c294..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals/set_signal_status/set_signal_status_type_dependents.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { setSignalStatusValidateTypeDependents } from './set_signal_status_type_dependents'; -import type { SetSignalsStatusSchema } from './set_signal_status_route'; - -describe('update_rules_type_dependents', () => { - test('You can have just a "signals_id"', () => { - const schema: SetSignalsStatusSchema = { - status: 'open', - signal_ids: ['some-id'], - }; - const errors = setSignalStatusValidateTypeDependents(schema); - expect(errors).toEqual([]); - }); - - test('You can have just a "query"', () => { - const schema: SetSignalsStatusSchema = { - status: 'open', - query: {}, - }; - const errors = setSignalStatusValidateTypeDependents(schema); - expect(errors).toEqual([]); - }); - - test('You cannot have both a "signals_id" and a "query"', () => { - const schema: SetSignalsStatusSchema = { - status: 'open', - query: {}, - signal_ids: ['some-id'], - }; - const errors = setSignalStatusValidateTypeDependents(schema); - expect(errors).toEqual(['both "signal_ids" and "query" cannot exist, choose one or the other']); - }); - - test('You must set either an "signals_id" and a "query"', () => { - const schema: SetSignalsStatusSchema = { - status: 'open', - }; - const errors = setSignalStatusValidateTypeDependents(schema); - expect(errors).toEqual(['either "signal_ids" or "query" must be set']); - }); -}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals/set_signal_status/set_signal_status_type_dependents.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals/set_signal_status/set_signal_status_type_dependents.ts deleted file mode 100644 index a484fde33d107..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals/set_signal_status/set_signal_status_type_dependents.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { SetSignalsStatusSchema } from './set_signal_status_route'; - -export const validateId = (signalStatus: SetSignalsStatusSchema): string[] => { - if (signalStatus.signal_ids != null && signalStatus.query != null) { - return ['both "signal_ids" and "query" cannot exist, choose one or the other']; - } else if (signalStatus.signal_ids == null && signalStatus.query == null) { - return ['either "signal_ids" or "query" must be set']; - } else { - return []; - } -}; - -export const setSignalStatusValidateTypeDependents = (schema: SetSignalsStatusSchema): string[] => { - return [...validateId(schema)]; -}; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals/set_signal_status/set_signals_status_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals/set_signal_status/set_signals_status_route.gen.ts new file mode 100644 index 0000000000000..a64a35d16d83e --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals/set_signal_status/set_signals_status_route.gen.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Set alerts status API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { NonEmptyString } from '../../../model/primitives.gen'; +import { AlertStatus } from '../../../model/alert.gen'; + +export type SetAlertsStatusByIds = z.infer; +export const SetAlertsStatusByIds = z.object({ + signal_ids: z.array(NonEmptyString).min(1), + status: AlertStatus, +}); + +export type SetAlertsStatusByQuery = z.infer; +export const SetAlertsStatusByQuery = z.object({ + query: z.object({}).catchall(z.unknown()), + status: AlertStatus, + conflicts: z.enum(['abort', 'proceed']).optional().default('abort'), +}); + +export type SetAlertsStatusRequestBody = z.infer; +export const SetAlertsStatusRequestBody = z.union([SetAlertsStatusByIds, SetAlertsStatusByQuery]); +export type SetAlertsStatusRequestBodyInput = z.input; + +/** + * Elasticsearch update by query response + */ +export type SetAlertsStatusResponse = z.infer; +export const SetAlertsStatusResponse = z.object({}).catchall(z.unknown()); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals/set_signal_status/set_signals_status_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/signals/set_signal_status/set_signals_status_route.schema.yaml new file mode 100644 index 0000000000000..29ee065c77e6b --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals/set_signal_status/set_signals_status_route.schema.yaml @@ -0,0 +1,81 @@ +openapi: 3.0.0 +info: + title: Set alerts status API endpoint + version: '2023-10-31' +paths: + /api/detection_engine/signals/status: + post: + x-labels: [serverless, ess] + operationId: SetAlertsStatus + x-codegen-enabled: true + summary: Sets the status of one or more alerts + tags: + - Alerts API + requestBody: + description: An object containing desired status and explicit alert ids or a query to select alerts + required: true + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/SetAlertsStatusByIds' + - $ref: '#/components/schemas/SetAlertsStatusByQuery' + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + additionalProperties: true + description: Elasticsearch update by query response + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../model/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../model/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + +components: + schemas: + SetAlertsStatusByIds: + type: object + properties: + signal_ids: + type: array + items: + $ref: '../../../model/primitives.schema.yaml#/components/schemas/NonEmptyString' + minItems: 1 + status: + $ref: '../../../model/alert.schema.yaml#/components/schemas/AlertStatus' + required: [signal_ids, status] + + SetAlertsStatusByQuery: + type: object + properties: + query: + type: object + additionalProperties: true + status: + $ref: '../../../model/alert.schema.yaml#/components/schemas/AlertStatus' + conflicts: + type: string + enum: + - abort + - proceed + default: abort + required: [query, status] diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration.gen.ts new file mode 100644 index 0000000000000..f82dedc27a289 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration.gen.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Initiates alerts migration API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +import { NonEmptyString } from '../../../model/primitives.gen'; + +export type AlertsReindexOptions = z.infer; +export const AlertsReindexOptions = z.object({ + requests_per_second: z.number().int().min(1).optional(), + size: z.number().int().min(1).optional(), + slices: z.number().int().min(1).optional(), +}); + +export type AlertsIndexMigrationSuccess = z.infer; +export const AlertsIndexMigrationSuccess = z.object({ + index: z.string(), + migration_id: z.string(), + migration_index: z.string(), +}); + +export type AlertsIndexMigrationError = z.infer; +export const AlertsIndexMigrationError = z.object({ + index: z.string(), + error: z.object({ + message: z.string(), + status_code: z.string(), + }), +}); + +export type SkippedAlertsIndexMigration = z.infer; +export const SkippedAlertsIndexMigration = z.object({ + index: z.string(), +}); + +export type CreateAlertsMigrationRequestBody = z.infer; +export const CreateAlertsMigrationRequestBody = z + .object({ + index: z.array(NonEmptyString).min(1), + }) + .merge(AlertsReindexOptions); +export type CreateAlertsMigrationRequestBodyInput = z.input< + typeof CreateAlertsMigrationRequestBody +>; + +export type CreateAlertsMigrationResponse = z.infer; +export const CreateAlertsMigrationResponse = z.object({ + indices: z.array( + z.union([AlertsIndexMigrationSuccess, AlertsIndexMigrationError, SkippedAlertsIndexMigration]) + ), +}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration.schema.yaml new file mode 100644 index 0000000000000..26204ea0d6195 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration.schema.yaml @@ -0,0 +1,120 @@ +openapi: 3.0.0 +info: + title: Initiates alerts migration API endpoint + version: '2023-10-31' +paths: + /api/detection_engine/signals/migration: + post: + x-labels: [ess] + operationId: CreateAlertsMigration + x-codegen-enabled: true + summary: Initiates an alerts migration + tags: + - Alerts migration API + requestBody: + description: Alerts migration parameters + required: true + content: + application/json: + schema: + allOf: + - type: object + properties: + index: + type: array + items: + $ref: '../../../model/primitives.schema.yaml#/components/schemas/NonEmptyString' + minItems: 1 + required: [index] + - $ref: '#/components/schemas/AlertsReindexOptions' + + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + properties: + indices: + type: array + items: + oneOf: + - $ref: '#/components/schemas/AlertsIndexMigrationSuccess' + - $ref: '#/components/schemas/AlertsIndexMigrationError' + - $ref: '#/components/schemas/SkippedAlertsIndexMigration' + required: [indices] + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../model/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../model/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + +components: + schemas: + AlertsReindexOptions: + type: object + properties: + requests_per_second: + type: integer + minimum: 1 + size: + type: integer + minimum: 1 + slices: + type: integer + minimum: 1 + + AlertsIndexMigrationSuccess: + type: object + properties: + index: + type: string + migration_id: + type: string + migration_index: + type: string + required: + - index + - migration_id + - migration_index + + AlertsIndexMigrationError: + type: object + properties: + index: + type: string + error: + type: object + properties: + message: + type: string + status_code: + type: string + required: [message, status_code] + required: + - index + - error + + SkippedAlertsIndexMigration: + type: object + properties: + index: + type: string + required: + - index diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration_route.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration_route.mock.ts index ccb5543ef72d2..09a94fc86224c 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration_route.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration_route.mock.ts @@ -5,10 +5,10 @@ * 2.0. */ -import type { CreateSignalsMigrationSchema } from './create_signals_migration_route'; +import type { CreateAlertsMigrationRequestBody } from './create_signals_migration.gen'; export const getCreateSignalsMigrationSchemaMock = ( index: string = 'signals-index' -): CreateSignalsMigrationSchema => ({ +): CreateAlertsMigrationRequestBody => ({ index: [index], }); 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 deleted file mode 100644 index 9089efeb87d05..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration_route.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { PositiveInteger, PositiveIntegerGreaterThanZero } from '@kbn/securitysolution-io-ts-types'; - -export const signalsReindexOptions = t.partial({ - requests_per_second: t.number, - size: PositiveIntegerGreaterThanZero, - slices: PositiveInteger, -}); - -export type SignalsReindexOptions = t.TypeOf; - -export const createSignalsMigrationSchema = t.intersection([ - t.exact( - t.type({ - index: t.array(t.string), - }) - ), - t.exact(signalsReindexOptions), -]); - -export type CreateSignalsMigrationSchema = t.TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/delete_signals_migration/delete_signals_migration.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/delete_signals_migration/delete_signals_migration.gen.ts new file mode 100644 index 0000000000000..c82407052c972 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/delete_signals_migration/delete_signals_migration.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. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Alerts migration cleanup API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +export type MigrationCleanupResult = z.infer; +export const MigrationCleanupResult = z.object({ + id: z.string(), + destinationIndex: z.string(), + status: z.enum(['success', 'failure', 'pending']), + sourceIndex: z.string(), + version: z.string(), + updated: z.string().datetime(), + error: z + .object({ + message: z.string(), + status_code: z.number().int(), + }) + .optional(), +}); + +export type AlertsMigrationCleanupRequestBody = z.infer; +export const AlertsMigrationCleanupRequestBody = z.object({ + migration_ids: z.array(z.string()).min(1), +}); +export type AlertsMigrationCleanupRequestBodyInput = z.input< + typeof AlertsMigrationCleanupRequestBody +>; + +export type AlertsMigrationCleanupResponse = z.infer; +export const AlertsMigrationCleanupResponse = z.array(MigrationCleanupResult); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/delete_signals_migration/delete_signals_migration.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/delete_signals_migration/delete_signals_migration.schema.yaml new file mode 100644 index 0000000000000..7b8136f3702cf --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/delete_signals_migration/delete_signals_migration.schema.yaml @@ -0,0 +1,100 @@ +openapi: 3.0.0 +info: + title: Alerts migration cleanup API endpoint + version: '2023-10-31' +paths: + /api/detection_engine/signals/migration: + delete: + x-labels: [ess] + operationId: AlertsMigrationCleanup + x-codegen-enabled: true + summary: Performs alerts migration(s) cleanup + description: | + Migrations favor data integrity over shard size. Consequently, unused or orphaned indices are artifacts of + the migration process. A successful migration will result in both the old and new indices being present. + As such, the old, orphaned index can (and likely should) be deleted. While you can delete these indices manually, + the endpoint accomplishes this task by applying a deletion policy to the relevant index, causing it to be deleted + after 30 days. It also deletes other artifacts specific to the migration implementation. + tags: + - Alerts migration API + requestBody: + description: Array of `migration_id`s to cleanup + required: true + content: + application/json: + schema: + type: object + properties: + migration_ids: + type: array + items: + type: string + minItems: 1 + required: [migration_ids] + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/MigrationCleanupResult' + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../model/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../model/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + +components: + schemas: + MigrationCleanupResult: + type: object + properties: + id: + type: string + destinationIndex: + type: string + status: + type: string + enum: + - success + - failure + - pending + sourceIndex: + type: string + version: + type: string + updated: + type: string + format: date-time + error: + type: object + properties: + message: + type: string + status_code: + type: integer + required: [message, status_code] + required: + - id + - destinationIndex + - status + - sourceIndex + - version + - updated diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/finalize_signals_migration/finalize_signals_migration.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/finalize_signals_migration/finalize_signals_migration.gen.ts new file mode 100644 index 0000000000000..cd18cad8e1cfa --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/finalize_signals_migration/finalize_signals_migration.gen.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Finalize alerts migration API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; + +export type MigrationFinalizationResult = z.infer; +export const MigrationFinalizationResult = z.object({ + id: z.string(), + completed: z.boolean(), + destinationIndex: z.string(), + status: z.enum(['success', 'failure', 'pending']), + sourceIndex: z.string(), + version: z.string(), + updated: z.string().datetime(), + error: z + .object({ + message: z.string(), + status_code: z.number().int(), + }) + .optional(), +}); + +export type FinalizeAlertsMigrationRequestBody = z.infer; +export const FinalizeAlertsMigrationRequestBody = z.object({ + migration_ids: z.array(z.string()).min(1), +}); +export type FinalizeAlertsMigrationRequestBodyInput = z.input< + typeof FinalizeAlertsMigrationRequestBody +>; + +export type FinalizeAlertsMigrationResponse = z.infer; +export const FinalizeAlertsMigrationResponse = z.array(MigrationFinalizationResult); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/finalize_signals_migration/finalize_signals_migration.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/finalize_signals_migration/finalize_signals_migration.schema.yaml new file mode 100644 index 0000000000000..3654973f9de7e --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/finalize_signals_migration/finalize_signals_migration.schema.yaml @@ -0,0 +1,101 @@ +openapi: 3.0.0 +info: + title: Finalize alerts migration API endpoint + version: '2023-10-31' +paths: + /api/detection_engine/signals/finalize_migration: + post: + x-labels: [ess] + operationId: FinalizeAlertsMigration + x-codegen-enabled: true + summary: Finalizes alerts migration(s) + description: | + The finalization endpoint replaces the original index's alias with the successfully migrated index's alias. + The endpoint is idempotent; therefore, it can safely be used to poll a given migration and, upon completion, + finalize it. + tags: + - Alerts migration API + requestBody: + description: Array of `migration_id`s to finalize + required: true + content: + application/json: + schema: + type: object + properties: + migration_ids: + type: array + items: + type: string + minItems: 1 + required: [migration_ids] + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/MigrationFinalizationResult' + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../model/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../model/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + +components: + schemas: + MigrationFinalizationResult: + type: object + properties: + id: + type: string + completed: + type: boolean + destinationIndex: + type: string + status: + type: string + enum: + - success + - failure + - pending + sourceIndex: + type: string + version: + type: string + updated: + type: string + format: date-time + error: + type: object + properties: + message: + type: string + status_code: + type: integer + required: [message, status_code] + required: + - id + - completed + - destinationIndex + - status + - sourceIndex + - version + - updated diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/finalize_signals_migration/finalize_signals_migration_route.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/finalize_signals_migration/finalize_signals_migration_route.mock.ts index 25d66c5b68d09..b43a04819e67f 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/finalize_signals_migration/finalize_signals_migration_route.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/finalize_signals_migration/finalize_signals_migration_route.mock.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { FinalizeSignalsMigrationSchema } from './finalize_signals_migration_route'; +import type { FinalizeAlertsMigrationRequestBody } from './finalize_signals_migration.gen'; -export const getFinalizeSignalsMigrationSchemaMock = (): FinalizeSignalsMigrationSchema => ({ +export const getFinalizeSignalsMigrationSchemaMock = (): FinalizeAlertsMigrationRequestBody => ({ migration_ids: ['migrationSOIdentifier'], }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status.gen.ts new file mode 100644 index 0000000000000..acf9c9edd389d --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status.gen.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Get alerts migration status API endpoint + * version: 2023-10-31 + */ + +import { z } from 'zod'; +import { isValidDateMath } from '@kbn/zod-helpers'; + +import { NonEmptyString } from '../../../model/primitives.gen'; + +export type AlertVersion = z.infer; +export const AlertVersion = z.object({ + version: z.number().int(), + count: z.number().int(), +}); + +export type MigrationStatus = z.infer; +export const MigrationStatus = z.object({ + id: NonEmptyString, + status: z.enum(['success', 'failure', 'pending']), + version: z.number().int(), + updated: z.string().datetime(), +}); + +export type IndexMigrationStatus = z.infer; +export const IndexMigrationStatus = z.object({ + index: NonEmptyString, + version: z.number().int(), + signal_versions: z.array(AlertVersion), + migrations: z.array(MigrationStatus), + is_outdated: z.boolean(), +}); + +export type GetAlertsMigrationStatusRequestQuery = z.infer< + typeof GetAlertsMigrationStatusRequestQuery +>; +export const GetAlertsMigrationStatusRequestQuery = z.object({ + /** + * Maximum age of qualifying detection alerts + */ + from: z.string().superRefine(isValidDateMath), +}); +export type GetAlertsMigrationStatusRequestQueryInput = z.input< + typeof GetAlertsMigrationStatusRequestQuery +>; + +export type GetAlertsMigrationStatusResponse = z.infer; +export const GetAlertsMigrationStatusResponse = z.object({ + indices: z.array(IndexMigrationStatus), +}); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status.schema.yaml new file mode 100644 index 0000000000000..b480b4374498b --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status.schema.yaml @@ -0,0 +1,114 @@ +openapi: 3.0.0 +info: + title: Get alerts migration status API endpoint + version: '2023-10-31' +paths: + /api/detection_engine/signals/migration_status: + post: + x-labels: [ess] + operationId: GetAlertsMigrationStatus + x-codegen-enabled: true + summary: Returns an alerts migration status + tags: + - Alerts migration API + parameters: + - name: from + in: query + description: Maximum age of qualifying detection alerts + required: true + schema: + type: string + description: | + Time from which data is analyzed. For example, now-4200s means the rule analyzes data from 70 minutes + before its start time. Defaults to now-6m (analyzes data from 6 minutes before the start time). + format: date-math + responses: + 200: + description: Successful response + content: + application/json: + schema: + type: object + properties: + indices: + type: array + items: + $ref: '#/components/schemas/IndexMigrationStatus' + required: [indices] + 400: + description: Invalid input data response + content: + application/json: + schema: + oneOf: + - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + - $ref: '../../../model/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + 401: + description: Unsuccessful authentication response + content: + application/json: + schema: + $ref: '../../../model/error_responses.schema.yaml#/components/schemas/PlatformErrorResponse' + 500: + description: Internal server error response + content: + application/json: + schema: + $ref: '../../../model/error_responses.schema.yaml#/components/schemas/SiemErrorResponse' + +components: + schemas: + AlertVersion: + type: object + properties: + version: + type: integer + count: + type: integer + required: [version, count] + + MigrationStatus: + type: object + properties: + id: + $ref: '../../../model/primitives.schema.yaml#/components/schemas/NonEmptyString' + status: + type: string + enum: + - success + - failure + - pending + version: + type: integer + updated: + type: string + format: date-time + required: + - id + - status + - version + - updated + + IndexMigrationStatus: + type: object + properties: + index: + $ref: '../../../model/primitives.schema.yaml#/components/schemas/NonEmptyString' + version: + type: integer + signal_versions: + type: array + items: + $ref: '#/components/schemas/AlertVersion' + migrations: + type: array + items: + $ref: '#/components/schemas/MigrationStatus' + is_outdated: + type: boolean + required: + - index + - version + - signal_versions + - migrations + - is_outdated diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status_route.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status_route.mock.ts index 8cc78ffa9700e..968d72d6f40d0 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status_route.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status_route.mock.ts @@ -5,8 +5,8 @@ * 2.0. */ -import type { GetSignalsMigrationStatusSchema } from './get_signals_migration_status_route'; +import type { GetAlertsMigrationStatusRequestQuery } from './get_signals_migration_status.gen'; -export const getSignalsMigrationStatusSchemaMock = (): GetSignalsMigrationStatusSchema => ({ +export const getSignalsMigrationStatusSchemaMock = (): GetAlertsMigrationStatusRequestQuery => ({ from: 'now-30d', }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status_route.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status_route.ts deleted file mode 100644 index f2a9fc210df2b..0000000000000 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status_route.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { RuleIntervalFrom } from '@kbn/securitysolution-io-ts-alerting-types'; - -export const getSignalsMigrationStatusSchema = t.exact( - t.type({ - from: RuleIntervalFrom, - }) -); - -export type GetSignalsMigrationStatusSchema = t.TypeOf; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/index.ts b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/index.ts index 78452dc62f46c..84b00fc9f297c 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/index.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/signals_migration/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -export * from './create_signals_migration/create_signals_migration_route'; -export * from './delete_signals_migration/delete_signals_migration_route'; -export * from './finalize_signals_migration/finalize_signals_migration_route'; -export * from './get_signals_migration_status/get_signals_migration_status_route'; +export * from './create_signals_migration/create_signals_migration.gen'; +export * from './delete_signals_migration/delete_signals_migration.gen'; +export * from './finalize_signals_migration/finalize_signals_migration.gen'; +export * from './get_signals_migration_status/get_signals_migration_status.gen'; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/users/suggest_user_profiles_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/users/suggest_user_profiles_route.schema.yaml index a4778969d0312..e4254bee52a37 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/users/suggest_user_profiles_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/users/suggest_user_profiles_route.schema.yaml @@ -6,8 +6,10 @@ paths: /internal/detection_engine/users/_find: summary: Suggests user profiles based on provided search term post: - operationId: SuggestUserProfiles + x-labels: [ess, serverless] + x-internal: true x-codegen-enabled: true + operationId: SuggestUserProfiles description: Suggests user profiles. parameters: - name: searchTerm diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/bulk_upload_asset_criticality.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/bulk_upload_asset_criticality.gen.ts new file mode 100644 index 0000000000000..c0d00e394b6b1 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/bulk_upload_asset_criticality.gen.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Asset Criticality Bulk Upsert Schema + * version: 1 + */ + +import { z } from 'zod'; + +import { CreateAssetCriticalityRecord } from './common.gen'; + +export type AssetCriticalityBulkUploadRequest = z.infer; +export const AssetCriticalityBulkUploadRequest = z.object({ + records: z.array(CreateAssetCriticalityRecord).min(1).max(1000), +}); diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/bulk_upload_asset_criticality.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/bulk_upload_asset_criticality.schema.yaml new file mode 100644 index 0000000000000..b4b7d5d2f1fe4 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/bulk_upload_asset_criticality.schema.yaml @@ -0,0 +1,52 @@ +openapi: 3.0.0 +info: + version: '1' + title: Asset Criticality Bulk Upsert Schema +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/asset_criticality/bulk: + post: + x-labels: [ess, serverless] + summary: Bulk upsert asset criticality data, creating or updating records as needed + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AssetCriticalityBulkUploadRequest' + + responses: + '200': + description: Bulk upload successful + content: + application/json: + schema: + $ref: './common.schema.yaml#/components/schemas/AssetCriticalityBulkUploadResponse' + '413': + description: File too large +components: + schemas: + AssetCriticalityBulkUploadRequest: + type: object + example: + records: + - id_value: 'host-1' + id_field: 'host.name' + criticality_level: 'low_impact' + - id_value: 'host-2' + id_field: 'host.name' + criticality_level: 'medium_impact' + properties: + records: + type: array + minItems: 1 + maxItems: 1000 + items: + $ref: './common.schema.yaml#/components/schemas/CreateAssetCriticalityRecord' + required: + - records diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.gen.ts index feff1e5126b78..4b689d22944e1 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.gen.ts +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.gen.ts @@ -50,6 +50,14 @@ export type CreateAssetCriticalityRecord = z.infer; +export const CreateSingleAssetCriticalityRequest = CreateAssetCriticalityRecord.merge( + z.object({ /** * If 'wait_for' the request will wait for the index refresh. */ @@ -76,3 +84,24 @@ export const AssetCriticalityRecord = CreateAssetCriticalityRecord.merge( '@timestamp': z.string().datetime(), }) ); + +export type AssetCriticalityBulkUploadErrorItem = z.infer< + typeof AssetCriticalityBulkUploadErrorItem +>; +export const AssetCriticalityBulkUploadErrorItem = z.object({ + message: z.string(), + index: z.number().int(), +}); + +export type AssetCriticalityBulkUploadStats = z.infer; +export const AssetCriticalityBulkUploadStats = z.object({ + successful: z.number().int(), + failed: z.number().int(), + total: z.number().int(), +}); + +export type AssetCriticalityBulkUploadResponse = z.infer; +export const AssetCriticalityBulkUploadResponse = z.object({ + errors: z.array(AssetCriticalityBulkUploadErrorItem), + stats: AssetCriticalityBulkUploadStats, +}); diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.schema.yaml index 294cba2dd89ea..3218ec07e0fe2 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.schema.yaml @@ -49,7 +49,6 @@ components: - high_impact - extreme_impact description: The criticality level of the asset. - CreateAssetCriticalityRecord: allOf: - $ref: '#/components/schemas/AssetCriticalityRecordIdParts' @@ -57,12 +56,17 @@ components: properties: criticality_level: $ref: '#/components/schemas/AssetCriticalityLevel' + required: + - criticality_level + CreateSingleAssetCriticalityRequest: + allOf: + - $ref: '#/components/schemas/CreateAssetCriticalityRecord' + - type: object + properties: refresh: type: string enum: [wait_for] description: If 'wait_for' the request will wait for the index refresh. - required: - - criticality_level DeleteAssetCriticalityRecord: allOf: - $ref: '#/components/schemas/AssetCriticalityRecordIdParts' @@ -84,3 +88,46 @@ components: description: The time the record was created or updated. required: - '@timestamp' + AssetCriticalityBulkUploadErrorItem: + type: object + properties: + message: + type: string + index: + type: integer + required: + - message + - index + AssetCriticalityBulkUploadStats: + type: object + properties: + successful: + type: integer + failed: + type: integer + total: + type: integer + required: + - successful + - failed + - total + AssetCriticalityBulkUploadResponse: + type: object + example: + errors: + - message: 'Invalid ID field' + index: 0 + stats: + successful: 1 + failed: 1 + total: 2 + properties: + errors: + type: array + items: + $ref: '#/components/schemas/AssetCriticalityBulkUploadErrorItem' + stats: + $ref: '#/components/schemas/AssetCriticalityBulkUploadStats' + required: + - errors + - stats diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/create_asset_criticality.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/create_asset_criticality.schema.yaml index 64f321c5a56cb..d59ce99c8717c 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/create_asset_criticality.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/create_asset_criticality.schema.yaml @@ -15,13 +15,33 @@ paths: x-labels: [ess, serverless] x-internal: true operationId: AssetCriticalityCreateRecord + summary: Deprecated Internal Create Criticality Record + requestBody: + required: true + content: + application/json: + schema: + $ref: './common.schema.yaml#/components/schemas/CreateSingleAssetCriticalityRequest' + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: './common.schema.yaml#/components/schemas/AssetCriticalityRecord' + '400': + description: Invalid request + /api/asset_criticality: + post: + x-labels: [ess, serverless] + operationId: AssetCriticalityCreateRecord summary: Create Criticality Record requestBody: required: true content: application/json: schema: - $ref: './common.schema.yaml#/components/schemas/CreateAssetCriticalityRecord' + $ref: './common.schema.yaml#/components/schemas/CreateSingleAssetCriticalityRequest' responses: '200': description: Successful response diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.schema.yaml index be882228c0bb0..94e1cc82e15ad 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.schema.yaml @@ -15,6 +15,19 @@ paths: x-labels: [ess, serverless] x-internal: true operationId: AssetCriticalityDeleteRecord + summary: Deprecated Internal Delete Criticality Record + parameters: + - $ref: './common.schema.yaml#/components/parameters/id_value' + - $ref: './common.schema.yaml#/components/parameters/id_field' + responses: + '200': + description: Successful response + '400': + description: Invalid request + /api/asset_criticality: + delete: + x-labels: [ess, serverless] + operationId: AssetCriticalityDeleteRecord summary: Delete Criticality Record parameters: - $ref: './common.schema.yaml#/components/parameters/id_value' diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality.schema.yaml index 664d8dbace2bf..56f3e37de1126 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/get_asset_criticality.schema.yaml @@ -15,6 +15,25 @@ paths: x-labels: [ess, serverless] x-internal: true operationId: AssetCriticalityGetRecord + summary: Deprecated Internal Get Criticality Record + parameters: + - $ref: './common.schema.yaml#/components/parameters/id_value' + - $ref: './common.schema.yaml#/components/parameters/id_field' + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: './common.schema.yaml#/components/schemas/AssetCriticalityRecord' + '400': + description: Invalid request + '404': + description: Criticality record not found + /api/asset_criticality: + get: + x-labels: [ess, serverless] + operationId: AssetCriticalityGetRecord summary: Get Criticality Record parameters: - $ref: './common.schema.yaml#/components/parameters/id_value' diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/index.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/index.ts index cc79a94a41fcb..5394f0c9cf69f 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/index.ts +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/index.ts @@ -7,5 +7,5 @@ export * from './common.gen'; export * from './get_asset_criticality_status.gen'; -export * from './upload_asset_criticality_csv.gen'; export * from './get_asset_criticality_privileges.gen'; +export * from './bulk_upload_asset_criticality.gen'; diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/upload_asset_criticality_csv.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/upload_asset_criticality_csv.gen.ts deleted file mode 100644 index dcad4f6596bac..0000000000000 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/upload_asset_criticality_csv.gen.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* - * NOTICE: Do not edit this file manually. - * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. - * - * info: - * title: Asset Criticality Create Record Schema - * version: 1 - */ - -import { z } from 'zod'; - -export type ErrorItem = z.infer; -export const ErrorItem = z.object({ - message: z.string(), - index: z.number().int(), -}); - -export type Stats = z.infer; -export const Stats = z.object({ - successful: z.number().int(), - failed: z.number().int(), - total: z.number().int(), -}); - -export type AssetCriticalityCsvUploadResponse = z.infer; -export const AssetCriticalityCsvUploadResponse = z.object({ - errors: z.array(ErrorItem), - stats: Stats, -}); diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/upload_asset_criticality_csv.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/upload_asset_criticality_csv.schema.yaml index 440955f8954c2..c348dcefa8b78 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/upload_asset_criticality_csv.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/upload_asset_criticality_csv.schema.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: version: '1' - title: Asset Criticality Create Record Schema + title: Asset Criticality CSV Upload Schema servers: - url: 'http://{kibana_host}:{port}' variables: @@ -11,6 +11,32 @@ servers: default: '5601' paths: /internal/asset_criticality/upload_csv: + post: + x-labels: [ess, serverless] + x-internal: true + summary: Deprecated internal API which Uploads a CSV file containing asset criticality data + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + description: The CSV file to upload. + required: + - file + responses: + '200': + description: Bulk upload successful + content: + application/json: + schema: + $ref: '#./common/components/schemas/AssetCriticalityBulkUploadResponse' + '413': + description: File too large + /api/asset_criticality/upload_csv: post: x-labels: [ess, serverless] x-internal: true @@ -29,47 +55,10 @@ paths: - file responses: '200': - description: CSV upload successful + description: Bulk upload successful content: application/json: schema: - $ref: '#/components/schemas/AssetCriticalityCsvUploadResponse' + $ref: '#./common/components/schemas/AssetCriticalityBulkUploadResponse' '413': description: File too large -components: - schemas: - ErrorItem: - type: object - properties: - message: - type: string - index: - type: integer - required: - - message - - index - Stats: - type: object - properties: - successful: - type: integer - failed: - type: integer - total: - type: integer - required: - - successful - - failed - - total - AssetCriticalityCsvUploadResponse: - type: object - properties: - errors: - type: array - items: - $ref: '#/components/schemas/ErrorItem' - stats: - $ref: '#/components/schemas/Stats' - required: - - errors - - stats diff --git a/x-pack/plugins/security_solution/common/api/model/alert.gen.ts b/x-pack/plugins/security_solution/common/api/model/alert.gen.ts index 19ebcf2dee734..d149d36ab64b8 100644 --- a/x-pack/plugins/security_solution/common/api/model/alert.gen.ts +++ b/x-pack/plugins/security_solution/common/api/model/alert.gen.ts @@ -23,3 +23,14 @@ import { NonEmptyString } from './primitives.gen'; */ export type AlertIds = z.infer; export const AlertIds = z.array(NonEmptyString).min(1); + +export type AlertTag = z.infer; +export const AlertTag = NonEmptyString; + +export type AlertTags = z.infer; +export const AlertTags = z.array(AlertTag); + +export type AlertStatus = z.infer; +export const AlertStatus = z.enum(['open', 'closed', 'acknowledged', 'in-progress']); +export type AlertStatusEnum = typeof AlertStatus.enum; +export const AlertStatusEnum = AlertStatus.enum; diff --git a/x-pack/plugins/security_solution/common/api/model/alert.schema.yaml b/x-pack/plugins/security_solution/common/api/model/alert.schema.yaml index f28508dc620f2..ecf7e02d6ebe3 100644 --- a/x-pack/plugins/security_solution/common/api/model/alert.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/model/alert.schema.yaml @@ -12,3 +12,19 @@ components: $ref: './primitives.schema.yaml#/components/schemas/NonEmptyString' minItems: 1 description: A list of alerts ids. + + AlertTag: + $ref: './primitives.schema.yaml#/components/schemas/NonEmptyString' + + AlertTags: + type: array + items: + $ref: '#/components/schemas/AlertTag' + + AlertStatus: + type: string + enum: + - open + - closed + - acknowledged + - in-progress diff --git a/x-pack/plugins/security_solution/common/api/model/error_responses.gen.ts b/x-pack/plugins/security_solution/common/api/model/error_responses.gen.ts new file mode 100644 index 0000000000000..17d2c59bd338f --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/model/error_responses.gen.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* + * NOTICE: Do not edit this file manually. + * This file is automatically generated by the OpenAPI Generator, @kbn/openapi-generator. + * + * info: + * title: Error Schema + * version: not applicable + */ + +import { z } from 'zod'; + +export type PlatformErrorResponse = z.infer; +export const PlatformErrorResponse = z.object({ + statusCode: z.number().int(), + error: z.string(), + message: z.string(), +}); + +export type SiemErrorResponse = z.infer; +export const SiemErrorResponse = z.object({ + status_code: z.number().int(), + message: z.string(), +}); diff --git a/x-pack/plugins/security_solution/common/api/model/error_responses.schema.yaml b/x-pack/plugins/security_solution/common/api/model/error_responses.schema.yaml new file mode 100644 index 0000000000000..8f0891f3f574f --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/model/error_responses.schema.yaml @@ -0,0 +1,32 @@ +openapi: 3.0.0 +info: + title: Error Schema + version: 'not applicable' +paths: {} +components: + x-codegen-enabled: true + schemas: + PlatformErrorResponse: + type: object + properties: + statusCode: + type: integer + error: + type: string + message: + type: string + required: + - statusCode + - error + - message + + SiemErrorResponse: + type: object + properties: + status_code: + type: integer + message: + type: string + required: + - status_code + - message diff --git a/x-pack/plugins/security_solution/common/api/search_strategy/asset_criticality/all.ts b/x-pack/plugins/security_solution/common/api/search_strategy/asset_criticality/all.ts new file mode 100644 index 0000000000000..cbbc885cc6091 --- /dev/null +++ b/x-pack/plugins/security_solution/common/api/search_strategy/asset_criticality/all.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { z } from 'zod'; +import { requestBasicOptionsSchema } from '../model/request_basic_options'; + +export const assetCriticalityRequestOptionsSchema = requestBasicOptionsSchema.extend({ + pagination: z + .object({ + cursorStart: z.number(), + querySize: z.number(), + }) + .optional(), +}); + +export type AssetCriticalityRequestOptions = z.infer; diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 1822e6be5cb81..70fb550e798e1 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -121,20 +121,7 @@ export const ENTITY_ANALYTICS_PATH = '/entity_analytics' as const; export const ENTITY_ANALYTICS_MANAGEMENT_PATH = `/entity_analytics_management` as const; export const ENTITY_ANALYTICS_ASSET_CRITICALITY_PATH = `/entity_analytics_asset_criticality` as const; -export const APP_OVERVIEW_PATH = `${APP_PATH}${OVERVIEW_PATH}` as const; -export const APP_LANDING_PATH = `${APP_PATH}${LANDING_PATH}` as const; -export const APP_DETECTION_RESPONSE_PATH = `${APP_PATH}${DETECTION_RESPONSE_PATH}` as const; -export const APP_MANAGEMENT_PATH = `${APP_PATH}${MANAGEMENT_PATH}` as const; - export const APP_ALERTS_PATH = `${APP_PATH}${ALERTS_PATH}` as const; -export const APP_RULES_PATH = `${APP_PATH}${RULES_PATH}` as const; -export const APP_EXCEPTIONS_PATH = `${APP_PATH}${EXCEPTIONS_PATH}` as const; - -export const APP_HOSTS_PATH = `${APP_PATH}${HOSTS_PATH}` as const; -export const APP_USERS_PATH = `${APP_PATH}${USERS_PATH}` as const; -export const APP_NETWORK_PATH = `${APP_PATH}${NETWORK_PATH}` as const; -export const APP_KUBERNETES_PATH = `${APP_PATH}${KUBERNETES_PATH}` as const; -export const APP_TIMELINES_PATH = `${APP_PATH}${TIMELINES_PATH}` as const; export const APP_CASES_PATH = `${APP_PATH}${CASES_PATH}` as const; export const APP_ENDPOINTS_PATH = `${APP_PATH}${ENDPOINTS_PATH}` as const; export const APP_POLICIES_PATH = `${APP_PATH}${POLICIES_PATH}` as const; @@ -145,8 +132,7 @@ export const APP_HOST_ISOLATION_EXCEPTIONS_PATH = export const APP_BLOCKLIST_PATH = `${APP_PATH}${BLOCKLIST_PATH}` as const; export const APP_RESPONSE_ACTIONS_HISTORY_PATH = `${APP_PATH}${RESPONSE_ACTIONS_HISTORY_PATH}` as const; -export const APP_ENTITY_ANALYTICS_PATH = `${APP_PATH}${ENTITY_ANALYTICS_PATH}` as const; -export const APP_DATA_QUALITY_PATH = `${APP_PATH}${DATA_QUALITY_PATH}` as const; +export const NOTES_MANAGEMENT_PATH = `/notes_management` as const; // cloud logs to exclude from default index pattern export const EXCLUDE_ELASTIC_CLOUD_INDICES = ['-*elastic-cloud-logs-*']; diff --git a/x-pack/plugins/security_solution/common/endpoint/service/artifacts/constants.ts b/x-pack/plugins/security_solution/common/endpoint/service/artifacts/constants.ts index fd244d3d90c8e..362156ce3b5f7 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/artifacts/constants.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/artifacts/constants.ts @@ -11,6 +11,8 @@ export const BY_POLICY_ARTIFACT_TAG_PREFIX = 'policy:'; export const GLOBAL_ARTIFACT_TAG = `${BY_POLICY_ARTIFACT_TAG_PREFIX}all`; +export const FILTER_PROCESS_DESCENDANTS_TAG = 'filter_process_descendants'; + // TODO: refact all uses of `ALL_ENDPOINT_ARTIFACTS_LIST_IDS to sue new const from shared package export const ALL_ENDPOINT_ARTIFACT_LIST_IDS = ENDPOINT_ARTIFACT_LIST_IDS; diff --git a/x-pack/plugins/security_solution/common/endpoint/service/artifacts/index.ts b/x-pack/plugins/security_solution/common/endpoint/service/artifacts/index.ts index 6bdf0fb59a3a8..ac291c7997aa3 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/artifacts/index.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/artifacts/index.ts @@ -5,6 +5,12 @@ * 2.0. */ -export { isArtifactGlobal, isArtifactByPolicy, getPolicyIdsFromArtifact } from './utils'; +export { + isArtifactGlobal, + isArtifactByPolicy, + getPolicyIdsFromArtifact, + getEffectedPolicySelectionByTags, + getArtifactTagsByPolicySelection, +} from './utils'; export { BY_POLICY_ARTIFACT_TAG_PREFIX, GLOBAL_ARTIFACT_TAG } from './constants'; diff --git a/x-pack/plugins/security_solution/common/endpoint/service/artifacts/utils.test.ts b/x-pack/plugins/security_solution/common/endpoint/service/artifacts/utils.test.ts index 35931d78210ee..dba34da5bf1e5 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/artifacts/utils.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/artifacts/utils.test.ts @@ -6,12 +6,22 @@ */ import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; -import { BY_POLICY_ARTIFACT_TAG_PREFIX, GLOBAL_ARTIFACT_TAG } from './constants'; +import type { PolicyData } from '../../types'; +import { + BY_POLICY_ARTIFACT_TAG_PREFIX, + FILTER_PROCESS_DESCENDANTS_TAG, + GLOBAL_ARTIFACT_TAG, +} from './constants'; import { createExceptionListItemForCreate, + getArtifactTagsByPolicySelection, + getEffectedPolicySelectionByTags, getPolicyIdsFromArtifact, isArtifactByPolicy, isArtifactGlobal, + isFilterProcessDescendantsEnabled, + isFilterProcessDescendantsTag, + isPolicySelectionTag, } from './utils'; describe('Endpoint artifact utilities', () => { @@ -45,6 +55,10 @@ describe('Endpoint artifact utilities', () => { it('should return `false` if artifact is per-policy but not assigned to any policy', () => { expect(isArtifactGlobal(perPolicyNoPolicies)).toBe(false); }); + + it('should return `false` if `tags` is undefined', () => { + expect(isArtifactGlobal({})).toBe(false); + }); }); describe('when using `isArtifactByPolicy()', () => { @@ -71,6 +85,95 @@ describe('Endpoint artifact utilities', () => { }); }); + describe('when using `isPolicySelectionTag()`', () => { + it('should return true if tag starts with prefix', () => { + expect(isPolicySelectionTag(`${BY_POLICY_ARTIFACT_TAG_PREFIX}cheese`)).toBe(true); + }); + + it('should return true if tag equals global artifact tag', () => { + expect(isPolicySelectionTag(GLOBAL_ARTIFACT_TAG)).toBe(true); + }); + + it('should return false otherwise', () => { + expect(isPolicySelectionTag('otherwise')).toBe(false); + }); + }); + + describe('when using `getArtifactTagsByPolicySelection()`', () => { + const policyData: Array> = [ + { id: 'id1' }, + { id: 'id2' }, + ] as PolicyData[]; + + it('should return global artifact tag if is global', () => { + expect( + getArtifactTagsByPolicySelection({ + isGlobal: true, + selected: policyData as PolicyData[], + }) + ).toStrictEqual([GLOBAL_ARTIFACT_TAG]); + }); + + it('should return every passed policy id with tag prefix if not global', () => { + expect( + getArtifactTagsByPolicySelection({ + isGlobal: false, + selected: policyData as PolicyData[], + }) + ).toStrictEqual(['policy:id1', 'policy:id2']); + }); + }); + + describe('when using `getEffectedPolicySelectionByTags()`', () => { + const policyData: Array> = [{ id: 'id1' }, { id: 'id2' }, { id: 'id3' }]; + + it('should return `isGlobal: true` when global tag is amongst tags', () => { + expect( + getEffectedPolicySelectionByTags( + ['cheese', GLOBAL_ARTIFACT_TAG, 'bacon'], + policyData as PolicyData[] + ) + ).toStrictEqual({ isGlobal: true, selected: [] }); + }); + + it('should return relevant policy data when not global', () => { + expect( + getEffectedPolicySelectionByTags( + ['cheese', 'policy:id3', 'bacon', 'policy:id1'], + policyData as PolicyData[] + ) + ).toStrictEqual({ isGlobal: false, selected: [{ id: 'id3' }, { id: 'id1' }] }); + }); + }); + + describe('when using `isFilterProcessDescendantsEnabled()`', () => { + it('should return false when `tags` is undefined', () => { + expect(isFilterProcessDescendantsEnabled({})).toBe(false); + }); + + it('should return false when `tags` does not contain the relevant tag', () => { + expect(isFilterProcessDescendantsEnabled({ tags: ['aaa', 'bbb', 'ccc'] })).toBe(false); + }); + + it('should return true when `tags` contain the relevant tag', () => { + expect( + isFilterProcessDescendantsEnabled({ + tags: ['aaa', 'bbb', FILTER_PROCESS_DESCENDANTS_TAG, 'ccc'], + }) + ).toBe(true); + }); + }); + + describe('when using `isFilterProcessDescendantsTag()`', () => { + it('should return true if tag equals with the filter process descendants tag', () => { + expect(isFilterProcessDescendantsTag(FILTER_PROCESS_DESCENDANTS_TAG)).toBe(true); + }); + + it('should return false otherwise', () => { + expect(isFilterProcessDescendantsTag('otherwise')).toBe(false); + }); + }); + describe('when using `createExceptionListItemForCreate()`', () => { it('should return an empty exception list ready for create', () => { expect(createExceptionListItemForCreate('abc')).toEqual({ diff --git a/x-pack/plugins/security_solution/common/endpoint/service/artifacts/utils.ts b/x-pack/plugins/security_solution/common/endpoint/service/artifacts/utils.ts index 725a13e618b6d..50b4861a345ce 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/artifacts/utils.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/artifacts/utils.ts @@ -10,11 +10,19 @@ import type { CreateExceptionListItemSchema, } from '@kbn/securitysolution-io-ts-list-types'; import { v4 as uuidv4 } from 'uuid'; -import { BY_POLICY_ARTIFACT_TAG_PREFIX, GLOBAL_ARTIFACT_TAG } from './constants'; +import type { EffectedPolicySelection } from '../../../../public/management/components/effected_policy_select'; +import type { PolicyData } from '../../types'; +import { + BY_POLICY_ARTIFACT_TAG_PREFIX, + FILTER_PROCESS_DESCENDANTS_TAG, + GLOBAL_ARTIFACT_TAG, +} from './constants'; + +export type TagFilter = (tag: string) => boolean; const POLICY_ID_START_POSITION = BY_POLICY_ARTIFACT_TAG_PREFIX.length; -export const isArtifactGlobal = (item: Pick): boolean => { +export const isArtifactGlobal = (item: Partial>): boolean => { return (item.tags ?? []).find((tag) => tag === GLOBAL_ARTIFACT_TAG) !== undefined; }; @@ -35,6 +43,64 @@ export const getPolicyIdsFromArtifact = (item: Pick + tag.startsWith(BY_POLICY_ARTIFACT_TAG_PREFIX) || tag === GLOBAL_ARTIFACT_TAG; + +/** + * Return a list of artifact policy tags based on a current + * selection by the EffectedPolicySelection component. + */ +export const getArtifactTagsByPolicySelection = (selection: EffectedPolicySelection): string[] => { + if (selection.isGlobal) { + return [GLOBAL_ARTIFACT_TAG]; + } + + return selection.selected.map((policy) => { + return `${BY_POLICY_ARTIFACT_TAG_PREFIX}${policy.id}`; + }); +}; + +/** + * Given a list of an Exception item tags it will return + * the parsed policies from it. + * + * Policy tags follow the pattern `policy:id` + * non policy tags will be ignored. + */ +export const getEffectedPolicySelectionByTags = ( + tags: string[], + policies: PolicyData[] +): EffectedPolicySelection => { + if (tags.find((tag) => tag === GLOBAL_ARTIFACT_TAG)) { + return { + isGlobal: true, + selected: [], + }; + } + const selected: PolicyData[] = tags.reduce((acc, tag) => { + // edge case: a left over tag with a non-existed policy + // will be removed by verifying the policy exists + const id = tag.split(':')[1]; + const foundPolicy = policies.find((policy) => policy.id === id); + if (foundPolicy !== undefined) { + acc.push(foundPolicy); + } + return acc; + }, [] as PolicyData[]); + + return { + isGlobal: false, + selected, + }; +}; + +export const isFilterProcessDescendantsEnabled = ( + item: Partial> +): boolean => (item.tags ?? []).find((tag) => tag === FILTER_PROCESS_DESCENDANTS_TAG) !== undefined; + +export const isFilterProcessDescendantsTag: TagFilter = (tag) => + tag === FILTER_PROCESS_DESCENDANTS_TAG; + export const createExceptionListItemForCreate = (listId: string): CreateExceptionListItemSchema => { return { comments: [], diff --git a/x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.test.ts b/x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.test.ts deleted file mode 100644 index 48928fb435fec..0000000000000 --- a/x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { isVersionSupported, isOsSupported, isIsolationSupported } from './utils'; - -describe('Host Isolation utils isVersionSupported', () => { - // NOTE: the `7.15.0.8295.0` and the text current versions are invalid. - test.each` - currentVersion | minVersionRequired | expected - ${'8.14.0'} | ${'7.13.0'} | ${true} - ${'7.14.0'} | ${'7.13.0'} | ${true} - ${'7.14.1'} | ${'7.14.0'} | ${true} - ${'8.14.0'} | ${'9.14.0'} | ${false} - ${'7.13.0'} | ${'7.14.0'} | ${false} - ${'7.14.0'} | ${'7.14.1'} | ${false} - ${'7.14.0'} | ${'7.14.0'} | ${true} - ${'7.14.0-SNAPSHOT'} | ${'7.14.0'} | ${true} - ${'7.14.0-SNAPSHOT-beta'} | ${'7.14.0'} | ${true} - ${'7.14.0-alpha'} | ${'7.14.0'} | ${true} - ${'8.0.0-SNAPSHOT'} | ${'7.14.0'} | ${true} - ${'8.0.0'} | ${'7.14.0'} | ${true} - ${'7.15.0.8295.0'} | ${'7.14.0'} | ${false} - ${'NOT_SEMVER'} | ${'7.14.0'} | ${false} - `( - 'should validate that version $a is compatible($expected) to $b', - ({ currentVersion, minVersionRequired, expected }) => { - expect( - isVersionSupported({ - currentVersion, - minVersionRequired, - }) - ).toEqual(expected); - } - ); -}); - -describe('Host Isolation utils isOsSupported', () => { - test.each` - currentOs | supportedOss | expected - ${'linux'} | ${{ macos: true, linux: true }} | ${true} - ${'linux'} | ${{ macos: true, windows: true }} | ${false} - `( - 'should validate that os $a is compatible($expected) to $b', - ({ currentOs, supportedOss, expected }) => { - expect( - isOsSupported({ - currentOs, - supportedOss, - }) - ).toEqual(expected); - } - ); -}); - -describe('Host Isolation utils isIsolationSupported', () => { - test.each` - osName | version | capabilities | expected - ${'windows'} | ${'7.14.0'} | ${[]} | ${true} - ${'linux'} | ${'7.13.0'} | ${['isolation']} | ${false} - ${'linux'} | ${'7.14.0'} | ${['isolation']} | ${false} - ${'macos'} | ${'7.13.0'} | ${['isolation']} | ${false} - ${'linux'} | ${'7.13.0'} | ${['isolation']} | ${false} - ${'windows'} | ${'7.15.0'} | ${[]} | ${false} - ${'macos'} | ${'7.15.0'} | ${[]} | ${false} - ${'linux'} | ${'7.15.0'} | ${['isolation']} | ${true} - ${'macos'} | ${'7.15.0'} | ${['isolation']} | ${true} - ${'linux'} | ${'7.16.0'} | ${['isolation']} | ${true} - `( - 'should validate that os $a, version $b, and capabilities $c supports hostIsolation($expected)', - ({ osName, version, capabilities, expected }) => { - expect( - isIsolationSupported({ - osName, - version, - capabilities, - }) - ).toEqual(expected); - } - ); -}); diff --git a/x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.ts b/x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.ts deleted file mode 100644 index 97d7eb2db641f..0000000000000 --- a/x-pack/plugins/security_solution/common/endpoint/service/host_isolation/utils.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import semverLte from 'semver/functions/lte'; -import type { ImmutableArray } from '../../types'; - -const minSupportedVersion = '7.14.0'; -const minCapabilitiesVersion = '7.15.0'; -const supportedOssMap = { - macos: true, - windows: true, -}; -const isolationCapability = 'isolation'; - -function parseSemver(semver: string) { - return semver.includes('-') ? semver.substring(0, semver.indexOf('-')) : semver; -} - -export const isVersionSupported = ({ - currentVersion, - minVersionRequired = minSupportedVersion, -}: { - currentVersion: string; - minVersionRequired?: string; -}) => { - // `parseSemver()` will throw if the version provided is not a valid semver value. - // If that happens, then just return false from this function - try { - const parsedCurrentVersion = parseSemver(currentVersion); - return semverLte(minVersionRequired, parsedCurrentVersion); - } catch (e) { - // If running in the browser, log to console - if (window && window.console) { - window.console.warn( - `SecuritySolution: isVersionSupported(): Unable to determine if current version [${currentVersion}] meets minimum version [${minVersionRequired}]. Error: ${e.message}` - ); - } - return false; - } -}; - -export const isOsSupported = ({ - currentOs, - supportedOss = supportedOssMap, -}: { - currentOs: string; - supportedOss?: { [os: string]: boolean }; -}) => !!supportedOss[currentOs]; - -function isCapabilitiesSupported(semver: string): boolean { - const parsedVersion = parseSemver(semver); - // capabilities is only available from 7.15+ - return semverLte(minCapabilitiesVersion, parsedVersion); -} - -function isIsolationSupportedCapabilities(capabilities: ImmutableArray = []): boolean { - return capabilities.includes(isolationCapability); -} - -// capabilities isn't introduced until 7.15 so check the OS for support -function isIsolationSupportedOS(osName: string): boolean { - const normalizedOs = osName.toLowerCase(); - return isOsSupported({ currentOs: normalizedOs }); -} - -export const isIsolationSupported = ({ - osName, - version, - capabilities, -}: { - osName: string; - version: string; - capabilities?: ImmutableArray; -}): boolean => { - if (!version || !isVersionSupported({ currentVersion: version })) return false; - - return isCapabilitiesSupported(version) - ? isIsolationSupportedCapabilities(capabilities) - : isIsolationSupportedOS(osName); -}; diff --git a/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts b/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts index 32773a3fafef4..fdfa5ed02cb73 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts @@ -174,3 +174,14 @@ export const RESPONSE_ACTIONS_ZIP_PASSCODE: Readonly +> = Object.freeze({ + endpoint: 'agent.id', + sentinel_one: 'observer.serial_number', + crowdstrike: 'crowdstrike.event.DeviceId', +}); diff --git a/x-pack/plugins/security_solution/common/entity_analytics/asset_criticality/constants.ts b/x-pack/plugins/security_solution/common/entity_analytics/asset_criticality/constants.ts index 73d9cccabc940..42be15209624c 100644 --- a/x-pack/plugins/security_solution/common/entity_analytics/asset_criticality/constants.ts +++ b/x-pack/plugins/security_solution/common/entity_analytics/asset_criticality/constants.ts @@ -5,10 +5,19 @@ * 2.0. */ -export const ASSET_CRITICALITY_URL = `/internal/asset_criticality` as const; -export const ASSET_CRITICALITY_PRIVILEGES_URL = `${ASSET_CRITICALITY_URL}/privileges` as const; -export const ASSET_CRITICALITY_STATUS_URL = `${ASSET_CRITICALITY_URL}/status` as const; -export const ASSET_CRITICALITY_CSV_UPLOAD_URL = `${ASSET_CRITICALITY_URL}/upload_csv` as const; +export const ASSET_CRITICALITY_INTERNAL_URL = `/internal/asset_criticality` as const; +export const ASSET_CRITICALITY_INTERNAL_PRIVILEGES_URL = + `${ASSET_CRITICALITY_INTERNAL_URL}/privileges` as const; +export const ASSET_CRITICALITY_INTERNAL_STATUS_URL = + `${ASSET_CRITICALITY_INTERNAL_URL}/status` as const; +export const ASSET_CRITICALITY_INTERNAL_CSV_UPLOAD_URL = + `${ASSET_CRITICALITY_INTERNAL_URL}/upload_csv` as const; + +export const ASSET_CRITICALITY_PUBLIC_URL = `/api/asset_criticality` as const; +export const ASSET_CRITICALITY_PUBLIC_CSV_UPLOAD_URL = + `${ASSET_CRITICALITY_PUBLIC_URL}/upload_csv` as const; +export const ASSET_CRITICALITY_PUBLIC_BULK_UPLOAD_URL = + `${ASSET_CRITICALITY_PUBLIC_URL}/bulk` as const; export const ASSET_CRITICALITY_INDEX_PATTERN = '.asset-criticality.asset-criticality-*'; diff --git a/x-pack/plugins/security_solution/common/entity_analytics/constants.ts b/x-pack/plugins/security_solution/common/entity_analytics/constants.ts index 08ee2d6afd517..c0a9567acd09c 100644 --- a/x-pack/plugins/security_solution/common/entity_analytics/constants.ts +++ b/x-pack/plugins/security_solution/common/entity_analytics/constants.ts @@ -8,3 +8,12 @@ export * from './asset_criticality/constants'; export * from './risk_engine/constants'; export * from './risk_score/constants'; + +export const API_VERSIONS = { + public: { + v1: '2023-10-31', + }, + internal: { + v1: '1', + }, +}; diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 0e0065e6823ea..761ec8d26035f 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -114,6 +114,11 @@ export const allowedExperimentalValues = Object.freeze({ */ expandableFlyoutDisabled: false, + /** + * Enables new notes + */ + notesEnabled: false, + /** * Enables the Assistant Model Evaluation advanced setting and API endpoint, introduced in `8.11.0`. */ @@ -257,6 +262,11 @@ export const allowedExperimentalValues = Object.freeze({ * Enables the manual rule run */ manualRuleRunEnabled: false, + + /** + * Adds a new option to filter descendants of a process for Management / Event Filters + */ + filterProcessDescendantsForEventFiltersEnabled: false, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts index b434ee20cfb03..15dda8058221d 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts @@ -37,6 +37,7 @@ export interface HostItem { host?: Maybe; lastSeen?: Maybe; risk?: string; + criticality?: string; } export interface HostValue { diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/all/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/all/index.ts index 19406bbca35d9..1d8feedf95e74 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/all/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/users/all/index.ts @@ -14,6 +14,7 @@ export interface User { lastSeen: string; domain: string; risk?: RiskSeverity; + criticality?: string; } export interface UsersStrategyResponse extends IEsSearchResponse { diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml new file mode 100644 index 0000000000000..ca8bae8f42f0f --- /dev/null +++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_detections_api_2023_10_31.bundled.schema.yaml @@ -0,0 +1,5682 @@ +openapi: 3.0.3 +info: + description: >- + You can create rules that automatically turn events and external alerts sent + to Elastic Security into detection alerts. These alerts are displayed on the + Detections page. + title: Security Solution Detections API (Elastic Cloud and self-hosted) + version: '2023-10-31' +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/detection_engine/rules: + delete: + description: Deletes a single rule using the `rule_id` or `id` field. + operationId: DeleteRule + parameters: + - description: The rule's `id` value. + in: query + name: id + required: false + schema: + $ref: '#/components/schemas/RuleObjectId' + - description: The rule's `rule_id` value. + in: query + name: rule_id + required: false + schema: + $ref: '#/components/schemas/RuleSignatureId' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RuleResponse' + description: Indicates a successful call. + tags: + - Rules API + get: + description: Read a single rule + operationId: ReadRule + parameters: + - description: The rule's `id` value. + in: query + name: id + required: false + schema: + $ref: '#/components/schemas/RuleObjectId' + - description: The rule's `rule_id` value. + in: query + name: rule_id + required: false + schema: + $ref: '#/components/schemas/RuleSignatureId' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RuleResponse' + description: Indicates a successful call. + tags: + - Rules API + patch: + description: Patch a single rule + operationId: PatchRule + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RulePatchProps' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RuleResponse' + description: Indicates a successful call. + tags: + - Rules API + post: + description: Create a single detection rule + operationId: CreateRule + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RuleCreateProps' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RuleResponse' + description: Indicates a successful call. + tags: + - Rules API + put: + description: Update a single rule + operationId: UpdateRule + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RuleUpdateProps' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RuleResponse' + description: Indicates a successful call. + tags: + - Rules API + /api/detection_engine/rules/_bulk_action: + post: + description: >- + The bulk action is applied to all rules that match the filter or to the + list of rules by their IDs. + operationId: PerformBulkAction + parameters: + - description: Enables dry run mode for the request call. + in: query + name: dry_run + required: false + schema: + type: boolean + requestBody: + content: + application/json: + schema: + 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': + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/BulkEditActionResponse' + - $ref: '#/components/schemas/BulkExportActionResponse' + description: OK + summary: Applies a bulk action to multiple rules + tags: + - Bulk API + /api/detection_engine/rules/_bulk_create: + post: + deprecated: true + description: Creates new detection rules in bulk. + operationId: BulkCreateRules + requestBody: + content: + application/json: + schema: + items: + $ref: '#/components/schemas/RuleCreateProps' + type: array + description: 'A JSON array of rules, where each rule contains the required fields.' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BulkCrudRulesResponse' + description: Indicates a successful call. + tags: + - Bulk API + /api/detection_engine/rules/_bulk_delete: + delete: + deprecated: true + description: Deletes multiple rules. + operationId: BulkDeleteRules + requestBody: + content: + application/json: + schema: + items: + type: object + properties: + id: + $ref: '#/components/schemas/RuleObjectId' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + type: array + description: >- + A JSON array of `id` or `rule_id` fields of the rules you want to + delete. + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BulkCrudRulesResponse' + description: Indicates a successful call. + tags: + - Bulk API + /api/detection_engine/rules/_bulk_update: + patch: + deprecated: true + description: Updates multiple rules using the `PATCH` method. + operationId: BulkPatchRules + requestBody: + content: + application/json: + schema: + items: + $ref: '#/components/schemas/RulePatchProps' + type: array + description: 'A JSON array of rules, where each rule contains the required fields.' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BulkCrudRulesResponse' + description: Indicates a successful call. + tags: + - Bulk API + put: + deprecated: true + description: Updates multiple rules using the `PUT` method. + operationId: BulkUpdateRules + requestBody: + content: + application/json: + schema: + items: + $ref: '#/components/schemas/RuleUpdateProps' + type: array + description: >- + A JSON array where each element includes the `id` or `rule_id` field + of the rule you want to update and the fields you want to modify. + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/BulkCrudRulesResponse' + description: Indicates a successful call. + tags: + - Bulk API + /api/detection_engine/rules/_export: + post: + description: >- + Exports rules to an `.ndjson` file. The following configuration items + are also included in the `.ndjson` file - Actions, Exception lists. + Prebuilt rules cannot be exported. + operationId: ExportRules + parameters: + - description: Determines whether a summary of the exported rules is returned. + in: query + name: exclude_export_details + required: false + schema: + default: false + type: boolean + - description: File name for saving the exported rules. + in: query + name: file_name + required: false + schema: + default: export.ndjson + type: string + requestBody: + content: + application/json: + schema: + nullable: true + type: object + properties: + objects: + description: >- + Array of `rule_id` fields. Exports all rules when + unspecified. + items: + type: object + properties: + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + required: + - rule_id + type: array + required: + - objects + required: false + responses: + '200': + content: + application/ndjson: + schema: + description: An `.ndjson` file containing the returned rules. + format: binary + type: string + description: Indicates a successful call. + summary: Export rules + tags: + - Import/Export API + summary: Exports rules to an `.ndjson` file + /api/detection_engine/rules/_find: + get: + description: Finds rules that match the given query. + operationId: FindRules + parameters: + - in: query + name: fields + required: false + schema: + items: + type: string + type: array + - description: Search query + in: query + name: filter + required: false + schema: + type: string + - description: Field to sort by + in: query + name: sort_field + required: false + schema: + $ref: '#/components/schemas/FindRulesSortField' + - description: Sort order + in: query + name: sort_order + required: false + schema: + $ref: '#/components/schemas/SortOrder' + - description: Page number + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: Rules per page + in: query + name: per_page + required: false + schema: + default: 20 + minimum: 0 + type: integer + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + items: + $ref: '#/components/schemas/RuleResponse' + type: array + page: + type: integer + perPage: + type: integer + total: + type: integer + required: + - page + - perPage + - total + - data + description: Successful response + tags: + - Rules API + /api/detection_engine/rules/_import: + post: + description: >- + Imports rules from an `.ndjson` file, including actions and exception + lists. + operationId: ImportRules + parameters: + - description: >- + Determines whether existing rules with the same `rule_id` are + overwritten. + in: query + name: overwrite + required: false + schema: + default: false + type: boolean + - description: >- + Determines whether existing exception lists with the same `list_id` + are overwritten. + in: query + name: overwrite_exceptions + required: false + schema: + default: false + type: boolean + - description: >- + Determines whether existing actions with the same + `kibana.alert.rule.actions.id` are overwritten. + in: query + name: overwrite_action_connectors + required: false + schema: + default: false + type: boolean + - description: Generates a new list ID for each imported exception list. + in: query + name: as_new_list + required: false + schema: + default: false + type: boolean + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + file: + description: The `.ndjson` file containing the rules. + format: binary + type: string + required: true + responses: + '200': + content: + application/json: + schema: + additionalProperties: false + type: object + properties: + action_connectors_errors: + items: + $ref: '#/components/schemas/ErrorSchema' + type: array + action_connectors_success: + type: boolean + action_connectors_success_count: + minimum: 0 + type: integer + action_connectors_warnings: + items: + $ref: '#/components/schemas/WarningSchema' + type: array + errors: + items: + $ref: '#/components/schemas/ErrorSchema' + type: array + exceptions_errors: + items: + $ref: '#/components/schemas/ErrorSchema' + type: array + exceptions_success: + type: boolean + exceptions_success_count: + minimum: 0 + type: integer + rules_count: + minimum: 0 + type: integer + success: + type: boolean + success_count: + minimum: 0 + type: integer + required: + - exceptions_success + - exceptions_success_count + - exceptions_errors + - rules_count + - success + - success_count + - errors + - action_connectors_errors + - action_connectors_warnings + - action_connectors_success + - action_connectors_success_count + description: Indicates a successful call. + summary: Import rules + tags: + - Import/Export API + summary: Imports rules from an `.ndjson` file + /api/detection_engine/rules/prepackaged: + put: + operationId: InstallPrebuiltRulesAndTimelines + responses: + '200': + content: + application/json: + schema: + additionalProperties: false + type: object + properties: + rules_installed: + description: The number of rules installed + minimum: 0 + type: integer + rules_updated: + description: The number of rules updated + minimum: 0 + type: integer + timelines_installed: + description: The number of timelines installed + minimum: 0 + type: integer + timelines_updated: + description: The number of timelines updated + minimum: 0 + type: integer + required: + - rules_installed + - rules_updated + - timelines_installed + - timelines_updated + description: Indicates a successful call + summary: Installs all Elastic prebuilt rules and timelines + tags: + - Prebuilt Rules API + /api/detection_engine/rules/prepackaged/_status: + get: + operationId: GetPrebuiltRulesAndTimelinesStatus + responses: + '200': + content: + application/json: + schema: + additionalProperties: false + type: object + properties: + rules_custom_installed: + description: The total number of custom rules + minimum: 0 + type: integer + rules_installed: + description: The total number of installed prebuilt rules + minimum: 0 + type: integer + rules_not_installed: + description: >- + The total number of available prebuilt rules that are not + installed + minimum: 0 + type: integer + rules_not_updated: + description: The total number of outdated prebuilt rules + minimum: 0 + type: integer + timelines_installed: + description: The total number of installed prebuilt timelines + minimum: 0 + type: integer + timelines_not_installed: + description: >- + The total number of available prebuilt timelines that are + not installed + minimum: 0 + type: integer + timelines_not_updated: + description: The total number of outdated prebuilt timelines + minimum: 0 + type: integer + required: + - rules_custom_installed + - rules_installed + - rules_not_installed + - rules_not_updated + - timelines_installed + - timelines_not_installed + - timelines_not_updated + description: Indicates a successful call + summary: Get the status of Elastic prebuilt rules + tags: + - Prebuilt Rules API + /api/detection_engine/signals/assignees: + post: + description: Assigns users to alerts. + operationId: SetAlertAssignees + requestBody: + content: + application/json: + schema: + type: object + properties: + assignees: + $ref: '#/components/schemas/AlertAssignees' + description: Details about the assignees to assign and unassign. + ids: + $ref: '#/components/schemas/AlertIds' + description: List of alerts ids to assign and unassign passed assignees. + required: + - assignees + - ids + required: true + responses: + '200': + description: Indicates a successful call. + '400': + description: Invalid request. + summary: Assigns users to alerts + /api/detection_engine/tags: + get: + operationId: ReadTags + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RuleTagArray' + description: Indicates a successful call + summary: Aggregates and returns all unique tags from all rules + tags: + - Tags API + summary: Aggregates and returns rule tags +components: + schemas: + AlertAssignees: + type: object + properties: + add: + description: A list of users ids to assign. + items: + $ref: '#/components/schemas/NonEmptyString' + type: array + remove: + description: A list of users ids to unassign. + items: + $ref: '#/components/schemas/NonEmptyString' + type: array + required: + - add + - remove + AlertIds: + description: A list of alerts ids. + items: + $ref: '#/components/schemas/NonEmptyString' + minItems: 1 + type: array + AlertsIndex: + deprecated: true + description: (deprecated) Has no effect. + type: string + AlertsIndexNamespace: + description: Has no effect. + type: string + AlertSuppression: + type: object + properties: + duration: + $ref: '#/components/schemas/AlertSuppressionDuration' + group_by: + $ref: '#/components/schemas/AlertSuppressionGroupBy' + missing_fields_strategy: + $ref: '#/components/schemas/AlertSuppressionMissingFieldsStrategy' + required: + - group_by + AlertSuppressionDuration: + type: object + properties: + unit: + enum: + - s + - m + - h + type: string + value: + minimum: 1 + type: integer + required: + - value + - unit + AlertSuppressionGroupBy: + items: + type: string + maxItems: 3 + minItems: 1 + type: array + AlertSuppressionMissingFieldsStrategy: + description: >- + Describes how alerts will be generated for documents with missing + suppress by fields: + + doNotSuppress - per each document a separate alert will be created + + suppress - only alert will be created per suppress by bucket + enum: + - doNotSuppress + - suppress + type: string + AnomalyThreshold: + description: Anomaly threshold + minimum: 0 + type: integer + BuildingBlockType: + description: >- + Determines if the rule acts as a building block. By default, + building-block alerts are not displayed in the UI. These rules are used + as a foundation for other rules that do generate alerts. Its value must + be default. + type: string + BulkActionEditPayload: + anyOf: + - $ref: '#/components/schemas/BulkActionEditPayloadTags' + - $ref: '#/components/schemas/BulkActionEditPayloadIndexPatterns' + - $ref: '#/components/schemas/BulkActionEditPayloadInvestigationFields' + - $ref: '#/components/schemas/BulkActionEditPayloadTimeline' + - $ref: '#/components/schemas/BulkActionEditPayloadRuleActions' + - $ref: '#/components/schemas/BulkActionEditPayloadSchedule' + BulkActionEditPayloadIndexPatterns: + type: object + properties: + overwrite_data_views: + type: boolean + type: + enum: + - add_index_patterns + - delete_index_patterns + - set_index_patterns + type: string + value: + $ref: '#/components/schemas/IndexPatternArray' + required: + - type + - value + BulkActionEditPayloadInvestigationFields: + type: object + properties: + type: + enum: + - add_investigation_fields + - delete_investigation_fields + - set_investigation_fields + type: string + value: + $ref: '#/components/schemas/InvestigationFields' + required: + - type + - value + BulkActionEditPayloadRuleActions: + type: object + properties: + type: + enum: + - add_rule_actions + - set_rule_actions + type: string + value: + type: object + properties: + actions: + items: + $ref: '#/components/schemas/NormalizedRuleAction' + type: array + throttle: + $ref: '#/components/schemas/ThrottleForBulkActions' + required: + - actions + required: + - type + - value + BulkActionEditPayloadSchedule: + type: object + properties: + type: + enum: + - set_schedule + type: string + value: + type: object + properties: + interval: + description: Interval in which the rule is executed + example: 1h + pattern: '^[1-9]\d*[smh]$' + type: string + lookback: + description: Lookback time for the rule + example: 1h + pattern: '^[1-9]\d*[smh]$' + type: string + required: + - interval + - lookback + required: + - type + - value + BulkActionEditPayloadTags: + type: object + properties: + type: + enum: + - add_tags + - delete_tags + - set_tags + type: string + value: + $ref: '#/components/schemas/RuleTagArray' + required: + - type + - value + BulkActionEditPayloadTimeline: + type: object + properties: + type: + enum: + - set_timeline + type: string + value: + type: object + properties: + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + required: + - timeline_id + - timeline_title + required: + - type + - value + BulkActionsDryRunErrCode: + enum: + - IMMUTABLE + - MACHINE_LEARNING_AUTH + - MACHINE_LEARNING_INDEX_PATTERN + - ESQL_INDEX_PATTERN + - INVESTIGATION_FIELDS_FEATURE + type: string + BulkActionSkipResult: + type: object + properties: + id: + type: string + name: + type: string + skip_reason: + $ref: '#/components/schemas/BulkEditSkipReason' + required: + - id + - skip_reason + BulkCrudRulesResponse: + items: + oneOf: + - $ref: '#/components/schemas/RuleResponse' + - $ref: '#/components/schemas/ErrorSchema' + type: array + BulkDeleteRules: + type: object + properties: + action: + enum: + - delete + type: string + ids: + description: Array of rule IDs + items: + type: string + minItems: 1 + type: array + query: + description: Query to filter rules + type: string + required: + - action + BulkDisableRules: + type: object + properties: + action: + enum: + - disable + type: string + ids: + description: Array of rule IDs + items: + type: string + minItems: 1 + type: array + query: + description: Query to filter rules + type: string + required: + - action + BulkDuplicateRules: + type: object + properties: + action: + enum: + - duplicate + type: string + duplicate: + type: object + properties: + include_exceptions: + description: Whether to copy exceptions from the original rule + type: boolean + include_expired_exceptions: + description: Whether to copy expired exceptions from the original rule + type: boolean + required: + - include_exceptions + - include_expired_exceptions + ids: + description: Array of rule IDs + items: + type: string + minItems: 1 + type: array + query: + description: Query to filter rules + type: string + required: + - action + BulkEditActionResponse: + type: object + properties: + attributes: + type: object + properties: + errors: + items: + $ref: '#/components/schemas/NormalizedRuleError' + type: array + results: + $ref: '#/components/schemas/BulkEditActionResults' + summary: + $ref: '#/components/schemas/BulkEditActionSummary' + required: + - results + - summary + message: + type: string + rules_count: + type: integer + status_code: + type: integer + success: + type: boolean + required: + - attributes + BulkEditActionResults: + type: object + properties: + created: + items: + $ref: '#/components/schemas/RuleResponse' + type: array + deleted: + items: + $ref: '#/components/schemas/RuleResponse' + type: array + skipped: + items: + $ref: '#/components/schemas/BulkActionSkipResult' + type: array + updated: + items: + $ref: '#/components/schemas/RuleResponse' + type: array + required: + - updated + - created + - deleted + - skipped + BulkEditActionSummary: + type: object + properties: + failed: + type: integer + skipped: + type: integer + succeeded: + type: integer + total: + type: integer + required: + - failed + - skipped + - succeeded + - total + BulkEditRules: + type: object + properties: + action: + enum: + - edit + type: string + edit: + description: Array of objects containing the edit operations + items: + $ref: '#/components/schemas/BulkActionEditPayload' + minItems: 1 + type: array + ids: + description: Array of rule IDs + items: + type: string + minItems: 1 + type: array + query: + description: Query to filter rules + type: string + required: + - action + - edit + BulkEditSkipReason: + enum: + - RULE_NOT_MODIFIED + type: string + BulkEnableRules: + type: object + properties: + action: + enum: + - enable + type: string + ids: + description: Array of rule IDs + items: + type: string + minItems: 1 + type: array + query: + description: Query to filter rules + type: string + required: + - action + BulkExportActionResponse: + type: string + BulkExportRules: + type: object + properties: + action: + enum: + - export + type: string + ids: + description: Array of rule IDs + items: + type: string + minItems: 1 + type: array + query: + description: Query to filter rules + type: string + required: + - action + ConcurrentSearches: + minimum: 1 + type: integer + DataViewId: + type: string + DefaultParams: + type: object + properties: + command: + enum: + - isolate + type: string + comment: + type: string + required: + - command + EcsMapping: + additionalProperties: + type: object + properties: + field: + type: string + value: + oneOf: + - type: string + - items: + type: string + type: array + type: object + EndpointResponseAction: + type: object + properties: + action_type_id: + enum: + - .endpoint + type: string + params: + oneOf: + - $ref: '#/components/schemas/DefaultParams' + - $ref: '#/components/schemas/ProcessesParams' + required: + - action_type_id + - params + EqlOptionalFields: + type: object + properties: + alert_suppression: + $ref: '#/components/schemas/AlertSuppression' + data_view_id: + $ref: '#/components/schemas/DataViewId' + event_category_override: + $ref: '#/components/schemas/EventCategoryOverride' + filters: + $ref: '#/components/schemas/RuleFilterArray' + index: + $ref: '#/components/schemas/IndexPatternArray' + tiebreaker_field: + $ref: '#/components/schemas/TiebreakerField' + timestamp_field: + $ref: '#/components/schemas/TimestampField' + EqlQueryLanguage: + enum: + - eql + type: string + EqlRequiredFields: + type: object + properties: + language: + $ref: '#/components/schemas/EqlQueryLanguage' + description: Query language to use + query: + $ref: '#/components/schemas/RuleQuery' + description: EQL query to execute + type: + description: Rule type + enum: + - eql + type: string + required: + - type + - query + - language + EqlRule: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - version + - tags + - enabled + - risk_score_mapping + - severity_mapping + - interval + - from + - to + - actions + - exceptions_list + - author + - false_positives + - references + - max_signals + - threat + - setup + - related_integrations + - required_fields + - $ref: '#/components/schemas/ResponseFields' + - $ref: '#/components/schemas/EqlRuleResponseFields' + EqlRuleCreateFields: + allOf: + - $ref: '#/components/schemas/EqlRequiredFields' + - $ref: '#/components/schemas/EqlOptionalFields' + EqlRuleCreateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/EqlRuleCreateFields' + EqlRulePatchFields: + allOf: + - type: object + properties: + language: + $ref: '#/components/schemas/EqlQueryLanguage' + description: Query language to use + query: + $ref: '#/components/schemas/RuleQuery' + description: EQL query to execute + type: + description: Rule type + enum: + - eql + type: string + - $ref: '#/components/schemas/EqlOptionalFields' + EqlRulePatchProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + - $ref: '#/components/schemas/EqlRulePatchFields' + EqlRuleResponseFields: + allOf: + - $ref: '#/components/schemas/EqlRequiredFields' + - $ref: '#/components/schemas/EqlOptionalFields' + EqlRuleUpdateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/EqlRuleCreateFields' + ErrorSchema: + additionalProperties: false + type: object + properties: + error: + type: object + properties: + message: + type: string + status_code: + minimum: 400 + type: integer + required: + - status_code + - message + id: + type: string + item_id: + minLength: 1 + type: string + list_id: + minLength: 1 + type: string + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + required: + - error + EsqlQueryLanguage: + enum: + - esql + type: string + EsqlRule: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - version + - tags + - enabled + - risk_score_mapping + - severity_mapping + - interval + - from + - to + - actions + - exceptions_list + - author + - false_positives + - references + - max_signals + - threat + - setup + - related_integrations + - required_fields + - $ref: '#/components/schemas/ResponseFields' + - $ref: '#/components/schemas/EsqlRuleResponseFields' + EsqlRuleCreateFields: + allOf: + - $ref: '#/components/schemas/EsqlRuleOptionalFields' + - $ref: '#/components/schemas/EsqlRuleRequiredFields' + EsqlRuleCreateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/EsqlRuleCreateFields' + EsqlRuleOptionalFields: + type: object + properties: + alert_suppression: + $ref: '#/components/schemas/AlertSuppression' + EsqlRulePatchProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + language: + $ref: '#/components/schemas/EsqlQueryLanguage' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + query: + $ref: '#/components/schemas/RuleQuery' + description: ESQL query to execute + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + type: + description: Rule type + enum: + - esql + type: string + version: + $ref: '#/components/schemas/RuleVersion' + - $ref: '#/components/schemas/EsqlRuleOptionalFields' + EsqlRuleRequiredFields: + type: object + properties: + language: + $ref: '#/components/schemas/EsqlQueryLanguage' + query: + $ref: '#/components/schemas/RuleQuery' + description: ESQL query to execute + type: + description: Rule type + enum: + - esql + type: string + required: + - type + - language + - query + EsqlRuleResponseFields: + allOf: + - $ref: '#/components/schemas/EsqlRuleOptionalFields' + - $ref: '#/components/schemas/EsqlRuleRequiredFields' + EsqlRuleUpdateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/EsqlRuleCreateFields' + EventCategoryOverride: + type: string + ExceptionListType: + description: The exception type + enum: + - detection + - rule_default + - endpoint + - endpoint_trusted_apps + - endpoint_events + - endpoint_host_isolation_exceptions + - endpoint_blocklists + type: string + ExternalRuleSource: + description: >- + Type of rule source for externally sourced rules, i.e. rules that have + an external source, such as the Elastic Prebuilt rules repo. + type: object + properties: + is_customized: + $ref: '#/components/schemas/IsExternalRuleCustomized' + type: + enum: + - external + type: string + required: + - type + - is_customized + FindRulesSortField: + 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 + type: string + HistoryWindowStart: + $ref: '#/components/schemas/NonEmptyString' + IndexPatternArray: + items: + type: string + type: array + InternalRuleSource: + description: >- + Type of rule source for internally sourced rules, i.e. created within + the Kibana apps. + type: object + properties: + type: + enum: + - internal + type: string + required: + - type + InvestigationFields: + type: object + properties: + field_names: + items: + $ref: '#/components/schemas/NonEmptyString' + minItems: 1 + type: array + required: + - field_names + InvestigationGuide: + description: Notes to help investigate alerts produced by the rule. + type: string + IsExternalRuleCustomized: + description: >- + Determines whether an external/prebuilt rule has been customized by the + user (i.e. any of its fields have been modified and diverged from the + base value). + type: boolean + IsRuleEnabled: + description: Determines whether the rule is enabled. + type: boolean + IsRuleImmutable: + deprecated: true + description: >- + This field determines whether the rule is a prebuilt Elastic rule. It + will be replaced with the `rule_source` field. + type: boolean + ItemsPerSearch: + minimum: 1 + type: integer + KqlQueryLanguage: + enum: + - kuery + - lucene + type: string + MachineLearningJobId: + description: Machine learning job ID + oneOf: + - type: string + - items: + type: string + minItems: 1 + type: array + MachineLearningRule: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - version + - tags + - enabled + - risk_score_mapping + - severity_mapping + - interval + - from + - to + - actions + - exceptions_list + - author + - false_positives + - references + - max_signals + - threat + - setup + - related_integrations + - required_fields + - $ref: '#/components/schemas/ResponseFields' + - $ref: '#/components/schemas/MachineLearningRuleResponseFields' + MachineLearningRuleCreateFields: + $ref: '#/components/schemas/MachineLearningRuleRequiredFields' + MachineLearningRuleCreateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/MachineLearningRuleCreateFields' + MachineLearningRulePatchFields: + type: object + properties: + anomaly_threshold: + $ref: '#/components/schemas/AnomalyThreshold' + machine_learning_job_id: + $ref: '#/components/schemas/MachineLearningJobId' + type: + description: Rule type + enum: + - machine_learning + type: string + MachineLearningRulePatchProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + - $ref: '#/components/schemas/MachineLearningRulePatchFields' + MachineLearningRuleRequiredFields: + type: object + properties: + anomaly_threshold: + $ref: '#/components/schemas/AnomalyThreshold' + machine_learning_job_id: + $ref: '#/components/schemas/MachineLearningJobId' + type: + description: Rule type + enum: + - machine_learning + type: string + required: + - type + - machine_learning_job_id + - anomaly_threshold + MachineLearningRuleResponseFields: + $ref: '#/components/schemas/MachineLearningRuleRequiredFields' + MachineLearningRuleUpdateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/MachineLearningRuleCreateFields' + MaxSignals: + minimum: 1 + type: integer + NewTermsFields: + items: + type: string + maxItems: 3 + minItems: 1 + type: array + NewTermsRule: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - version + - tags + - enabled + - risk_score_mapping + - severity_mapping + - interval + - from + - to + - actions + - exceptions_list + - author + - false_positives + - references + - max_signals + - threat + - setup + - related_integrations + - required_fields + - $ref: '#/components/schemas/ResponseFields' + - $ref: '#/components/schemas/NewTermsRuleResponseFields' + NewTermsRuleCreateFields: + allOf: + - $ref: '#/components/schemas/NewTermsRuleRequiredFields' + - $ref: '#/components/schemas/NewTermsRuleOptionalFields' + - $ref: '#/components/schemas/NewTermsRuleDefaultableFields' + NewTermsRuleCreateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/NewTermsRuleCreateFields' + NewTermsRuleDefaultableFields: + type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + NewTermsRuleOptionalFields: + type: object + properties: + alert_suppression: + $ref: '#/components/schemas/AlertSuppression' + data_view_id: + $ref: '#/components/schemas/DataViewId' + filters: + $ref: '#/components/schemas/RuleFilterArray' + index: + $ref: '#/components/schemas/IndexPatternArray' + NewTermsRulePatchFields: + allOf: + - type: object + properties: + history_window_start: + $ref: '#/components/schemas/HistoryWindowStart' + new_terms_fields: + $ref: '#/components/schemas/NewTermsFields' + query: + $ref: '#/components/schemas/RuleQuery' + type: + description: Rule type + enum: + - new_terms + type: string + - $ref: '#/components/schemas/NewTermsRuleOptionalFields' + - $ref: '#/components/schemas/NewTermsRuleDefaultableFields' + NewTermsRulePatchProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + - $ref: '#/components/schemas/NewTermsRulePatchFields' + NewTermsRuleRequiredFields: + type: object + properties: + history_window_start: + $ref: '#/components/schemas/HistoryWindowStart' + new_terms_fields: + $ref: '#/components/schemas/NewTermsFields' + query: + $ref: '#/components/schemas/RuleQuery' + type: + description: Rule type + enum: + - new_terms + type: string + required: + - type + - query + - new_terms_fields + - history_window_start + NewTermsRuleResponseFields: + allOf: + - $ref: '#/components/schemas/NewTermsRuleRequiredFields' + - $ref: '#/components/schemas/NewTermsRuleOptionalFields' + - type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + required: + - language + NewTermsRuleUpdateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/NewTermsRuleCreateFields' + NonEmptyString: + description: A string that is not empty and does not contain only whitespace + minLength: 1 + pattern: ^(?! *$).+$ + type: string + NormalizedRuleAction: + additionalProperties: false + type: object + properties: + alerts_filter: + $ref: '#/components/schemas/RuleActionAlertsFilter' + frequency: + $ref: '#/components/schemas/RuleActionFrequency' + group: + $ref: '#/components/schemas/RuleActionGroup' + id: + $ref: '#/components/schemas/RuleActionId' + params: + $ref: '#/components/schemas/RuleActionParams' + required: + - group + - id + - params + NormalizedRuleError: + type: object + properties: + err_code: + $ref: '#/components/schemas/BulkActionsDryRunErrCode' + message: + type: string + rules: + items: + $ref: '#/components/schemas/RuleDetailsInError' + type: array + status_code: + type: integer + required: + - message + - status_code + - rules + OsqueryParams: + type: object + properties: + ecs_mapping: + $ref: '#/components/schemas/EcsMapping' + pack_id: + type: string + queries: + items: + $ref: '#/components/schemas/OsqueryQuery' + type: array + query: + type: string + saved_query_id: + type: string + timeout: + type: number + OsqueryQuery: + type: object + properties: + ecs_mapping: + $ref: '#/components/schemas/EcsMapping' + id: + description: Query ID + type: string + platform: + type: string + query: + description: Query to execute + type: string + removed: + type: boolean + snapshot: + type: boolean + version: + description: Query version + type: string + required: + - id + - query + OsqueryResponseAction: + type: object + properties: + action_type_id: + enum: + - .osquery + type: string + params: + $ref: '#/components/schemas/OsqueryParams' + required: + - action_type_id + - params + ProcessesParams: + type: object + properties: + command: + enum: + - kill-process + - suspend-process + type: string + comment: + type: string + config: + type: object + properties: + field: + description: Field to use instead of process.pid + type: string + overwrite: + default: true + description: Whether to overwrite field with process.pid + type: boolean + required: + - field + required: + - command + - config + QueryRule: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - version + - tags + - enabled + - risk_score_mapping + - severity_mapping + - interval + - from + - to + - actions + - exceptions_list + - author + - false_positives + - references + - max_signals + - threat + - setup + - related_integrations + - required_fields + - $ref: '#/components/schemas/ResponseFields' + - $ref: '#/components/schemas/QueryRuleResponseFields' + QueryRuleCreateFields: + allOf: + - $ref: '#/components/schemas/QueryRuleRequiredFields' + - $ref: '#/components/schemas/QueryRuleOptionalFields' + - $ref: '#/components/schemas/QueryRuleDefaultableFields' + QueryRuleCreateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/QueryRuleCreateFields' + QueryRuleDefaultableFields: + type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + query: + $ref: '#/components/schemas/RuleQuery' + QueryRuleOptionalFields: + type: object + properties: + alert_suppression: + $ref: '#/components/schemas/AlertSuppression' + data_view_id: + $ref: '#/components/schemas/DataViewId' + filters: + $ref: '#/components/schemas/RuleFilterArray' + index: + $ref: '#/components/schemas/IndexPatternArray' + response_actions: + items: + $ref: '#/components/schemas/ResponseAction' + type: array + saved_id: + $ref: '#/components/schemas/SavedQueryId' + QueryRulePatchFields: + allOf: + - type: object + properties: + type: + description: Rule type + enum: + - query + type: string + - $ref: '#/components/schemas/QueryRuleOptionalFields' + - $ref: '#/components/schemas/QueryRuleDefaultableFields' + QueryRulePatchProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + - $ref: '#/components/schemas/QueryRulePatchFields' + QueryRuleRequiredFields: + type: object + properties: + type: + description: Rule type + enum: + - query + type: string + required: + - type + QueryRuleResponseFields: + allOf: + - $ref: '#/components/schemas/QueryRuleRequiredFields' + - $ref: '#/components/schemas/QueryRuleOptionalFields' + - type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + query: + $ref: '#/components/schemas/RuleQuery' + required: + - query + - language + QueryRuleUpdateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/QueryRuleCreateFields' + RelatedIntegration: + type: object + properties: + integration: + $ref: '#/components/schemas/NonEmptyString' + package: + $ref: '#/components/schemas/NonEmptyString' + version: + $ref: '#/components/schemas/NonEmptyString' + required: + - package + - version + RelatedIntegrationArray: + items: + $ref: '#/components/schemas/RelatedIntegration' + type: array + RequiredField: + description: Describes an Elasticsearch field that is needed for the rule to function + type: object + properties: + ecs: + description: Whether the field is an ECS field + type: boolean + name: + $ref: '#/components/schemas/NonEmptyString' + description: Name of an Elasticsearch field + type: + $ref: '#/components/schemas/NonEmptyString' + description: Type of the Elasticsearch field + required: + - name + - type + - ecs + RequiredFieldArray: + items: + $ref: '#/components/schemas/RequiredField' + type: array + RequiredFieldInput: + description: >- + Input parameters to create a RequiredField. Does not include the `ecs` + field, because `ecs` is calculated on the backend based on the field + name and type. + type: object + properties: + name: + $ref: '#/components/schemas/NonEmptyString' + description: Name of an Elasticsearch field + type: + $ref: '#/components/schemas/NonEmptyString' + description: Type of an Elasticsearch field + required: + - name + - type + ResponseAction: + oneOf: + - $ref: '#/components/schemas/OsqueryResponseAction' + - $ref: '#/components/schemas/EndpointResponseAction' + ResponseFields: + type: object + properties: + created_at: + format: date-time + type: string + created_by: + type: string + execution_summary: + $ref: '#/components/schemas/RuleExecutionSummary' + id: + $ref: '#/components/schemas/RuleObjectId' + immutable: + $ref: '#/components/schemas/IsRuleImmutable' + required_fields: + $ref: '#/components/schemas/RequiredFieldArray' + revision: + minimum: 0 + type: integer + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_source: + $ref: '#/components/schemas/RuleSource' + updated_at: + format: date-time + type: string + updated_by: + type: string + required: + - id + - rule_id + - immutable + - updated_at + - updated_by + - created_at + - created_by + - revision + - related_integrations + - required_fields + RiskScore: + description: Risk score (0 to 100) + maximum: 100 + minimum: 0 + type: integer + RiskScoreMapping: + description: >- + Overrides generated alerts' risk_score with a value from the source + event + items: + type: object + properties: + field: + type: string + operator: + enum: + - equals + type: string + risk_score: + $ref: '#/components/schemas/RiskScore' + value: + type: string + required: + - field + - operator + - value + type: array + RuleAction: + type: object + properties: + action_type_id: + description: The action type used for sending notifications. + type: string + alerts_filter: + $ref: '#/components/schemas/RuleActionAlertsFilter' + frequency: + $ref: '#/components/schemas/RuleActionFrequency' + group: + $ref: '#/components/schemas/RuleActionGroup' + id: + $ref: '#/components/schemas/RuleActionId' + params: + $ref: '#/components/schemas/RuleActionParams' + uuid: + $ref: '#/components/schemas/NonEmptyString' + required: + - action_type_id + - group + - id + - params + RuleActionAlertsFilter: + additionalProperties: true + type: object + RuleActionFrequency: + description: >- + The action frequency defines when the action runs (for example, only on + rule execution or at specific time intervals). + type: object + properties: + notifyWhen: + $ref: '#/components/schemas/RuleActionNotifyWhen' + summary: + description: >- + Action summary indicates whether we will send a summary notification + about all the generate alerts or notification per individual alert + type: boolean + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + nullable: true + required: + - summary + - notifyWhen + - throttle + RuleActionGroup: + description: >- + Optionally groups actions by use cases. Use `default` for alert + notifications. + type: string + RuleActionId: + description: The connector ID. + type: string + RuleActionNotifyWhen: + description: >- + The condition for throttling the notification: `onActionGroupChange`, + `onActiveAlert`, or `onThrottleInterval` + enum: + - onActiveAlert + - onThrottleInterval + - onActionGroupChange + type: string + RuleActionParams: + additionalProperties: true + description: >- + Object containing the allowed connector fields, which varies according + to the connector type. + type: object + RuleActionThrottle: + description: Defines the interval on which a rule's actions are executed. + oneOf: + - enum: + - no_actions + - rule + type: string + - description: 'Time interval in seconds, minutes, hours, or days.' + example: 1h + pattern: '^[1-9]\d*[smhd]$' + type: string + RuleAuthorArray: + items: + type: string + type: array + RuleCreateProps: + anyOf: + - $ref: '#/components/schemas/EqlRuleCreateProps' + - $ref: '#/components/schemas/QueryRuleCreateProps' + - $ref: '#/components/schemas/SavedQueryRuleCreateProps' + - $ref: '#/components/schemas/ThresholdRuleCreateProps' + - $ref: '#/components/schemas/ThreatMatchRuleCreateProps' + - $ref: '#/components/schemas/MachineLearningRuleCreateProps' + - $ref: '#/components/schemas/NewTermsRuleCreateProps' + - $ref: '#/components/schemas/EsqlRuleCreateProps' + discriminator: + propertyName: type + RuleDescription: + minLength: 1 + type: string + RuleDetailsInError: + type: object + properties: + id: + type: string + name: + type: string + required: + - id + RuleExceptionList: + type: object + properties: + id: + $ref: '#/components/schemas/NonEmptyString' + description: ID of the exception container + list_id: + $ref: '#/components/schemas/NonEmptyString' + description: List ID of the exception container + namespace_type: + description: Determines the exceptions validity in rule's Kibana space + enum: + - agnostic + - single + type: string + type: + $ref: '#/components/schemas/ExceptionListType' + required: + - id + - list_id + - type + - namespace_type + RuleExecutionMetrics: + type: object + properties: + execution_gap_duration_s: + description: Duration in seconds of execution gap + minimum: 0 + type: integer + total_enrichment_duration_ms: + description: >- + Total time spent enriching documents during current rule execution + cycle + minimum: 0 + type: integer + total_indexing_duration_ms: + description: >- + Total time spent indexing documents during current rule execution + cycle + minimum: 0 + type: integer + total_search_duration_ms: + description: >- + Total time spent performing ES searches as measured by Kibana; + includes network latency and time spent serializing/deserializing + request/response + minimum: 0 + type: integer + RuleExecutionStatus: + description: >- + Custom execution status of Security rules that is different from the + status used in the Alerting Framework. We merge our custom status with + the Framework's status to determine the resulting status of a rule. + + - going to run - @deprecated Replaced by the 'running' status but left + for backwards compatibility with rule execution events already written + to Event Log in the prior versions of Kibana. Don't use when writing + rule status changes. + + - running - Rule execution started but not reached any intermediate or + final status. + + - partial failure - Rule can partially fail for various reasons either + in the middle of an execution (in this case we update its status right + away) or in the end of it. So currently this status can be both + intermediate and final at the same time. A typical reason for a partial + failure: not all the indices that the rule searches over actually exist. + + - failed - Rule failed to execute due to unhandled exception or a reason + defined in the business logic of its executor function. + + - succeeded - Rule executed successfully without any issues. Note: this + status is just an indication of a rule's "health". The rule might or + might not generate any alerts despite of it. + enum: + - going to run + - running + - partial failure + - failed + - succeeded + type: string + RuleExecutionStatusOrder: + type: integer + RuleExecutionSummary: + type: object + properties: + last_execution: + type: object + properties: + date: + description: Date of the last execution + format: date-time + type: string + message: + type: string + metrics: + $ref: '#/components/schemas/RuleExecutionMetrics' + status: + $ref: '#/components/schemas/RuleExecutionStatus' + description: Status of the last execution + status_order: + $ref: '#/components/schemas/RuleExecutionStatusOrder' + required: + - date + - status + - status_order + - message + - metrics + required: + - last_execution + RuleFalsePositiveArray: + items: + type: string + type: array + RuleFilterArray: + items: {} + type: array + RuleInterval: + description: >- + Frequency of rule execution, using a date math range. For example, "1h" + means the rule runs every hour. Defaults to 5m (5 minutes). + type: string + RuleIntervalFrom: + description: >- + Time from which data is analyzed each time the rule executes, using a + date math range. For example, now-4200s means the rule analyzes data + from 70 minutes before its start time. Defaults to now-6m (analyzes data + from 6 minutes before the start time). + format: date-math + type: string + RuleIntervalTo: + type: string + RuleLicense: + description: The rule's license. + type: string + RuleMetadata: + additionalProperties: true + type: object + RuleName: + minLength: 1 + type: string + RuleNameOverride: + description: Sets the source field for the alert's signal.rule.name value + type: string + RuleObjectId: + $ref: '#/components/schemas/UUID' + RulePatchProps: + anyOf: + - $ref: '#/components/schemas/EqlRulePatchProps' + - $ref: '#/components/schemas/QueryRulePatchProps' + - $ref: '#/components/schemas/SavedQueryRulePatchProps' + - $ref: '#/components/schemas/ThresholdRulePatchProps' + - $ref: '#/components/schemas/ThreatMatchRulePatchProps' + - $ref: '#/components/schemas/MachineLearningRulePatchProps' + - $ref: '#/components/schemas/NewTermsRulePatchProps' + - $ref: '#/components/schemas/EsqlRulePatchProps' + RuleQuery: + type: string + RuleReferenceArray: + items: + type: string + type: array + RuleResponse: + anyOf: + - $ref: '#/components/schemas/EqlRule' + - $ref: '#/components/schemas/QueryRule' + - $ref: '#/components/schemas/SavedQueryRule' + - $ref: '#/components/schemas/ThresholdRule' + - $ref: '#/components/schemas/ThreatMatchRule' + - $ref: '#/components/schemas/MachineLearningRule' + - $ref: '#/components/schemas/NewTermsRule' + - $ref: '#/components/schemas/EsqlRule' + discriminator: + propertyName: type + RuleSignatureId: + description: 'Could be any string, not necessarily a UUID' + type: string + RuleSource: + description: >- + Discriminated union that determines whether the rule is internally + sourced (created within the Kibana app) or has an external source, such + as the Elastic Prebuilt rules repo. + discriminator: + propertyName: type + oneOf: + - $ref: '#/components/schemas/ExternalRuleSource' + - $ref: '#/components/schemas/InternalRuleSource' + RuleTagArray: + description: >- + String array containing words and phrases to help categorize, filter, + and search rules. Defaults to an empty array. + items: + type: string + type: array + RuleUpdateProps: + anyOf: + - $ref: '#/components/schemas/EqlRuleUpdateProps' + - $ref: '#/components/schemas/QueryRuleUpdateProps' + - $ref: '#/components/schemas/SavedQueryRuleUpdateProps' + - $ref: '#/components/schemas/ThresholdRuleUpdateProps' + - $ref: '#/components/schemas/ThreatMatchRuleUpdateProps' + - $ref: '#/components/schemas/MachineLearningRuleUpdateProps' + - $ref: '#/components/schemas/NewTermsRuleUpdateProps' + - $ref: '#/components/schemas/EsqlRuleUpdateProps' + discriminator: + propertyName: type + RuleVersion: + description: The rule's version number. + minimum: 1 + type: integer + SavedObjectResolveAliasPurpose: + enum: + - savedObjectConversion + - savedObjectImport + type: string + SavedObjectResolveAliasTargetId: + type: string + SavedObjectResolveOutcome: + enum: + - exactMatch + - aliasMatch + - conflict + type: string + SavedQueryId: + type: string + SavedQueryRule: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - version + - tags + - enabled + - risk_score_mapping + - severity_mapping + - interval + - from + - to + - actions + - exceptions_list + - author + - false_positives + - references + - max_signals + - threat + - setup + - related_integrations + - required_fields + - $ref: '#/components/schemas/ResponseFields' + - $ref: '#/components/schemas/SavedQueryRuleResponseFields' + SavedQueryRuleCreateFields: + allOf: + - $ref: '#/components/schemas/SavedQueryRuleRequiredFields' + - $ref: '#/components/schemas/SavedQueryRuleOptionalFields' + - $ref: '#/components/schemas/SavedQueryRuleDefaultableFields' + SavedQueryRuleCreateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/SavedQueryRuleCreateFields' + SavedQueryRuleDefaultableFields: + type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + SavedQueryRuleOptionalFields: + type: object + properties: + alert_suppression: + $ref: '#/components/schemas/AlertSuppression' + data_view_id: + $ref: '#/components/schemas/DataViewId' + filters: + $ref: '#/components/schemas/RuleFilterArray' + index: + $ref: '#/components/schemas/IndexPatternArray' + query: + $ref: '#/components/schemas/RuleQuery' + response_actions: + items: + $ref: '#/components/schemas/ResponseAction' + type: array + SavedQueryRulePatchFields: + allOf: + - type: object + properties: + saved_id: + $ref: '#/components/schemas/SavedQueryId' + type: + description: Rule type + enum: + - saved_query + type: string + - $ref: '#/components/schemas/SavedQueryRuleOptionalFields' + - $ref: '#/components/schemas/SavedQueryRuleDefaultableFields' + SavedQueryRulePatchProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + - $ref: '#/components/schemas/SavedQueryRulePatchFields' + SavedQueryRuleRequiredFields: + type: object + properties: + saved_id: + $ref: '#/components/schemas/SavedQueryId' + type: + description: Rule type + enum: + - saved_query + type: string + required: + - type + - saved_id + SavedQueryRuleResponseFields: + allOf: + - $ref: '#/components/schemas/SavedQueryRuleRequiredFields' + - $ref: '#/components/schemas/SavedQueryRuleOptionalFields' + - type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + required: + - language + SavedQueryRuleUpdateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/SavedQueryRuleCreateFields' + SetupGuide: + type: string + Severity: + description: Severity of the rule + enum: + - low + - medium + - high + - critical + type: string + SeverityMapping: + description: Overrides generated alerts' severity with values from the source event + items: + type: object + properties: + field: + type: string + operator: + enum: + - equals + type: string + severity: + $ref: '#/components/schemas/Severity' + value: + type: string + required: + - field + - operator + - severity + - value + type: array + SortOrder: + enum: + - asc + - desc + type: string + Threat: + type: object + properties: + framework: + description: Relevant attack framework + type: string + tactic: + $ref: '#/components/schemas/ThreatTactic' + technique: + description: Array containing information on the attack techniques (optional) + items: + $ref: '#/components/schemas/ThreatTechnique' + type: array + required: + - framework + - tactic + ThreatArray: + items: + $ref: '#/components/schemas/Threat' + type: array + ThreatFilters: + items: + description: >- + Query and filter context array used to filter documents from the + Elasticsearch index containing the threat values + type: array + ThreatIndex: + items: + type: string + type: array + ThreatIndicatorPath: + description: >- + Defines the path to the threat indicator in the indicator documents + (optional) + type: string + ThreatMapping: + items: + type: object + properties: + entries: + items: + type: object + properties: + field: + $ref: '#/components/schemas/NonEmptyString' + type: + enum: + - mapping + type: string + value: + $ref: '#/components/schemas/NonEmptyString' + required: + - field + - type + - value + type: array + required: + - entries + minItems: 1 + type: array + ThreatMatchRule: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - version + - tags + - enabled + - risk_score_mapping + - severity_mapping + - interval + - from + - to + - actions + - exceptions_list + - author + - false_positives + - references + - max_signals + - threat + - setup + - related_integrations + - required_fields + - $ref: '#/components/schemas/ResponseFields' + - $ref: '#/components/schemas/ThreatMatchRuleResponseFields' + ThreatMatchRuleCreateFields: + allOf: + - $ref: '#/components/schemas/ThreatMatchRuleRequiredFields' + - $ref: '#/components/schemas/ThreatMatchRuleOptionalFields' + - $ref: '#/components/schemas/ThreatMatchRuleDefaultableFields' + ThreatMatchRuleCreateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/ThreatMatchRuleCreateFields' + ThreatMatchRuleDefaultableFields: + type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + ThreatMatchRuleOptionalFields: + type: object + properties: + alert_suppression: + $ref: '#/components/schemas/AlertSuppression' + concurrent_searches: + $ref: '#/components/schemas/ConcurrentSearches' + data_view_id: + $ref: '#/components/schemas/DataViewId' + filters: + $ref: '#/components/schemas/RuleFilterArray' + index: + $ref: '#/components/schemas/IndexPatternArray' + items_per_search: + $ref: '#/components/schemas/ItemsPerSearch' + saved_id: + $ref: '#/components/schemas/SavedQueryId' + threat_filters: + $ref: '#/components/schemas/ThreatFilters' + threat_indicator_path: + $ref: '#/components/schemas/ThreatIndicatorPath' + threat_language: + $ref: '#/components/schemas/KqlQueryLanguage' + ThreatMatchRulePatchFields: + allOf: + - type: object + properties: + query: + $ref: '#/components/schemas/RuleQuery' + threat_index: + $ref: '#/components/schemas/ThreatIndex' + threat_mapping: + $ref: '#/components/schemas/ThreatMapping' + threat_query: + $ref: '#/components/schemas/ThreatQuery' + type: + description: Rule type + enum: + - threat_match + type: string + - $ref: '#/components/schemas/ThreatMatchRuleOptionalFields' + - $ref: '#/components/schemas/ThreatMatchRuleDefaultableFields' + ThreatMatchRulePatchProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + - $ref: '#/components/schemas/ThreatMatchRulePatchFields' + ThreatMatchRuleRequiredFields: + type: object + properties: + query: + $ref: '#/components/schemas/RuleQuery' + threat_index: + $ref: '#/components/schemas/ThreatIndex' + threat_mapping: + $ref: '#/components/schemas/ThreatMapping' + threat_query: + $ref: '#/components/schemas/ThreatQuery' + type: + description: Rule type + enum: + - threat_match + type: string + required: + - type + - query + - threat_query + - threat_mapping + - threat_index + ThreatMatchRuleResponseFields: + allOf: + - $ref: '#/components/schemas/ThreatMatchRuleRequiredFields' + - $ref: '#/components/schemas/ThreatMatchRuleOptionalFields' + - type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + required: + - language + ThreatMatchRuleUpdateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/ThreatMatchRuleCreateFields' + ThreatQuery: + description: Query to execute + type: string + ThreatSubtechnique: + type: object + properties: + id: + description: Subtechnique ID + type: string + name: + description: Subtechnique name + type: string + reference: + description: Subtechnique reference + type: string + required: + - id + - name + - reference + ThreatTactic: + type: object + properties: + id: + description: Tactic ID + type: string + name: + description: Tactic name + type: string + reference: + description: Tactic reference + type: string + required: + - id + - name + - reference + ThreatTechnique: + type: object + properties: + id: + description: Technique ID + type: string + name: + description: Technique name + type: string + reference: + description: Technique reference + type: string + subtechnique: + description: Array containing more specific information on the attack technique + items: + $ref: '#/components/schemas/ThreatSubtechnique' + type: array + required: + - id + - name + - reference + Threshold: + type: object + properties: + cardinality: + $ref: '#/components/schemas/ThresholdCardinality' + field: + $ref: '#/components/schemas/ThresholdField' + value: + $ref: '#/components/schemas/ThresholdValue' + required: + - field + - value + ThresholdAlertSuppression: + type: object + properties: + duration: + $ref: '#/components/schemas/AlertSuppressionDuration' + required: + - duration + ThresholdCardinality: + items: + type: object + properties: + field: + type: string + value: + minimum: 0 + type: integer + required: + - field + - value + type: array + ThresholdField: + description: Field to aggregate on + oneOf: + - type: string + - items: + type: string + type: array + ThresholdRule: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - version + - tags + - enabled + - risk_score_mapping + - severity_mapping + - interval + - from + - to + - actions + - exceptions_list + - author + - false_positives + - references + - max_signals + - threat + - setup + - related_integrations + - required_fields + - $ref: '#/components/schemas/ResponseFields' + - $ref: '#/components/schemas/ThresholdRuleResponseFields' + ThresholdRuleCreateFields: + allOf: + - $ref: '#/components/schemas/ThresholdRuleRequiredFields' + - $ref: '#/components/schemas/ThresholdRuleOptionalFields' + - $ref: '#/components/schemas/ThresholdRuleDefaultableFields' + ThresholdRuleCreateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/ThresholdRuleCreateFields' + ThresholdRuleDefaultableFields: + type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + ThresholdRuleOptionalFields: + type: object + properties: + alert_suppression: + $ref: '#/components/schemas/ThresholdAlertSuppression' + data_view_id: + $ref: '#/components/schemas/DataViewId' + filters: + $ref: '#/components/schemas/RuleFilterArray' + index: + $ref: '#/components/schemas/IndexPatternArray' + saved_id: + $ref: '#/components/schemas/SavedQueryId' + ThresholdRulePatchFields: + allOf: + - type: object + properties: + query: + $ref: '#/components/schemas/RuleQuery' + threshold: + $ref: '#/components/schemas/Threshold' + type: + description: Rule type + enum: + - threshold + type: string + - $ref: '#/components/schemas/ThresholdRuleOptionalFields' + - $ref: '#/components/schemas/ThresholdRuleDefaultableFields' + ThresholdRulePatchProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + - $ref: '#/components/schemas/ThresholdRulePatchFields' + ThresholdRuleRequiredFields: + type: object + properties: + query: + $ref: '#/components/schemas/RuleQuery' + threshold: + $ref: '#/components/schemas/Threshold' + type: + description: Rule type + enum: + - threshold + type: string + required: + - type + - query + - threshold + ThresholdRuleResponseFields: + allOf: + - $ref: '#/components/schemas/ThresholdRuleRequiredFields' + - $ref: '#/components/schemas/ThresholdRuleOptionalFields' + - type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + required: + - language + ThresholdRuleUpdateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/ThresholdRuleCreateFields' + ThresholdValue: + description: Threshold value + minimum: 1 + type: integer + ThrottleForBulkActions: + description: >- + The condition for throttling the notification: 'rule', 'no_actions', or + time duration + enum: + - rule + - 1h + - 1d + - 7d + type: string + TiebreakerField: + description: Sets a secondary field for sorting events + type: string + TimelineTemplateId: + description: Timeline template ID + type: string + TimelineTemplateTitle: + description: Timeline template title + type: string + TimestampField: + description: Contains the event timestamp used for sorting a sequence of events + type: string + TimestampOverride: + description: Sets the time field used to query indices + type: string + TimestampOverrideFallbackDisabled: + description: Disables the fallback to the event's @timestamp field + type: boolean + UUID: + description: A universally unique identifier + format: uuid + type: string + WarningSchema: + type: object + properties: + actionPath: + type: string + buttonLabel: + type: string + message: + type: string + type: + type: string + required: + - type + - message + - actionPath + securitySchemes: + BasicAuth: + scheme: basic + type: http +security: + - BasicAuth: [] diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_detections_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_detections_api_2023_10_31.bundled.schema.yaml new file mode 100644 index 0000000000000..94682a8e1b8f9 --- /dev/null +++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_detections_api_2023_10_31.bundled.schema.yaml @@ -0,0 +1,5485 @@ +openapi: 3.0.3 +info: + description: >- + You can create rules that automatically turn events and external alerts sent + to Elastic Security into detection alerts. These alerts are displayed on the + Detections page. + title: Security Solution Detections API (Elastic Cloud Serverless) + version: '2023-10-31' +servers: + - url: 'http://{kibana_host}:{port}' + variables: + kibana_host: + default: localhost + port: + default: '5601' +paths: + /api/detection_engine/rules: + delete: + description: Deletes a single rule using the `rule_id` or `id` field. + operationId: DeleteRule + parameters: + - description: The rule's `id` value. + in: query + name: id + required: false + schema: + $ref: '#/components/schemas/RuleObjectId' + - description: The rule's `rule_id` value. + in: query + name: rule_id + required: false + schema: + $ref: '#/components/schemas/RuleSignatureId' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RuleResponse' + description: Indicates a successful call. + tags: + - Rules API + get: + description: Read a single rule + operationId: ReadRule + parameters: + - description: The rule's `id` value. + in: query + name: id + required: false + schema: + $ref: '#/components/schemas/RuleObjectId' + - description: The rule's `rule_id` value. + in: query + name: rule_id + required: false + schema: + $ref: '#/components/schemas/RuleSignatureId' + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RuleResponse' + description: Indicates a successful call. + tags: + - Rules API + patch: + description: Patch a single rule + operationId: PatchRule + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RulePatchProps' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RuleResponse' + description: Indicates a successful call. + tags: + - Rules API + post: + description: Create a single detection rule + operationId: CreateRule + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RuleCreateProps' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RuleResponse' + description: Indicates a successful call. + tags: + - Rules API + put: + description: Update a single rule + operationId: UpdateRule + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RuleUpdateProps' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RuleResponse' + description: Indicates a successful call. + tags: + - Rules API + /api/detection_engine/rules/_bulk_action: + post: + description: >- + The bulk action is applied to all rules that match the filter or to the + list of rules by their IDs. + operationId: PerformBulkAction + parameters: + - description: Enables dry run mode for the request call. + in: query + name: dry_run + required: false + schema: + type: boolean + requestBody: + content: + application/json: + schema: + 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': + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/BulkEditActionResponse' + - $ref: '#/components/schemas/BulkExportActionResponse' + description: OK + summary: Applies a bulk action to multiple rules + tags: + - Bulk API + /api/detection_engine/rules/_export: + post: + description: >- + Exports rules to an `.ndjson` file. The following configuration items + are also included in the `.ndjson` file - Actions, Exception lists. + Prebuilt rules cannot be exported. + operationId: ExportRules + parameters: + - description: Determines whether a summary of the exported rules is returned. + in: query + name: exclude_export_details + required: false + schema: + default: false + type: boolean + - description: File name for saving the exported rules. + in: query + name: file_name + required: false + schema: + default: export.ndjson + type: string + requestBody: + content: + application/json: + schema: + nullable: true + type: object + properties: + objects: + description: >- + Array of `rule_id` fields. Exports all rules when + unspecified. + items: + type: object + properties: + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + required: + - rule_id + type: array + required: + - objects + required: false + responses: + '200': + content: + application/ndjson: + schema: + description: An `.ndjson` file containing the returned rules. + format: binary + type: string + description: Indicates a successful call. + summary: Export rules + tags: + - Import/Export API + summary: Exports rules to an `.ndjson` file + /api/detection_engine/rules/_find: + get: + description: Finds rules that match the given query. + operationId: FindRules + parameters: + - in: query + name: fields + required: false + schema: + items: + type: string + type: array + - description: Search query + in: query + name: filter + required: false + schema: + type: string + - description: Field to sort by + in: query + name: sort_field + required: false + schema: + $ref: '#/components/schemas/FindRulesSortField' + - description: Sort order + in: query + name: sort_order + required: false + schema: + $ref: '#/components/schemas/SortOrder' + - description: Page number + in: query + name: page + required: false + schema: + default: 1 + minimum: 1 + type: integer + - description: Rules per page + in: query + name: per_page + required: false + schema: + default: 20 + minimum: 0 + type: integer + responses: + '200': + content: + application/json: + schema: + type: object + properties: + data: + items: + $ref: '#/components/schemas/RuleResponse' + type: array + page: + type: integer + perPage: + type: integer + total: + type: integer + required: + - page + - perPage + - total + - data + description: Successful response + tags: + - Rules API + /api/detection_engine/rules/_import: + post: + description: >- + Imports rules from an `.ndjson` file, including actions and exception + lists. + operationId: ImportRules + parameters: + - description: >- + Determines whether existing rules with the same `rule_id` are + overwritten. + in: query + name: overwrite + required: false + schema: + default: false + type: boolean + - description: >- + Determines whether existing exception lists with the same `list_id` + are overwritten. + in: query + name: overwrite_exceptions + required: false + schema: + default: false + type: boolean + - description: >- + Determines whether existing actions with the same + `kibana.alert.rule.actions.id` are overwritten. + in: query + name: overwrite_action_connectors + required: false + schema: + default: false + type: boolean + - description: Generates a new list ID for each imported exception list. + in: query + name: as_new_list + required: false + schema: + default: false + type: boolean + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + file: + description: The `.ndjson` file containing the rules. + format: binary + type: string + required: true + responses: + '200': + content: + application/json: + schema: + additionalProperties: false + type: object + properties: + action_connectors_errors: + items: + $ref: '#/components/schemas/ErrorSchema' + type: array + action_connectors_success: + type: boolean + action_connectors_success_count: + minimum: 0 + type: integer + action_connectors_warnings: + items: + $ref: '#/components/schemas/WarningSchema' + type: array + errors: + items: + $ref: '#/components/schemas/ErrorSchema' + type: array + exceptions_errors: + items: + $ref: '#/components/schemas/ErrorSchema' + type: array + exceptions_success: + type: boolean + exceptions_success_count: + minimum: 0 + type: integer + rules_count: + minimum: 0 + type: integer + success: + type: boolean + success_count: + minimum: 0 + type: integer + required: + - exceptions_success + - exceptions_success_count + - exceptions_errors + - rules_count + - success + - success_count + - errors + - action_connectors_errors + - action_connectors_warnings + - action_connectors_success + - action_connectors_success_count + description: Indicates a successful call. + summary: Import rules + tags: + - Import/Export API + summary: Imports rules from an `.ndjson` file + /api/detection_engine/signals/assignees: + post: + description: Assigns users to alerts. + operationId: SetAlertAssignees + requestBody: + content: + application/json: + schema: + type: object + properties: + assignees: + $ref: '#/components/schemas/AlertAssignees' + description: Details about the assignees to assign and unassign. + ids: + $ref: '#/components/schemas/AlertIds' + description: List of alerts ids to assign and unassign passed assignees. + required: + - assignees + - ids + required: true + responses: + '200': + description: Indicates a successful call. + '400': + description: Invalid request. + summary: Assigns users to alerts + /api/detection_engine/tags: + get: + operationId: ReadTags + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RuleTagArray' + description: Indicates a successful call + summary: Aggregates and returns all unique tags from all rules + tags: + - Tags API + summary: Aggregates and returns rule tags +components: + schemas: + AlertAssignees: + type: object + properties: + add: + description: A list of users ids to assign. + items: + $ref: '#/components/schemas/NonEmptyString' + type: array + remove: + description: A list of users ids to unassign. + items: + $ref: '#/components/schemas/NonEmptyString' + type: array + required: + - add + - remove + AlertIds: + description: A list of alerts ids. + items: + $ref: '#/components/schemas/NonEmptyString' + minItems: 1 + type: array + AlertsIndex: + deprecated: true + description: (deprecated) Has no effect. + type: string + AlertsIndexNamespace: + description: Has no effect. + type: string + AlertSuppression: + type: object + properties: + duration: + $ref: '#/components/schemas/AlertSuppressionDuration' + group_by: + $ref: '#/components/schemas/AlertSuppressionGroupBy' + missing_fields_strategy: + $ref: '#/components/schemas/AlertSuppressionMissingFieldsStrategy' + required: + - group_by + AlertSuppressionDuration: + type: object + properties: + unit: + enum: + - s + - m + - h + type: string + value: + minimum: 1 + type: integer + required: + - value + - unit + AlertSuppressionGroupBy: + items: + type: string + maxItems: 3 + minItems: 1 + type: array + AlertSuppressionMissingFieldsStrategy: + description: >- + Describes how alerts will be generated for documents with missing + suppress by fields: + + doNotSuppress - per each document a separate alert will be created + + suppress - only alert will be created per suppress by bucket + enum: + - doNotSuppress + - suppress + type: string + AnomalyThreshold: + description: Anomaly threshold + minimum: 0 + type: integer + BuildingBlockType: + description: >- + Determines if the rule acts as a building block. By default, + building-block alerts are not displayed in the UI. These rules are used + as a foundation for other rules that do generate alerts. Its value must + be default. + type: string + BulkActionEditPayload: + anyOf: + - $ref: '#/components/schemas/BulkActionEditPayloadTags' + - $ref: '#/components/schemas/BulkActionEditPayloadIndexPatterns' + - $ref: '#/components/schemas/BulkActionEditPayloadInvestigationFields' + - $ref: '#/components/schemas/BulkActionEditPayloadTimeline' + - $ref: '#/components/schemas/BulkActionEditPayloadRuleActions' + - $ref: '#/components/schemas/BulkActionEditPayloadSchedule' + BulkActionEditPayloadIndexPatterns: + type: object + properties: + overwrite_data_views: + type: boolean + type: + enum: + - add_index_patterns + - delete_index_patterns + - set_index_patterns + type: string + value: + $ref: '#/components/schemas/IndexPatternArray' + required: + - type + - value + BulkActionEditPayloadInvestigationFields: + type: object + properties: + type: + enum: + - add_investigation_fields + - delete_investigation_fields + - set_investigation_fields + type: string + value: + $ref: '#/components/schemas/InvestigationFields' + required: + - type + - value + BulkActionEditPayloadRuleActions: + type: object + properties: + type: + enum: + - add_rule_actions + - set_rule_actions + type: string + value: + type: object + properties: + actions: + items: + $ref: '#/components/schemas/NormalizedRuleAction' + type: array + throttle: + $ref: '#/components/schemas/ThrottleForBulkActions' + required: + - actions + required: + - type + - value + BulkActionEditPayloadSchedule: + type: object + properties: + type: + enum: + - set_schedule + type: string + value: + type: object + properties: + interval: + description: Interval in which the rule is executed + example: 1h + pattern: '^[1-9]\d*[smh]$' + type: string + lookback: + description: Lookback time for the rule + example: 1h + pattern: '^[1-9]\d*[smh]$' + type: string + required: + - interval + - lookback + required: + - type + - value + BulkActionEditPayloadTags: + type: object + properties: + type: + enum: + - add_tags + - delete_tags + - set_tags + type: string + value: + $ref: '#/components/schemas/RuleTagArray' + required: + - type + - value + BulkActionEditPayloadTimeline: + type: object + properties: + type: + enum: + - set_timeline + type: string + value: + type: object + properties: + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + required: + - timeline_id + - timeline_title + required: + - type + - value + BulkActionsDryRunErrCode: + enum: + - IMMUTABLE + - MACHINE_LEARNING_AUTH + - MACHINE_LEARNING_INDEX_PATTERN + - ESQL_INDEX_PATTERN + - INVESTIGATION_FIELDS_FEATURE + type: string + BulkActionSkipResult: + type: object + properties: + id: + type: string + name: + type: string + skip_reason: + $ref: '#/components/schemas/BulkEditSkipReason' + required: + - id + - skip_reason + BulkDeleteRules: + type: object + properties: + action: + enum: + - delete + type: string + ids: + description: Array of rule IDs + items: + type: string + minItems: 1 + type: array + query: + description: Query to filter rules + type: string + required: + - action + BulkDisableRules: + type: object + properties: + action: + enum: + - disable + type: string + ids: + description: Array of rule IDs + items: + type: string + minItems: 1 + type: array + query: + description: Query to filter rules + type: string + required: + - action + BulkDuplicateRules: + type: object + properties: + action: + enum: + - duplicate + type: string + duplicate: + type: object + properties: + include_exceptions: + description: Whether to copy exceptions from the original rule + type: boolean + include_expired_exceptions: + description: Whether to copy expired exceptions from the original rule + type: boolean + required: + - include_exceptions + - include_expired_exceptions + ids: + description: Array of rule IDs + items: + type: string + minItems: 1 + type: array + query: + description: Query to filter rules + type: string + required: + - action + BulkEditActionResponse: + type: object + properties: + attributes: + type: object + properties: + errors: + items: + $ref: '#/components/schemas/NormalizedRuleError' + type: array + results: + $ref: '#/components/schemas/BulkEditActionResults' + summary: + $ref: '#/components/schemas/BulkEditActionSummary' + required: + - results + - summary + message: + type: string + rules_count: + type: integer + status_code: + type: integer + success: + type: boolean + required: + - attributes + BulkEditActionResults: + type: object + properties: + created: + items: + $ref: '#/components/schemas/RuleResponse' + type: array + deleted: + items: + $ref: '#/components/schemas/RuleResponse' + type: array + skipped: + items: + $ref: '#/components/schemas/BulkActionSkipResult' + type: array + updated: + items: + $ref: '#/components/schemas/RuleResponse' + type: array + required: + - updated + - created + - deleted + - skipped + BulkEditActionSummary: + type: object + properties: + failed: + type: integer + skipped: + type: integer + succeeded: + type: integer + total: + type: integer + required: + - failed + - skipped + - succeeded + - total + BulkEditRules: + type: object + properties: + action: + enum: + - edit + type: string + edit: + description: Array of objects containing the edit operations + items: + $ref: '#/components/schemas/BulkActionEditPayload' + minItems: 1 + type: array + ids: + description: Array of rule IDs + items: + type: string + minItems: 1 + type: array + query: + description: Query to filter rules + type: string + required: + - action + - edit + BulkEditSkipReason: + enum: + - RULE_NOT_MODIFIED + type: string + BulkEnableRules: + type: object + properties: + action: + enum: + - enable + type: string + ids: + description: Array of rule IDs + items: + type: string + minItems: 1 + type: array + query: + description: Query to filter rules + type: string + required: + - action + BulkExportActionResponse: + type: string + BulkExportRules: + type: object + properties: + action: + enum: + - export + type: string + ids: + description: Array of rule IDs + items: + type: string + minItems: 1 + type: array + query: + description: Query to filter rules + type: string + required: + - action + ConcurrentSearches: + minimum: 1 + type: integer + DataViewId: + type: string + DefaultParams: + type: object + properties: + command: + enum: + - isolate + type: string + comment: + type: string + required: + - command + EcsMapping: + additionalProperties: + type: object + properties: + field: + type: string + value: + oneOf: + - type: string + - items: + type: string + type: array + type: object + EndpointResponseAction: + type: object + properties: + action_type_id: + enum: + - .endpoint + type: string + params: + oneOf: + - $ref: '#/components/schemas/DefaultParams' + - $ref: '#/components/schemas/ProcessesParams' + required: + - action_type_id + - params + EqlOptionalFields: + type: object + properties: + alert_suppression: + $ref: '#/components/schemas/AlertSuppression' + data_view_id: + $ref: '#/components/schemas/DataViewId' + event_category_override: + $ref: '#/components/schemas/EventCategoryOverride' + filters: + $ref: '#/components/schemas/RuleFilterArray' + index: + $ref: '#/components/schemas/IndexPatternArray' + tiebreaker_field: + $ref: '#/components/schemas/TiebreakerField' + timestamp_field: + $ref: '#/components/schemas/TimestampField' + EqlQueryLanguage: + enum: + - eql + type: string + EqlRequiredFields: + type: object + properties: + language: + $ref: '#/components/schemas/EqlQueryLanguage' + description: Query language to use + query: + $ref: '#/components/schemas/RuleQuery' + description: EQL query to execute + type: + description: Rule type + enum: + - eql + type: string + required: + - type + - query + - language + EqlRule: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - version + - tags + - enabled + - risk_score_mapping + - severity_mapping + - interval + - from + - to + - actions + - exceptions_list + - author + - false_positives + - references + - max_signals + - threat + - setup + - related_integrations + - required_fields + - $ref: '#/components/schemas/ResponseFields' + - $ref: '#/components/schemas/EqlRuleResponseFields' + EqlRuleCreateFields: + allOf: + - $ref: '#/components/schemas/EqlRequiredFields' + - $ref: '#/components/schemas/EqlOptionalFields' + EqlRuleCreateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/EqlRuleCreateFields' + EqlRulePatchFields: + allOf: + - type: object + properties: + language: + $ref: '#/components/schemas/EqlQueryLanguage' + description: Query language to use + query: + $ref: '#/components/schemas/RuleQuery' + description: EQL query to execute + type: + description: Rule type + enum: + - eql + type: string + - $ref: '#/components/schemas/EqlOptionalFields' + EqlRulePatchProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + - $ref: '#/components/schemas/EqlRulePatchFields' + EqlRuleResponseFields: + allOf: + - $ref: '#/components/schemas/EqlRequiredFields' + - $ref: '#/components/schemas/EqlOptionalFields' + EqlRuleUpdateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/EqlRuleCreateFields' + ErrorSchema: + additionalProperties: false + type: object + properties: + error: + type: object + properties: + message: + type: string + status_code: + minimum: 400 + type: integer + required: + - status_code + - message + id: + type: string + item_id: + minLength: 1 + type: string + list_id: + minLength: 1 + type: string + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + required: + - error + EsqlQueryLanguage: + enum: + - esql + type: string + EsqlRule: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - version + - tags + - enabled + - risk_score_mapping + - severity_mapping + - interval + - from + - to + - actions + - exceptions_list + - author + - false_positives + - references + - max_signals + - threat + - setup + - related_integrations + - required_fields + - $ref: '#/components/schemas/ResponseFields' + - $ref: '#/components/schemas/EsqlRuleResponseFields' + EsqlRuleCreateFields: + allOf: + - $ref: '#/components/schemas/EsqlRuleOptionalFields' + - $ref: '#/components/schemas/EsqlRuleRequiredFields' + EsqlRuleCreateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/EsqlRuleCreateFields' + EsqlRuleOptionalFields: + type: object + properties: + alert_suppression: + $ref: '#/components/schemas/AlertSuppression' + EsqlRulePatchProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + language: + $ref: '#/components/schemas/EsqlQueryLanguage' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + query: + $ref: '#/components/schemas/RuleQuery' + description: ESQL query to execute + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + type: + description: Rule type + enum: + - esql + type: string + version: + $ref: '#/components/schemas/RuleVersion' + - $ref: '#/components/schemas/EsqlRuleOptionalFields' + EsqlRuleRequiredFields: + type: object + properties: + language: + $ref: '#/components/schemas/EsqlQueryLanguage' + query: + $ref: '#/components/schemas/RuleQuery' + description: ESQL query to execute + type: + description: Rule type + enum: + - esql + type: string + required: + - type + - language + - query + EsqlRuleResponseFields: + allOf: + - $ref: '#/components/schemas/EsqlRuleOptionalFields' + - $ref: '#/components/schemas/EsqlRuleRequiredFields' + EsqlRuleUpdateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/EsqlRuleCreateFields' + EventCategoryOverride: + type: string + ExceptionListType: + description: The exception type + enum: + - detection + - rule_default + - endpoint + - endpoint_trusted_apps + - endpoint_events + - endpoint_host_isolation_exceptions + - endpoint_blocklists + type: string + ExternalRuleSource: + description: >- + Type of rule source for externally sourced rules, i.e. rules that have + an external source, such as the Elastic Prebuilt rules repo. + type: object + properties: + is_customized: + $ref: '#/components/schemas/IsExternalRuleCustomized' + type: + enum: + - external + type: string + required: + - type + - is_customized + FindRulesSortField: + 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 + type: string + HistoryWindowStart: + $ref: '#/components/schemas/NonEmptyString' + IndexPatternArray: + items: + type: string + type: array + InternalRuleSource: + description: >- + Type of rule source for internally sourced rules, i.e. created within + the Kibana apps. + type: object + properties: + type: + enum: + - internal + type: string + required: + - type + InvestigationFields: + type: object + properties: + field_names: + items: + $ref: '#/components/schemas/NonEmptyString' + minItems: 1 + type: array + required: + - field_names + InvestigationGuide: + description: Notes to help investigate alerts produced by the rule. + type: string + IsExternalRuleCustomized: + description: >- + Determines whether an external/prebuilt rule has been customized by the + user (i.e. any of its fields have been modified and diverged from the + base value). + type: boolean + IsRuleEnabled: + description: Determines whether the rule is enabled. + type: boolean + IsRuleImmutable: + deprecated: true + description: >- + This field determines whether the rule is a prebuilt Elastic rule. It + will be replaced with the `rule_source` field. + type: boolean + ItemsPerSearch: + minimum: 1 + type: integer + KqlQueryLanguage: + enum: + - kuery + - lucene + type: string + MachineLearningJobId: + description: Machine learning job ID + oneOf: + - type: string + - items: + type: string + minItems: 1 + type: array + MachineLearningRule: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - version + - tags + - enabled + - risk_score_mapping + - severity_mapping + - interval + - from + - to + - actions + - exceptions_list + - author + - false_positives + - references + - max_signals + - threat + - setup + - related_integrations + - required_fields + - $ref: '#/components/schemas/ResponseFields' + - $ref: '#/components/schemas/MachineLearningRuleResponseFields' + MachineLearningRuleCreateFields: + $ref: '#/components/schemas/MachineLearningRuleRequiredFields' + MachineLearningRuleCreateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/MachineLearningRuleCreateFields' + MachineLearningRulePatchFields: + type: object + properties: + anomaly_threshold: + $ref: '#/components/schemas/AnomalyThreshold' + machine_learning_job_id: + $ref: '#/components/schemas/MachineLearningJobId' + type: + description: Rule type + enum: + - machine_learning + type: string + MachineLearningRulePatchProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + - $ref: '#/components/schemas/MachineLearningRulePatchFields' + MachineLearningRuleRequiredFields: + type: object + properties: + anomaly_threshold: + $ref: '#/components/schemas/AnomalyThreshold' + machine_learning_job_id: + $ref: '#/components/schemas/MachineLearningJobId' + type: + description: Rule type + enum: + - machine_learning + type: string + required: + - type + - machine_learning_job_id + - anomaly_threshold + MachineLearningRuleResponseFields: + $ref: '#/components/schemas/MachineLearningRuleRequiredFields' + MachineLearningRuleUpdateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/MachineLearningRuleCreateFields' + MaxSignals: + minimum: 1 + type: integer + NewTermsFields: + items: + type: string + maxItems: 3 + minItems: 1 + type: array + NewTermsRule: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - version + - tags + - enabled + - risk_score_mapping + - severity_mapping + - interval + - from + - to + - actions + - exceptions_list + - author + - false_positives + - references + - max_signals + - threat + - setup + - related_integrations + - required_fields + - $ref: '#/components/schemas/ResponseFields' + - $ref: '#/components/schemas/NewTermsRuleResponseFields' + NewTermsRuleCreateFields: + allOf: + - $ref: '#/components/schemas/NewTermsRuleRequiredFields' + - $ref: '#/components/schemas/NewTermsRuleOptionalFields' + - $ref: '#/components/schemas/NewTermsRuleDefaultableFields' + NewTermsRuleCreateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/NewTermsRuleCreateFields' + NewTermsRuleDefaultableFields: + type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + NewTermsRuleOptionalFields: + type: object + properties: + alert_suppression: + $ref: '#/components/schemas/AlertSuppression' + data_view_id: + $ref: '#/components/schemas/DataViewId' + filters: + $ref: '#/components/schemas/RuleFilterArray' + index: + $ref: '#/components/schemas/IndexPatternArray' + NewTermsRulePatchFields: + allOf: + - type: object + properties: + history_window_start: + $ref: '#/components/schemas/HistoryWindowStart' + new_terms_fields: + $ref: '#/components/schemas/NewTermsFields' + query: + $ref: '#/components/schemas/RuleQuery' + type: + description: Rule type + enum: + - new_terms + type: string + - $ref: '#/components/schemas/NewTermsRuleOptionalFields' + - $ref: '#/components/schemas/NewTermsRuleDefaultableFields' + NewTermsRulePatchProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + - $ref: '#/components/schemas/NewTermsRulePatchFields' + NewTermsRuleRequiredFields: + type: object + properties: + history_window_start: + $ref: '#/components/schemas/HistoryWindowStart' + new_terms_fields: + $ref: '#/components/schemas/NewTermsFields' + query: + $ref: '#/components/schemas/RuleQuery' + type: + description: Rule type + enum: + - new_terms + type: string + required: + - type + - query + - new_terms_fields + - history_window_start + NewTermsRuleResponseFields: + allOf: + - $ref: '#/components/schemas/NewTermsRuleRequiredFields' + - $ref: '#/components/schemas/NewTermsRuleOptionalFields' + - type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + required: + - language + NewTermsRuleUpdateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/NewTermsRuleCreateFields' + NonEmptyString: + description: A string that is not empty and does not contain only whitespace + minLength: 1 + pattern: ^(?! *$).+$ + type: string + NormalizedRuleAction: + additionalProperties: false + type: object + properties: + alerts_filter: + $ref: '#/components/schemas/RuleActionAlertsFilter' + frequency: + $ref: '#/components/schemas/RuleActionFrequency' + group: + $ref: '#/components/schemas/RuleActionGroup' + id: + $ref: '#/components/schemas/RuleActionId' + params: + $ref: '#/components/schemas/RuleActionParams' + required: + - group + - id + - params + NormalizedRuleError: + type: object + properties: + err_code: + $ref: '#/components/schemas/BulkActionsDryRunErrCode' + message: + type: string + rules: + items: + $ref: '#/components/schemas/RuleDetailsInError' + type: array + status_code: + type: integer + required: + - message + - status_code + - rules + OsqueryParams: + type: object + properties: + ecs_mapping: + $ref: '#/components/schemas/EcsMapping' + pack_id: + type: string + queries: + items: + $ref: '#/components/schemas/OsqueryQuery' + type: array + query: + type: string + saved_query_id: + type: string + timeout: + type: number + OsqueryQuery: + type: object + properties: + ecs_mapping: + $ref: '#/components/schemas/EcsMapping' + id: + description: Query ID + type: string + platform: + type: string + query: + description: Query to execute + type: string + removed: + type: boolean + snapshot: + type: boolean + version: + description: Query version + type: string + required: + - id + - query + OsqueryResponseAction: + type: object + properties: + action_type_id: + enum: + - .osquery + type: string + params: + $ref: '#/components/schemas/OsqueryParams' + required: + - action_type_id + - params + ProcessesParams: + type: object + properties: + command: + enum: + - kill-process + - suspend-process + type: string + comment: + type: string + config: + type: object + properties: + field: + description: Field to use instead of process.pid + type: string + overwrite: + default: true + description: Whether to overwrite field with process.pid + type: boolean + required: + - field + required: + - command + - config + QueryRule: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - version + - tags + - enabled + - risk_score_mapping + - severity_mapping + - interval + - from + - to + - actions + - exceptions_list + - author + - false_positives + - references + - max_signals + - threat + - setup + - related_integrations + - required_fields + - $ref: '#/components/schemas/ResponseFields' + - $ref: '#/components/schemas/QueryRuleResponseFields' + QueryRuleCreateFields: + allOf: + - $ref: '#/components/schemas/QueryRuleRequiredFields' + - $ref: '#/components/schemas/QueryRuleOptionalFields' + - $ref: '#/components/schemas/QueryRuleDefaultableFields' + QueryRuleCreateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/QueryRuleCreateFields' + QueryRuleDefaultableFields: + type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + query: + $ref: '#/components/schemas/RuleQuery' + QueryRuleOptionalFields: + type: object + properties: + alert_suppression: + $ref: '#/components/schemas/AlertSuppression' + data_view_id: + $ref: '#/components/schemas/DataViewId' + filters: + $ref: '#/components/schemas/RuleFilterArray' + index: + $ref: '#/components/schemas/IndexPatternArray' + response_actions: + items: + $ref: '#/components/schemas/ResponseAction' + type: array + saved_id: + $ref: '#/components/schemas/SavedQueryId' + QueryRulePatchFields: + allOf: + - type: object + properties: + type: + description: Rule type + enum: + - query + type: string + - $ref: '#/components/schemas/QueryRuleOptionalFields' + - $ref: '#/components/schemas/QueryRuleDefaultableFields' + QueryRulePatchProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + - $ref: '#/components/schemas/QueryRulePatchFields' + QueryRuleRequiredFields: + type: object + properties: + type: + description: Rule type + enum: + - query + type: string + required: + - type + QueryRuleResponseFields: + allOf: + - $ref: '#/components/schemas/QueryRuleRequiredFields' + - $ref: '#/components/schemas/QueryRuleOptionalFields' + - type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + query: + $ref: '#/components/schemas/RuleQuery' + required: + - query + - language + QueryRuleUpdateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/QueryRuleCreateFields' + RelatedIntegration: + type: object + properties: + integration: + $ref: '#/components/schemas/NonEmptyString' + package: + $ref: '#/components/schemas/NonEmptyString' + version: + $ref: '#/components/schemas/NonEmptyString' + required: + - package + - version + RelatedIntegrationArray: + items: + $ref: '#/components/schemas/RelatedIntegration' + type: array + RequiredField: + description: Describes an Elasticsearch field that is needed for the rule to function + type: object + properties: + ecs: + description: Whether the field is an ECS field + type: boolean + name: + $ref: '#/components/schemas/NonEmptyString' + description: Name of an Elasticsearch field + type: + $ref: '#/components/schemas/NonEmptyString' + description: Type of the Elasticsearch field + required: + - name + - type + - ecs + RequiredFieldArray: + items: + $ref: '#/components/schemas/RequiredField' + type: array + RequiredFieldInput: + description: >- + Input parameters to create a RequiredField. Does not include the `ecs` + field, because `ecs` is calculated on the backend based on the field + name and type. + type: object + properties: + name: + $ref: '#/components/schemas/NonEmptyString' + description: Name of an Elasticsearch field + type: + $ref: '#/components/schemas/NonEmptyString' + description: Type of an Elasticsearch field + required: + - name + - type + ResponseAction: + oneOf: + - $ref: '#/components/schemas/OsqueryResponseAction' + - $ref: '#/components/schemas/EndpointResponseAction' + ResponseFields: + type: object + properties: + created_at: + format: date-time + type: string + created_by: + type: string + execution_summary: + $ref: '#/components/schemas/RuleExecutionSummary' + id: + $ref: '#/components/schemas/RuleObjectId' + immutable: + $ref: '#/components/schemas/IsRuleImmutable' + required_fields: + $ref: '#/components/schemas/RequiredFieldArray' + revision: + minimum: 0 + type: integer + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_source: + $ref: '#/components/schemas/RuleSource' + updated_at: + format: date-time + type: string + updated_by: + type: string + required: + - id + - rule_id + - immutable + - updated_at + - updated_by + - created_at + - created_by + - revision + - related_integrations + - required_fields + RiskScore: + description: Risk score (0 to 100) + maximum: 100 + minimum: 0 + type: integer + RiskScoreMapping: + description: >- + Overrides generated alerts' risk_score with a value from the source + event + items: + type: object + properties: + field: + type: string + operator: + enum: + - equals + type: string + risk_score: + $ref: '#/components/schemas/RiskScore' + value: + type: string + required: + - field + - operator + - value + type: array + RuleAction: + type: object + properties: + action_type_id: + description: The action type used for sending notifications. + type: string + alerts_filter: + $ref: '#/components/schemas/RuleActionAlertsFilter' + frequency: + $ref: '#/components/schemas/RuleActionFrequency' + group: + $ref: '#/components/schemas/RuleActionGroup' + id: + $ref: '#/components/schemas/RuleActionId' + params: + $ref: '#/components/schemas/RuleActionParams' + uuid: + $ref: '#/components/schemas/NonEmptyString' + required: + - action_type_id + - group + - id + - params + RuleActionAlertsFilter: + additionalProperties: true + type: object + RuleActionFrequency: + description: >- + The action frequency defines when the action runs (for example, only on + rule execution or at specific time intervals). + type: object + properties: + notifyWhen: + $ref: '#/components/schemas/RuleActionNotifyWhen' + summary: + description: >- + Action summary indicates whether we will send a summary notification + about all the generate alerts or notification per individual alert + type: boolean + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + nullable: true + required: + - summary + - notifyWhen + - throttle + RuleActionGroup: + description: >- + Optionally groups actions by use cases. Use `default` for alert + notifications. + type: string + RuleActionId: + description: The connector ID. + type: string + RuleActionNotifyWhen: + description: >- + The condition for throttling the notification: `onActionGroupChange`, + `onActiveAlert`, or `onThrottleInterval` + enum: + - onActiveAlert + - onThrottleInterval + - onActionGroupChange + type: string + RuleActionParams: + additionalProperties: true + description: >- + Object containing the allowed connector fields, which varies according + to the connector type. + type: object + RuleActionThrottle: + description: Defines the interval on which a rule's actions are executed. + oneOf: + - enum: + - no_actions + - rule + type: string + - description: 'Time interval in seconds, minutes, hours, or days.' + example: 1h + pattern: '^[1-9]\d*[smhd]$' + type: string + RuleAuthorArray: + items: + type: string + type: array + RuleCreateProps: + anyOf: + - $ref: '#/components/schemas/EqlRuleCreateProps' + - $ref: '#/components/schemas/QueryRuleCreateProps' + - $ref: '#/components/schemas/SavedQueryRuleCreateProps' + - $ref: '#/components/schemas/ThresholdRuleCreateProps' + - $ref: '#/components/schemas/ThreatMatchRuleCreateProps' + - $ref: '#/components/schemas/MachineLearningRuleCreateProps' + - $ref: '#/components/schemas/NewTermsRuleCreateProps' + - $ref: '#/components/schemas/EsqlRuleCreateProps' + discriminator: + propertyName: type + RuleDescription: + minLength: 1 + type: string + RuleDetailsInError: + type: object + properties: + id: + type: string + name: + type: string + required: + - id + RuleExceptionList: + type: object + properties: + id: + $ref: '#/components/schemas/NonEmptyString' + description: ID of the exception container + list_id: + $ref: '#/components/schemas/NonEmptyString' + description: List ID of the exception container + namespace_type: + description: Determines the exceptions validity in rule's Kibana space + enum: + - agnostic + - single + type: string + type: + $ref: '#/components/schemas/ExceptionListType' + required: + - id + - list_id + - type + - namespace_type + RuleExecutionMetrics: + type: object + properties: + execution_gap_duration_s: + description: Duration in seconds of execution gap + minimum: 0 + type: integer + total_enrichment_duration_ms: + description: >- + Total time spent enriching documents during current rule execution + cycle + minimum: 0 + type: integer + total_indexing_duration_ms: + description: >- + Total time spent indexing documents during current rule execution + cycle + minimum: 0 + type: integer + total_search_duration_ms: + description: >- + Total time spent performing ES searches as measured by Kibana; + includes network latency and time spent serializing/deserializing + request/response + minimum: 0 + type: integer + RuleExecutionStatus: + description: >- + Custom execution status of Security rules that is different from the + status used in the Alerting Framework. We merge our custom status with + the Framework's status to determine the resulting status of a rule. + + - going to run - @deprecated Replaced by the 'running' status but left + for backwards compatibility with rule execution events already written + to Event Log in the prior versions of Kibana. Don't use when writing + rule status changes. + + - running - Rule execution started but not reached any intermediate or + final status. + + - partial failure - Rule can partially fail for various reasons either + in the middle of an execution (in this case we update its status right + away) or in the end of it. So currently this status can be both + intermediate and final at the same time. A typical reason for a partial + failure: not all the indices that the rule searches over actually exist. + + - failed - Rule failed to execute due to unhandled exception or a reason + defined in the business logic of its executor function. + + - succeeded - Rule executed successfully without any issues. Note: this + status is just an indication of a rule's "health". The rule might or + might not generate any alerts despite of it. + enum: + - going to run + - running + - partial failure + - failed + - succeeded + type: string + RuleExecutionStatusOrder: + type: integer + RuleExecutionSummary: + type: object + properties: + last_execution: + type: object + properties: + date: + description: Date of the last execution + format: date-time + type: string + message: + type: string + metrics: + $ref: '#/components/schemas/RuleExecutionMetrics' + status: + $ref: '#/components/schemas/RuleExecutionStatus' + description: Status of the last execution + status_order: + $ref: '#/components/schemas/RuleExecutionStatusOrder' + required: + - date + - status + - status_order + - message + - metrics + required: + - last_execution + RuleFalsePositiveArray: + items: + type: string + type: array + RuleFilterArray: + items: {} + type: array + RuleInterval: + description: >- + Frequency of rule execution, using a date math range. For example, "1h" + means the rule runs every hour. Defaults to 5m (5 minutes). + type: string + RuleIntervalFrom: + description: >- + Time from which data is analyzed each time the rule executes, using a + date math range. For example, now-4200s means the rule analyzes data + from 70 minutes before its start time. Defaults to now-6m (analyzes data + from 6 minutes before the start time). + format: date-math + type: string + RuleIntervalTo: + type: string + RuleLicense: + description: The rule's license. + type: string + RuleMetadata: + additionalProperties: true + type: object + RuleName: + minLength: 1 + type: string + RuleNameOverride: + description: Sets the source field for the alert's signal.rule.name value + type: string + RuleObjectId: + $ref: '#/components/schemas/UUID' + RulePatchProps: + anyOf: + - $ref: '#/components/schemas/EqlRulePatchProps' + - $ref: '#/components/schemas/QueryRulePatchProps' + - $ref: '#/components/schemas/SavedQueryRulePatchProps' + - $ref: '#/components/schemas/ThresholdRulePatchProps' + - $ref: '#/components/schemas/ThreatMatchRulePatchProps' + - $ref: '#/components/schemas/MachineLearningRulePatchProps' + - $ref: '#/components/schemas/NewTermsRulePatchProps' + - $ref: '#/components/schemas/EsqlRulePatchProps' + RuleQuery: + type: string + RuleReferenceArray: + items: + type: string + type: array + RuleResponse: + anyOf: + - $ref: '#/components/schemas/EqlRule' + - $ref: '#/components/schemas/QueryRule' + - $ref: '#/components/schemas/SavedQueryRule' + - $ref: '#/components/schemas/ThresholdRule' + - $ref: '#/components/schemas/ThreatMatchRule' + - $ref: '#/components/schemas/MachineLearningRule' + - $ref: '#/components/schemas/NewTermsRule' + - $ref: '#/components/schemas/EsqlRule' + discriminator: + propertyName: type + RuleSignatureId: + description: 'Could be any string, not necessarily a UUID' + type: string + RuleSource: + description: >- + Discriminated union that determines whether the rule is internally + sourced (created within the Kibana app) or has an external source, such + as the Elastic Prebuilt rules repo. + discriminator: + propertyName: type + oneOf: + - $ref: '#/components/schemas/ExternalRuleSource' + - $ref: '#/components/schemas/InternalRuleSource' + RuleTagArray: + description: >- + String array containing words and phrases to help categorize, filter, + and search rules. Defaults to an empty array. + items: + type: string + type: array + RuleUpdateProps: + anyOf: + - $ref: '#/components/schemas/EqlRuleUpdateProps' + - $ref: '#/components/schemas/QueryRuleUpdateProps' + - $ref: '#/components/schemas/SavedQueryRuleUpdateProps' + - $ref: '#/components/schemas/ThresholdRuleUpdateProps' + - $ref: '#/components/schemas/ThreatMatchRuleUpdateProps' + - $ref: '#/components/schemas/MachineLearningRuleUpdateProps' + - $ref: '#/components/schemas/NewTermsRuleUpdateProps' + - $ref: '#/components/schemas/EsqlRuleUpdateProps' + discriminator: + propertyName: type + RuleVersion: + description: The rule's version number. + minimum: 1 + type: integer + SavedObjectResolveAliasPurpose: + enum: + - savedObjectConversion + - savedObjectImport + type: string + SavedObjectResolveAliasTargetId: + type: string + SavedObjectResolveOutcome: + enum: + - exactMatch + - aliasMatch + - conflict + type: string + SavedQueryId: + type: string + SavedQueryRule: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - version + - tags + - enabled + - risk_score_mapping + - severity_mapping + - interval + - from + - to + - actions + - exceptions_list + - author + - false_positives + - references + - max_signals + - threat + - setup + - related_integrations + - required_fields + - $ref: '#/components/schemas/ResponseFields' + - $ref: '#/components/schemas/SavedQueryRuleResponseFields' + SavedQueryRuleCreateFields: + allOf: + - $ref: '#/components/schemas/SavedQueryRuleRequiredFields' + - $ref: '#/components/schemas/SavedQueryRuleOptionalFields' + - $ref: '#/components/schemas/SavedQueryRuleDefaultableFields' + SavedQueryRuleCreateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/SavedQueryRuleCreateFields' + SavedQueryRuleDefaultableFields: + type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + SavedQueryRuleOptionalFields: + type: object + properties: + alert_suppression: + $ref: '#/components/schemas/AlertSuppression' + data_view_id: + $ref: '#/components/schemas/DataViewId' + filters: + $ref: '#/components/schemas/RuleFilterArray' + index: + $ref: '#/components/schemas/IndexPatternArray' + query: + $ref: '#/components/schemas/RuleQuery' + response_actions: + items: + $ref: '#/components/schemas/ResponseAction' + type: array + SavedQueryRulePatchFields: + allOf: + - type: object + properties: + saved_id: + $ref: '#/components/schemas/SavedQueryId' + type: + description: Rule type + enum: + - saved_query + type: string + - $ref: '#/components/schemas/SavedQueryRuleOptionalFields' + - $ref: '#/components/schemas/SavedQueryRuleDefaultableFields' + SavedQueryRulePatchProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + - $ref: '#/components/schemas/SavedQueryRulePatchFields' + SavedQueryRuleRequiredFields: + type: object + properties: + saved_id: + $ref: '#/components/schemas/SavedQueryId' + type: + description: Rule type + enum: + - saved_query + type: string + required: + - type + - saved_id + SavedQueryRuleResponseFields: + allOf: + - $ref: '#/components/schemas/SavedQueryRuleRequiredFields' + - $ref: '#/components/schemas/SavedQueryRuleOptionalFields' + - type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + required: + - language + SavedQueryRuleUpdateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/SavedQueryRuleCreateFields' + SetupGuide: + type: string + Severity: + description: Severity of the rule + enum: + - low + - medium + - high + - critical + type: string + SeverityMapping: + description: Overrides generated alerts' severity with values from the source event + items: + type: object + properties: + field: + type: string + operator: + enum: + - equals + type: string + severity: + $ref: '#/components/schemas/Severity' + value: + type: string + required: + - field + - operator + - severity + - value + type: array + SortOrder: + enum: + - asc + - desc + type: string + Threat: + type: object + properties: + framework: + description: Relevant attack framework + type: string + tactic: + $ref: '#/components/schemas/ThreatTactic' + technique: + description: Array containing information on the attack techniques (optional) + items: + $ref: '#/components/schemas/ThreatTechnique' + type: array + required: + - framework + - tactic + ThreatArray: + items: + $ref: '#/components/schemas/Threat' + type: array + ThreatFilters: + items: + description: >- + Query and filter context array used to filter documents from the + Elasticsearch index containing the threat values + type: array + ThreatIndex: + items: + type: string + type: array + ThreatIndicatorPath: + description: >- + Defines the path to the threat indicator in the indicator documents + (optional) + type: string + ThreatMapping: + items: + type: object + properties: + entries: + items: + type: object + properties: + field: + $ref: '#/components/schemas/NonEmptyString' + type: + enum: + - mapping + type: string + value: + $ref: '#/components/schemas/NonEmptyString' + required: + - field + - type + - value + type: array + required: + - entries + minItems: 1 + type: array + ThreatMatchRule: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - version + - tags + - enabled + - risk_score_mapping + - severity_mapping + - interval + - from + - to + - actions + - exceptions_list + - author + - false_positives + - references + - max_signals + - threat + - setup + - related_integrations + - required_fields + - $ref: '#/components/schemas/ResponseFields' + - $ref: '#/components/schemas/ThreatMatchRuleResponseFields' + ThreatMatchRuleCreateFields: + allOf: + - $ref: '#/components/schemas/ThreatMatchRuleRequiredFields' + - $ref: '#/components/schemas/ThreatMatchRuleOptionalFields' + - $ref: '#/components/schemas/ThreatMatchRuleDefaultableFields' + ThreatMatchRuleCreateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/ThreatMatchRuleCreateFields' + ThreatMatchRuleDefaultableFields: + type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + ThreatMatchRuleOptionalFields: + type: object + properties: + alert_suppression: + $ref: '#/components/schemas/AlertSuppression' + concurrent_searches: + $ref: '#/components/schemas/ConcurrentSearches' + data_view_id: + $ref: '#/components/schemas/DataViewId' + filters: + $ref: '#/components/schemas/RuleFilterArray' + index: + $ref: '#/components/schemas/IndexPatternArray' + items_per_search: + $ref: '#/components/schemas/ItemsPerSearch' + saved_id: + $ref: '#/components/schemas/SavedQueryId' + threat_filters: + $ref: '#/components/schemas/ThreatFilters' + threat_indicator_path: + $ref: '#/components/schemas/ThreatIndicatorPath' + threat_language: + $ref: '#/components/schemas/KqlQueryLanguage' + ThreatMatchRulePatchFields: + allOf: + - type: object + properties: + query: + $ref: '#/components/schemas/RuleQuery' + threat_index: + $ref: '#/components/schemas/ThreatIndex' + threat_mapping: + $ref: '#/components/schemas/ThreatMapping' + threat_query: + $ref: '#/components/schemas/ThreatQuery' + type: + description: Rule type + enum: + - threat_match + type: string + - $ref: '#/components/schemas/ThreatMatchRuleOptionalFields' + - $ref: '#/components/schemas/ThreatMatchRuleDefaultableFields' + ThreatMatchRulePatchProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + - $ref: '#/components/schemas/ThreatMatchRulePatchFields' + ThreatMatchRuleRequiredFields: + type: object + properties: + query: + $ref: '#/components/schemas/RuleQuery' + threat_index: + $ref: '#/components/schemas/ThreatIndex' + threat_mapping: + $ref: '#/components/schemas/ThreatMapping' + threat_query: + $ref: '#/components/schemas/ThreatQuery' + type: + description: Rule type + enum: + - threat_match + type: string + required: + - type + - query + - threat_query + - threat_mapping + - threat_index + ThreatMatchRuleResponseFields: + allOf: + - $ref: '#/components/schemas/ThreatMatchRuleRequiredFields' + - $ref: '#/components/schemas/ThreatMatchRuleOptionalFields' + - type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + required: + - language + ThreatMatchRuleUpdateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/ThreatMatchRuleCreateFields' + ThreatQuery: + description: Query to execute + type: string + ThreatSubtechnique: + type: object + properties: + id: + description: Subtechnique ID + type: string + name: + description: Subtechnique name + type: string + reference: + description: Subtechnique reference + type: string + required: + - id + - name + - reference + ThreatTactic: + type: object + properties: + id: + description: Tactic ID + type: string + name: + description: Tactic name + type: string + reference: + description: Tactic reference + type: string + required: + - id + - name + - reference + ThreatTechnique: + type: object + properties: + id: + description: Technique ID + type: string + name: + description: Technique name + type: string + reference: + description: Technique reference + type: string + subtechnique: + description: Array containing more specific information on the attack technique + items: + $ref: '#/components/schemas/ThreatSubtechnique' + type: array + required: + - id + - name + - reference + Threshold: + type: object + properties: + cardinality: + $ref: '#/components/schemas/ThresholdCardinality' + field: + $ref: '#/components/schemas/ThresholdField' + value: + $ref: '#/components/schemas/ThresholdValue' + required: + - field + - value + ThresholdAlertSuppression: + type: object + properties: + duration: + $ref: '#/components/schemas/AlertSuppressionDuration' + required: + - duration + ThresholdCardinality: + items: + type: object + properties: + field: + type: string + value: + minimum: 0 + type: integer + required: + - field + - value + type: array + ThresholdField: + description: Field to aggregate on + oneOf: + - type: string + - items: + type: string + type: array + ThresholdRule: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - version + - tags + - enabled + - risk_score_mapping + - severity_mapping + - interval + - from + - to + - actions + - exceptions_list + - author + - false_positives + - references + - max_signals + - threat + - setup + - related_integrations + - required_fields + - $ref: '#/components/schemas/ResponseFields' + - $ref: '#/components/schemas/ThresholdRuleResponseFields' + ThresholdRuleCreateFields: + allOf: + - $ref: '#/components/schemas/ThresholdRuleRequiredFields' + - $ref: '#/components/schemas/ThresholdRuleOptionalFields' + - $ref: '#/components/schemas/ThresholdRuleDefaultableFields' + ThresholdRuleCreateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/ThresholdRuleCreateFields' + ThresholdRuleDefaultableFields: + type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + ThresholdRuleOptionalFields: + type: object + properties: + alert_suppression: + $ref: '#/components/schemas/ThresholdAlertSuppression' + data_view_id: + $ref: '#/components/schemas/DataViewId' + filters: + $ref: '#/components/schemas/RuleFilterArray' + index: + $ref: '#/components/schemas/IndexPatternArray' + saved_id: + $ref: '#/components/schemas/SavedQueryId' + ThresholdRulePatchFields: + allOf: + - type: object + properties: + query: + $ref: '#/components/schemas/RuleQuery' + threshold: + $ref: '#/components/schemas/Threshold' + type: + description: Rule type + enum: + - threshold + type: string + - $ref: '#/components/schemas/ThresholdRuleOptionalFields' + - $ref: '#/components/schemas/ThresholdRuleDefaultableFields' + ThresholdRulePatchProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + - $ref: '#/components/schemas/ThresholdRulePatchFields' + ThresholdRuleRequiredFields: + type: object + properties: + query: + $ref: '#/components/schemas/RuleQuery' + threshold: + $ref: '#/components/schemas/Threshold' + type: + description: Rule type + enum: + - threshold + type: string + required: + - type + - query + - threshold + ThresholdRuleResponseFields: + allOf: + - $ref: '#/components/schemas/ThresholdRuleRequiredFields' + - $ref: '#/components/schemas/ThresholdRuleOptionalFields' + - type: object + properties: + language: + $ref: '#/components/schemas/KqlQueryLanguage' + required: + - language + ThresholdRuleUpdateProps: + allOf: + - type: object + properties: + actions: + items: + $ref: '#/components/schemas/RuleAction' + type: array + alias_purpose: + $ref: '#/components/schemas/SavedObjectResolveAliasPurpose' + alias_target_id: + $ref: '#/components/schemas/SavedObjectResolveAliasTargetId' + author: + $ref: '#/components/schemas/RuleAuthorArray' + building_block_type: + $ref: '#/components/schemas/BuildingBlockType' + description: + $ref: '#/components/schemas/RuleDescription' + enabled: + $ref: '#/components/schemas/IsRuleEnabled' + exceptions_list: + items: + $ref: '#/components/schemas/RuleExceptionList' + type: array + false_positives: + $ref: '#/components/schemas/RuleFalsePositiveArray' + from: + $ref: '#/components/schemas/RuleIntervalFrom' + id: + $ref: '#/components/schemas/RuleObjectId' + interval: + $ref: '#/components/schemas/RuleInterval' + investigation_fields: + $ref: '#/components/schemas/InvestigationFields' + license: + $ref: '#/components/schemas/RuleLicense' + max_signals: + $ref: '#/components/schemas/MaxSignals' + meta: + $ref: '#/components/schemas/RuleMetadata' + name: + $ref: '#/components/schemas/RuleName' + namespace: + $ref: '#/components/schemas/AlertsIndexNamespace' + note: + $ref: '#/components/schemas/InvestigationGuide' + outcome: + $ref: '#/components/schemas/SavedObjectResolveOutcome' + output_index: + $ref: '#/components/schemas/AlertsIndex' + references: + $ref: '#/components/schemas/RuleReferenceArray' + related_integrations: + $ref: '#/components/schemas/RelatedIntegrationArray' + required_fields: + items: + $ref: '#/components/schemas/RequiredFieldInput' + type: array + risk_score: + $ref: '#/components/schemas/RiskScore' + risk_score_mapping: + $ref: '#/components/schemas/RiskScoreMapping' + rule_id: + $ref: '#/components/schemas/RuleSignatureId' + rule_name_override: + $ref: '#/components/schemas/RuleNameOverride' + setup: + $ref: '#/components/schemas/SetupGuide' + severity: + $ref: '#/components/schemas/Severity' + severity_mapping: + $ref: '#/components/schemas/SeverityMapping' + tags: + $ref: '#/components/schemas/RuleTagArray' + threat: + $ref: '#/components/schemas/ThreatArray' + throttle: + $ref: '#/components/schemas/RuleActionThrottle' + timeline_id: + $ref: '#/components/schemas/TimelineTemplateId' + timeline_title: + $ref: '#/components/schemas/TimelineTemplateTitle' + timestamp_override: + $ref: '#/components/schemas/TimestampOverride' + timestamp_override_fallback_disabled: + $ref: '#/components/schemas/TimestampOverrideFallbackDisabled' + to: + $ref: '#/components/schemas/RuleIntervalTo' + version: + $ref: '#/components/schemas/RuleVersion' + required: + - name + - description + - risk_score + - severity + - $ref: '#/components/schemas/ThresholdRuleCreateFields' + ThresholdValue: + description: Threshold value + minimum: 1 + type: integer + ThrottleForBulkActions: + description: >- + The condition for throttling the notification: 'rule', 'no_actions', or + time duration + enum: + - rule + - 1h + - 1d + - 7d + type: string + TiebreakerField: + description: Sets a secondary field for sorting events + type: string + TimelineTemplateId: + description: Timeline template ID + type: string + TimelineTemplateTitle: + description: Timeline template title + type: string + TimestampField: + description: Contains the event timestamp used for sorting a sequence of events + type: string + TimestampOverride: + description: Sets the time field used to query indices + type: string + TimestampOverrideFallbackDisabled: + description: Disables the fallback to the event's @timestamp field + type: boolean + UUID: + description: A universally unique identifier + format: uuid + type: string + WarningSchema: + type: object + properties: + actionPath: + type: string + buttonLabel: + type: string + message: + type: string + type: + type: string + required: + - type + - message + - actionPath + securitySchemes: + BasicAuth: + scheme: basic + type: http +security: + - BasicAuth: [] diff --git a/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/installation_and_upgrade.md b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/installation_and_upgrade.md index e6454eb424141..e2f98296e199c 100644 --- a/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/installation_and_upgrade.md +++ b/x-pack/plugins/security_solution/docs/testing/test_plans/detection_response/prebuilt_rules/installation_and_upgrade.md @@ -893,8 +893,8 @@ And field is customized by the user (current version != base versio And field is updated by Elastic in this upgrade (target version != base version) And customized field is the same as the Elastic update in this upgrade (current version == target version) Then for field the diff algorithm should output the current version as the merged one without a conflict -And field should not be returned from the `upgrade/_review` API endpoint -And field should not be shown in the upgrade preview UI +And field should be returned from the `upgrade/_review` API endpoint +And field should be shown in the upgrade preview UI Examples: | field_name | base_version | current_version | target_version | diff --git a/x-pack/plugins/security_solution/public/app/translations.ts b/x-pack/plugins/security_solution/public/app/translations.ts index 0449881d6a636..dcadd74245f24 100644 --- a/x-pack/plugins/security_solution/public/app/translations.ts +++ b/x-pack/plugins/security_solution/public/app/translations.ts @@ -25,6 +25,10 @@ export const ENTITY_ANALYTICS_RISK_SCORE = i18n.translate( } ); +export const NOTES = i18n.translate('xpack.securitySolution.navigation.notesManagement', { + defaultMessage: 'Notes', +}); + export const ASSET_CRITICALITY = i18n.translate( 'xpack.securitySolution.navigation.assetCriticality', { @@ -143,12 +147,6 @@ export const HOST_ISOLATION_EXCEPTIONS = i18n.translate( defaultMessage: 'Host isolation exceptions', } ); -export const DETECT = i18n.translate('xpack.securitySolution.navigation.detect', { - defaultMessage: 'Detect', -}); -export const FINDINGS = i18n.translate('xpack.securitySolution.navigation.findings', { - defaultMessage: 'Findings', -}); export const EXPLORE = i18n.translate('xpack.securitySolution.navigation.explore', { defaultMessage: 'Explore', }); @@ -177,10 +175,3 @@ export const PROTECTION_UPDATES = i18n.translate( export const CREATE_NEW_RULE = i18n.translate('xpack.securitySolution.navigation.newRuleTitle', { defaultMessage: 'Create new rule', }); - -export const THREAT_INTELLIGENCE = i18n.translate( - 'xpack.securitySolution.navigation.threatIntelligence', - { - defaultMessage: 'Intelligence', - } -); diff --git a/x-pack/plugins/security_solution/public/assistant/get_comments/stream/stream_observable.test.ts b/x-pack/plugins/security_solution/public/assistant/get_comments/stream/stream_observable.test.ts index 07f38251d3873..f4257e12e1587 100644 --- a/x-pack/plugins/security_solution/public/assistant/get_comments/stream/stream_observable.test.ts +++ b/x-pack/plugins/security_solution/public/assistant/get_comments/stream/stream_observable.test.ts @@ -398,6 +398,147 @@ describe('getStreamObservable', () => { error: (err) => done(err), }); }); + it('should emit loading state and chunks for Gemini', (done) => { + const chunk1 = `data: {"candidates": [{"content":{"role":"model","parts":[{"text":"My"}]}}]}\rdata: {"candidates": [{"content":{"role":"model","parts":[{"text":" new"}]}}]}`; + const chunk2 = `\rdata: {"candidates": [{"content": {"role": "model","parts": [{"text": " message"}]},"finishReason": "STOP"}],"usageMetadata": {"promptTokenCount": 23,"candidatesTokenCount": 50,"totalTokenCount": 73}}`; + const completeSubject = new Subject(); + const expectedStates: PromptObservableState[] = [ + { chunks: [], loading: true }, + { + chunks: ['My ', ' ', 'new ', ' message'], + message: 'My ', + loading: true, + }, + { + chunks: ['My ', ' ', 'new ', ' message'], + message: 'My ', + loading: true, + }, + { + chunks: ['My ', ' ', 'new ', ' message'], + message: 'My new ', + loading: true, + }, + { + chunks: ['My ', ' ', 'new ', ' message'], + message: 'My new message', + loading: false, + }, + ]; + + mockReader.read + .mockResolvedValueOnce({ + done: false, + value: new Uint8Array(new TextEncoder().encode(chunk1)), + }) + .mockResolvedValueOnce({ + done: false, + value: new Uint8Array(new TextEncoder().encode(chunk2)), + }) + .mockResolvedValueOnce({ + done: false, + value: new Uint8Array(new TextEncoder().encode('')), + }) + .mockResolvedValue({ + done: true, + }); + + const source = getStreamObservable({ + ...defaultProps, + actionTypeId: '.gemini', + }); + const emittedStates: PromptObservableState[] = []; + + source.subscribe({ + next: (state) => { + return emittedStates.push(state); + }, + complete: () => { + expect(emittedStates).toEqual(expectedStates); + done(); + + completeSubject.subscribe({ + next: () => { + expect(setLoading).toHaveBeenCalledWith(false); + expect(typedReader.cancel).toHaveBeenCalled(); + done(); + }, + }); + }, + error: (err) => done(err), + }); + }); + + it('should emit loading state and chunks for partial response Gemini', (done) => { + const chunk1 = `data: {"candidates": [{"content":{"role":"model","parts":[{"text":"My"}]}}]}\rdata: {"candidates": [{"content":{"role":"model","parts":[{"text":" new"}]}}]}`; + const chunk2 = `\rdata: {"candidates": [{"content": {"role": "model","parts": [{"text": " message"}]},"finishReason": "STOP"}],"usageMetadata": {"promptTokenCount": 23,"candidatesTokenCount": 50,"totalTokenCount": 73}}`; + const completeSubject = new Subject(); + const expectedStates: PromptObservableState[] = [ + { chunks: [], loading: true }, + { + chunks: ['My ', ' ', 'new ', ' message'], + message: 'My ', + loading: true, + }, + { + chunks: ['My ', ' ', 'new ', ' message'], + message: 'My ', + loading: true, + }, + { + chunks: ['My ', ' ', 'new ', ' message'], + message: 'My new ', + loading: true, + }, + { + chunks: ['My ', ' ', 'new ', ' message'], + message: 'My new message', + loading: false, + }, + ]; + + mockReader.read + .mockResolvedValueOnce({ + done: false, + value: new Uint8Array(new TextEncoder().encode(chunk1)), + }) + .mockResolvedValueOnce({ + done: false, + value: new Uint8Array(new TextEncoder().encode(chunk2)), + }) + .mockResolvedValueOnce({ + done: false, + value: new Uint8Array(new TextEncoder().encode('')), + }) + .mockResolvedValue({ + done: true, + }); + + const source = getStreamObservable({ + ...defaultProps, + actionTypeId: '.gemini', + }); + const emittedStates: PromptObservableState[] = []; + + source.subscribe({ + next: (state) => { + return emittedStates.push(state); + }, + complete: () => { + expect(emittedStates).toEqual(expectedStates); + done(); + + completeSubject.subscribe({ + next: () => { + expect(setLoading).toHaveBeenCalledWith(false); + expect(typedReader.cancel).toHaveBeenCalled(); + done(); + }, + }); + }, + error: (err) => done(err), + }); + }); it('should stream errors when reader contains errors', (done) => { const completeSubject = new Subject(); diff --git a/x-pack/plugins/security_solution/public/assistant/get_comments/stream/stream_observable.ts b/x-pack/plugins/security_solution/public/assistant/get_comments/stream/stream_observable.ts index 2ee9b2aa1e3d4..338ed7b661f84 100644 --- a/x-pack/plugins/security_solution/public/assistant/get_comments/stream/stream_observable.ts +++ b/x-pack/plugins/security_solution/public/assistant/get_comments/stream/stream_observable.ts @@ -19,6 +19,30 @@ interface StreamObservable { reader: ReadableStreamDefaultReader; setLoading: Dispatch>; } + +interface ResponseSchema { + candidates: Candidate[]; + usageMetadata: { + promptTokenCount: number; + candidatesTokenCount: number; + totalTokenCount: number; + }; +} + +interface Part { + text: string; +} + +interface Candidate { + content: Content; + finishReason: string; +} + +interface Content { + role: string; + parts: Part[]; +} + /** * Returns an Observable that reads data from a ReadableStream and emits values representing the state of the data processing. * @@ -44,9 +68,10 @@ export const getStreamObservable = ({ let openAIBuffer: string = ''; // Initialize an empty string to store the LangChain buffer. let langChainBuffer: string = ''; - // Initialize an empty Uint8Array to store the Bedrock concatenated buffer. let bedrockBuffer: Uint8Array = new Uint8Array(0); + // Initialize an empty string to store the Gemini buffer. + let geminiBuffer: string = ''; // read data from LangChain stream function readLangChain() { @@ -203,6 +228,54 @@ export const getStreamObservable = ({ }); } + // read data from Gemini stream + function readGemini() { + reader + .read() + .then(({ done, value }: { done: boolean; value?: Uint8Array }) => { + try { + if (done) { + if (geminiBuffer) { + chunks.push(getGeminiChunks([geminiBuffer])[0]); + } + observer.next({ + chunks, + message: chunks.join(''), + loading: false, + }); + observer.complete(); + return; + } + + const decoded = decoder.decode(value, { stream: true }); + const lines = decoded.split('\r'); + lines[0] = geminiBuffer + lines[0]; + geminiBuffer = lines.pop() || ''; + + const nextChunks = getGeminiChunks(lines); + + nextChunks.forEach((chunk: string) => { + const splitBySpace = chunk.split(' '); + for (const word of splitBySpace) { + chunks.push(`${word} `); + observer.next({ + chunks, + message: chunks.join(''), + loading: true, + }); + } + }); + } catch (err) { + observer.error(err); + return; + } + readGemini(); + }) + .catch((err) => { + observer.error(err); + }); + } + // this should never actually happen function badConnector() { observer.next({ @@ -215,6 +288,7 @@ export const getStreamObservable = ({ if (isEnabledLangChain) readLangChain(); else if (actionTypeId === '.bedrock') readBedrock(); else if (actionTypeId === '.gen-ai') readOpenAI(); + else if (actionTypeId === '.gemini') readGemini(); else badConnector(); return () => { @@ -292,4 +366,23 @@ const getLangChainChunks = (lines: string[]): string[] => return acc; }, []); +/** + * Parses an Gemini response from a string. + * @param lines + * @returns {string[]} - Parsed string array from the Gemini response. + */ +const getGeminiChunks = (lines: string[]): string[] => { + return lines + .filter((str) => !!str && str !== '[DONE]') + .map((line) => { + try { + const newLine = line.replaceAll('data: ', ''); + const geminiResponse: ResponseSchema = JSON.parse(newLine); + return geminiResponse.candidates[0]?.content.parts.map((part) => part.text).join('') ?? ''; + } catch (err) { + return ''; + } + }); +}; + export const getPlaceholderObservable = () => new Observable(); diff --git a/x-pack/plugins/security_solution/public/cases/pages/index.tsx b/x-pack/plugins/security_solution/public/cases/pages/index.tsx index e3eb2290e6e77..54ece0123738f 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/index.tsx @@ -10,6 +10,7 @@ import { useDispatch } from 'react-redux'; import type { CaseViewRefreshPropInterface } from '@kbn/cases-plugin/common'; import { CaseMetricsFeature } from '@kbn/cases-plugin/common'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import { CaseDetailsRefreshContext } from '../../common/components/endpoint'; import { useIsExperimentalFeatureEnabled } from '../../common/hooks/use_experimental_features'; import { DocumentDetailsRightPanelKey } from '../../flyout/document_details/shared/constants/panel_keys'; import { useTourContext } from '../../common/components/guided_onboarding_tour'; @@ -26,7 +27,6 @@ import { APP_ID, CASES_PATH, SecurityPageName } from '../../../common/constants' import { timelineActions } from '../../timelines/store'; import { useSourcererDataView } from '../../sourcerer/containers'; import { SourcererScopeName } from '../../sourcerer/store/model'; -import { CaseDetailsRefreshContext } from '../../common/components/endpoint/host_isolation/endpoint_host_isolation_cases_context'; import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; import { getEndpointDetailsPath } from '../../management/common/routing'; import { SpyRoute } from '../../common/utils/route/spy_routes'; diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/cloud_security_posture_pli_auth_block_extension.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/cloud_security_posture_pli_auth_block_extension.tsx new file mode 100644 index 0000000000000..9abc7bcfbdf82 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/cloud_security_posture_pli_auth_block_extension.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PropsWithChildren } from 'react'; +import React, { memo } from 'react'; +import { useUpsellingComponent } from '../common/hooks/use_upselling'; + +export const CloudSecurityPosturePliAuthBlockExtension = memo>( + ({ children }) => { + const Component = useUpsellingComponent('cloud_security_posture_integration_installation'); + if (!Component) { + return <>{children}; + } + return ; + } +); + +CloudSecurityPosturePliAuthBlockExtension.displayName = 'CloudSecurityPosturePliAuthBlockExtension'; diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/lazy_cloud_security_posture_pli_auth_block_extension.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/lazy_cloud_security_posture_pli_auth_block_extension.tsx new file mode 100644 index 0000000000000..1e4ddebb236ff --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/lazy_cloud_security_posture_pli_auth_block_extension.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { lazy } from 'react'; +import type { FleetUiExtensionGetterOptions } from '../common/types'; + +export const getLazyCloudSecurityPosturePliAuthBlockExtension = ({ + coreStart, + depsStart, + services, +}: FleetUiExtensionGetterOptions) => + lazy(async () => { + const [{ withSecurityContext }, { CloudSecurityPosturePliAuthBlockExtension }] = + await Promise.all([ + import('../common/components/with_security_context/with_security_context'), + import('./cloud_security_posture_pli_auth_block_extension'), + ]); + return { + default: withSecurityContext({ + coreStart, + depsStart, + services, + WrappedComponent: CloudSecurityPosturePliAuthBlockExtension, + }), + }; + }); diff --git a/x-pack/plugins/security_solution/public/common/components/agents/agent_status/agent_response_action_status.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status/agent_response_action_status.tsx similarity index 95% rename from x-pack/plugins/security_solution/public/common/components/agents/agent_status/agent_response_action_status.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status/agent_response_action_status.tsx index 72c3258a62863..275cc4751e6fe 100644 --- a/x-pack/plugins/security_solution/public/common/components/agents/agent_status/agent_response_action_status.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status/agent_response_action_status.tsx @@ -8,11 +8,11 @@ import React, { memo, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiTextColor, EuiToolTip } from '@elastic/eui'; -import type { EndpointPendingActions } from '../../../../../common/endpoint/types'; -import type { ResponseActionsApiCommandNames } from '../../../../../common/endpoint/service/response_actions/constants'; -import { RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP } from '../../../../../common/endpoint/service/response_actions/constants'; +import type { EndpointPendingActions } from '../../../../../../common/endpoint/types'; +import type { ResponseActionsApiCommandNames } from '../../../../../../common/endpoint/service/response_actions/constants'; +import { RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP } from '../../../../../../common/endpoint/service/response_actions/constants'; import { ISOLATED_LABEL, ISOLATING_LABEL, RELEASING_LABEL } from './endpoint/endpoint_agent_status'; -import { useTestIdGenerator } from '../../../../management/hooks/use_test_id_generator'; +import { useTestIdGenerator } from '../../../../../management/hooks/use_test_id_generator'; const TOOLTIP_CONTENT_STYLES: React.CSSProperties = Object.freeze({ width: 150 }); diff --git a/x-pack/plugins/security_solution/public/common/components/agents/agent_status/agent_status.test.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status/agent_status.test.tsx similarity index 93% rename from x-pack/plugins/security_solution/public/common/components/agents/agent_status/agent_status.test.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status/agent_status.test.tsx index 47210272781c0..b384cf9a542a2 100644 --- a/x-pack/plugins/security_solution/public/common/components/agents/agent_status/agent_status.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status/agent_status.test.tsx @@ -11,17 +11,17 @@ import { AgentStatus } from './agent_status'; import { useAgentStatusHook, useGetAgentStatus, -} from '../../../../management/hooks/agents/use_get_agent_status'; +} from '../../../../../management/hooks/agents/use_get_agent_status'; import { RESPONSE_ACTION_AGENT_TYPE, type ResponseActionAgentType, -} from '../../../../../common/endpoint/service/response_actions/constants'; -import type { AppContextTestRender } from '../../../mock/endpoint'; -import { createAppRootMockRenderer } from '../../../mock/endpoint'; -import { HostStatus } from '../../../../../common/endpoint/types'; +} from '../../../../../../common/endpoint/service/response_actions/constants'; +import type { AppContextTestRender } from '../../../../mock/endpoint'; +import { createAppRootMockRenderer } from '../../../../mock/endpoint'; +import { HostStatus } from '../../../../../../common/endpoint/types'; -jest.mock('../../../hooks/use_experimental_features'); -jest.mock('../../../../management/hooks/agents/use_get_agent_status'); +jest.mock('../../../../hooks/use_experimental_features'); +jest.mock('../../../../../management/hooks/agents/use_get_agent_status'); const getAgentStatusMock = useGetAgentStatus as jest.Mock; const useAgentStatusHookMock = useAgentStatusHook as jest.Mock; diff --git a/x-pack/plugins/security_solution/public/common/components/agents/agent_status/agent_status.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status/agent_status.tsx similarity index 84% rename from x-pack/plugins/security_solution/public/common/components/agents/agent_status/agent_status.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status/agent_status.tsx index 363e233baab59..c4e61103e6a82 100644 --- a/x-pack/plugins/security_solution/public/common/components/agents/agent_status/agent_status.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status/agent_status.tsx @@ -8,12 +8,12 @@ import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React, { useMemo } from 'react'; import styled from 'styled-components'; -import type { ResponseActionAgentType } from '../../../../../common/endpoint/service/response_actions/constants'; -import type { EndpointPendingActions } from '../../../../../common/endpoint/types'; -import { useAgentStatusHook } from '../../../../management/hooks/agents/use_get_agent_status'; -import { useTestIdGenerator } from '../../../../management/hooks/use_test_id_generator'; -import { HOST_STATUS_TO_BADGE_COLOR } from '../../../../management/pages/endpoint_hosts/view/host_constants'; -import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features'; +import type { ResponseActionAgentType } from '../../../../../../common/endpoint/service/response_actions/constants'; +import type { EndpointPendingActions } from '../../../../../../common/endpoint/types'; +import { useAgentStatusHook } from '../../../../../management/hooks/agents/use_get_agent_status'; +import { useTestIdGenerator } from '../../../../../management/hooks/use_test_id_generator'; +import { HOST_STATUS_TO_BADGE_COLOR } from '../../../../../management/pages/endpoint_hosts/view/host_constants'; +import { useIsExperimentalFeatureEnabled } from '../../../../hooks/use_experimental_features'; import { getAgentStatusText } from '../agent_status_text'; import { AgentResponseActionsStatus } from './agent_response_action_status'; export enum SENTINEL_ONE_NETWORK_STATUS { diff --git a/x-pack/plugins/security_solution/public/common/components/agents/agent_status/endpoint/endpoint_agent_status.test.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status/endpoint/endpoint_agent_status.test.tsx similarity index 92% rename from x-pack/plugins/security_solution/public/common/components/agents/agent_status/endpoint/endpoint_agent_status.test.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status/endpoint/endpoint_agent_status.test.tsx index 58ef96a42b934..40119d452d2c1 100644 --- a/x-pack/plugins/security_solution/public/common/components/agents/agent_status/endpoint/endpoint_agent_status.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status/endpoint/endpoint_agent_status.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { AppContextTestRender } from '../../../../mock/endpoint'; -import { createAppRootMockRenderer } from '../../../../mock/endpoint'; +import type { AppContextTestRender } from '../../../../../mock/endpoint'; +import { createAppRootMockRenderer } from '../../../../../mock/endpoint'; import type { EndpointAgentStatusByIdProps, EndpointAgentStatusProps, @@ -15,18 +15,18 @@ import { EndpointAgentStatus, EndpointAgentStatusById } from './endpoint_agent_s import type { EndpointPendingActions, HostInfoInterface, -} from '../../../../../../common/endpoint/types'; -import { HostStatus } from '../../../../../../common/endpoint/types'; +} from '../../../../../../../common/endpoint/types'; +import { HostStatus } from '../../../../../../../common/endpoint/types'; import React from 'react'; -import { EndpointActionGenerator } from '../../../../../../common/endpoint/data_generators/endpoint_action_generator'; -import { EndpointDocGenerator } from '../../../../../../common/endpoint/generate_data'; -import { composeHttpHandlerMocks } from '../../../../mock/endpoint/http_handler_mock_factory'; -import type { EndpointMetadataHttpMocksInterface } from '../../../../../management/pages/endpoint_hosts/mocks'; -import { endpointMetadataHttpMocks } from '../../../../../management/pages/endpoint_hosts/mocks'; -import type { ResponseActionsHttpMocksInterface } from '../../../../../management/mocks/response_actions_http_mocks'; -import { responseActionsHttpMocks } from '../../../../../management/mocks/response_actions_http_mocks'; +import { EndpointActionGenerator } from '../../../../../../../common/endpoint/data_generators/endpoint_action_generator'; +import { EndpointDocGenerator } from '../../../../../../../common/endpoint/generate_data'; +import { composeHttpHandlerMocks } from '../../../../../mock/endpoint/http_handler_mock_factory'; +import type { EndpointMetadataHttpMocksInterface } from '../../../../../../management/pages/endpoint_hosts/mocks'; +import { endpointMetadataHttpMocks } from '../../../../../../management/pages/endpoint_hosts/mocks'; +import type { ResponseActionsHttpMocksInterface } from '../../../../../../management/mocks/response_actions_http_mocks'; +import { responseActionsHttpMocks } from '../../../../../../management/mocks/response_actions_http_mocks'; import { waitFor, within, fireEvent } from '@testing-library/react'; -import { getEmptyValue } from '../../../empty_value'; +import { getEmptyValue } from '../../../../empty_value'; import { clone, set } from 'lodash'; type AgentStatusApiMocksInterface = EndpointMetadataHttpMocksInterface & diff --git a/x-pack/plugins/security_solution/public/common/components/agents/agent_status/endpoint/endpoint_agent_status.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status/endpoint/endpoint_agent_status.tsx similarity index 91% rename from x-pack/plugins/security_solution/public/common/components/agents/agent_status/endpoint/endpoint_agent_status.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status/endpoint/endpoint_agent_status.tsx index 85568daa312b4..a2b6d869f9d2d 100644 --- a/x-pack/plugins/security_solution/public/common/components/agents/agent_status/endpoint/endpoint_agent_status.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status/endpoint/endpoint_agent_status.tsx @@ -9,14 +9,14 @@ import React, { memo, useMemo } from 'react'; import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; -import { DEFAULT_POLL_INTERVAL } from '../../../../../management/common/constants'; -import { HOST_STATUS_TO_BADGE_COLOR } from '../../../../../management/pages/endpoint_hosts/view/host_constants'; -import { getEmptyValue } from '../../../empty_value'; - -import { useGetEndpointPendingActionsSummary } from '../../../../../management/hooks/response_actions/use_get_endpoint_pending_actions_summary'; -import { useTestIdGenerator } from '../../../../../management/hooks/use_test_id_generator'; -import type { EndpointPendingActions, HostInfo } from '../../../../../../common/endpoint/types'; -import { useGetEndpointDetails } from '../../../../../management/hooks'; +import { DEFAULT_POLL_INTERVAL } from '../../../../../../management/common/constants'; +import { HOST_STATUS_TO_BADGE_COLOR } from '../../../../../../management/pages/endpoint_hosts/view/host_constants'; +import { getEmptyValue } from '../../../../empty_value'; + +import { useGetEndpointPendingActionsSummary } from '../../../../../../management/hooks/response_actions/use_get_endpoint_pending_actions_summary'; +import { useTestIdGenerator } from '../../../../../../management/hooks/use_test_id_generator'; +import type { EndpointPendingActions, HostInfo } from '../../../../../../../common/endpoint/types'; +import { useGetEndpointDetails } from '../../../../../../management/hooks'; import { getAgentStatusText } from '../../agent_status_text'; import { AgentResponseActionsStatus } from '../agent_response_action_status'; diff --git a/x-pack/plugins/security_solution/public/common/components/agents/agent_status/index.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status/index.ts similarity index 100% rename from x-pack/plugins/security_solution/public/common/components/agents/agent_status/index.ts rename to x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status/index.ts diff --git a/x-pack/plugins/security_solution/public/common/components/agents/agent_status_text.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status_text.ts similarity index 90% rename from x-pack/plugins/security_solution/public/common/components/agents/agent_status_text.ts rename to x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status_text.ts index 45ffd9a90ce93..ac0987e295283 100644 --- a/x-pack/plugins/security_solution/public/common/components/agents/agent_status_text.ts +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/agents/agent_status_text.ts @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import type { HostStatus } from '../../../../common/endpoint/types'; +import type { HostStatus } from '../../../../../common/endpoint/types'; export const getAgentStatusText = (hostStatus: HostStatus) => { return i18n.translate('xpack.securitySolution.endpoint.list.hostStatusValue', { diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/__mocks__/index.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/__mocks__/index.ts new file mode 100644 index 0000000000000..98e117c7017e0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/__mocks__/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 '../from_alerts/__mocks__'; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/__mocks__/index.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/__mocks__/index.ts new file mode 100644 index 0000000000000..033cb24d79ac3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/__mocks__/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 './use_host_isolation_action'; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/__mocks__/use_host_isolation_action.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/__mocks__/use_host_isolation_action.tsx new file mode 100644 index 0000000000000..6bbe386ff176d --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/__mocks__/use_host_isolation_action.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { AlertTableContextMenuItem } from '../../../../../../detections/components/alerts_table/types'; +import { ISOLATE_HOST } from '../translations'; + +const useHostIsolationActionMock = (): AlertTableContextMenuItem[] => { + return [ + { + key: 'isolate-host-action-item', + 'data-test-subj': 'isolate-host-action-item', + disabled: false, + onClick: jest.fn(), + name: ISOLATE_HOST, + }, + ]; +}; + +export { useHostIsolationActionMock as useHostIsolationAction }; diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/host_isolation_panel.test.tsx similarity index 69% rename from x-pack/plugins/security_solution/public/detections/components/host_isolation/index.test.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/host_isolation_panel.test.tsx index 6a357e232a59d..c3a37d9b03128 100644 --- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/host_isolation_panel.test.tsx @@ -8,8 +8,10 @@ import React from 'react'; import { renderReactTestingLibraryWithI18n as render } from '@kbn/test-jest-helpers'; import { HostIsolationPanel } from '.'; -import { useKibana as mockUseKibana } from '../../../common/lib/kibana/__mocks__'; +import { useKibana as mockUseKibana } from '../../../../lib/kibana/__mocks__'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { endpointAlertDataMock } from '../../../../mock/endpoint'; +import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; const queryClient = new QueryClient({ logger: { @@ -19,36 +21,40 @@ const queryClient = new QueryClient({ }, }); +jest.mock('../../../../experimental_features_service'); + const useKibanaMock = mockUseKibana as jest.Mock; -jest.mock('../../../common/lib/kibana'); +jest.mock('../../../../lib/kibana'); describe('HostIsolationPanel', () => { const renderWithContext = (Element: React.ReactElement) => render({Element}); + let cancelCallback: () => void; + let details: TimelineEventsDetailsItem[]; + beforeEach(() => { useKibanaMock.mockReturnValue({ ...mockUseKibana(), services: { ...mockUseKibana().services, notifications: { toasts: jest.fn() } }, }); + + cancelCallback = jest.fn(); + details = endpointAlertDataMock.generateEndpointAlertDetailsItemData(); }); - const details = [ - { - category: 'observer', - field: 'observer.serial_number', - values: ['expectedSentinelOneAgentId'], - originalValue: ['expectedSentinelOneAgentId'], - isObjectArray: false, - }, - { - category: 'crowdstrike', - field: 'crowdstrike.event.DeviceId', - values: ['expectedCrowdstrikeAgentId'], - originalValue: ['expectedCrowdstrikeAgentId'], - isObjectArray: false, - }, - ]; - const cancelCallback = jest.fn(); + it('should render warning callout if alert data host does not support response actions', () => { + const { getByTestId } = renderWithContext( + + ); + + expect(getByTestId('unsupportedAlertHost')).toHaveTextContent( + "The alert's host () does not support host isolation response actions." + ); + }); it('renders IsolateHost when isolateAction is "isolateHost"', () => { const { getByText } = renderWithContext( diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/host_isolation_panel.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/host_isolation_panel.tsx new file mode 100644 index 0000000000000..38e6ff1010d5c --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/host_isolation_panel.tsx @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiCallOut } from '@elastic/eui'; +import { getAlertDetailsFieldValue } from '../../../../lib/endpoint/utils/get_event_details_field_values'; +import { useCasesFromAlerts } from '../../../../../detections/containers/detection_engine/alerts/use_cases_from_alerts'; +import type { TimelineEventsDetailsItem } from '../../../../../../common/search_strategy'; +import { IsolateHost } from './isolate'; +import { UnisolateHost } from './unisolate'; +import { useAlertResponseActionsSupport } from '../../../../hooks/endpoint/use_alert_response_actions_support'; + +export const HostIsolationPanel = React.memo( + ({ + details, + cancelCallback, + successCallback, + isolateAction, + }: { + details: TimelineEventsDetailsItem[] | null; + cancelCallback: () => void; + successCallback?: () => void; + isolateAction: string; + }) => { + const { + isSupported: alertHostSupportsResponseActions, + details: { agentId, agentType, hostName }, + } = useAlertResponseActionsSupport(details); + + const alertId = useMemo( + () => getAlertDetailsFieldValue({ category: '_id', field: '_id' }, details), + [details] + ); + + const { casesInfo } = useCasesFromAlerts({ alertId }); + + const formProps: React.ComponentProps & + React.ComponentProps = useMemo(() => { + return { + endpointId: agentId, + hostName, + casesInfo, + agentType, + cancelCallback, + successCallback, + }; + }, [agentId, agentType, cancelCallback, casesInfo, hostName, successCallback]); + + if (!alertHostSupportsResponseActions) { + return ( + + + + ); + } + + return isolateAction === 'isolateHost' ? ( + + ) : ( + + ); + } +); + +HostIsolationPanel.displayName = 'HostIsolationContent'; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/index.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/index.ts new file mode 100644 index 0000000000000..3ea62053d1ba9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './host_isolation_panel'; +export * from './use_host_isolation_action'; +export * from './translations'; diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/isolate.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/isolate.tsx similarity index 85% rename from x-pack/plugins/security_solution/public/detections/components/host_isolation/isolate.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/isolate.tsx index 37fa3ad4bfccc..df141abcba5e7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/isolate.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/isolate.tsx @@ -8,15 +8,12 @@ import React, { useMemo, useState, useCallback } from 'react'; import { EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { ResponseActionAgentType } from '../../../../common/endpoint/service/response_actions/constants'; -import { useHostIsolation } from '../../containers/detection_engine/alerts/use_host_isolation'; +import type { ResponseActionAgentType } from '../../../../../../common/endpoint/service/response_actions/constants'; +import { useHostIsolation } from './use_host_isolation'; import { CASES_ASSOCIATED_WITH_ALERT, RETURN_TO_ALERT_DETAILS } from './translations'; -import type { EndpointIsolatedFormProps } from '../../../common/components/endpoint/host_isolation'; -import { - EndpointIsolateForm, - ActionCompletionReturnButton, -} from '../../../common/components/endpoint/host_isolation'; -import type { CasesFromAlertsResponse } from '../../containers/detection_engine/alerts/types'; +import type { EndpointIsolatedFormProps } from '..'; +import { EndpointIsolateForm, ActionCompletionReturnButton } from '..'; +import type { CasesFromAlertsResponse } from '../../../../../detections/containers/detection_engine/alerts/types'; export const IsolateHost = React.memo( ({ diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/translations.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/translations.ts similarity index 100% rename from x-pack/plugins/security_solution/public/detections/components/host_isolation/translations.ts rename to x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/translations.ts diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/unisolate.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/unisolate.tsx similarity index 85% rename from x-pack/plugins/security_solution/public/detections/components/host_isolation/unisolate.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/unisolate.tsx index 15c59da521579..171b0283a15c8 100644 --- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/unisolate.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/unisolate.tsx @@ -8,15 +8,12 @@ import React, { useMemo, useState, useCallback } from 'react'; import { EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { ResponseActionAgentType } from '../../../../common/endpoint/service/response_actions/constants'; +import type { ResponseActionAgentType } from '../../../../../../common/endpoint/service/response_actions/constants'; import { CASES_ASSOCIATED_WITH_ALERT, RETURN_TO_ALERT_DETAILS } from './translations'; -import type { EndpointIsolatedFormProps } from '../../../common/components/endpoint/host_isolation'; -import { - EndpointUnisolateForm, - ActionCompletionReturnButton, -} from '../../../common/components/endpoint/host_isolation'; -import { useHostUnisolation } from '../../containers/detection_engine/alerts/use_host_unisolation'; -import type { CasesFromAlertsResponse } from '../../containers/detection_engine/alerts/types'; +import type { EndpointIsolatedFormProps } from '..'; +import { EndpointUnisolateForm, ActionCompletionReturnButton } from '..'; +import { useHostUnisolation } from './use_host_unisolation'; +import type { CasesFromAlertsResponse } from '../../../../../detections/containers/detection_engine/alerts/types'; export const UnisolateHost = React.memo( ({ diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/use_host_isolation.tsx similarity index 78% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/use_host_isolation.tsx index 85b2bea5d2695..6964ee1a34308 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/use_host_isolation.tsx @@ -6,10 +6,10 @@ */ import { useCallback, useState } from 'react'; -import type { ResponseActionAgentType } from '../../../../../common/endpoint/service/response_actions/constants'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { HOST_ISOLATION_FAILURE } from './translations'; -import { createHostIsolation } from './api'; +import type { ResponseActionAgentType } from '../../../../../../common/endpoint/service/response_actions/constants'; +import { useAppToasts } from '../../../../hooks/use_app_toasts'; +import { HOST_ISOLATION_FAILURE } from '../../../../../detections/containers/detection_engine/alerts/translations'; +import { createHostIsolation } from '../../../../../detections/containers/detection_engine/alerts/api'; interface HostIsolationStatus { loading: boolean; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/use_host_isolation_action.test.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/use_host_isolation_action.test.tsx new file mode 100644 index 0000000000000..5c459286fe11b --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/use_host_isolation_action.test.tsx @@ -0,0 +1,144 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FC, PropsWithChildren } from 'react'; +import React from 'react'; +import { renderHook } from '@testing-library/react-hooks'; +import { useHostIsolationAction } from './use_host_isolation_action'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { + useAgentStatusHook, + useGetAgentStatus, + useGetSentinelOneAgentStatus, +} from '../../../../../management/hooks/agents/use_get_agent_status'; +import { useIsExperimentalFeatureEnabled } from '../../../../hooks/use_experimental_features'; +import type { ResponseActionAgentType } from '../../../../../../common/endpoint/service/response_actions/constants'; +import { ExperimentalFeaturesService as ExperimentalFeaturesServiceMock } from '../../../../experimental_features_service'; +import { endpointAlertDataMock } from '../../../../mock/endpoint'; + +jest.mock('../../../../../management/hooks/agents/use_get_agent_status'); +jest.mock('../../../../hooks/use_experimental_features'); +jest.mock('../../../../experimental_features_service'); + +const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock; +const useGetSentinelOneAgentStatusMock = useGetSentinelOneAgentStatus as jest.Mock; +const useGetAgentStatusMock = useGetAgentStatus as jest.Mock; +const useAgentStatusHookMock = useAgentStatusHook as jest.Mock; + +describe('useHostIsolationAction', () => { + const setFeatureFlags = (isEnabled: boolean = true): void => { + useIsExperimentalFeatureEnabledMock.mockReturnValue(isEnabled); + (ExperimentalFeaturesServiceMock.get as jest.Mock).mockReturnValue({ + responseActionsSentinelOneV1Enabled: isEnabled, + responseActionsCrowdstrikeManualHostIsolationEnabled: isEnabled, + }); + }; + + const createReactQueryWrapper = () => { + const queryClient = new QueryClient(); + const wrapper: FC> = ({ children }) => ( + {children} + ); + return wrapper; + }; + + it('should NOT return the menu item for Events', () => { + useAgentStatusHookMock.mockImplementation(() => { + return jest.fn(() => { + return { data: {} }; + }); + }); + setFeatureFlags(true); + const { result } = renderHook( + () => { + return useHostIsolationAction({ + closePopover: jest.fn(), + detailsData: endpointAlertDataMock.generateAlertDetailsItemDataForAgentType('foo', { + 'kibana.alert.rule.uuid': undefined, + }), + isHostIsolationPanelOpen: false, + onAddIsolationStatusClick: jest.fn(), + }); + }, + { wrapper: createReactQueryWrapper() } + ); + + expect(result.current).toHaveLength(0); + }); + + // FIXME:PT refactor describe below - its not actually testing the component! Tests seem to be for `useAgentStatusHook()` + describe.each([ + ['useGetSentinelOneAgentStatus', useGetSentinelOneAgentStatusMock], + ['useGetAgentStatus', useGetAgentStatusMock], + ])('works with %s hook', (name, hook) => { + const render = (agentTypeAlert: ResponseActionAgentType) => + renderHook( + () => + useHostIsolationAction({ + closePopover: jest.fn(), + detailsData: + endpointAlertDataMock.generateAlertDetailsItemDataForAgentType(agentTypeAlert), + isHostIsolationPanelOpen: false, + onAddIsolationStatusClick: jest.fn(), + }), + { + wrapper: createReactQueryWrapper(), + } + ); + + beforeEach(() => { + useAgentStatusHookMock.mockImplementation(() => hook); + setFeatureFlags(true); + }); + + afterEach(() => { + jest.clearAllMocks(); + (ExperimentalFeaturesServiceMock.get as jest.Mock).mockReset(); + }); + + it(`${name} is invoked as 'enabled' when SentinelOne alert and FF enabled`, () => { + render('sentinel_one'); + + expect(hook).toHaveBeenCalledWith(['abfe4a35-d5b4-42a0-a539-bd054c791769'], 'sentinel_one', { + enabled: true, + }); + }); + it(`${name} is invoked as 'enabled' when Crowdstrike alert and FF enabled`, () => { + render('crowdstrike'); + + expect(hook).toHaveBeenCalledWith(['abfe4a35-d5b4-42a0-a539-bd054c791769'], 'crowdstrike', { + enabled: true, + }); + }); + + it(`${name} is invoked as 'disabled' when SentinelOne alert and FF disabled`, () => { + setFeatureFlags(false); + render('sentinel_one'); + + expect(hook).toHaveBeenCalledWith(['abfe4a35-d5b4-42a0-a539-bd054c791769'], 'sentinel_one', { + enabled: false, + }); + }); + + it(`${name} is invoked as 'disabled' when Crowdstrike alert and FF disabled`, () => { + setFeatureFlags(false); + render('crowdstrike'); + + expect(hook).toHaveBeenCalledWith(['abfe4a35-d5b4-42a0-a539-bd054c791769'], 'crowdstrike', { + enabled: false, + }); + }); + + it(`${name} is invoked as 'disabled' when endpoint alert`, () => { + render('endpoint'); + + expect(hook).toHaveBeenCalledWith(['abfe4a35-d5b4-42a0-a539-bd054c791769'], 'endpoint', { + enabled: false, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/use_host_isolation_action.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/use_host_isolation_action.tsx new file mode 100644 index 0000000000000..42f31ba946887 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/use_host_isolation_action.tsx @@ -0,0 +1,175 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { useCallback, useMemo } from 'react'; +import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import { + HOST_ENDPOINT_UNENROLLED_TOOLTIP, + LOADING_ENDPOINT_DATA_TOOLTIP, + NOT_FROM_ENDPOINT_HOST_TOOLTIP, +} from '../../responder'; +import { useAlertResponseActionsSupport } from '../../../../hooks/endpoint/use_alert_response_actions_support'; +import { useIsExperimentalFeatureEnabled } from '../../../../hooks/use_experimental_features'; +import type { AgentStatusInfo } from '../../../../../../common/endpoint/types'; +import { HostStatus } from '../../../../../../common/endpoint/types'; +import { useEndpointHostIsolationStatus } from './use_host_isolation_status'; +import { ISOLATE_HOST, UNISOLATE_HOST } from './translations'; +import { useUserPrivileges } from '../../../user_privileges'; +import type { AlertTableContextMenuItem } from '../../../../../detections/components/alerts_table/types'; +import { useAgentStatusHook } from '../../../../../management/hooks/agents/use_get_agent_status'; + +interface UseHostIsolationActionProps { + closePopover: () => void; + detailsData: TimelineEventsDetailsItem[] | null; + isHostIsolationPanelOpen: boolean; + onAddIsolationStatusClick: (action: 'isolateHost' | 'unisolateHost') => void; +} + +export const useHostIsolationAction = ({ + closePopover, + detailsData, + isHostIsolationPanelOpen, + onAddIsolationStatusClick, +}: UseHostIsolationActionProps): AlertTableContextMenuItem[] => { + const { + isSupported: hostSupportsResponseActions, + isAlert, + unsupportedReason, + details: { + agentType, + agentId, + agentSupport: { isolate: isolationSupported }, + }, + } = useAlertResponseActionsSupport(detailsData); + const agentStatusClientEnabled = useIsExperimentalFeatureEnabled('agentStatusClientEnabled'); + const useAgentStatus = useAgentStatusHook(); + const { canIsolateHost, canUnIsolateHost } = useUserPrivileges().endpointPrivileges; + + const isEndpointAgent = useMemo(() => { + return agentType === 'endpoint'; + }, [agentType]); + + const { + loading: loadingHostIsolationStatus, + isIsolated, + agentStatus, + capabilities, + } = useEndpointHostIsolationStatus({ + agentId, + agentType, + }); + + const { data: externalAgentData } = useAgentStatus([agentId], agentType, { + enabled: hostSupportsResponseActions && !isEndpointAgent, + }); + + const externalAgentStatus = externalAgentData?.[agentId]; + + const isHostIsolated = useMemo((): boolean => { + if (!isEndpointAgent) { + return Boolean(externalAgentStatus?.isolated); + } + + return isIsolated; + }, [isEndpointAgent, isIsolated, externalAgentStatus?.isolated]); + + const doesHostSupportIsolation = useMemo(() => { + // With Elastic Defend Endpoint, we check that the actual `endpoint` agent on + // this host reported that capability + if (agentType === 'endpoint') { + return capabilities.includes('isolation'); + } + + return Boolean(externalAgentStatus?.found && isolationSupported); + }, [agentType, externalAgentStatus?.found, isolationSupported, capabilities]); + + const isolateHostHandler = useCallback(() => { + closePopover(); + if (!isHostIsolated) { + onAddIsolationStatusClick('isolateHost'); + } else { + onAddIsolationStatusClick('unisolateHost'); + } + }, [closePopover, isHostIsolated, onAddIsolationStatusClick]); + + const isHostAgentUnEnrolled = useMemo(() => { + if (!hostSupportsResponseActions) { + return true; + } + + if (isEndpointAgent) { + return agentStatus === HostStatus.UNENROLLED; + } + + // NON-Endpoint agent types + // 8.15 use FF for computing if action is enabled + if (agentStatusClientEnabled) { + return externalAgentStatus?.status === HostStatus.UNENROLLED; + } + + // else use the old way + if (!externalAgentStatus) { + return true; + } + + const { isUninstalled, isPendingUninstall } = externalAgentStatus as AgentStatusInfo[string]; + + return isUninstalled || isPendingUninstall; + }, [ + hostSupportsResponseActions, + isEndpointAgent, + agentStatusClientEnabled, + externalAgentStatus, + agentStatus, + ]); + + return useMemo(() => { + // If not an Alert OR user has no Authz, then don't show the menu item at all + if (!isAlert || (isHostIsolated && !canUnIsolateHost) || !canIsolateHost) { + return []; + } + + const menuItem: AlertTableContextMenuItem = { + key: 'isolate-host-action-item', + 'data-test-subj': 'isolate-host-action-item', + disabled: isHostAgentUnEnrolled || isHostIsolationPanelOpen, + onClick: isolateHostHandler, + name: isHostIsolated ? UNISOLATE_HOST : ISOLATE_HOST, + }; + + // Determine if menu item should be disabled + if (!doesHostSupportIsolation) { + menuItem.disabled = true; + // If we were able to calculate the agentType and we have a reason why the host is does not + // support response actions, then show that as the tooltip. Else, just show the normal "enroll" message + menuItem.toolTipContent = + agentType && unsupportedReason ? unsupportedReason : NOT_FROM_ENDPOINT_HOST_TOOLTIP; + } else if (isEndpointAgent && loadingHostIsolationStatus) { + menuItem.disabled = true; + menuItem.toolTipContent = LOADING_ENDPOINT_DATA_TOOLTIP; + } else if (isHostAgentUnEnrolled) { + menuItem.disabled = true; + menuItem.toolTipContent = isEndpointAgent + ? HOST_ENDPOINT_UNENROLLED_TOOLTIP + : NOT_FROM_ENDPOINT_HOST_TOOLTIP; + } + + return [menuItem]; + }, [ + isAlert, + isHostIsolated, + canUnIsolateHost, + canIsolateHost, + isHostAgentUnEnrolled, + isHostIsolationPanelOpen, + isolateHostHandler, + doesHostSupportIsolation, + isEndpointAgent, + loadingHostIsolationStatus, + agentType, + unsupportedReason, + ]); +}; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation_status.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/use_host_isolation_status.tsx similarity index 83% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation_status.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/use_host_isolation_status.tsx index 0646a9904e939..825cff88ab95e 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation_status.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/use_host_isolation_status.tsx @@ -7,11 +7,11 @@ import { isEmpty } from 'lodash'; import { useEffect, useState } from 'react'; -import type { ResponseActionAgentType } from '../../../../../common/endpoint/service/response_actions/constants'; -import { getHostMetadata } from './api'; -import { fetchPendingActionsByAgentId } from '../../../../common/lib/endpoint_pending_actions'; -import { isEndpointHostIsolated } from '../../../../common/utils/validators'; -import { HostStatus } from '../../../../../common/endpoint/types'; +import type { ResponseActionAgentType } from '../../../../../../common/endpoint/service/response_actions/constants'; +import { getHostMetadata } from '../../../../../detections/containers/detection_engine/alerts/api'; +import { fetchPendingActionsByAgentId } from '../../../../lib/endpoint/endpoint_pending_actions'; +import { isEndpointHostIsolated } from '../../../../utils/validators'; +import { HostStatus } from '../../../../../../common/endpoint/types'; interface HostIsolationStatusResponse { loading: boolean; @@ -22,8 +22,9 @@ interface HostIsolationStatusResponse { pendingUnisolation: number; } -/* - * Retrieves the current isolation status of a host and the agent/host status */ +/** + * Retrieves the current isolation status of a host and the agent/host status + */ export const useEndpointHostIsolationStatus = ({ agentId, agentType, @@ -79,16 +80,15 @@ export const useEndpointHostIsolationStatus = ({ } } catch (error) { // silently catch non-user initiated error - return; - } - - if (isMounted) { - setLoading(false); } }; if (!isEmpty(agentId) && agentType === 'endpoint') { - fetchData(); + fetchData().finally(() => { + if (isMounted) { + setLoading(false); + } + }); } return () => { // updates to show component is unmounted diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_unisolation.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/use_host_unisolation.tsx similarity index 77% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_unisolation.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/use_host_unisolation.tsx index 47c09186f7cba..1cd0d7fe3b886 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_unisolation.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/use_host_unisolation.tsx @@ -6,10 +6,10 @@ */ import { useCallback, useState } from 'react'; -import type { ResponseActionAgentType } from '../../../../../common/endpoint/service/response_actions/constants'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { HOST_ISOLATION_FAILURE } from './translations'; -import { createHostUnIsolation } from './api'; +import type { ResponseActionAgentType } from '../../../../../../common/endpoint/service/response_actions/constants'; +import { useAppToasts } from '../../../../hooks/use_app_toasts'; +import { HOST_ISOLATION_FAILURE } from '../../../../../detections/containers/detection_engine/alerts/translations'; +import { createHostUnIsolation } from '../../../../../detections/containers/detection_engine/alerts/api'; interface HostUnisolationStatus { loading: boolean; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_cases_context.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_cases/endpoint_host_isolation_cases_context.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/endpoint_host_isolation_cases_context.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_cases/endpoint_host_isolation_cases_context.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_cases/index.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_cases/index.ts new file mode 100644 index 0000000000000..930e3d362683a --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_cases/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 './endpoint_host_isolation_cases_context'; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/index.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/index.ts index 41763a6e88d37..077bb416c800f 100644 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/index.ts @@ -9,3 +9,5 @@ export * from './isolate_success'; export * from './isolate_form'; export * from './unisolate_form'; export * from './action_completion_return_button'; +export * from './from_alerts'; +export * from './from_cases'; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/index.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/index.ts new file mode 100644 index 0000000000000..49ad6c8bfd9d2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './host_isolation'; +export * from './responder'; +export * from './link_to_app'; +export * from './route_capture'; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/responder/__mocks__/index.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/__mocks__/index.ts new file mode 100644 index 0000000000000..98e117c7017e0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/__mocks__/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 '../from_alerts/__mocks__'; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/__mocks__/index.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/__mocks__/index.ts new file mode 100644 index 0000000000000..9f070ce76a53d --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/__mocks__/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './use_responder_action_data'; +export * from './use_responder_action_item'; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/__mocks__/use_responder_action_data.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/__mocks__/use_responder_action_data.ts new file mode 100644 index 0000000000000..c84c5ea9dafbd --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/__mocks__/use_responder_action_data.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ResponderActionData, UseWithResponderActionDataFromAlertProps } from '../../..'; + +const useWithResponderActionDataFromAlertMock = ( + options: UseWithResponderActionDataFromAlertProps +): ResponderActionData => { + return { + handleResponseActionsClick: jest.fn(() => { + if (options.onClick) { + options.onClick(); + } + }), + isDisabled: false, + tooltip: null, + }; +}; + +export { useWithResponderActionDataFromAlertMock as useWithResponderActionDataFromAlert }; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/__mocks__/use_responder_action_item.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/__mocks__/use_responder_action_item.tsx new file mode 100644 index 0000000000000..bdeed284fa5ed --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/__mocks__/use_responder_action_item.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { AlertTableContextMenuItem } from '../../../../../../detections/components/alerts_table/types'; + +const useResponderActionItemMock = (): AlertTableContextMenuItem[] => { + return [ + { + key: 'endpointResponseActions-action-item', + 'data-test-subj': 'endpointResponseActions-action-item', + disabled: false, + toolTipContent: undefined, + size: 's', + onClick: jest.fn(), + name: 'Respond', + }, + ]; +}; + +export { useResponderActionItemMock as useResponderActionItem }; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/index.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/index.ts new file mode 100644 index 0000000000000..2f39f582e93fc --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_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 * from './responder_action_button'; +export * from './use_responder_action_item'; +export * from './use_responder_action_data'; +export * from './translations'; diff --git a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/responder_action_button.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/responder_action_button.tsx similarity index 76% rename from x-pack/plugins/security_solution/public/detections/components/endpoint_responder/responder_action_button.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/responder_action_button.tsx index cb5876f27dd67..5d3aba47c2184 100644 --- a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/responder_action_button.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/responder_action_button.tsx @@ -8,18 +8,15 @@ import { EuiButton, EuiToolTip } from '@elastic/eui'; import React, { memo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { - type ResponderContextMenuItemProps, - useResponderActionData, -} from './use_responder_action_data'; -import { useUserPrivileges } from '../../../common/components/user_privileges'; +import type { ResponseActionAgentType } from '../../../../../../common/endpoint/service/response_actions/constants'; +import { useResponderActionData } from './use_responder_action_data'; +import { useUserPrivileges } from '../../../user_privileges'; -export const ResponderActionButton = memo( - ({ agentType, endpointId, onClick }) => { +export const ResponderActionButton = memo<{ agentId: string; agentType: ResponseActionAgentType }>( + ({ agentType, agentId }) => { const { handleResponseActionsClick, isDisabled, tooltip } = useResponderActionData({ agentType, - endpointId, - onClick, + agentId, }); const endpointPrivileges = useUserPrivileges().endpointPrivileges; diff --git a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/translations.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/translations.ts similarity index 59% rename from x-pack/plugins/security_solution/public/detections/components/endpoint_responder/translations.ts rename to x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/translations.ts index 52dd93cc5c7cc..3df858b9673f1 100644 --- a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/translations.ts @@ -6,8 +6,6 @@ */ import { i18n } from '@kbn/i18n'; -import { CROWDSTRIKE_AGENT_ID_FIELD } from '../../../common/utils/crowdstrike_alert_check'; -import { SENTINEL_ONE_AGENT_ID_FIELD } from '../../../common/utils/sentinelone_alert_check'; export const NOT_FROM_ENDPOINT_HOST_TOOLTIP = i18n.translate( 'xpack.securitySolution.endpoint.detections.takeAction.responseActionConsole.notSupportedTooltip', @@ -27,22 +25,3 @@ export const METADATA_API_ERROR_TOOLTIP = i18n.translate( 'xpack.securitySolution.endpoint.detections.takeAction.responseActionConsole.generalMetadataErrorTooltip', { defaultMessage: 'Failed to retrieve Endpoint metadata' } ); - -export const SENTINEL_ONE_AGENT_ID_PROPERTY_MISSING = i18n.translate( - 'xpack.securitySolution.endpoint.detections.takeAction.responseActionConsole.missingSentinelOneAgentId', - { - defaultMessage: 'Event data missing SentinelOne agent identifier ({field})', - values: { - field: SENTINEL_ONE_AGENT_ID_FIELD, - }, - } -); -export const CROWDSTRIKE_AGENT_ID_PROPERTY_MISSING = i18n.translate( - 'xpack.securitySolution.endpoint.detections.takeAction.responseActionConsole.missingCrowdstrikeAgentId', - { - defaultMessage: 'Event data missing Crowdstrike agent identifier ({field})', - values: { - field: CROWDSTRIKE_AGENT_ID_FIELD, - }, - } -); diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/use_responder_action_data.test.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/use_responder_action_data.test.ts new file mode 100644 index 0000000000000..89ad874726d91 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/use_responder_action_data.test.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 type { + UseWithResponderActionDataFromAlertProps, + ResponderActionData, + UseResponderActionDataProps, +} from './use_responder_action_data'; +import { + useResponderActionData, + useWithResponderActionDataFromAlert, +} from './use_responder_action_data'; +import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import { + HOST_ENDPOINT_UNENROLLED_TOOLTIP, + LOADING_ENDPOINT_DATA_TOOLTIP, + METADATA_API_ERROR_TOOLTIP, + NOT_FROM_ENDPOINT_HOST_TOOLTIP, +} from './translations'; +import type { AppContextTestRender } from '../../../../mock/endpoint'; +import { createAppRootMockRenderer, endpointAlertDataMock } from '../../../../mock/endpoint'; +import { HOST_METADATA_LIST_ROUTE } from '../../../../../../common/endpoint/constants'; +import { endpointMetadataHttpMocks } from '../../../../../management/pages/endpoint_hosts/mocks'; +import type { RenderHookResult } from '@testing-library/react-hooks/src/types'; +import { createHttpFetchError } from '@kbn/core-http-browser-mocks'; +import { HostStatus } from '../../../../../../common/endpoint/types'; +import { + RESPONSE_ACTION_AGENT_TYPE, + RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD, +} from '../../../../../../common/endpoint/service/response_actions/constants'; +import { getAgentTypeName } from '../../../../translations'; +import { ALERT_EVENT_DATA_MISSING_AGENT_ID_FIELD } from '../../../../hooks/endpoint/use_alert_response_actions_support'; + +describe('use responder action data hooks', () => { + let appContextMock: AppContextTestRender; + let onClickMock: UseWithResponderActionDataFromAlertProps['onClick']; + + const getExpectedResponderActionData = ( + overrides: Partial = {} + ): ResponderActionData => { + return { + isDisabled: false, + tooltip: undefined, + handleResponseActionsClick: expect.any(Function), + ...overrides, + }; + }; + + beforeEach(() => { + appContextMock = createAppRootMockRenderer(); + onClickMock = jest.fn(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('useWithResponderActionDataFromAlert() hook', () => { + let renderHook: () => RenderHookResult< + UseWithResponderActionDataFromAlertProps, + ResponderActionData + >; + let alertDetailItemData: TimelineEventsDetailsItem[]; + + beforeEach(() => { + renderHook = () => { + return appContextMock.renderHook< + UseWithResponderActionDataFromAlertProps, + ResponderActionData + >(() => + useWithResponderActionDataFromAlert({ + eventData: alertDetailItemData, + onClick: onClickMock, + }) + ); + }; + }); + + describe('Common behaviours', () => { + it('should show action as disabled if agent does not support response actions', () => { + alertDetailItemData = endpointAlertDataMock.generateAlertDetailsItemDataForAgentType('foo'); + + expect(renderHook().result.current).toEqual( + getExpectedResponderActionData({ + isDisabled: true, + tooltip: NOT_FROM_ENDPOINT_HOST_TOOLTIP, + }) + ); + }); + + it('should call `onClick()` function prop when is pass to the hook', () => { + alertDetailItemData = endpointAlertDataMock.generateSentinelOneAlertDetailsItemData(); + const { result } = renderHook(); + result.current.handleResponseActionsClick(); + + expect(onClickMock).toHaveBeenCalled(); + }); + + it('should NOT call `onClick` if the action is disabled', () => { + alertDetailItemData = endpointAlertDataMock.generateAlertDetailsItemDataForAgentType('foo'); + const { result } = renderHook(); + result.current.handleResponseActionsClick(); + + expect(onClickMock).not.toHaveBeenCalled(); + }); + }); + + describe('and agentType is NOT Endpoint', () => { + beforeEach(() => { + alertDetailItemData = endpointAlertDataMock.generateSentinelOneAlertDetailsItemData(); + }); + + it('should show action when agentType is supported', () => { + expect(renderHook().result.current).toEqual(getExpectedResponderActionData()); + }); + + it('should NOT call the endpoint host metadata api', () => { + renderHook(); + const wasMetadataApiCalled = appContextMock.coreStart.http.get.mock.calls.some(([path]) => { + return (path as unknown as string).includes(HOST_METADATA_LIST_ROUTE); + }); + + expect(wasMetadataApiCalled).toBe(false); + }); + + it.each([...RESPONSE_ACTION_AGENT_TYPE])( + 'should show action disabled with tooltip for %s if agent id field is missing', + (agentType) => { + alertDetailItemData = endpointAlertDataMock.generateAlertDetailsItemDataForAgentType( + agentType, + { + [RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD[agentType]]: undefined, + } + ); + + expect(renderHook().result.current).toEqual( + getExpectedResponderActionData({ + isDisabled: true, + tooltip: ALERT_EVENT_DATA_MISSING_AGENT_ID_FIELD( + getAgentTypeName(agentType), + RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD[agentType] + ), + }) + ); + } + ); + }); + + describe('and agentType IS Endpoint', () => { + let metadataApiMocks: ReturnType; + + beforeEach(() => { + alertDetailItemData = endpointAlertDataMock.generateEndpointAlertDetailsItemData(); + metadataApiMocks = endpointMetadataHttpMocks(appContextMock.coreStart.http); + }); + + it('should show action disabled with tooltip while retrieving host metadata', () => { + expect(renderHook().result.current).toEqual( + getExpectedResponderActionData({ + isDisabled: true, + tooltip: LOADING_ENDPOINT_DATA_TOOLTIP, + }) + ); + }); + + it('should show action enabled if host metadata was retrieved and host is enrolled', async () => { + const { result, waitForValueToChange } = renderHook(); + await waitForValueToChange(() => result.current.isDisabled); + + expect(result.current).toEqual(getExpectedResponderActionData()); + }); + + it('should show action disabled if host was not found', async () => { + metadataApiMocks.responseProvider.metadataDetails.mockImplementation(() => { + throw createHttpFetchError('Not found', undefined, undefined, undefined, { + statusCode: 404, + }); + }); + const { result, waitForValueToChange } = renderHook(); + await waitForValueToChange(() => result.current.tooltip); + + expect(result.current).toEqual( + getExpectedResponderActionData({ + isDisabled: true, + tooltip: NOT_FROM_ENDPOINT_HOST_TOOLTIP, + }) + ); + }); + + it('should show action as disabled with tooltip when host is found, but has a status of unenrolled', async () => { + const hostMetadata = { + ...metadataApiMocks.responseProvider.metadataDetails(), + host_status: HostStatus.UNENROLLED, + }; + metadataApiMocks.responseProvider.metadataDetails.mockReturnValue(hostMetadata); + + const { result, waitForValueToChange } = renderHook(); + await waitForValueToChange(() => result.current.tooltip); + + expect(result.current).toEqual( + getExpectedResponderActionData({ + isDisabled: true, + tooltip: HOST_ENDPOINT_UNENROLLED_TOOLTIP, + }) + ); + }); + + it('should show action disabled if a metadata API error was encountered', async () => { + metadataApiMocks.responseProvider.metadataDetails.mockImplementation(() => { + throw createHttpFetchError('Server error', undefined, undefined, undefined, { + statusCode: 500, + }); + }); + const { result, waitForValueToChange } = renderHook(); + await waitForValueToChange(() => result.current.tooltip); + + expect(result.current).toEqual( + getExpectedResponderActionData({ + isDisabled: true, + tooltip: METADATA_API_ERROR_TOOLTIP, + }) + ); + }); + }); + }); + + describe('useResponderActionData() hook', () => { + let hookProps: UseResponderActionDataProps; + let renderHook: () => RenderHookResult; + + beforeEach(() => { + endpointMetadataHttpMocks(appContextMock.coreStart.http); + hookProps = { + agentId: 'agent-123', + agentType: 'endpoint', + onClick: onClickMock, + }; + renderHook = () => { + return appContextMock.renderHook(() => + useResponderActionData(hookProps) + ); + }; + }); + + it('should show action enabled when agentType is Endpoint and host is enabled', async () => { + const { result, waitForValueToChange } = renderHook(); + await waitForValueToChange(() => result.current.isDisabled); + + expect(result.current).toEqual(getExpectedResponderActionData()); + }); + + it('should show action disabled if agent type is not Endpoint', () => { + hookProps.agentType = 'crowdstrike'; + + expect(renderHook().result.current).toEqual( + getExpectedResponderActionData({ + isDisabled: true, + tooltip: NOT_FROM_ENDPOINT_HOST_TOOLTIP, + }) + ); + }); + + it('should call `onClick` prop when action is enabled', async () => { + const { result, waitForValueToChange } = renderHook(); + await waitForValueToChange(() => result.current.isDisabled); + result.current.handleResponseActionsClick(); + + expect(onClickMock).toHaveBeenCalled(); + }); + + it('should not call `onCLick` prop when action is disabled', () => { + hookProps.agentType = 'sentinel_one'; + const { result } = renderHook(); + result.current.handleResponseActionsClick(); + + expect(onClickMock).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/use_responder_action_data.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/use_responder_action_data.ts new file mode 100644 index 0000000000000..266425dd452da --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/use_responder_action_data.ts @@ -0,0 +1,271 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { ReactNode } from 'react'; +import { useCallback, useMemo } from 'react'; +import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import { useAlertResponseActionsSupport } from '../../../../hooks/endpoint/use_alert_response_actions_support'; +import type { + EndpointCapabilities, + ResponseActionAgentType, +} from '../../../../../../common/endpoint/service/response_actions/constants'; +import { useGetEndpointDetails, useWithShowResponder } from '../../../../../management/hooks'; +import { HostStatus } from '../../../../../../common/endpoint/types'; +import { + HOST_ENDPOINT_UNENROLLED_TOOLTIP, + LOADING_ENDPOINT_DATA_TOOLTIP, + METADATA_API_ERROR_TOOLTIP, + NOT_FROM_ENDPOINT_HOST_TOOLTIP, +} from './translations'; + +export interface UseWithResponderActionDataFromAlertProps { + eventData: TimelineEventsDetailsItem[] | null; + onClick?: () => void; +} + +export interface ResponderActionData { + handleResponseActionsClick: () => void; + isDisabled: boolean; + tooltip: ReactNode; +} + +/** + * This hook is used to get the data needed to show the context menu items for the responder + * actions using Alert data. + * + * NOTE: If wanting to get teh same type of response but don't have Alert + * data, use `useResponderActionData()` instead + * + * @param onClick the callback to handle the click event + * @param eventData the event data, exists only when agentType !== 'endpoint' + * @returns an object with the data needed to show the context menu item + */ +export const useWithResponderActionDataFromAlert = ({ + eventData = [], + onClick, +}: UseWithResponderActionDataFromAlertProps): ResponderActionData => { + const { + isSupported: hostSupportsResponseActions, + unsupportedReason, + details: { agentType, agentId, platform, hostName }, + } = useAlertResponseActionsSupport(eventData); + + const isEndpointHost = agentType === 'endpoint'; + + const endpointHostData = useResponderDataForEndpointHost( + agentId, + hostSupportsResponseActions && isEndpointHost + ); + const showResponseActionsConsole = useWithShowResponder(); + + const [isDisabled, tooltip]: [disabled: boolean, tooltip: ReactNode] = useMemo(() => { + if (!hostSupportsResponseActions) { + return [ + true, + agentType && unsupportedReason ? unsupportedReason : NOT_FROM_ENDPOINT_HOST_TOOLTIP, + ]; + } + + if (isEndpointHost) { + return [endpointHostData.isDisabled, endpointHostData.tooltip]; + } + + return [false, undefined]; + }, [ + hostSupportsResponseActions, + isEndpointHost, + agentType, + unsupportedReason, + endpointHostData.isDisabled, + endpointHostData.tooltip, + ]); + + const handleResponseActionsClick = useCallback(() => { + if (!isDisabled) { + showResponseActionsConsole({ + agentId, + agentType, + hostName, + platform, + capabilities: isEndpointHost ? endpointHostData.capabilities : [], + }); + + if (onClick) { + onClick(); + } + } + }, [ + isDisabled, + showResponseActionsConsole, + agentId, + agentType, + hostName, + platform, + isEndpointHost, + endpointHostData.capabilities, + onClick, + ]); + + return { + handleResponseActionsClick, + isDisabled, + tooltip, + }; +}; + +type ResponderDataForEndpointHost = Omit & { + capabilities: EndpointCapabilities[]; + hostName: string; + platform: string; +}; + +/** + * Hook to specifically for the responder data for Elastic Defend endpoints + * @param endpointAgentId + * @param enabled + * + * @private + */ +const useResponderDataForEndpointHost = ( + endpointAgentId: string, + enabled: boolean = true +): ResponderDataForEndpointHost => { + // FIXME:PT is this the correct API to call? or should we call the agent status api instead + + const { + data: endpointHostInfo, + isFetching, + error, + } = useGetEndpointDetails(endpointAgentId, { + enabled, + }); + + return useMemo(() => { + const response: ResponderDataForEndpointHost = { + isDisabled: false, + tooltip: undefined, + capabilities: [], + hostName: '', + platform: '', + }; + + if (!enabled) { + response.isDisabled = true; + return response; + } + + if (isFetching) { + response.isDisabled = true; + response.tooltip = LOADING_ENDPOINT_DATA_TOOLTIP; + return response; + } + + // if we got an error, and it's a 404, it means the endpoint is not from the endpoint host + if (error && error.body?.statusCode === 404) { + response.isDisabled = true; + response.tooltip = NOT_FROM_ENDPOINT_HOST_TOOLTIP; + return response; + } + + // if we got an error and, + // it's a 400 with unenrolled in the error message (alerts can exist for endpoint that are no longer around) + // or, + // the Host status is `unenrolled` + if ( + (error && error.body?.statusCode === 400 && error.body?.message.includes('unenrolled')) || + endpointHostInfo?.host_status === HostStatus.UNENROLLED + ) { + response.isDisabled = true; + response.tooltip = HOST_ENDPOINT_UNENROLLED_TOOLTIP; + return response; + } + + // return general error tooltip + if (error) { + response.isDisabled = true; + response.tooltip = METADATA_API_ERROR_TOOLTIP; + } + + response.capabilities = (endpointHostInfo?.metadata.Endpoint.capabilities ?? + []) as EndpointCapabilities[]; + response.hostName = endpointHostInfo?.metadata.host.name ?? ''; + response.platform = endpointHostInfo?.metadata.host.os.name.toLowerCase() ?? ''; + + return response; + }, [ + enabled, + isFetching, + error, + endpointHostInfo?.host_status, + endpointHostInfo?.metadata.Endpoint.capabilities, + endpointHostInfo?.metadata.host.name, + endpointHostInfo?.metadata.host.os.name, + ]); +}; + +export interface UseResponderActionDataProps { + agentId: string; + agentType: ResponseActionAgentType; + onClick?: () => void; +} + +/** + * Returns the data necessary to render a Responder action item (ex. menu item) when only the + * `agentId` and `agentType` is available (ex. when showing the `Respond` button on the Host + * details page of SIEM + * @param onClick + * @param agentId + * @param agentType + */ +export const useResponderActionData = ({ + onClick, + agentId, + agentType, +}: UseResponderActionDataProps): ResponderActionData => { + const isEndpointHost = agentType === 'endpoint'; + + const showResponseActionsConsole = useWithShowResponder(); + const { tooltip, isDisabled, capabilities, hostName, platform } = useResponderDataForEndpointHost( + agentId, + isEndpointHost + ); + + // TODO:PT add support for other agent types once we add the `Respond` button to the Host details page in SIEM + + const handleResponseActionsClick = useCallback(() => { + if (!isDisabled) { + showResponseActionsConsole({ + agentId, + agentType, + hostName, + platform, + capabilities: isEndpointHost ? capabilities : [], + }); + + if (onClick) { + onClick(); + } + } + }, [ + isDisabled, + showResponseActionsConsole, + agentId, + agentType, + hostName, + platform, + isEndpointHost, + capabilities, + onClick, + ]); + + return useMemo(() => { + return { + isDisabled: isEndpointHost ? isDisabled : true, + tooltip: isEndpointHost ? tooltip : NOT_FROM_ENDPOINT_HOST_TOOLTIP, + handleResponseActionsClick, + }; + }, [handleResponseActionsClick, isDisabled, isEndpointHost, tooltip]); +}; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/use_responder_action_item.test.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/use_responder_action_item.test.tsx new file mode 100644 index 0000000000000..b499f8a795fa9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/use_responder_action_item.test.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 { useResponderActionItem } from './use_responder_action_item'; +import { useUserPrivileges as _useUserPrivileges } from '../../../user_privileges'; +import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import type { AppContextTestRender } from '../../../../mock/endpoint'; +import { createAppRootMockRenderer } from '../../../../mock/endpoint'; +import { endpointAlertDataMock } from '../../../../mock/endpoint/endpoint_alert_data_mock'; + +jest.mock('../../../user_privileges'); +jest.mock('./use_responder_action_data'); + +const useUserPrivilegesMock = _useUserPrivileges as jest.Mock; + +describe('useResponderActionItem', () => { + let alertDetailItemData: TimelineEventsDetailsItem[]; + let renderHook: () => ReturnType; + + beforeEach(() => { + const appContextMock = createAppRootMockRenderer(); + + // This is on purpose - an alert for an unsupported agent type. The menu item should always be + // visible as long as the user has authz to it. In this case it will be disabled. + alertDetailItemData = endpointAlertDataMock.generateAlertDetailsItemDataForAgentType('foo'); + + renderHook = () => + appContextMock.renderHook(() => useResponderActionItem(alertDetailItemData, () => {})); + + useUserPrivilegesMock.mockReturnValue({ + endpointPrivileges: { loading: false, canAccessResponseConsole: true }, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return Respond action menu item if user has Authz', () => { + expect(renderHook().result.current).toHaveLength(1); + }); + + it('should NOT return the Respond action menu item if user is not Authorized', () => { + useUserPrivilegesMock.mockReturnValue({ + endpointPrivileges: { loading: false, canAccessResponseConsole: false }, + }); + expect(renderHook().result.current).toHaveLength(0); + }); + + it('should NOT return the Respond action menu item for Events', () => { + alertDetailItemData = endpointAlertDataMock.generateAlertDetailsItemDataForAgentType('foo', { + 'kibana.alert.rule.uuid': undefined, + }); + + expect(renderHook().result.current).toHaveLength(0); + }); +}); diff --git a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_item.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/use_responder_action_item.tsx similarity index 52% rename from x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_item.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/use_responder_action_item.tsx index 57b51c23f2032..6ccc6b4447907 100644 --- a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_item.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/use_responder_action_item.tsx @@ -8,14 +8,10 @@ import React, { useMemo } from 'react'; import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; import { FormattedMessage } from '@kbn/i18n-react'; -import { isAlertFromCrowdstrikeEvent } from '../../../common/utils/crowdstrike_alert_check'; -import { isAlertFromSentinelOneEvent } from '../../../common/utils/sentinelone_alert_check'; -import type { ResponseActionAgentType } from '../../../../common/endpoint/service/response_actions/constants'; -import { useUserPrivileges } from '../../../common/components/user_privileges'; -import { isTimelineEventItemAnAlert } from '../../../common/utils/endpoint_alert_check'; -import { getFieldValue } from '../host_isolation/helpers'; -import type { AlertTableContextMenuItem } from '../alerts_table/types'; -import { useResponderActionData } from './use_responder_action_data'; +import { useAlertResponseActionsSupport } from '../../../../hooks/endpoint/use_alert_response_actions_support'; +import { useUserPrivileges } from '../../../user_privileges'; +import type { AlertTableContextMenuItem } from '../../../../../detections/components/alerts_table/types'; +import { useWithResponderActionDataFromAlert } from './use_responder_action_data'; export const useResponderActionItem = ( eventDetailsData: TimelineEventsDetailsItem[] | null, @@ -23,36 +19,10 @@ export const useResponderActionItem = ( ): AlertTableContextMenuItem[] => { const { loading: isAuthzLoading, canAccessResponseConsole } = useUserPrivileges().endpointPrivileges; - - const isAlert = useMemo(() => { - return isTimelineEventItemAnAlert(eventDetailsData || []); - }, [eventDetailsData]); - - const endpointId: string = useMemo( - () => getFieldValue({ category: 'agent', field: 'agent.id' }, eventDetailsData), - [eventDetailsData] - ); - - const agentType: ResponseActionAgentType = useMemo(() => { - if (!eventDetailsData) { - return 'endpoint'; - } - - if (isAlertFromSentinelOneEvent({ data: eventDetailsData })) { - return 'sentinel_one'; - } - if (isAlertFromCrowdstrikeEvent({ data: eventDetailsData })) { - return 'crowdstrike'; - } - - return 'endpoint'; - }, [eventDetailsData]); - - const { handleResponseActionsClick, isDisabled, tooltip } = useResponderActionData({ - endpointId, + const { isAlert } = useAlertResponseActionsSupport(eventDetailsData); + const { handleResponseActionsClick, isDisabled, tooltip } = useWithResponderActionDataFromAlert({ onClick, - agentType, - eventData: agentType !== 'endpoint' ? eventDetailsData : null, + eventData: eventDetailsData, }); return useMemo(() => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/constants/index.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/index.ts similarity index 87% rename from x-pack/plugins/fleet/public/applications/fleet/sections/settings/constants/index.tsx rename to x-pack/plugins/security_solution/public/common/components/endpoint/responder/index.ts index 8d29433e7232b..c030b4780dd26 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/constants/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export const FLYOUT_MAX_WIDTH = 670; +export * from './from_alerts'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.test.ts b/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.test.ts index 60bc2b7f3ce31..fc793df1025e7 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.test.ts @@ -5,73 +5,41 @@ * 2.0. */ -import { isAlertFromEndpointEvent } from '../../utils/endpoint_alert_check'; -import { - isAlertFromSentinelOneEvent, - SENTINEL_ONE_AGENT_ID_FIELD, -} from '../../utils/sentinelone_alert_check'; -import { - CROWDSTRIKE_AGENT_ID_FIELD, - isAlertFromCrowdstrikeEvent, -} from '../../utils/crowdstrike_alert_check'; -import { renderHook } from '@testing-library/react-hooks'; +import type { UseSummaryRowsProps } from './get_alert_summary_rows'; import { useSummaryRows } from './get_alert_summary_rows'; -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; - -jest.mock('../../utils/endpoint_alert_check'); -jest.mock('../../utils/sentinelone_alert_check'); -jest.mock('../../utils/crowdstrike_alert_check'); -jest.mock('../../hooks/use_experimental_features', () => ({ - useIsExperimentalFeatureEnabled: jest.fn(), -})); +import { createAppRootMockRenderer, endpointAlertDataMock } from '../../mock/endpoint'; +import type { RenderHookResult } from '@testing-library/react-hooks/src/types'; +import type { AlertSummaryRow } from './helpers'; describe('useSummaryRows', () => { - const mockData: TimelineEventsDetailsItem[] = [ - { - category: 'event', - field: 'event.category', - originalValue: ['process'], - values: ['process'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.rule.uuid', - originalValue: 'rule-uuid', - values: ['rule-uuid'], - isObjectArray: false, - }, - { - category: 'host', - field: 'host.name', - originalValue: 'test-host', - values: ['text-host'], - isObjectArray: false, - }, - ]; - - const mockBrowserFields = {}; - const mockScopeId = 'scope-id'; - const mockEventId = 'event-id'; - const mockInvestigationFields: string[] = []; + let hookProps: UseSummaryRowsProps; + let renderHook: () => RenderHookResult; beforeEach(() => { - jest.clearAllMocks(); - (isAlertFromEndpointEvent as jest.Mock).mockReturnValue(true); - (isAlertFromCrowdstrikeEvent as jest.Mock).mockReturnValue(false); + const appContextMock = createAppRootMockRenderer(); + + appContextMock.setExperimentalFlag({ + responseActionsSentinelOneV1Enabled: true, + responseActionsCrowdstrikeManualHostIsolationEnabled: true, + }); + + hookProps = { + data: endpointAlertDataMock.generateEndpointAlertDetailsItemData(), + browserFields: {}, + scopeId: 'scope-id', + eventId: 'event-id', + investigationFields: [], + }; + + renderHook = () => { + return appContextMock.renderHook(() => + useSummaryRows(hookProps) + ); + }; }); it('returns summary rows for default event categories', () => { - const { result } = renderHook(() => - useSummaryRows({ - data: mockData, - browserFields: mockBrowserFields, - scopeId: mockScopeId, - eventId: mockEventId, - investigationFields: mockInvestigationFields, - }) - ); + const { result } = renderHook(); expect(result.current).toEqual( expect.arrayContaining([ @@ -81,17 +49,7 @@ describe('useSummaryRows', () => { }); it('excludes fields not related to the event source', () => { - (isAlertFromEndpointEvent as jest.Mock).mockReturnValue(false); - - const { result } = renderHook(() => - useSummaryRows({ - data: mockData, - browserFields: mockBrowserFields, - scopeId: mockScopeId, - eventId: mockEventId, - investigationFields: mockInvestigationFields, - }) - ); + const { result } = renderHook(); expect(result.current).not.toEqual( expect.arrayContaining([ @@ -104,70 +62,32 @@ describe('useSummaryRows', () => { }); it('includes sentinel_one agent status field', () => { - (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); - (isAlertFromSentinelOneEvent as jest.Mock).mockReturnValue(true); - - const sentinelOneData: TimelineEventsDetailsItem[] = [ - ...mockData, - { - category: 'host', - field: SENTINEL_ONE_AGENT_ID_FIELD, - originalValue: 'sentinelone-agent-id', - values: ['sentinelone-agent-id'], - isObjectArray: false, - }, - ]; - - const { result } = renderHook(() => - useSummaryRows({ - data: sentinelOneData, - browserFields: mockBrowserFields, - scopeId: mockScopeId, - eventId: mockEventId, - investigationFields: mockInvestigationFields, - }) - ); + hookProps.data = endpointAlertDataMock.generateSentinelOneAlertDetailsItemData(); + const { result } = renderHook(); expect(result.current).toEqual( expect.arrayContaining([ expect.objectContaining({ title: 'Agent status', - description: expect.objectContaining({ values: ['sentinelone-agent-id'] }), + description: expect.objectContaining({ + values: ['abfe4a35-d5b4-42a0-a539-bd054c791769'], + }), }), ]) ); }); it('includes crowdstrike agent status field', () => { - (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); - (isAlertFromCrowdstrikeEvent as jest.Mock).mockReturnValue(true); - - const crowdstrikeData: TimelineEventsDetailsItem[] = [ - ...mockData, - { - category: 'host', - field: CROWDSTRIKE_AGENT_ID_FIELD, - originalValue: 'crowdstrike-agent-id', - values: ['crowdstrike-agent-id'], - isObjectArray: false, - }, - ]; - - const { result } = renderHook(() => - useSummaryRows({ - data: crowdstrikeData, - browserFields: mockBrowserFields, - scopeId: mockScopeId, - eventId: mockEventId, - investigationFields: mockInvestigationFields, - }) - ); + hookProps.data = endpointAlertDataMock.generateCrowdStrikeAlertDetailsItemData(); + const { result } = renderHook(); expect(result.current).toEqual( expect.arrayContaining([ expect.objectContaining({ title: 'Agent status', - description: expect.objectContaining({ values: ['crowdstrike-agent-id'] }), + description: expect.objectContaining({ + values: ['abfe4a35-d5b4-42a0-a539-bd054c791769'], + }), }), ]) ); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx index b079d3575f7c7..f632e48301b67 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx @@ -9,6 +9,9 @@ import { find, isEmpty, uniqBy } from 'lodash/fp'; import { ALERT_RULE_PARAMETERS, ALERT_RULE_TYPE } from '@kbn/rule-data-utils'; import { EventCode, EventCategory } from '@kbn/securitysolution-ecs'; +import { RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD } from '../../../../common/endpoint/service/response_actions/constants'; +import { isResponseActionsAlertAgentIdField } from '../../lib/endpoint'; +import { useAlertResponseActionsSupport } from '../../hooks/endpoint/use_alert_response_actions_support'; import * as i18n from './translations'; import type { BrowserFields } from '../../../../common/search_strategy/index_fields'; import { @@ -33,17 +36,6 @@ import { getEnrichedFieldInfo } from './helpers'; import type { EventSummaryField, EnrichedFieldInfo } from './types'; import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline'; -import { isAlertFromEndpointEvent } from '../../utils/endpoint_alert_check'; -import { - SENTINEL_ONE_AGENT_ID_FIELD, - isAlertFromSentinelOneEvent, -} from '../../utils/sentinelone_alert_check'; -import { - CROWDSTRIKE_AGENT_ID_FIELD, - isAlertFromCrowdstrikeEvent, -} from '../../utils/crowdstrike_alert_check'; -import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; - const THRESHOLD_TERMS_FIELD = `${ALERT_THRESHOLD_RESULT}.terms.field`; const THRESHOLD_TERMS_VALUE = `${ALERT_THRESHOLD_RESULT}.terms.value`; const THRESHOLD_CARDINALITY_FIELD = `${ALERT_THRESHOLD_RESULT}.cardinality.field`; @@ -56,12 +48,12 @@ const alwaysDisplayedFields: EventSummaryField[] = [ // ENDPOINT-related field // { id: 'agent.id', overrideField: AGENT_STATUS_FIELD_NAME, label: i18n.AGENT_STATUS }, { - id: SENTINEL_ONE_AGENT_ID_FIELD, + id: RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD.sentinel_one, overrideField: AGENT_STATUS_FIELD_NAME, label: i18n.AGENT_STATUS, }, { - id: CROWDSTRIKE_AGENT_ID_FIELD, + id: RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD.crowdstrike, overrideField: AGENT_STATUS_FIELD_NAME, label: i18n.AGENT_STATUS, }, @@ -307,6 +299,16 @@ export function getEventCategoriesFromData(data: TimelineEventsDetailsItem[]): E return { primaryEventCategory, allEventCategories }; } +export interface UseSummaryRowsProps { + data: TimelineEventsDetailsItem[]; + browserFields: BrowserFields; + scopeId: string; + eventId: string; + investigationFields?: string[]; + isDraggable?: boolean; + isReadOnly?: boolean; +} + export const useSummaryRows = ({ data, browserFields, @@ -315,21 +317,9 @@ export const useSummaryRows = ({ isDraggable = false, isReadOnly = false, investigationFields, -}: { - data: TimelineEventsDetailsItem[]; - browserFields: BrowserFields; - scopeId: string; - eventId: string; - investigationFields?: string[]; - isDraggable?: boolean; - isReadOnly?: boolean; -}) => { - const sentinelOneManualHostActionsEnabled = useIsExperimentalFeatureEnabled( - 'sentinelOneManualHostActionsEnabled' - ); - const crowdstrikeManualHostActionsEnabled = useIsExperimentalFeatureEnabled( - 'responseActionsCrowdstrikeManualHostIsolationEnabled' - ); +}: UseSummaryRowsProps): AlertSummaryRow[] => { + const responseActionsSupport = useAlertResponseActionsSupport(data); + return useMemo(() => { const eventCategories = getEventCategoriesFromData(data); @@ -381,22 +371,14 @@ export const useSummaryRows = ({ isReadOnly, }; - if (field.id === 'agent.id' && !isAlertFromEndpointEvent({ data })) { - return acc; - } - + // If the field is one used by a supported Response Actions agentType, + // and the alert's host supports response actions + // but the alert field is not the one that the agentType on the alert host uses, + // then exit and return accumulator if ( - field.id === SENTINEL_ONE_AGENT_ID_FIELD && - sentinelOneManualHostActionsEnabled && - !isAlertFromSentinelOneEvent({ data }) - ) { - return acc; - } - - if ( - field.id === CROWDSTRIKE_AGENT_ID_FIELD && - crowdstrikeManualHostActionsEnabled && - !isAlertFromCrowdstrikeEvent({ data }) + isResponseActionsAlertAgentIdField(field.id) && + responseActionsSupport.isSupported && + responseActionsSupport.details.agentIdField !== field.id ) { return acc; } @@ -429,15 +411,15 @@ export const useSummaryRows = ({ }, []) : []; }, [ - browserFields, data, + investigationFields, + scopeId, + browserFields, eventId, isDraggable, - scopeId, isReadOnly, - investigationFields, - sentinelOneManualHostActionsEnabled, - crowdstrikeManualHostActionsEnabled, + responseActionsSupport.details.agentIdField, + responseActionsSupport.isSupported, ]); }; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx index dccea29321671..693df1902873a 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx @@ -23,8 +23,7 @@ import { AGENT_STATUS_FIELD_NAME, QUARANTINED_PATH_FIELD_NAME, } from '../../../timelines/components/timeline/body/renderers/constants'; -import { SENTINEL_ONE_AGENT_ID_FIELD } from '../../utils/sentinelone_alert_check'; -import { CROWDSTRIKE_AGENT_ID_FIELD } from '../../utils/crowdstrike_alert_check'; +import { RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD } from '../../../../common/endpoint/service/response_actions/constants'; /** * Defines the behavior of the search input that appears above the table of data @@ -183,8 +182,8 @@ export function getEnrichedFieldInfo({ export const FIELDS_WITHOUT_ACTIONS: { [field: string]: boolean } = { [AGENT_STATUS_FIELD_NAME]: true, [QUARANTINED_PATH_FIELD_NAME]: true, - [SENTINEL_ONE_AGENT_ID_FIELD]: true, - [CROWDSTRIKE_AGENT_ID_FIELD]: true, + [RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD.sentinel_one]: true, + [RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD.crowdstrike]: true, }; /** diff --git a/x-pack/plugins/security_solution/public/common/components/header_actions/actions.tsx b/x-pack/plugins/security_solution/public/common/components/header_actions/actions.tsx index 6efaaa5d01ccd..16fabb97fb464 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_actions/actions.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_actions/actions.tsx @@ -11,6 +11,7 @@ import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import styled from 'styled-components'; import { TimelineTabs, TableId } from '@kbn/securitysolution-data-table'; +import { selectTimelineById } from '../../../timelines/store/selectors'; import { eventHasNotes, getEventType, @@ -18,7 +19,7 @@ import { } from '../../../timelines/components/timeline/body/helpers'; import { getScopedActions, isTimelineScope } from '../../../helpers'; import { useIsInvestigateInResolverActionEnabled } from '../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver'; -import { timelineActions, timelineSelectors } from '../../../timelines/store'; +import { timelineActions } from '../../../timelines/store'; import type { ActionProps, OnPinEvent } from '../../../../common/types'; import { TimelineId } from '../../../../common/types'; import { AddEventNoteAction } from './add_note_icon_item'; @@ -66,11 +67,10 @@ const ActionsComponent: React.FC = ({ 'unifiedComponentsInTimelineEnabled' ); const emptyNotes: string[] = []; - const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); - const timelineType = useShallowEqualSelector( - (state) => - (isTimelineScope(timelineId) ? getTimeline(state, timelineId) : timelineDefaults).timelineType + const { timelineType } = useShallowEqualSelector((state) => + isTimelineScope(timelineId) ? selectTimelineById(state, timelineId) : timelineDefaults ); + const { startTransaction } = useStartTransaction(); const isEnterprisePlus = useLicense().isEnterprise(); @@ -213,8 +213,8 @@ const ActionsComponent: React.FC = ({ onEventDetailsPanelOpened(); }, [activeStep, incrementStep, isTourAnchor, isTourShown, onEventDetailsPanelOpened]); const showExpandEvent = useMemo( - () => !unifiedComponentsInTimelineEnabled || isEventViewer || timelineId !== TimelineId.active, - [isEventViewer, timelineId, unifiedComponentsInTimelineEnabled] + () => !unifiedComponentsInTimelineEnabled || isEventViewer, + [isEventViewer, unifiedComponentsInTimelineEnabled] ); return ( diff --git a/x-pack/plugins/security_solution/public/common/components/header_actions/header_actions.tsx b/x-pack/plugins/security_solution/public/common/components/header_actions/header_actions.tsx index d65b6035eaeea..7b60434d5c288 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_actions/header_actions.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_actions/header_actions.tsx @@ -16,8 +16,7 @@ import { TimelineTabs, TimelineId } from '../../../../common/types'; import { isFullScreen } from '../../../timelines/components/timeline/body/column_headers'; import { isActiveTimeline } from '../../../helpers'; import { getColumnHeader } from '../../../timelines/components/timeline/body/column_headers/helpers'; -import { timelineActions, timelineSelectors } from '../../../timelines/store'; -import { useDeepEqualSelector } from '../../hooks/use_selector'; +import { timelineActions } from '../../../timelines/store'; import { useGlobalFullScreen, useTimelineFullScreen } from '../../containers/use_full_screen'; import { useKibana } from '../../lib/kibana'; import { DEFAULT_ACTION_BUTTON_WIDTH } from '.'; @@ -27,6 +26,8 @@ import { EXIT_FULL_SCREEN } from '../exit_full_screen/translations'; import { EventsSelect } from '../../../timelines/components/timeline/body/column_headers/events_select'; import * as i18n from './translations'; import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; +import { useDeepEqualSelector } from '../../hooks/use_selector'; +import { selectTimelineById } from '../../../timelines/store/selectors'; const SortingColumnsContainer = styled.div` button { @@ -90,14 +91,14 @@ const HeaderActionsComponent: React.FC = memo( const { timelineFullScreen, setTimelineFullScreen } = useTimelineFullScreen(); const dispatch = useDispatch(); - const getManageTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); - const { defaultColumns } = useDeepEqualSelector((state) => - getManageTimeline(state, timelineId) - ); const unifiedComponentsInTimelineEnabled = useIsExperimentalFeatureEnabled( 'unifiedComponentsInTimelineEnabled' ); + const { defaultColumns } = useDeepEqualSelector((state) => + selectTimelineById(state, timelineId) + ); + const toggleFullScreen = useCallback(() => { if (timelineId === TimelineId.active) { setTimelineFullScreen(!timelineFullScreen); diff --git a/x-pack/plugins/security_solution/public/common/components/inspect/modal.test.tsx b/x-pack/plugins/security_solution/public/common/components/inspect/modal.test.tsx index 31c417294e7f8..b101410dd6c26 100644 --- a/x-pack/plugins/security_solution/public/common/components/inspect/modal.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/inspect/modal.test.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { mount } from 'enzyme'; import React from 'react'; import { TestProviders } from '../../mock'; @@ -13,6 +12,7 @@ import { NO_ALERT_INDEX } from '../../../../common/constants'; import type { ModalInspectProps } from './modal'; import { ModalInspectQuery, formatIndexPatternRequested } from './modal'; import { InputsModelId } from '../../store/inputs/constants'; +import { fireEvent, render, screen } from '@testing-library/react'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -35,61 +35,55 @@ const request = getRequest(); const response = '{"took": 880,"timed_out": false,"_shards": {"total": 26,"successful": 26,"skipped": 0,"failed": 0},"hits": {"max_score": null,"hits": []},"aggregations": {"hosts": {"value": 541},"hosts_histogram": {"buckets": [{"key_as_string": "2019 - 07 - 05T01: 00: 00.000Z", "key": 1562288400000, "doc_count": 1492321, "count": { "value": 105 }}, {"key_as_string": "2019 - 07 - 05T13: 00: 00.000Z", "key": 1562331600000, "doc_count": 2412761, "count": { "value": 453}},{"key_as_string": "2019 - 07 - 06T01: 00: 00.000Z", "key": 1562374800000, "doc_count": 111658, "count": { "value": 15}}],"interval": "12h"}},"status": 200}'; +const closeModal = jest.fn(); + +const defaultProps: ModalInspectProps = { + closeModal, + inputId: InputsModelId.timeline, + request, + response, + title: 'My title', +}; +const renderModal = (props: ModalInspectProps = defaultProps) => { + return render( + + + + ); +}; + describe('Modal Inspect', () => { - const closeModal = jest.fn(); - const defaultProps: ModalInspectProps = { - closeModal, - inputId: InputsModelId.timeline, - request, - response, - title: 'My title', - }; + describe('functionality from tab statistics', () => { + test('should show statistics tab correctly', () => { + renderModal(); + + fireEvent.click(screen.getByTestId('modal-inspect-statistics-tab')); + expect(screen.getByTestId('modal-inspect-statistics-tab')).toHaveAttribute( + 'aria-selected', + 'true' + ); - describe('functionality from tab statistics/request/response', () => { - test('Click on statistic Tab', () => { - const wrapper = mount( - - - + expect(screen.getByTestId('index-pattern-title')).toHaveTextContent('Index pattern'); + expect(screen.getByTestId('index-pattern-description')).toHaveTextContent( + 'auditbeat-*, filebeat-*, packetbeat-*, winlogbeat-*' ); + expect(screen.getByTestId('query-time-title')).toHaveTextContent('Query time'); - wrapper.find('button.euiTab').first().simulate('click'); - wrapper.update(); - - expect( - wrapper.find('.euiDescriptionList__title span[data-test-subj="index-pattern-title"]').text() - ).toContain('Index pattern '); - expect( - wrapper - .find('.euiDescriptionList__description span[data-test-subj="index-pattern-description"]') - .text() - ).toBe('auditbeat-*, filebeat-*, packetbeat-*, winlogbeat-*'); - expect( - wrapper.find('.euiDescriptionList__title span[data-test-subj="query-time-title"]').text() - ).toContain('Query time '); - expect( - wrapper - .find('.euiDescriptionList__description span[data-test-subj="query-time-description"]') - .text() - ).toBe('880ms'); - expect( - wrapper - .find('.euiDescriptionList__title span[data-test-subj="request-timestamp-title"]') - .text() - ).toContain('Request timestamp '); + expect(screen.getByTestId('query-time-description')).toHaveTextContent('880ms'); + expect(screen.getByTestId('request-timestamp-title')).toHaveTextContent('Request timestamp'); }); - test('Click on request Tab', () => { - const wrapper = mount( - - - - ); + test('should show response Tab content correctly', () => { + renderModal(); - wrapper.find('button.euiTab').at(2).simulate('click'); - wrapper.update(); + fireEvent.click(screen.getByTestId('modal-inspect-response-tab')); + expect(screen.getByTestId('modal-inspect-response-tab')).toHaveAttribute( + 'aria-selected', + 'true' + ); - expect(JSON.parse(wrapper.find('EuiCodeBlock').text())).toEqual({ + const responseTextContent = screen.getByRole('tabpanel').textContent ?? ''; + expect(JSON.parse(responseTextContent)).toMatchObject({ took: 880, timed_out: false, _shards: { @@ -140,17 +134,18 @@ describe('Modal Inspect', () => { }); }); - test('Click on response Tab', () => { - const wrapper = mount( - - - + test('should show request tab correctly', () => { + renderModal(); + + fireEvent.click(screen.getByTestId('modal-inspect-request-tab')); + expect(screen.getByTestId('modal-inspect-request-tab')).toHaveAttribute( + 'aria-selected', + 'true' ); - wrapper.find('button.euiTab').at(1).simulate('click'); - wrapper.update(); + const requestTextContent = screen.getByRole('tabpanel').textContent ?? ''; - expect(JSON.parse(wrapper.find('EuiCodeBlock').text())).toEqual({ + expect(JSON.parse(requestTextContent)).toMatchObject({ aggregations: { hosts: { cardinality: { field: 'host.name' } }, hosts_histogram: { @@ -170,62 +165,56 @@ describe('Modal Inspect', () => { }); describe('events', () => { - test('Make sure that toggle function has been called when you click on the close button', () => { - const wrapper = mount( - - - - ); + test('should make sure that toggle function has been called when you click on the close button', () => { + renderModal(); - wrapper.find('button[data-test-subj="modal-inspect-close"]').simulate('click'); - wrapper.update(); + fireEvent.click(screen.getByTestId('modal-inspect-close')); expect(closeModal).toHaveBeenCalled(); }); }); describe('formatIndexPatternRequested', () => { - test('Return specific messages to NO_ALERT_INDEX if we only have one index and we match the index name `NO_ALERT_INDEX`', () => { + test('should return specific messages to NO_ALERT_INDEX if we only have one index and we match the index name `NO_ALERT_INDEX`', () => { const expected = formatIndexPatternRequested([NO_ALERT_INDEX]); expect(expected).toEqual({'No alert index found'}); }); - test('Ignore NO_ALERT_INDEX if you have more than one indices', () => { + test('should ignore NO_ALERT_INDEX if you have more than one indices', () => { const expected = formatIndexPatternRequested([NO_ALERT_INDEX, 'indice-1']); expect(expected).toEqual('indice-1'); }); - test('Happy path', () => { + test('should format indices correctly', () => { const expected = formatIndexPatternRequested(['indice-1, indice-2']); expect(expected).toEqual('indice-1, indice-2'); }); - test('Empty array with no indices', () => { + test('should show error when indices array is empty', () => { const expected = formatIndexPatternRequested([]); expect(expected).toEqual('Sorry about that, something went wrong.'); }); - test('Undefined indices', () => { + test('should show error when indices are Undefined', () => { const expected = formatIndexPatternRequested(undefined); expect(expected).toEqual('Sorry about that, something went wrong.'); }); }); describe('index pattern messaging', () => { - test('no messaging when all patterns are in sourcerer selection', () => { - const wrapper = mount( - - - - ); - expect(wrapper.find('i[data-test-subj="not-sourcerer-msg"]').first().exists()).toEqual(false); + test('should show no messaging when all patterns match sourcerer selection', () => { + renderModal(); + + expect(screen.queryByTestId('not-sourcerer-msg')).toBeNull(); }); - test('not-sourcerer-msg when not all patterns are in sourcerer selection', () => { - const wrapper = mount( - - - + test('should show not-sourcerer-msg when not all patterns are in sourcerer selection', () => { + renderModal({ + ...defaultProps, + request: getRequest(['differentbeat-*']), + }); + + expect(screen.getByTestId('not-sourcerer-msg')).toHaveTextContent( + 'This element has a unique index pattern separate from the data view setting.' ); - expect(wrapper.find('i[data-test-subj="not-sourcerer-msg"]').first().exists()).toEqual(true); }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx b/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx index 175c02dfdbed5..bb0a20b7736b0 100644 --- a/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx +++ b/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx @@ -59,13 +59,25 @@ interface Response { const MyEuiModal = styled(EuiModal)` width: min(768px, calc(100vw - 16px)); - min-height: 41vh; + height: 41vh; + .euiModal__flex { width: 60vw; } - .euiCodeBlock { - height: auto !important; - max-width: 718px; + + [role='tabpanel'] { + /* + * Current tabpanel height is based on the content inside it and since we are using virtualized codeblock, + * which needs to have a fixed height of parent to render the codeblock properly, we will set tabpanel height + * to take up any remaining space after header, footer and tabs in the Modal. + * + * height of the tabPanel is calculated according to the Modal height of 41vh + * and then subtracting the height of the header, footer and the space between the tabs and the codeblock + * + * headerHeight + footerHeight + tabsHeight + paddingAroundCodeBlock = 208px + * + */ + height: calc(41vh - 208px) !important; } `; @@ -110,14 +122,18 @@ export const ModalInspectQuery = ({ inputId === 'timeline' ? SourcererScopeName.timeline : getScopeFromPath(pathname) ); - const requests: string[] = [request, ...(additionalRequests != null ? additionalRequests : [])]; - const responses: string[] = [ - response, - ...(additionalResponses != null ? additionalResponses : []), - ]; + const requests: string[] = useMemo( + () => [request, ...(additionalRequests != null ? additionalRequests : [])], + [request, additionalRequests] + ); + + const responses: string[] = useMemo( + () => [response, ...(additionalResponses != null ? additionalResponses : [])], + [response, additionalResponses] + ); - const inspectRequests: Request[] = parseInspectStrings(requests); - const inspectResponses: Response[] = parseInspectStrings(responses); + const inspectRequests: Request[] = useMemo(() => parseInspectStrings(requests), [requests]); + const inspectResponses: Response[] = useMemo(() => parseInspectStrings(responses), [responses]); const isSourcererPattern = useMemo( () => @@ -130,129 +146,142 @@ export const ModalInspectQuery = ({ const statistics: Array<{ title: NonNullable; description: NonNullable; - }> = [ - { - title: ( - - {i18n.INDEX_PATTERN}{' '} - - - ), - description: ( - -

- {formatIndexPatternRequested( - adHocDataViews != null && adHocDataViews.length > 0 - ? adHocDataViews - : inspectRequests[0]?.index ?? [] - )} -

- - {!isSourcererPattern && ( + }> = useMemo( + () => [ + { + title: ( + + {i18n.INDEX_PATTERN}{' '} + + + ), + description: ( +

- - {i18n.INSPECT_PATTERN_DIFFERENT} - + {formatIndexPatternRequested( + adHocDataViews != null && adHocDataViews.length > 0 + ? adHocDataViews + : inspectRequests[0]?.index ?? [] + )}

- )} -
- ), - }, - { - title: ( - - {i18n.QUERY_TIME}{' '} - - - ), - description: ( - - {inspectResponses[0]?.took === 0 - ? '0ms' - : inspectResponses[0]?.took - ? `${numeral(inspectResponses[0].took).format('0,0')}ms` - : i18n.SOMETHING_WENT_WRONG} - - ), - }, - { - title: ( - - {i18n.REQUEST_TIMESTAMP}{' '} - - - ), - description: ( - {new Date().toISOString()} - ), - }, - ]; + {!isSourcererPattern && ( +

+ + {i18n.INSPECT_PATTERN_DIFFERENT} + +

+ )} +
+ ), + }, - const tabs = [ - { - id: 'statistics', - name: 'Statistics', - content: ( - <> - - - - ), - }, - { - id: 'request', - name: 'Request', - content: - inspectRequests.length > 0 ? ( - inspectRequests.map((inspectRequest, index) => ( - - - - {manageStringify(inspectRequest.body)} - - - )) - ) : ( - {i18n.SOMETHING_WENT_WRONG} + { + title: ( + + {i18n.QUERY_TIME}{' '} + + + ), + description: ( + + {inspectResponses[0]?.took === 0 + ? '0ms' + : inspectResponses[0]?.took + ? `${numeral(inspectResponses[0].took).format('0,0')}ms` + : i18n.SOMETHING_WENT_WRONG} + ), - }, - { - id: 'response', - name: 'Response', - content: - inspectResponses.length > 0 ? ( - responses.map((responseText, index) => ( - - - - {responseText} - - - )) - ) : ( - {i18n.SOMETHING_WENT_WRONG} + }, + { + title: ( + + {i18n.REQUEST_TIMESTAMP}{' '} + + ), - }, - ]; + description: ( + {new Date().toISOString()} + ), + }, + ], + [adHocDataViews, inspectRequests, inspectResponses, isSourcererPattern] + ); + + const tabs = useMemo( + () => [ + { + id: 'statistics', + name: 'Statistics', + 'data-test-subj': 'modal-inspect-statistics-tab', + content: ( + <> + + + + ), + }, + { + id: 'request', + name: 'Request', + 'data-test-subj': 'modal-inspect-request-tab', + content: + inspectRequests.length > 0 ? ( + inspectRequests.map((inspectRequest, index) => ( + + + {manageStringify(inspectRequest.body)} + + + )) + ) : ( + {i18n.SOMETHING_WENT_WRONG} + ), + }, + { + id: 'response', + name: 'Response', + 'data-test-subj': 'modal-inspect-response-tab', + content: + inspectResponses.length > 0 ? ( + responses.map((responseText, index) => ( + + + {responseText} + + + )) + ) : ( + {i18n.SOMETHING_WENT_WRONG} + ), + }, + ], + [inspectRequests, inspectResponses, responses, statistics] + ); return ( diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/index.test.tsx index 0eaf44e9cc69c..8939ffd1456cd 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/index.test.tsx @@ -42,6 +42,16 @@ describe('WelcomeHeaderComponent', () => { expect(titleElement).toBeInTheDocument(); }); + it('should render username when fullName is an empty string', () => { + const fullName = ''; + const username = 'jd'; + mockUseCurrentUser.mockReturnValue({ fullName, username }); + + const { getByText } = render(); + const titleElement = getByText(`Hi ${username}!`); + expect(titleElement).toBeInTheDocument(); + }); + it('should render username when fullName is not provided', () => { const username = 'jd'; mockUseCurrentUser.mockReturnValue({ username }); diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/index.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/index.tsx index 848eece5b243a..f659f8a0a4599 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/index.tsx @@ -24,7 +24,7 @@ const WelcomeHeaderComponent: React.FC<{ productTier?: ProductTier }> = ({ produ const userName = useCurrentUser(); // Full name could be null, user name should always exist - const name = userName?.fullName ?? userName?.username; + const name = userName?.fullName || userName?.username; const projectFeaturesUrl = useProjectFeaturesUrl(); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/components/with_security_context/index.ts b/x-pack/plugins/security_solution/public/common/components/with_security_context/index.ts similarity index 100% rename from x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/components/with_security_context/index.ts rename to x-pack/plugins/security_solution/public/common/components/with_security_context/index.ts diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/components/with_security_context/render_context_providers.tsx b/x-pack/plugins/security_solution/public/common/components/with_security_context/render_context_providers.tsx similarity index 68% rename from x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/components/with_security_context/render_context_providers.tsx rename to x-pack/plugins/security_solution/public/common/components/with_security_context/render_context_providers.tsx index 4ad30f3945ee6..0d493f1e305d5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/components/with_security_context/render_context_providers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/with_security_context/render_context_providers.tsx @@ -11,14 +11,14 @@ import { Provider as ReduxStoreProvider } from 'react-redux'; import type { Store } from 'redux'; import { NavigationProvider } from '@kbn/security-solution-navigation'; import type { UpsellingService } from '@kbn/security-solution-upselling/service'; -import { UpsellingProvider } from '../../../../../../../common/components/upselling_provider'; -import { UserPrivilegesProvider } from '../../../../../../../common/components/user_privileges/user_privileges_context'; -import type { SecuritySolutionQueryClient } from '../../../../../../../common/containers/query_client/query_client_provider'; -import { ReactQueryClientProvider } from '../../../../../../../common/containers/query_client/query_client_provider'; -import { SecuritySolutionStartDependenciesContext } from '../../../../../../../common/components/user_privileges/endpoint/security_solution_start_dependencies'; -import { CurrentLicense } from '../../../../../../../common/components/current_license'; -import type { StartPlugins } from '../../../../../../../types'; -import { useKibana } from '../../../../../../../common/lib/kibana'; +import { UpsellingProvider } from '../upselling_provider'; +import { UserPrivilegesProvider } from '../user_privileges/user_privileges_context'; +import type { SecuritySolutionQueryClient } from '../../containers/query_client/query_client_provider'; +import { ReactQueryClientProvider } from '../../containers/query_client/query_client_provider'; +import { SecuritySolutionStartDependenciesContext } from '../user_privileges/endpoint/security_solution_start_dependencies'; +import { CurrentLicense } from '../current_license'; +import type { StartPlugins } from '../../../types'; +import { useKibana } from '../../lib/kibana'; export type RenderContextProvidersProps = PropsWithChildren<{ store: Store; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/components/with_security_context/store.ts b/x-pack/plugins/security_solution/public/common/components/with_security_context/store.ts similarity index 76% rename from x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/components/with_security_context/store.ts rename to x-pack/plugins/security_solution/public/common/components/with_security_context/store.ts index 512408b5e27c3..5a1179a4f5695 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/components/with_security_context/store.ts +++ b/x-pack/plugins/security_solution/public/common/components/with_security_context/store.ts @@ -8,14 +8,14 @@ import type { Dispatch, Middleware, PreloadedState, ReducersMapObject } from 'redux'; import { applyMiddleware, combineReducers, compose, createStore } from 'redux'; import type { CoreStart } from '@kbn/core/public'; -import { managementReducer } from '../../../../../../store/reducer'; -import { appReducer } from '../../../../../../../common/store/app'; -import { ExperimentalFeaturesService } from '../../../../../../../common/experimental_features_service'; -import { managementMiddlewareFactory } from '../../../../../../store/middleware'; -import type { StartPlugins } from '../../../../../../../types'; -import type { State } from '../../../../../../../common/store'; -import type { AppAction } from '../../../../../../../common/store/actions'; -import type { Immutable } from '../../../../../../../../common/endpoint/types'; +import { managementReducer } from '../../../management/store/reducer'; +import { appReducer } from '../../store/app'; +import { ExperimentalFeaturesService } from '../../experimental_features_service'; +import { managementMiddlewareFactory } from '../../../management/store/middleware'; +import type { StartPlugins } from '../../../types'; +import type { State } from '../../store'; +import type { AppAction } from '../../store/actions'; +import type { Immutable } from '../../../../common/endpoint/types'; type ComposeType = typeof compose; declare global { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/components/with_security_context/with_security_context.tsx b/x-pack/plugins/security_solution/public/common/components/with_security_context/with_security_context.tsx similarity index 100% rename from x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/components/with_security_context/with_security_context.tsx rename to x-pack/plugins/security_solution/public/common/components/with_security_context/with_security_context.tsx diff --git a/x-pack/plugins/security_solution/public/common/hooks/endpoint/__mocks__/use_alert_response_actions_support.ts b/x-pack/plugins/security_solution/public/common/hooks/endpoint/__mocks__/use_alert_response_actions_support.ts new file mode 100644 index 0000000000000..dcced934f3bc9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/endpoint/__mocks__/use_alert_response_actions_support.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 type { AlertResponseActionsSupport } from '../use_alert_response_actions_support'; +import { + RESPONSE_ACTION_API_COMMANDS_NAMES, + RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD, +} from '../../../../../common/endpoint/service/response_actions/constants'; + +const useAlertResponseActionsSupportMock = (): AlertResponseActionsSupport => { + return { + isSupported: true, + unsupportedReason: undefined, + isAlert: true, + details: { + agentId: '123', + agentType: 'endpoint', + agentIdField: RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD.endpoint, + hostName: 'host-a', + platform: 'linux', + agentSupport: RESPONSE_ACTION_API_COMMANDS_NAMES.reduce< + AlertResponseActionsSupport['details']['agentSupport'] + >((acc, responseActionName) => { + acc[responseActionName] = true; + return acc; + }, {} as AlertResponseActionsSupport['details']['agentSupport']), + }, + }; +}; + +export { useAlertResponseActionsSupportMock as useAlertResponseActionsSupport }; diff --git a/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_alert_response_actions_support.test.ts b/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_alert_response_actions_support.test.ts new file mode 100644 index 0000000000000..b5a07b34c65bb --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_alert_response_actions_support.test.ts @@ -0,0 +1,207 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import type { AppContextTestRender } from '../../mock/endpoint'; +import { createAppRootMockRenderer, endpointAlertDataMock } from '../../mock/endpoint'; +import type { ResponseActionAgentType } from '../../../../common/endpoint/service/response_actions/constants'; +import { + RESPONSE_ACTION_AGENT_TYPE, + RESPONSE_ACTION_API_COMMANDS_NAMES, + RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD, +} from '../../../../common/endpoint/service/response_actions/constants'; +import type { AlertResponseActionsSupport } from './use_alert_response_actions_support'; +import { + ALERT_EVENT_DATA_MISSING_AGENT_ID_FIELD, + RESPONSE_ACTIONS_ONLY_SUPPORTED_ON_ALERTS, + useAlertResponseActionsSupport, +} from './use_alert_response_actions_support'; +import { isAgentTypeAndActionSupported } from '../../lib/endpoint'; +import type { DeepPartial } from 'utility-types'; +import { merge } from 'lodash'; + +describe('When using `useAlertResponseActionsSupport()` hook', () => { + let appContextMock: AppContextTestRender; + let alertDetailItemData: TimelineEventsDetailsItem[]; + let renderHook: () => ReturnType; + + const getExpectedResult = ( + overrides: DeepPartial = {}, + options: Partial<{ + /* If true, then all properties in `agentSupport` will be false */ + noAgentSupport: boolean; + }> = {} + ): AlertResponseActionsSupport => { + const agentType = overrides.details?.agentType ?? 'endpoint'; + + return merge( + { + isAlert: true, + isSupported: true, + unsupportedReason: undefined, + details: { + agentId: 'abfe4a35-d5b4-42a0-a539-bd054c791769', + agentIdField: RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD[agentType], + agentSupport: RESPONSE_ACTION_API_COMMANDS_NAMES.reduce((acc, commandName) => { + acc[commandName] = options.noAgentSupport + ? false + : isAgentTypeAndActionSupported(agentType, commandName); + return acc; + }, {} as AlertResponseActionsSupport['details']['agentSupport']), + agentType, + hostName: 'elastic-host-win', + platform: 'windows', + }, + }, + overrides + ); + }; + + beforeEach(() => { + appContextMock = createAppRootMockRenderer(); + + // Enable feature flags by default + appContextMock.setExperimentalFlag({ + responseActionsSentinelOneV1Enabled: true, + responseActionsSentinelOneGetFileEnabled: true, + responseActionsCrowdstrikeManualHostIsolationEnabled: true, + }); + + alertDetailItemData = endpointAlertDataMock.generateEndpointAlertDetailsItemData(); + renderHook = () => + appContextMock.renderHook(() => useAlertResponseActionsSupport(alertDetailItemData)); + }); + + it.each(RESPONSE_ACTION_AGENT_TYPE)( + 'should return expected response for agentType: `%s`', + (agentType) => { + alertDetailItemData = + endpointAlertDataMock.generateAlertDetailsItemDataForAgentType(agentType); + const { result } = renderHook(); + + expect(result.current).toEqual(getExpectedResult({ details: { agentType } })); + } + ); + + it('should set `isSupported` to `false` if no alert details item data is provided', () => { + alertDetailItemData = []; + + expect(renderHook().result.current).toEqual( + getExpectedResult( + { + isAlert: false, + isSupported: false, + unsupportedReason: RESPONSE_ACTIONS_ONLY_SUPPORTED_ON_ALERTS, + details: { + agentId: '', + agentIdField: '', + hostName: '', + platform: '', + }, + }, + { noAgentSupport: true } + ) + ); + }); + + it('should set `isSupported` to `false` for if not an Alert', () => { + alertDetailItemData = endpointAlertDataMock.generateAlertDetailsItemDataForAgentType( + 'sentinel_one', + { 'kibana.alert.rule.uuid': undefined } + ); + + expect(renderHook().result.current).toEqual( + getExpectedResult({ + isAlert: false, + isSupported: false, + unsupportedReason: RESPONSE_ACTIONS_ONLY_SUPPORTED_ON_ALERTS, + details: { + agentType: 'sentinel_one', + agentIdField: RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD.sentinel_one, + }, + }) + ); + }); + + it('should set `isSupported` to `false` if unable to get agent id', () => { + alertDetailItemData = endpointAlertDataMock.generateEndpointAlertDetailsItemData({ + [RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD.endpoint]: undefined, + }); + + expect(renderHook().result.current).toEqual( + getExpectedResult({ + isSupported: false, + unsupportedReason: ALERT_EVENT_DATA_MISSING_AGENT_ID_FIELD('Elastic Defend', 'agent.id'), + details: { agentId: '' }, + }) + ); + }); + + it('should set `isSupported` to `false` if unable to determine agent type', () => { + alertDetailItemData = endpointAlertDataMock.generateCrowdStrikeAlertDetailsItemData({ + 'event.module': undefined, + }); + + expect(renderHook().result.current).toEqual( + getExpectedResult( + { + isSupported: false, + details: { + agentId: '', + agentIdField: '', + }, + }, + { noAgentSupport: true } + ) + ); + }); + + it('should default `details.agentType` to `endpoint` for non-supported hosts', () => { + alertDetailItemData = endpointAlertDataMock.generateAlertDetailsItemDataForAgentType('foo'); + + expect(renderHook().result.current).toEqual( + getExpectedResult( + { + isSupported: false, + details: { + agentId: '', + agentIdField: '', + }, + }, + { noAgentSupport: true } + ) + ); + }); + + it.each( + RESPONSE_ACTION_AGENT_TYPE.filter((agentType) => agentType !== 'endpoint') as Array< + Exclude + > + )('should set `isSupported` to `false` for [%s] if feature flag is disabled', (agentType) => { + switch (agentType) { + case 'sentinel_one': + appContextMock.setExperimentalFlag({ responseActionsSentinelOneV1Enabled: false }); + break; + case 'crowdstrike': + appContextMock.setExperimentalFlag({ + responseActionsCrowdstrikeManualHostIsolationEnabled: false, + }); + break; + default: + throw new Error(`Unknown agent type: ${agentType}`); + } + + alertDetailItemData = endpointAlertDataMock.generateAlertDetailsItemDataForAgentType(agentType); + + expect(renderHook().result.current).toEqual( + getExpectedResult({ + isSupported: false, + details: { agentType }, + }) + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_alert_response_actions_support.ts b/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_alert_response_actions_support.ts new file mode 100644 index 0000000000000..7d4698695c3b8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_alert_response_actions_support.ts @@ -0,0 +1,241 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import { useMemo } from 'react'; +import { find, some } from 'lodash/fp'; +import { i18n } from '@kbn/i18n'; +import { getAlertDetailsFieldValue } from '../../lib/endpoint/utils/get_event_details_field_values'; +import { isAgentTypeAndActionSupported } from '../../lib/endpoint'; +import type { + ResponseActionAgentType, + ResponseActionsApiCommandNames, +} from '../../../../common/endpoint/service/response_actions/constants'; +import { + RESPONSE_ACTION_API_COMMANDS_NAMES, + RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD, +} from '../../../../common/endpoint/service/response_actions/constants'; +import { getAgentTypeName } from '../../translations'; + +export const ALERT_EVENT_DATA_MISSING_AGENT_ID_FIELD = ( + agentTypeName: string, + missingField: string +): string => { + return i18n.translate( + 'xpack.securitySolution.useAlertResponseActionsSupport.missingAgentIdField', + { + defaultMessage: + 'Alert event data missing {agentTypeName} agent identifier field ({missingField})', + values: { + missingField, + agentTypeName, + }, + } + ); +}; + +export const RESPONSE_ACTIONS_ONLY_SUPPORTED_ON_ALERTS = i18n.translate( + 'xpack.securitySolution.useAlertResponseActionsSupport.notAnAlert', + { defaultMessage: 'Response actions are only supported for Alerts (not events)' } +); + +export interface AlertResponseActionsSupport { + /** Does the host/agent for the given alert have support for response actions */ + isSupported: boolean; + + /** A i18n'd string value indicating the reason why the host does is unsupported */ + unsupportedReason: string | undefined; + + /** + * If the Event Data provide was for a SIEM alert (generated as a result of a Rule run) or + * just an event. + */ + isAlert: boolean; + + /** + * Full details around support for response actions. + * NOTE That some data may not be blank if `isSupported` is `false` + */ + details: { + /** Defaults to `endpoint` when unable to determine agent type */ + agentType: ResponseActionAgentType; + /** Agent ID could be an empty string if `isSupported` is `false` */ + agentId: string; + /** Host name could be an empty string if `isSupported` is `false` */ + hostName: string; + /** The OS platform - normally the ECS value from `host.os.family. could be an empty string if `isSupported` is `false` */ + platform: string; + /** + * A map with the response actions supported by this alert's agent type. This is only what is + * supported, not what the user has privileges to execute. + */ + agentSupport: AlertAgentActionsSupported; + /** The field that was/is used to store the agent ID in the ES document */ + agentIdField: string; + }; +} + +type AlertAgentActionsSupported = Record; + +/** + * Determines the level of support that an alert's host has for Response Actions. + * This hook already checks feature flags to determine the level of support that we have available + */ +export const useAlertResponseActionsSupport = ( + eventData: TimelineEventsDetailsItem[] | null = [] +): AlertResponseActionsSupport => { + const isAlert = useMemo(() => { + return some({ category: 'kibana', field: 'kibana.alert.rule.uuid' }, eventData); + }, [eventData]); + + const agentType: ResponseActionAgentType | undefined = useMemo(() => { + if ((find({ field: 'agent.type' }, eventData)?.values ?? []).includes('endpoint')) { + return 'endpoint'; + } + + const eventModuleValues = find({ field: 'event.module' }, eventData)?.values ?? []; + + if (eventModuleValues.includes('sentinel_one')) { + return 'sentinel_one'; + } + + if (eventModuleValues.includes('crowdstrike')) { + return 'crowdstrike'; + } + + return undefined; + }, [eventData]); + + const isFeatureEnabled: boolean = useMemo(() => { + return agentType ? isAgentTypeAndActionSupported(agentType) : false; + }, [agentType]); + + const agentId: string = useMemo(() => { + if (!agentType) { + return ''; + } + + if (agentType === 'endpoint') { + return getAlertDetailsFieldValue({ category: 'agent', field: 'agent.id' }, eventData); + } + + if (agentType === 'sentinel_one') { + return getAlertDetailsFieldValue( + { category: 'observer', field: RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD.sentinel_one }, + eventData + ); + } + + if (agentType === 'crowdstrike') { + return getAlertDetailsFieldValue( + { category: 'crowdstrike', field: RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD.crowdstrike }, + eventData + ); + } + + return ''; + }, [agentType, eventData]); + + const doesHostSupportResponseActions = useMemo(() => { + return Boolean(isFeatureEnabled && isAlert && agentId && agentType); + }, [agentId, agentType, isAlert, isFeatureEnabled]); + + const agentIdField = useMemo(() => { + if (agentType) { + return RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD[agentType]; + } + + return ''; + }, [agentType]); + + const supportedActions = useMemo(() => { + return RESPONSE_ACTION_API_COMMANDS_NAMES.reduce( + (acc, responseActionName) => { + acc[responseActionName] = false; + + if (agentType && isFeatureEnabled) { + acc[responseActionName] = isAgentTypeAndActionSupported( + agentType, + responseActionName, + 'manual' + ); + } + + return acc; + }, + {} as AlertAgentActionsSupported + ); + }, [agentType, isFeatureEnabled]); + + const hostName = useMemo(() => { + // TODO:PT need to check if crowdstrike event has `host.name` + if (agentType === 'crowdstrike') { + return getAlertDetailsFieldValue( + { category: 'crowdstrike', field: 'crowdstrike.event.HostName' }, + eventData + ); + } + + return getAlertDetailsFieldValue({ category: 'host', field: 'host.name' }, eventData); + }, [agentType, eventData]); + + const platform = useMemo(() => { + // TODO:PT need to check if crowdstrike event has `host.os.family` + if (agentType === 'crowdstrike') { + return getAlertDetailsFieldValue( + { category: 'crowdstrike', field: 'crowdstrike.event.Platform' }, + eventData + ); + } + + return getAlertDetailsFieldValue({ category: 'host', field: 'host.os.family' }, eventData); + }, [agentType, eventData]); + + const unsupportedReason = useMemo(() => { + if (!doesHostSupportResponseActions) { + if (!isAlert) { + return RESPONSE_ACTIONS_ONLY_SUPPORTED_ON_ALERTS; + } + + if (!agentType) { + // No message is provided for this condition because the + // return from this hook will always default to `endpoint` + return; + } + + if (!agentId) { + return ALERT_EVENT_DATA_MISSING_AGENT_ID_FIELD(getAgentTypeName(agentType), agentIdField); + } + } + }, [agentId, agentIdField, agentType, doesHostSupportResponseActions, isAlert]); + + return useMemo(() => { + return { + isSupported: doesHostSupportResponseActions, + unsupportedReason, + isAlert, + details: { + agentType: agentType || 'endpoint', + agentId, + hostName, + platform, + agentIdField, + agentSupport: supportedActions, + }, + }; + }, [ + agentId, + agentIdField, + agentType, + doesHostSupportResponseActions, + hostName, + isAlert, + platform, + supportedActions, + unsupportedReason, + ]); +}; diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint_isolation/index.test.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/index.test.ts similarity index 91% rename from x-pack/plugins/security_solution/public/common/lib/endpoint_isolation/index.test.ts rename to x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/index.test.ts index ebfac9c6508b6..13c9af46fbac5 100644 --- a/x-pack/plugins/security_solution/public/common/lib/endpoint_isolation/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/index.test.ts @@ -5,16 +5,16 @@ * 2.0. */ -import { KibanaServices } from '../kibana'; +import { KibanaServices } from '../../kibana'; import { coreMock } from '@kbn/core/public/mocks'; import { isolateHost, unIsolateHost } from '.'; import { hostIsolationRequestBodyMock } from './mocks'; import { ISOLATE_HOST_ROUTE_V2, UNISOLATE_HOST_ROUTE_V2, -} from '../../../../common/endpoint/constants'; +} from '../../../../../common/endpoint/constants'; -jest.mock('../kibana'); +jest.mock('../../kibana'); describe('When using Host Isolation library', () => { const mockKibanaServices = KibanaServices.get as jest.Mock; diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint_isolation/index.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/index.ts similarity index 84% rename from x-pack/plugins/security_solution/public/common/lib/endpoint_isolation/index.ts rename to x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/index.ts index 71cde358a17a3..dc119de848dec 100644 --- a/x-pack/plugins/security_solution/public/common/lib/endpoint_isolation/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/index.ts @@ -8,12 +8,14 @@ import type { HostIsolationRequestBody, ResponseActionApiResponse, -} from '../../../../common/endpoint/types'; -import { KibanaServices } from '../kibana'; +} from '../../../../../common/endpoint/types'; +import { KibanaServices } from '../../kibana'; import { ISOLATE_HOST_ROUTE_V2, UNISOLATE_HOST_ROUTE_V2, -} from '../../../../common/endpoint/constants'; +} from '../../../../../common/endpoint/constants'; + +// FIXME:PT refactor usage of these and use common hooks /** Isolates a Host running either elastic endpoint or fleet agent */ export const isolateHost = async ( diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint_isolation/mocks.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/mocks.ts similarity index 81% rename from x-pack/plugins/security_solution/public/common/lib/endpoint_isolation/mocks.ts rename to x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/mocks.ts index 5540f4c966773..4881fc3f1569f 100644 --- a/x-pack/plugins/security_solution/public/common/lib/endpoint_isolation/mocks.ts +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_isolation/mocks.ts @@ -8,13 +8,13 @@ import type { HostIsolationRequestBody, HostIsolationResponse, -} from '../../../../common/endpoint/types'; -import type { ResponseProvidersInterface } from '../../mock/endpoint/http_handler_mock_factory'; -import { httpHandlerMockFactory } from '../../mock/endpoint/http_handler_mock_factory'; +} from '../../../../../common/endpoint/types'; +import type { ResponseProvidersInterface } from '../../../mock/endpoint/http_handler_mock_factory'; +import { httpHandlerMockFactory } from '../../../mock/endpoint/http_handler_mock_factory'; import { ISOLATE_HOST_ROUTE_V2, UNISOLATE_HOST_ROUTE_V2, -} from '../../../../common/endpoint/constants'; +} from '../../../../../common/endpoint/constants'; export const hostIsolationRequestBodyMock = (): HostIsolationRequestBody => { return { diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint_pending_actions/endpoint_pending_actions.test.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_pending_actions/endpoint_pending_actions.test.ts similarity index 88% rename from x-pack/plugins/security_solution/public/common/lib/endpoint_pending_actions/endpoint_pending_actions.test.ts rename to x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_pending_actions/endpoint_pending_actions.test.ts index 4431486885a03..2b3a71104ff29 100644 --- a/x-pack/plugins/security_solution/public/common/lib/endpoint_pending_actions/endpoint_pending_actions.test.ts +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_pending_actions/endpoint_pending_actions.test.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { KibanaServices } from '../kibana'; +import { KibanaServices } from '../../kibana'; import { coreMock } from '@kbn/core/public/mocks'; import { fetchPendingActionsByAgentId } from './endpoint_pending_actions'; import { pendingActionsHttpMock, pendingActionsResponseMock } from './mocks'; -import { ACTION_STATUS_ROUTE } from '../../../../common/endpoint/constants'; +import { ACTION_STATUS_ROUTE } from '../../../../../common/endpoint/constants'; -jest.mock('../kibana'); +jest.mock('../../kibana'); describe('when using endpoint pending actions api service', () => { let coreHttp: ReturnType['http']; diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint_pending_actions/endpoint_pending_actions.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_pending_actions/endpoint_pending_actions.ts similarity index 77% rename from x-pack/plugins/security_solution/public/common/lib/endpoint_pending_actions/endpoint_pending_actions.ts rename to x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_pending_actions/endpoint_pending_actions.ts index 3d2d8cd5a4e96..86e3a88cc0d2b 100644 --- a/x-pack/plugins/security_solution/public/common/lib/endpoint_pending_actions/endpoint_pending_actions.ts +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_pending_actions/endpoint_pending_actions.ts @@ -8,9 +8,11 @@ import type { PendingActionsRequestQuery, PendingActionsResponse, -} from '../../../../common/endpoint/types'; -import { KibanaServices } from '../kibana'; -import { ACTION_STATUS_ROUTE } from '../../../../common/endpoint/constants'; +} from '../../../../../common/endpoint/types'; +import { KibanaServices } from '../../kibana'; +import { ACTION_STATUS_ROUTE } from '../../../../../common/endpoint/constants'; + +// FIXME:PT refactor these to use common hooks /** * Retrieve a list of pending actions against the given Fleet Agent Ids provided on input diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint_pending_actions/index.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_pending_actions/index.ts similarity index 100% rename from x-pack/plugins/security_solution/public/common/lib/endpoint_pending_actions/index.ts rename to x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_pending_actions/index.ts diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint_pending_actions/mocks.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_pending_actions/mocks.ts similarity index 82% rename from x-pack/plugins/security_solution/public/common/lib/endpoint_pending_actions/mocks.ts rename to x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_pending_actions/mocks.ts index 1ce00bb86ab85..97d91eecc0b38 100644 --- a/x-pack/plugins/security_solution/public/common/lib/endpoint_pending_actions/mocks.ts +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint/endpoint_pending_actions/mocks.ts @@ -8,10 +8,10 @@ import type { PendingActionsRequestQuery, PendingActionsResponse, -} from '../../../../common/endpoint/types'; -import type { ResponseProvidersInterface } from '../../mock/endpoint/http_handler_mock_factory'; -import { httpHandlerMockFactory } from '../../mock/endpoint/http_handler_mock_factory'; -import { ACTION_STATUS_ROUTE } from '../../../../common/endpoint/constants'; +} from '../../../../../common/endpoint/types'; +import type { ResponseProvidersInterface } from '../../../mock/endpoint/http_handler_mock_factory'; +import { httpHandlerMockFactory } from '../../../mock/endpoint/http_handler_mock_factory'; +import { ACTION_STATUS_ROUTE } from '../../../../../common/endpoint/constants'; export const pendingActionsResponseMock = (): PendingActionsResponse => ({ data: [ diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint/index.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/index.ts new file mode 100644 index 0000000000000..5bea833df9cad --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './utils'; +export * from './endpoint_isolation'; +export * from './endpoint_pending_actions'; diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/helpers.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/utils/get_event_details_field_values.ts similarity index 64% rename from x-pack/plugins/security_solution/public/detections/components/host_isolation/helpers.ts rename to x-pack/plugins/security_solution/public/common/lib/endpoint/utils/get_event_details_field_values.ts index 32fa616831dfb..d07b557a8e681 100644 --- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint/utils/get_event_details_field_values.ts @@ -4,11 +4,18 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { find } from 'lodash/fp'; -import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; +import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import { find } from 'lodash/fp'; -export const getFieldValues = ( +/** + * Gets the array of values for a given field in an Alert Details data + * + * @param category + * @param field + * @param data + */ +const getEventDetailsFieldValues = ( { category, field, @@ -17,7 +24,7 @@ export const getFieldValues = ( field: string; }, data: TimelineEventsDetailsItem[] | null -) => { +): string[] => { const categoryCompat = category === 'signal' ? 'kibana' : category === 'kibana' ? 'signal' : category; const fieldCompat = @@ -28,11 +35,20 @@ export const getFieldValues = ( : field; return ( find({ category, field }, data)?.values ?? - find({ category: categoryCompat, field: fieldCompat }, data)?.values + find({ category: categoryCompat, field: fieldCompat }, data)?.values ?? + [] ); }; -export const getFieldValue = ( +/** + * Gets a single value for a given Alert Details data field. If the field has multiple values, + * the first one will be returned. + * + * @param category + * @param field + * @param data + */ +export const getAlertDetailsFieldValue = ( { category, field, @@ -41,7 +57,7 @@ export const getFieldValue = ( field: string; }, data: TimelineEventsDetailsItem[] | null -) => { - const currentField = getFieldValues({ category, field }, data); +): string => { + const currentField = getEventDetailsFieldValues({ category, field }, data); return currentField && currentField.length > 0 ? currentField[0] : ''; }; diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint/utils/index.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/utils/index.ts new file mode 100644 index 0000000000000..b5e173e2c360f --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint/utils/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './is_agent_type_and_action_supported'; +export * from './is_response_actions_alert_agent_id_field'; diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint/utils/is_agent_type_and_action_supported.test.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/utils/is_agent_type_and_action_supported.test.ts new file mode 100644 index 0000000000000..79294c165ab22 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint/utils/is_agent_type_and_action_supported.test.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + ResponseActionAgentType, + ResponseActionsApiCommandNames, + ResponseActionType, +} from '../../../../../common/endpoint/service/response_actions/constants'; +import { isAgentTypeAndActionSupported } from './is_agent_type_and_action_supported'; +import { ExperimentalFeaturesService } from '../../../experimental_features_service'; +import type { ExperimentalFeatures } from '../../../../../common'; +import { allowedExperimentalValues } from '../../../../../common'; + +jest.mock('../../../experimental_features_service'); + +describe('isAgentTypeAndActionSupported() util', () => { + const enableFeatures = (overrides: Partial = {}): void => { + (ExperimentalFeaturesService.get as jest.Mock).mockReturnValue({ + ...allowedExperimentalValues, + responseActionsSentinelOneGetFileEnabled: true, + responseActionsCrowdstrikeManualHostIsolationEnabled: true, + ...overrides, + }); + }; + + const disableS1GetFileFeature = () => { + enableFeatures({ responseActionsSentinelOneGetFileEnabled: false }); + }; + + const resetFeatures = (): void => { + (ExperimentalFeaturesService.get as jest.Mock).mockReturnValue({ + ...allowedExperimentalValues, + }); + }; + + beforeEach(() => { + enableFeatures(); + }); + + afterEach(() => { + resetFeatures(); + }); + + it.each` + agentType | actionName | actionType | expectedValue | runSetup + ${'endpoint'} | ${undefined} | ${undefined} | ${true} | ${undefined} + ${'endpoint'} | ${'isolate'} | ${'manual'} | ${true} | ${undefined} + ${'endpoint'} | ${'isolate'} | ${'automated'} | ${true} | ${undefined} + ${'sentinel_one'} | ${undefined} | ${undefined} | ${true} | ${undefined} + ${'sentinel_one'} | ${'isolate'} | ${'manual'} | ${true} | ${undefined} + ${'sentinel_one'} | ${'get-file'} | ${'manual'} | ${true} | ${undefined} + ${'sentinel_one'} | ${'get-file'} | ${undefined} | ${false} | ${disableS1GetFileFeature} + ${'crowdstrike'} | ${undefined} | ${undefined} | ${true} | ${undefined} + ${'crowdstrike'} | ${'isolate'} | ${'manual'} | ${true} | ${undefined} + ${'crowdstrike'} | ${'isolate'} | ${undefined} | ${false} | ${resetFeatures} + `( + 'should return `$expectedValue` for $agentType $actionName ($actionType)', + ({ + agentType, + actionName, + actionType, + expectedValue, + runSetup, + }: { + agentType: ResponseActionAgentType; + actionName?: ResponseActionsApiCommandNames; + actionType?: ResponseActionType; + runSetup?: () => void; + expectedValue: boolean; + }) => { + if (runSetup) { + runSetup(); + } + + expect(isAgentTypeAndActionSupported(agentType, actionName, actionType)).toBe(expectedValue); + } + ); +}); diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint/utils/is_agent_type_and_action_supported.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/utils/is_agent_type_and_action_supported.ts new file mode 100644 index 0000000000000..ba70f96f1cfde --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint/utils/is_agent_type_and_action_supported.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + ResponseActionAgentType, + ResponseActionsApiCommandNames, + ResponseActionType, +} from '../../../../../common/endpoint/service/response_actions/constants'; +import { isActionSupportedByAgentType } from '../../../../../common/endpoint/service/response_actions/is_response_action_supported'; +import { ExperimentalFeaturesService } from '../../../experimental_features_service'; + +/** + * Checks if a given Agent type is supported (aka: is feature flag enabled) and optionally + * also checks if a given response action is implemented for that agent type. + */ +export const isAgentTypeAndActionSupported = ( + agentType: ResponseActionAgentType, + actionName?: ResponseActionsApiCommandNames, + actionType: ResponseActionType = 'manual' +): boolean => { + const features = ExperimentalFeaturesService.get(); + const isSentinelOneV1Enabled = features.responseActionsSentinelOneV1Enabled; + const isSentinelOneGetFileEnabled = features.responseActionsSentinelOneGetFileEnabled; + const isCrowdstrikeHostIsolationEnabled = + features.responseActionsCrowdstrikeManualHostIsolationEnabled; + + const isAgentTypeSupported = + agentType === 'endpoint' || + (agentType === 'sentinel_one' && isSentinelOneV1Enabled) || + (agentType === 'crowdstrike' && isCrowdstrikeHostIsolationEnabled); + + let isActionNameSupported: boolean = + !actionName || isActionSupportedByAgentType(agentType, actionName, actionType); + + // if response action is supported, then do specific response action FF checks + if (isAgentTypeSupported && isActionNameSupported && actionName) { + switch (agentType) { + case 'sentinel_one': + switch (actionName) { + case 'get-file': + if (!isSentinelOneGetFileEnabled) { + isActionNameSupported = false; + } + break; + } + + break; + + case 'crowdstrike': + // Placeholder for future individual response actions FF checks + break; + } + } + + return Boolean(isAgentTypeSupported && isActionNameSupported); +}; diff --git a/x-pack/plugins/security_solution/public/common/lib/endpoint/utils/is_response_actions_alert_agent_id_field.ts b/x-pack/plugins/security_solution/public/common/lib/endpoint/utils/is_response_actions_alert_agent_id_field.ts new file mode 100644 index 0000000000000..88b940ba3b748 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/lib/endpoint/utils/is_response_actions_alert_agent_id_field.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 { RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD } from '../../../../../common/endpoint/service/response_actions/constants'; + +const SUPPORTED_ALERT_FIELDS: Readonly = Object.values( + RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD +); + +/** + * Checks to see if a given alert field (ex. `agent.id`) is used by Agents that have support for response actions. + */ +export const isResponseActionsAlertAgentIdField = (field: string): boolean => { + return SUPPORTED_ALERT_FIELDS.includes(field); +}; diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/ai_assistant/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/ai_assistant/types.ts index 87dbb38b4b5c7..54a9660d3b2d8 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/ai_assistant/types.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/ai_assistant/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { RootSchema } from '@kbn/analytics-client'; +import type { RootSchema } from '@kbn/core/public'; import type { TelemetryEventTypes } from '../../constants'; export interface ReportAssistantInvokedParams { diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/alerts_grouping/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/alerts_grouping/types.ts index cc654e532f88d..d2b5e227ee66a 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/alerts_grouping/types.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/alerts_grouping/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { RootSchema } from '@kbn/analytics-client'; +import type { RootSchema } from '@kbn/core/public'; import type { TelemetryEventTypes } from '../../constants'; export interface ReportAlertsGroupingChangedParams { diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/attack_discovery/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/attack_discovery/types.ts index e76813d280bc0..dc83083bd38e3 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/attack_discovery/types.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/attack_discovery/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { RootSchema } from '@kbn/analytics-client'; +import type { RootSchema } from '@kbn/core/public'; import type { TelemetryEventTypes } from '../../constants'; export interface ReportAttackDiscoveriesGeneratedParams { diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/types.ts index 2b31e6cefea4f..9e1d012811e3b 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/types.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/data_quality/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { RootSchema } from '@kbn/analytics-client'; +import type { RootSchema } from '@kbn/core/public'; import type { TelemetryEventTypes } from '../../constants'; export type ReportDataQualityIndexCheckedParams = ReportDataQualityCheckAllCompletedParams & { diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/types.ts index a090686c91267..7a3ff374eae3c 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/types.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/document_details/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { RootSchema } from '@kbn/analytics-client'; +import type { RootSchema } from '@kbn/core/public'; import type { TelemetryEventTypes } from '../../constants'; export interface ReportDetailsFlyoutOpenedParams { diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/entity_analytics/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/entity_analytics/types.ts index 9d38694324e55..d71c48004d756 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/entity_analytics/types.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/entity_analytics/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { RootSchema } from '@kbn/analytics-client'; +import type { RootSchema } from '@kbn/core/public'; import type { RiskSeverity } from '../../../../../../common/search_strategy'; import type { TelemetryEventTypes } from '../../constants'; diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/onboarding/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/onboarding/types.ts index 51ceefb49d5be..f6f52a6d675d2 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/events/onboarding/types.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/events/onboarding/types.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { RootSchema } from '@kbn/analytics-client'; +import type { RootSchema } from '@kbn/core/public'; import type { StepLinkId } from '../../../../components/landing_page/onboarding/step_links/types'; import type { TelemetryEventTypes } from '../../constants'; diff --git a/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts b/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts index e42c2cd8ab23c..3be54678c0ad8 100644 --- a/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts +++ b/x-pack/plugins/security_solution/public/common/lib/telemetry/types.ts @@ -5,8 +5,7 @@ * 2.0. */ -import type { RootSchema } from '@kbn/analytics-client'; -import type { AnalyticsServiceSetup } from '@kbn/core/public'; +import type { AnalyticsServiceSetup, RootSchema } from '@kbn/core/public'; import type { AttackDiscoveryTelemetryEvent, ReportAttackDiscoveriesGeneratedParams, diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/endpoint_alert_data_mock.ts b/x-pack/plugins/security_solution/public/common/mock/endpoint/endpoint_alert_data_mock.ts new file mode 100644 index 0000000000000..96003266c1315 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/endpoint_alert_data_mock.ts @@ -0,0 +1,248 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import type { ResponseActionAgentType } from '../../../../common/endpoint/service/response_actions/constants'; +import { RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD } from '../../../../common/endpoint/service/response_actions/constants'; + +/** + * Provide overrides for data `fields`. If a field is set to `undefined`, then it will be removed + * from the array. If an override field name is not currently in the array, it will be added. + */ +interface AlertDetailsItemDataOverrides { + [field: string]: Partial | undefined; +} + +/** + * Will update (mutate) the data passed in with the override data defined + * @param data + * @param overrides + */ +const setAlertDetailsItemDataOverrides = ( + data: TimelineEventsDetailsItem[], + overrides: AlertDetailsItemDataOverrides +): TimelineEventsDetailsItem[] => { + if (Object.keys(overrides).length > 0) { + const definedFields: string[] = []; + const deleteIndexes: number[] = []; + + // Override current fields' values + data.forEach((item, index) => { + definedFields.push(item.field); + + if (item.field in overrides) { + // If value is undefined, then mark item for deletion + if (!overrides[item.field]) { + deleteIndexes.unshift(index); + } else { + Object.assign(item, overrides[item.field]); + } + } + }); + + // Delete any items from the array + if (deleteIndexes.length > 0) { + for (const index of deleteIndexes) { + data.splice(index, 1); + } + } + + // Add any new fields to the data + Object.entries(overrides).forEach(([field, fieldData]) => { + if (!definedFields.includes(field)) { + data.push({ + category: 'unknown', + field: 'unknonwn', + values: [], + originalValue: [], + isObjectArray: false, + ...fieldData, + }); + } + }); + } + + return data; +}; + +/** @private */ +const generateEndpointAlertDetailsItemDataMock = ( + overrides: AlertDetailsItemDataOverrides = {} +): TimelineEventsDetailsItem[] => { + const data = [ + { + category: 'kibana', + field: 'kibana.alert.rule.uuid', + values: ['b69d086c-325a-4f46-b17b-fb6d227006ba'], + originalValue: ['b69d086c-325a-4f46-b17b-fb6d227006ba'], + isObjectArray: false, + }, + { + category: 'agent', + field: 'agent.type', + values: ['endpoint'], + originalValue: ['endpoint'], + isObjectArray: false, + }, + { + category: 'agent', + field: 'agent.id', + values: ['abfe4a35-d5b4-42a0-a539-bd054c791769'], + originalValue: ['abfe4a35-d5b4-42a0-a539-bd054c791769'], + isObjectArray: false, + }, + { + category: 'event', + field: 'event.module', + values: ['endpoint'], + originalValue: ['endpoint'], + isObjectArray: false, + }, + { + category: 'event', + field: 'event.category', + originalValue: ['process'], + values: ['process'], + isObjectArray: false, + }, + { + category: 'host', + field: 'host.name', + values: ['elastic-host-win'], + originalValue: ['windows-native'], + isObjectArray: false, + }, + { + category: 'host', + field: 'host.os.family', + values: ['windows'], + originalValue: ['windows'], + isObjectArray: false, + }, + ]; + + setAlertDetailsItemDataOverrides(data, overrides); + + return data; +}; + +/** @private */ +const generateSentinelOneAlertDetailsItemDataMock = ( + overrides: AlertDetailsItemDataOverrides = {} +): TimelineEventsDetailsItem[] => { + const data = generateEndpointAlertDetailsItemDataMock(overrides); + + data.forEach((itemData) => { + switch (itemData.field) { + case 'event.module': + itemData.values = ['sentinel_one']; + itemData.originalValue = ['sentinel_one']; + break; + + case 'agent.type': + itemData.values = ['filebeat']; + itemData.originalValue = ['filebeat']; + break; + } + }); + + data.push({ + category: 'observer', + field: RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD.sentinel_one, + values: ['abfe4a35-d5b4-42a0-a539-bd054c791769'], + originalValue: ['abfe4a35-d5b4-42a0-a539-bd054c791769'], + isObjectArray: false, + }); + + setAlertDetailsItemDataOverrides(data, overrides); + + return data; +}; + +/** @private */ +const generateCrowdStrikeAlertDetailsItemDataMock = ( + overrides: AlertDetailsItemDataOverrides = {} +): TimelineEventsDetailsItem[] => { + const data = generateEndpointAlertDetailsItemDataMock(); + + data.forEach((itemData) => { + switch (itemData.field) { + case 'event.module': + itemData.values = ['crowdstrike']; + itemData.originalValue = ['crowdstrike']; + break; + + case 'agent.type': + itemData.values = ['filebeat']; + itemData.originalValue = ['filebeat']; + break; + } + }); + + data.push( + { + category: 'crowdstrike', + field: RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD.crowdstrike, + values: ['abfe4a35-d5b4-42a0-a539-bd054c791769'], + originalValue: ['abfe4a35-d5b4-42a0-a539-bd054c791769'], + isObjectArray: false, + }, + { + category: 'crowdstrike', + field: 'crowdstrike.event.HostName', + values: ['elastic-host-win'], + originalValue: ['windows-native'], + isObjectArray: false, + }, + { + category: 'crowdstrike', + field: 'crowdstrike.event.Platform', + values: ['windows'], + originalValue: ['windows'], + isObjectArray: false, + } + ); + + setAlertDetailsItemDataOverrides(data, overrides); + + return data; +}; + +/** + * Will return alert details item data for a known agent type or if unknown agent type is + * pass, then data will be for `filebeat` + * @param agentType + * @param overrides + */ +const generateAlertDetailsItemDataForAgentTypeMock = ( + agentType?: ResponseActionAgentType | string, + overrides: AlertDetailsItemDataOverrides = {} +): TimelineEventsDetailsItem[] => { + const unSupportedAgentType = agentType ?? 'filebeat'; + + switch (agentType) { + case 'endpoint': + return generateEndpointAlertDetailsItemDataMock(overrides); + case 'sentinel_one': + return generateSentinelOneAlertDetailsItemDataMock(overrides); + case 'crowdstrike': + return generateCrowdStrikeAlertDetailsItemDataMock(overrides); + default: + return generateEndpointAlertDetailsItemDataMock({ + 'agent.type': { values: [unSupportedAgentType], originalValue: [unSupportedAgentType] }, + 'event.module': { values: [unSupportedAgentType], originalValue: [unSupportedAgentType] }, + ...overrides, + }); + } +}; + +export const endpointAlertDataMock = Object.freeze({ + generateEndpointAlertDetailsItemData: generateEndpointAlertDetailsItemDataMock, + generateSentinelOneAlertDetailsItemData: generateSentinelOneAlertDetailsItemDataMock, + generateCrowdStrikeAlertDetailsItemData: generateCrowdStrikeAlertDetailsItemDataMock, + generateAlertDetailsItemDataForAgentType: generateAlertDetailsItemDataForAgentTypeMock, +}); diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/index.ts b/x-pack/plugins/security_solution/public/common/mock/endpoint/index.ts index 041a8b319ec46..a7d3f604af562 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/index.ts +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/index.ts @@ -7,3 +7,5 @@ export * from './dependencies_start_mock'; export * from './app_context_render'; +export * from './endpoint_alert_data_mock'; +export * from './http_handler_mock_factory'; diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts index b99e788246dce..7a77de9d23555 100644 --- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts +++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts @@ -7,6 +7,7 @@ import { TableId } from '@kbn/securitysolution-data-table'; import type { DataViewSpec, FieldSpec } from '@kbn/data-views-plugin/public'; +import { ReqStatus } from '../../notes/store/notes.slice'; import { HostsFields } from '../../../common/api/search_strategy/hosts/model/sort'; import { InputsModelId } from '../store/inputs/constants'; import { @@ -500,4 +501,26 @@ export const mockGlobalState: State = { */ management: mockManagementState as ManagementState, discover: getMockDiscoverInTimelineState(), + notes: { + ids: ['1'], + entities: { + '1': { + eventId: 'event-id', + noteId: '1', + note: 'note-1', + timelineId: 'timeline-1', + created: 1663882629000, + createdBy: 'elastic', + updated: 1663882629000, + updatedBy: 'elastic', + version: 'version', + }, + }, + status: { + fetchNotesByDocumentId: ReqStatus.Idle, + }, + error: { + fetchNotesByDocumentId: null, + }, + }, }; diff --git a/x-pack/plugins/security_solution/public/common/store/reducer.test.tsx b/x-pack/plugins/security_solution/public/common/store/reducer.test.tsx index 0710872ae547d..b5b6cd687205a 100644 --- a/x-pack/plugins/security_solution/public/common/store/reducer.test.tsx +++ b/x-pack/plugins/security_solution/public/common/store/reducer.test.tsx @@ -13,6 +13,7 @@ import { useSourcererDataView } from '../../sourcerer/containers'; import { renderHook } from '@testing-library/react-hooks'; import { initialGroupingState } from './grouping/reducer'; import { initialAnalyzerState } from '../../resolver/store/helpers'; +import { initialNotesState } from '../../notes/store/notes.slice'; jest.mock('../hooks/use_selector'); jest.mock('../lib/kibana', () => { @@ -69,7 +70,8 @@ describe('createInitialState', () => { }, { analyzer: initialAnalyzerState, - } + }, + initialNotesState ); test('indicesExist should be TRUE if patternList is NOT empty', async () => { @@ -107,7 +109,9 @@ describe('createInitialState', () => { }, { analyzer: initialAnalyzerState, - } + }, + + initialNotesState ); const { result } = renderHook(() => useSourcererDataView(), { wrapper: ({ children }) => ( diff --git a/x-pack/plugins/security_solution/public/common/store/reducer.ts b/x-pack/plugins/security_solution/public/common/store/reducer.ts index 5ec7ae66bb2bd..ea684909a8776 100644 --- a/x-pack/plugins/security_solution/public/common/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/common/store/reducer.ts @@ -35,6 +35,8 @@ import type { GroupState } from './grouping/types'; import { analyzerReducer } from '../../resolver/store/reducer'; import { securitySolutionDiscoverReducer } from './discover/reducer'; import type { AnalyzerState } from '../../resolver/types'; +import type { NotesState } from '../../notes/store/notes.slice'; +import { notesReducer } from '../../notes/store/notes.slice'; enableMapSet(); @@ -66,7 +68,8 @@ export const createInitialState = ( }, dataTableState: DataTableState, groupsState: GroupState, - analyzerState: AnalyzerState + analyzerState: AnalyzerState, + notesState: NotesState ): State => { const initialPatterns = { [SourcererScopeName.default]: getScopePatternListSelection( @@ -128,6 +131,7 @@ export const createInitialState = ( internal: undefined, savedSearch: undefined, }, + notes: notesState, }; return preloadedState; @@ -150,4 +154,5 @@ export const createReducer: ( analyzer: analyzerReducer, discover: securitySolutionDiscoverReducer, ...pluginsReducer, + notes: notesReducer, }); diff --git a/x-pack/plugins/security_solution/public/common/store/store.ts b/x-pack/plugins/security_solution/public/common/store/store.ts index 5b3500a58d9c6..34209fae78bcf 100644 --- a/x-pack/plugins/security_solution/public/common/store/store.ts +++ b/x-pack/plugins/security_solution/public/common/store/store.ts @@ -5,6 +5,7 @@ * 2.0. */ +import thunk from 'redux-thunk'; import type { Action, Store, @@ -54,6 +55,7 @@ import { dataAccessLayerFactory } from '../../resolver/data_access_layer/factory import { sourcererActions } from '../../sourcerer/store'; import { createMiddlewares } from './middlewares'; import { addNewTimeline } from '../../timelines/store/helpers'; +import { initialNotesState } from '../../notes/store/notes.slice'; let store: Store | null = null; @@ -168,7 +170,8 @@ export const createStoreFactory = async ( }, dataTableInitialState, groupsInitialState, - analyzerInitialState + analyzerInitialState, + initialNotesState ); const rootReducer = { @@ -284,7 +287,8 @@ export const createStore = ( const middlewareEnhancer = applyMiddleware( ...createMiddlewares(kibana, storage), telemetryMiddleware, - ...(additionalMiddleware ?? []) + ...(additionalMiddleware ?? []), + thunk ); store = createReduxStore( diff --git a/x-pack/plugins/security_solution/public/common/store/types.ts b/x-pack/plugins/security_solution/public/common/store/types.ts index 3623ec8837ad4..bf83f9146bdb2 100644 --- a/x-pack/plugins/security_solution/public/common/store/types.ts +++ b/x-pack/plugins/security_solution/public/common/store/types.ts @@ -25,11 +25,11 @@ import type { GlobalUrlParam } from './global_url_param'; import type { GroupState } from './grouping/types'; import type { SecuritySolutionDiscoverState } from './discover/model'; import type { AnalyzerState } from '../../resolver/types'; +import type { NotesState } from '../../notes/store/notes.slice'; export type State = HostsPluginState & UsersPluginState & NetworkPluginState & - UsersPluginState & TimelinePluginState & ManagementPluginState & { app: AppState; @@ -40,7 +40,7 @@ export type State = HostsPluginState & discover: SecuritySolutionDiscoverState; } & DataTableState & GroupState & - AnalyzerState; + AnalyzerState & { notes: NotesState }; /** * The Redux store type for the Security app. */ diff --git a/x-pack/plugins/security_solution/public/common/translations.ts b/x-pack/plugins/security_solution/public/common/translations.ts index 9720baa0d5aa0..dfb4d29f59371 100644 --- a/x-pack/plugins/security_solution/public/common/translations.ts +++ b/x-pack/plugins/security_solution/public/common/translations.ts @@ -101,7 +101,7 @@ export const UNSAVED_TIMELINE_SAVE_PROMPT_TITLE = i18n.translate( } ); -export const getAgentTypeName = (agentType: ResponseActionAgentType) => { +export const getAgentTypeName = (agentType: ResponseActionAgentType): string => { switch (agentType) { case 'endpoint': return 'Elastic Defend'; diff --git a/x-pack/plugins/security_solution/public/common/types.ts b/x-pack/plugins/security_solution/public/common/types.ts index 02cc712b6ec90..bf92b43245252 100644 --- a/x-pack/plugins/security_solution/public/common/types.ts +++ b/x-pack/plugins/security_solution/public/common/types.ts @@ -8,6 +8,9 @@ import type { ResponseErrorAttributes } from '@kbn/core/server'; import type { DataViewBase } from '@kbn/es-query'; import type { FieldSpec } from '@kbn/data-views-plugin/common'; +import type { CoreStart } from '@kbn/core-lifecycle-browser'; +import type { UpsellingService } from '@kbn/security-solution-upselling/service'; +import type { StartPlugins } from '../types'; export interface ServerApiError { statusCode: number; @@ -32,3 +35,11 @@ export interface SecuritySolutionDataViewBase extends DataViewBase { export type AlertWorkflowStatus = 'open' | 'closed' | 'acknowledged'; export type Refetch = () => void; + +export interface FleetUiExtensionGetterOptions { + coreStart: CoreStart; + depsStart: Pick; + services: { + upsellingService: UpsellingService; + }; +} diff --git a/x-pack/plugins/security_solution/public/common/utils/crowdstrike_alert_check.test.ts b/x-pack/plugins/security_solution/public/common/utils/crowdstrike_alert_check.test.ts deleted file mode 100644 index 3d585808b8885..0000000000000 --- a/x-pack/plugins/security_solution/public/common/utils/crowdstrike_alert_check.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; - -import { - isAlertFromCrowdstrikeAlert, - isAlertFromCrowdstrikeEvent, -} from './crowdstrike_alert_check'; - -describe('crowdstrike_alert_check', () => { - describe('isAlertFromCrowdstrikeEvent', () => { - it('returns false if data is not a timeline event alert', () => { - const data: TimelineEventsDetailsItem[] = []; - expect(isAlertFromCrowdstrikeEvent({ data })).toBe(false); - }); - - it('returns false if data is a timeline event alert but not from Crowdstrike', () => { - const data = [ - { - category: 'kibana', - field: 'kibana.alert.rule.uuid', - }, - ] as unknown as TimelineEventsDetailsItem[]; - expect(isAlertFromCrowdstrikeEvent({ data })).toBe(false); - }); - - it('returns true if data is a Crowdstrike timeline event alert', () => { - const data = [ - { - category: 'kibana', - field: 'kibana.alert.rule.uuid', - }, - { - field: 'event.module', - values: ['crowdstrike'], - }, - ] as unknown as TimelineEventsDetailsItem[]; - expect(isAlertFromCrowdstrikeEvent({ data })).toBe(true); - }); - }); - - describe('isAlertFromCrowdstrikeAlert', () => { - it('returns false if ecsData is null', () => { - expect(isAlertFromCrowdstrikeAlert({ ecsData: null })).toBe(false); - }); - - it('returns false if ecsData is not a Crowdstrike alert', () => { - const ecsData = { - 'kibana.alert.original_event.module': ['other'], - 'kibana.alert.original_event.dataset': ['other'], - } as unknown as Ecs; - expect(isAlertFromCrowdstrikeAlert({ ecsData })).toBe(false); - }); - - it('returns true if ecsData is a Crowdstrike alert', () => { - const ecsData = { - 'kibana.alert.original_event.module': ['crowdstrike'], - 'kibana.alert.original_event.dataset': ['alert'], - } as unknown as Ecs; - expect(isAlertFromCrowdstrikeAlert({ ecsData })).toBe(true); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/utils/crowdstrike_alert_check.ts b/x-pack/plugins/security_solution/public/common/utils/crowdstrike_alert_check.ts deleted file mode 100644 index 5bb1befbadd51..0000000000000 --- a/x-pack/plugins/security_solution/public/common/utils/crowdstrike_alert_check.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { find, getOr, some } from 'lodash/fp'; -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -import { getFieldValue } from '../../detections/components/host_isolation/helpers'; - -/** - * Check to see if a timeline event item is an Alert (vs an event) - * @param timelineEventItem - */ -export const isTimelineEventItemAnAlert = ( - timelineEventItem: TimelineEventsDetailsItem[] -): boolean => { - return some({ category: 'kibana', field: 'kibana.alert.rule.uuid' }, timelineEventItem); -}; - -export const CROWDSTRIKE_AGENT_ID_FIELD = 'crowdstrike.event.DeviceId'; - -export const getCrowdstrikeAgentId = ( - data: TimelineEventsDetailsItem[] | null -): string | undefined => { - return ( - getFieldValue({ category: 'crowdstrike', field: CROWDSTRIKE_AGENT_ID_FIELD }, data) || undefined - ); -}; - -/** - * Checks to see if the given set of Timeline event detail items includes data that indicates its - * an endpoint Alert. Note that it will NOT match on Events - only alerts - * @param data - */ -export const isAlertFromCrowdstrikeEvent = ({ - data, -}: { - data: TimelineEventsDetailsItem[]; -}): boolean => { - if (!isTimelineEventItemAnAlert(data)) { - return false; - } - - const findEndpointAlert = find({ field: 'event.module' }, data)?.values; - return findEndpointAlert ? findEndpointAlert[0] === 'crowdstrike' : false; -}; - -/** - * Checks to see if the given alert was generated out of the Crowdstrike Alerts dataset, coming from - * crowdstrike Fleet integration - * @param ecsData - */ -export const isAlertFromCrowdstrikeAlert = ({ - ecsData, -}: { - ecsData: Ecs | null | undefined; -}): boolean => { - if (ecsData == null) { - return false; - } - - const eventModules = getOr([], 'kibana.alert.original_event.module', ecsData); - const kinds = getOr([], 'kibana.alert.original_event.dataset', ecsData); - - return eventModules.includes('crowdstrike') && kinds.includes('alert'); -}; diff --git a/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts b/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts deleted file mode 100644 index d62347f6790b3..0000000000000 --- a/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import _ from 'lodash'; -import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -import { generateMockDetailItemData } from '../mock'; -import { isAlertFromEndpointAlert, isAlertFromEndpointEvent } from './endpoint_alert_check'; - -describe('isAlertFromEndpointEvent', () => { - let mockDetailItemData: ReturnType; - - beforeEach(() => { - mockDetailItemData = generateMockDetailItemData(); - - // Remove the filebeat agent type from the mock - _.remove(mockDetailItemData, { field: 'agent.type' }); - - mockDetailItemData.push( - // Must be an Alert - { - field: 'kibana.alert.rule.uuid', - category: 'kibana', - originalValue: 'endpoint', - values: ['endpoint'], - isObjectArray: false, - }, - // Must be from an endpoint agent - { - field: 'agent.type', - originalValue: 'endpoint', - values: ['endpoint'], - isObjectArray: false, - } - ); - }); - - it('should return true if detections data comes from an endpoint rule', () => { - expect(isAlertFromEndpointEvent({ data: mockDetailItemData })).toBe(true); - }); - - it('should return false if it is not an Alert (ex. maybe an event)', () => { - _.remove(mockDetailItemData, { field: 'kibana.alert.rule.uuid' }); - expect(isAlertFromEndpointEvent({ data: mockDetailItemData })).toBeFalsy(); - }); - - it('should return false if it is not an endpoint agent', () => { - _.remove(mockDetailItemData, { field: 'agent.type' }); - expect(isAlertFromEndpointEvent({ data: mockDetailItemData })).toBeFalsy(); - }); -}); - -describe('isAlertFromEndpointAlert', () => { - it('should return true if detections data comes from an endpoint rule', () => { - const mockEcsData = { - _id: 'mockId', - 'kibana.alert.original_event.module': ['endpoint'], - 'kibana.alert.original_event.kind': ['alert'], - } as Ecs; - expect(isAlertFromEndpointAlert({ ecsData: mockEcsData })).toBe(true); - }); - - it('should return false if ecsData is undefined', () => { - expect(isAlertFromEndpointAlert({ ecsData: undefined })).toBeFalsy(); - }); - - it('should return false if it is not an Alert', () => { - const mockEcsData = { - _id: 'mockId', - 'kibana.alert.original_event.module': ['endpoint'], - } as Ecs; - expect(isAlertFromEndpointAlert({ ecsData: mockEcsData })).toBeFalsy(); - }); - - it('should return false if it is not an endpoint module', () => { - const mockEcsData = { - _id: 'mockId', - 'kibana.alert.original_event.kind': ['alert'], - } as Ecs; - expect(isAlertFromEndpointAlert({ ecsData: mockEcsData })).toBeFalsy(); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts b/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts deleted file mode 100644 index b755094b679d3..0000000000000 --- a/x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { find, getOr, some } from 'lodash/fp'; -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; - -/** - * Check to see if a timeline event item is an Alert (vs an event) - * @param timelineEventItem - */ -export const isTimelineEventItemAnAlert = ( - timelineEventItem: TimelineEventsDetailsItem[] -): boolean => { - return some({ category: 'kibana', field: 'kibana.alert.rule.uuid' }, timelineEventItem); -}; - -/** - * Checks to see if the given set of Timeline event detail items includes data that indicates its - * an endpoint Alert. Note that it will NOT match on Events - only alerts - * @param data - */ -export const isAlertFromEndpointEvent = ({ - data, -}: { - data: TimelineEventsDetailsItem[]; -}): boolean => { - if (!isTimelineEventItemAnAlert(data)) { - return false; - } - - const findEndpointAlert = find({ field: 'agent.type' }, data)?.values; - return findEndpointAlert ? findEndpointAlert[0] === 'endpoint' : false; -}; - -export const isAlertFromEndpointAlert = ({ - ecsData, -}: { - ecsData: Ecs | null | undefined; -}): boolean => { - if (ecsData == null) { - return false; - } - - const eventModules = getOr([], 'kibana.alert.original_event.module', ecsData); - const kinds = getOr([], 'kibana.alert.original_event.kind', ecsData); - - return eventModules.includes('endpoint') && kinds.includes('alert'); -}; diff --git a/x-pack/plugins/security_solution/public/common/utils/sentinelone_alert_check.ts b/x-pack/plugins/security_solution/public/common/utils/sentinelone_alert_check.ts deleted file mode 100644 index b588ba7320144..0000000000000 --- a/x-pack/plugins/security_solution/public/common/utils/sentinelone_alert_check.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { find, getOr, some } from 'lodash/fp'; -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -import { getFieldValue } from '../../detections/components/host_isolation/helpers'; - -/** - * Check to see if a timeline event item is an Alert (vs an event) - * @param timelineEventItem - */ -export const isTimelineEventItemAnAlert = ( - timelineEventItem: TimelineEventsDetailsItem[] -): boolean => { - return some({ category: 'kibana', field: 'kibana.alert.rule.uuid' }, timelineEventItem); -}; - -export const SENTINEL_ONE_AGENT_ID_FIELD = 'observer.serial_number'; - -export const getSentinelOneAgentId = (data: TimelineEventsDetailsItem[] | null) => - getFieldValue({ category: 'observer', field: SENTINEL_ONE_AGENT_ID_FIELD }, data) || undefined; - -/** - * Checks to see if the given set of Timeline event detail items includes data that indicates its - * an endpoint Alert. Note that it will NOT match on Events - only alerts - * @param data - */ -export const isAlertFromSentinelOneEvent = ({ - data, -}: { - data: TimelineEventsDetailsItem[]; -}): boolean => { - if (!isTimelineEventItemAnAlert(data)) { - return false; - } - - const findEndpointAlert = find({ field: 'event.module' }, data)?.values; - return findEndpointAlert ? findEndpointAlert[0] === 'sentinel_one' : false; -}; - -/** - * Checks to see if the given alert was generated out of the SentinelOne Alerts dataset, coming from - * sentinel_one Fleet integration - * @param ecsData - */ -export const isAlertFromSentinelOneAlert = ({ - ecsData, -}: { - ecsData: Ecs | null | undefined; -}): boolean => { - if (ecsData == null) { - return false; - } - - const eventModules = getOr([], 'kibana.alert.original_event.module', ecsData); - const kinds = getOr([], 'kibana.alert.original_event.dataset', ecsData); - - return eventModules.includes('sentinel_one') && kinds.includes('alert'); -}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/schema.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/schema.tsx index b4fa6376ea818..83d79debb757d 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/schema.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/schema.tsx @@ -217,7 +217,7 @@ export const schema: FormSchema = { label: i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldMitreThreatLabel', { - defaultMessage: 'MITRE ATT&CK\\u2122', + defaultMessage: 'MITRE ATT&CK\u2122', } ), labelAppend: OptionalFieldLabel, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/helpers.ts index f08187800789d..fd933ba33ae7e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/helpers.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/helpers.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { RuleFieldsDiff, ThreeWayDiff } from '../../../../../common/api/detection_engine'; +import { ThreeWayDiffOutcome } from '../../../../../common/api/detection_engine'; import type { FieldsGroupDiff } from '../../model/rule_details/rule_field_diff'; import { ABOUT_UPGRADE_FIELD_ORDER, @@ -36,3 +38,21 @@ export const getSectionedFieldDiffs = (fields: FieldsGroupDiff[]) => { setupFields, }; }; + +/** + * Filters out any fields that have a `diff_outcome` of `CustomizedValueNoUpdate` + * or `CustomizedValueSameUpdate` as they are not supported for display in the + * current per-field rule diff flyout + */ +export const filterUnsupportedDiffOutcomes = ( + fields: Partial +): Partial => + Object.fromEntries( + Object.entries(fields).filter(([key, value]) => { + const diff = value as ThreeWayDiff; + return ( + diff.diff_outcome !== ThreeWayDiffOutcome.CustomizedValueNoUpdate && + diff.diff_outcome !== ThreeWayDiffOutcome.CustomizedValueSameUpdate + ); + }) + ); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.tsx index 4a90f8624d21e..f03cd8ed23bbf 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/per_field_rule_diff_tab.tsx @@ -10,7 +10,7 @@ import type { PartialRuleDiff, RuleFieldsDiff } from '../../../../../common/api/ import { getFormattedFieldDiffGroups } from './per_field_diff/get_formatted_field_diff'; import { UPGRADE_FIELD_ORDER } from './constants'; import { RuleDiffHeaderBar, RuleDiffSection } from './diff_components'; -import { getSectionedFieldDiffs } from './helpers'; +import { filterUnsupportedDiffOutcomes, getSectionedFieldDiffs } from './helpers'; import type { FieldsGroupDiff } from '../../model/rule_details/rule_field_diff'; import * as i18n from './translations'; @@ -21,9 +21,11 @@ interface PerFieldRuleDiffTabProps { export const PerFieldRuleDiffTab = ({ ruleDiff }: PerFieldRuleDiffTabProps) => { const fieldsToRender = useMemo(() => { const fields: FieldsGroupDiff[] = []; - for (const field of Object.keys(ruleDiff.fields)) { + // Filter out diff outcomes that we don't support displaying in the per-field diff flyout + const filteredFieldDiffs = filterUnsupportedDiffOutcomes(ruleDiff.fields); + for (const field of Object.keys(filteredFieldDiffs)) { const typedField = field as keyof RuleFieldsDiff; - const formattedDiffs = getFormattedFieldDiffGroups(typedField, ruleDiff.fields); + const formattedDiffs = getFormattedFieldDiffGroups(typedField, filteredFieldDiffs); fields.push({ formattedDiffs, fieldsGroupName: typedField }); } const sortedFields = fields.sort( diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts index 9025b184af1d3..3e75677d54da9 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/translations.ts @@ -171,7 +171,7 @@ export const RULE_NAME_OVERRIDE_FIELD_LABEL = i18n.translate( export const THREAT_FIELD_LABEL = i18n.translate( 'xpack.securitySolution.detectionEngine.ruleDetails.threatFieldLabel', { - defaultMessage: 'MITRE ATT&CK\\u2122', + defaultMessage: 'MITRE ATT&CK\u2122', } ); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 0143b584ea8c5..c4afc5ceb5f6a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -11,7 +11,7 @@ import { EuiButtonIcon, EuiContextMenu, EuiPopover, EuiToolTip } from '@elastic/ import { indexOf } from 'lodash'; import { useSelector } from 'react-redux'; import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; -import { get } from 'lodash/fp'; +import { get, getOr } from 'lodash/fp'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import { TableId } from '@kbn/securitysolution-data-table'; import { useRuleWithFallback } from '../../../../detection_engine/rule_management/logic/use_rule_with_fallback'; @@ -46,7 +46,6 @@ import type { import { ATTACH_ALERT_TO_CASE_FOR_ROW } from '../../../../timelines/components/timeline/body/translations'; import { useEventFilterAction } from './use_event_filter_action'; import { useAddToCaseActions } from './use_add_to_case_actions'; -import { isAlertFromEndpointAlert } from '../../../../common/utils/endpoint_alert_check'; import type { Rule } from '../../../../detection_engine/rule_management/logic/types'; import type { AlertTableContextMenuItem } from '../types'; import { useAlertTagsActions } from './use_alert_tags_actions'; @@ -115,6 +114,13 @@ const AlertContextMenuComponent: React.FC = ({ const isEvent = useMemo(() => indexOf(ecsRowData.event?.kind, 'event') !== -1, [ecsRowData]); const isAgentEndpoint = useMemo(() => ecsRowData.agent?.type?.includes('endpoint'), [ecsRowData]); const isEndpointEvent = useMemo(() => isEvent && isAgentEndpoint, [isEvent, isAgentEndpoint]); + const isAlertSourceEndpoint = useMemo(() => { + const eventModules = getOr([], 'kibana.alert.original_event.module', ecsRowData); + const kinds = getOr([], 'kibana.alert.original_event.kind', ecsRowData); + + return eventModules.includes('endpoint') && kinds.includes('alert'); + }, [ecsRowData]); + const scopeIdAllowsAddEndpointEventFilter = useMemo( () => scopeId === TableId.hostsPageEvents || scopeId === TableId.usersPageEvents, [scopeId] @@ -152,8 +158,9 @@ const AlertContextMenuComponent: React.FC = ({ refetchQuery([timelineQuery]); } else { refetchQuery(globalQuery); - if (refetch) refetch(); } + + if (refetch) refetch(); }, [scopeId, globalQuery, timelineQuery, refetch]); const ruleIndex = @@ -199,7 +206,7 @@ const AlertContextMenuComponent: React.FC = ({ }, [closePopover, onAddEventFilterClick]); const { exceptionActionItems } = useAlertExceptionActions({ - isEndpointAlert: isAlertFromEndpointAlert({ ecsData: ecsRowData }), + isEndpointAlert: isAlertSourceEndpoint, onAddExceptionTypeClick: handleOnAddExceptionTypeClick, }); const { eventFilterActionItems } = useEventFilterAction({ diff --git a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/get_external_edr_agent_info.test.ts b/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/get_external_edr_agent_info.test.ts deleted file mode 100644 index 9e20b45f55fe6..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/get_external_edr_agent_info.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { getSentinelOneAgentId } from '../../../common/utils/sentinelone_alert_check'; -import { getCrowdstrikeAgentId } from '../../../common/utils/crowdstrike_alert_check'; -import { getExternalEdrAgentInfo } from './get_external_edr_agent_info'; - -jest.mock('../../../common/utils/sentinelone_alert_check'); -jest.mock('../../../common/utils/crowdstrike_alert_check'); - -describe('getExternalEdrAgentInfo', () => { - const mockEventData = [ - { - category: 'event', - field: 'event.module', - values: ['sentinel_one'], - isObjectArray: false, - }, - { - category: 'host', - field: 'host.name', - values: ['test-host'], - isObjectArray: false, - }, - { - category: 'host', - field: 'host.os.name', - values: ['Windows'], - isObjectArray: false, - }, - { - category: 'host', - field: 'host.os.family', - values: ['windows'], - isObjectArray: false, - }, - { - category: 'host', - field: 'host.os.version', - values: ['10'], - isObjectArray: false, - }, - { - category: 'kibana', - field: 'kibana.alert.last_detected', - values: ['2023-05-01T12:34:56Z'], - isObjectArray: false, - }, - { - category: 'crowdstrike', - field: 'crowdstrike.event.HostName', - values: ['test-crowdstrike-host'], - isObjectArray: false, - }, - { - category: 'crowdstrike', - field: 'crowdstrike.event.Platform', - values: ['linux'], - isObjectArray: false, - }, - ]; - - beforeEach(() => { - (getSentinelOneAgentId as jest.Mock).mockReturnValue('sentinel-one-agent-id'); - (getCrowdstrikeAgentId as jest.Mock).mockReturnValue('crowdstrike-agent-id'); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should return correct info for sentinel_one agent type', () => { - const result = getExternalEdrAgentInfo(mockEventData, 'sentinel_one'); - expect(result).toEqual({ - agent: { - id: 'sentinel-one-agent-id', - type: 'sentinel_one', - }, - host: { - name: 'test-host', - os: { - name: 'Windows', - family: 'windows', - version: '10', - }, - }, - lastCheckin: '2023-05-01T12:34:56Z', - }); - }); - - it('should return correct info for crowdstrike agent type', () => { - const result = getExternalEdrAgentInfo(mockEventData, 'crowdstrike'); - expect(result).toEqual({ - agent: { - id: 'crowdstrike-agent-id', - type: 'crowdstrike', - }, - host: { - name: 'test-crowdstrike-host', - os: { - name: '', - family: 'linux', - version: '', - }, - }, - lastCheckin: '2023-05-01T12:34:56Z', - }); - }); - - it('should throw an error for unsupported agent type', () => { - expect(() => { - // @ts-expect-error testing purpose - getExternalEdrAgentInfo(mockEventData, 'unsupported_agent_type'); - }).toThrow('Unsupported agent type: unsupported_agent_type'); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/get_external_edr_agent_info.ts b/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/get_external_edr_agent_info.ts deleted file mode 100644 index 0c9c9ed9f0138..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/get_external_edr_agent_info.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import type { ResponseActionAgentType } from '../../../../common/endpoint/service/response_actions/constants'; -import type { ThirdPartyAgentInfo } from '../../../../common/types'; -import { getSentinelOneAgentId } from '../../../common/utils/sentinelone_alert_check'; -import { getFieldValue } from '../host_isolation/helpers'; -import { getCrowdstrikeAgentId } from '../../../common/utils/crowdstrike_alert_check'; - -export const getExternalEdrAgentInfo = ( - eventData: TimelineEventsDetailsItem[], - agentType: ResponseActionAgentType -): ThirdPartyAgentInfo => { - switch (agentType) { - case 'sentinel_one': - return { - agent: { - id: getSentinelOneAgentId(eventData) || '', - type: getFieldValue( - { category: 'event', field: 'event.module' }, - eventData - ) as ResponseActionAgentType, - }, - host: { - name: getFieldValue({ category: 'host', field: 'host.name' }, eventData), - os: { - name: getFieldValue({ category: 'host', field: 'host.os.name' }, eventData), - family: getFieldValue({ category: 'host', field: 'host.os.family' }, eventData), - version: getFieldValue({ category: 'host', field: 'host.os.version' }, eventData), - }, - }, - lastCheckin: getFieldValue( - { category: 'kibana', field: 'kibana.alert.last_detected' }, - eventData - ), - }; - case 'crowdstrike': - return { - agent: { - id: getCrowdstrikeAgentId(eventData) || '', - type: agentType, - }, - host: { - name: getFieldValue( - { category: 'crowdstrike', field: 'crowdstrike.event.HostName' }, - eventData - ), - os: { - name: '', - family: getFieldValue( - { category: 'crowdstrike', field: 'crowdstrike.event.Platform' }, - eventData - ), - version: '', - }, - }, - lastCheckin: getFieldValue( - { category: 'kibana', field: 'kibana.alert.last_detected' }, - eventData - ), - }; - default: - throw new Error(`Unsupported agent type: ${agentType}`); - } -}; diff --git a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.test.ts b/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.test.ts deleted file mode 100644 index 76be881fa553d..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.test.ts +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; -import { useResponderActionData } from './use_responder_action_data'; -import { renderHook } from '@testing-library/react-hooks'; -import { useGetEndpointDetails } from '../../../management/hooks'; -import { HostStatus } from '../../../../common/endpoint/types'; -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import { HOST_ENDPOINT_UNENROLLED_TOOLTIP } from './translations'; - -jest.mock('../../../common/hooks/use_experimental_features'); -jest.mock('../../../management/hooks', () => ({ - useGetEndpointDetails: (jest.fn() as jest.Mock).mockImplementation(() => ({ enabled: false })), - useWithShowResponder: jest.fn(), -})); - -const useGetEndpointDetailsMock = useGetEndpointDetails as jest.Mock; -const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock; - -describe('#useResponderActionData', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should return `responder` menu item as `disabled` if agentType is not `endpoint` and feature flag is enabled', () => { - useIsExperimentalFeatureEnabledMock.mockReturnValue(false); - - const { result } = renderHook(() => - useResponderActionData({ - endpointId: 'some-agent-type-id', - // @ts-expect-error this is for testing purpose - agentType: 'some_agent_type', - eventData: [], - }) - ); - expect(result.current.isDisabled).toEqual(true); - }); - - describe('when agentType is `endpoint`', () => { - it.each(Object.values(HostStatus).filter((status) => status !== 'unenrolled'))( - 'should return `responder` menu item as `enabled `if agentType is `endpoint` when endpoint is %s', - (hostStatus) => { - useGetEndpointDetailsMock.mockReturnValue({ - data: { - host_status: hostStatus, - }, - isFetching: false, - error: undefined, - }); - const { result } = renderHook(() => - useResponderActionData({ - endpointId: 'endpoint-id', - agentType: 'endpoint', - }) - ); - expect(result.current.isDisabled).toEqual(false); - } - ); - - it('should return responder menu item `disabled` if agentType is `endpoint` when endpoint is `unenrolled`', () => { - useGetEndpointDetailsMock.mockReturnValue({ - data: { - host_status: 'unenrolled', - }, - isFetching: false, - error: undefined, - }); - const { result } = renderHook(() => - useResponderActionData({ - endpointId: 'endpoint-id', - agentType: 'endpoint', - }) - ); - expect(result.current.isDisabled).toEqual(true); - }); - - it('should return responder menu item `disabled` if agentType is `endpoint` when endpoint data has error', () => { - useGetEndpointDetailsMock.mockReturnValue({ - data: { - host_status: 'online', - }, - isFetching: false, - error: new Error('uh oh!'), - }); - const { result } = renderHook(() => - useResponderActionData({ - endpointId: 'endpoint-id', - agentType: 'endpoint', - }) - ); - expect(result.current.isDisabled).toEqual(true); - }); - - it('should return responder menu item `disabled` if agentType is `endpoint` and endpoint data is fetching', () => { - useGetEndpointDetailsMock.mockReturnValue({ - data: undefined, - isFetching: true, - error: undefined, - }); - - const { result } = renderHook(() => - useResponderActionData({ - endpointId: 'endpoint-id', - agentType: 'endpoint', - }) - ); - expect(result.current.isDisabled).toEqual(true); - }); - - it('should return responder menu item `disabled` when agentType is `endpoint` but no endpoint id is provided', () => { - const { result } = renderHook(() => - useResponderActionData({ - endpointId: '', - agentType: 'endpoint', - }) - ); - expect(result.current.isDisabled).toEqual(true); - expect(result.current.tooltip).toEqual(HOST_ENDPOINT_UNENROLLED_TOOLTIP); - }); - }); - - describe('when agentType is `sentinel_one`', () => { - const createEventDataMock = (): TimelineEventsDetailsItem[] => { - return [ - { - category: 'observer', - field: 'observer.serial_number', - values: ['c06d63d9-9fa2-046d-e91e-dc94cf6695d8'], - originalValue: ['c06d63d9-9fa2-046d-e91e-dc94cf6695d8'], - isObjectArray: false, - }, - ]; - }; - - it('should return `responder` menu item as `disabled` if agentType is `sentinel_one` and feature flag is disabled', () => { - useIsExperimentalFeatureEnabledMock.mockReturnValue(false); - - const { result } = renderHook(() => - useResponderActionData({ - endpointId: 'sentinel-one-id', - agentType: 'sentinel_one', - eventData: createEventDataMock(), - }) - ); - expect(result.current.isDisabled).toEqual(true); - }); - - it('should return responder menu item as disabled with tooltip if agent id property is missing from event data', () => { - useIsExperimentalFeatureEnabledMock.mockReturnValue(true); - const { result } = renderHook(() => - useResponderActionData({ - endpointId: 'sentinel-one-id', - agentType: 'sentinel_one', - eventData: [], - }) - ); - expect(result.current.isDisabled).toEqual(true); - expect(result.current.tooltip).toEqual( - 'Event data missing SentinelOne agent identifier (observer.serial_number)' - ); - }); - - it('should return `responder` menu item as `enabled `if agentType is `sentinel_one` and feature flag is enabled', () => { - useIsExperimentalFeatureEnabledMock.mockReturnValue(true); - const { result } = renderHook(() => - useResponderActionData({ - endpointId: 'sentinel-one-id', - agentType: 'sentinel_one', - eventData: createEventDataMock(), - }) - ); - expect(result.current.isDisabled).toEqual(false); - }); - }); - describe('when agentType is `crowdstrike`', () => { - const createEventDataMock = (): TimelineEventsDetailsItem[] => { - return [ - { - category: 'crowdstrike', - field: 'crowdstrike.event.DeviceId', - values: ['mockedAgentId'], - originalValue: ['mockedAgentId'], - isObjectArray: false, - }, - ]; - }; - - it('should return `responder` menu item as `disabled` if agentType is `crowdstrike` and feature flag is disabled', () => { - useIsExperimentalFeatureEnabledMock.mockReturnValue(false); - - const { result } = renderHook(() => - useResponderActionData({ - endpointId: 'crowdstrike-id', - agentType: 'crowdstrike', - eventData: createEventDataMock(), - }) - ); - expect(result.current.isDisabled).toEqual(true); - }); - - it('should return responder menu item as disabled with tooltip if agent id property is missing from event data', () => { - useIsExperimentalFeatureEnabledMock.mockReturnValue(true); - const { result } = renderHook(() => - useResponderActionData({ - endpointId: 'crowdstrike-id', - agentType: 'crowdstrike', - eventData: [], - }) - ); - expect(result.current.isDisabled).toEqual(true); - expect(result.current.tooltip).toEqual( - 'Event data missing Crowdstrike agent identifier (crowdstrike.event.DeviceId)' - ); - }); - - it('should return `responder` menu item as `enabled `if agentType is `crowdstrike` and feature flag is enabled', () => { - useIsExperimentalFeatureEnabledMock.mockReturnValue(true); - const { result } = renderHook(() => - useResponderActionData({ - endpointId: 'crowdstrike-id', - agentType: 'crowdstrike', - eventData: createEventDataMock(), - }) - ); - expect(result.current.isDisabled).toEqual(false); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.ts b/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.ts deleted file mode 100644 index b2bf6d22643f4..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_data.ts +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import type { ReactNode } from 'react'; -import { useCallback, useMemo } from 'react'; -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import { getExternalEdrAgentInfo } from './get_external_edr_agent_info'; -import { getCrowdstrikeAgentId } from '../../../common/utils/crowdstrike_alert_check'; -import type { Platform } from '../../../management/components/endpoint_responder/components/header_info/platforms'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; -import { getSentinelOneAgentId } from '../../../common/utils/sentinelone_alert_check'; -import type { - ResponseActionAgentType, - EndpointCapabilities, -} from '../../../../common/endpoint/service/response_actions/constants'; -import { useGetEndpointDetails, useWithShowResponder } from '../../../management/hooks'; -import { HostStatus } from '../../../../common/endpoint/types'; -import { - CROWDSTRIKE_AGENT_ID_PROPERTY_MISSING, - HOST_ENDPOINT_UNENROLLED_TOOLTIP, - LOADING_ENDPOINT_DATA_TOOLTIP, - METADATA_API_ERROR_TOOLTIP, - NOT_FROM_ENDPOINT_HOST_TOOLTIP, - SENTINEL_ONE_AGENT_ID_PROPERTY_MISSING, -} from './translations'; - -export interface ResponderContextMenuItemProps { - endpointId: string; - onClick?: () => void; - agentType: ResponseActionAgentType; - eventData?: TimelineEventsDetailsItem[] | null; -} - -/** - * This hook is used to get the data needed to show the context menu items for the responder - * actions. - * @param endpointId the id of the endpoint - * @param onClick the callback to handle the click event - * @param agentType the type of agent, defaults to 'endpoint' - * @param eventData the event data, exists only when agentType !== 'endpoint' - * @returns an object with the data needed to show the context menu item - */ - -export const useResponderActionData = ({ - endpointId, - onClick, - agentType, - eventData, -}: ResponderContextMenuItemProps): { - handleResponseActionsClick: () => void; - isDisabled: boolean; - tooltip: ReactNode; -} => { - const isEndpointHost = agentType === 'endpoint'; - const showResponseActionsConsole = useWithShowResponder(); - - const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled( - 'responseActionsSentinelOneV1Enabled' - ); - const responseActionsCrowdstrikeManualHostIsolationEnabled = useIsExperimentalFeatureEnabled( - 'responseActionsCrowdstrikeManualHostIsolationEnabled' - ); - const { - data: hostInfo, - isFetching, - error, - } = useGetEndpointDetails(endpointId, { enabled: Boolean(endpointId && isEndpointHost) }); - - const [isDisabled, tooltip]: [disabled: boolean, tooltip: ReactNode] = useMemo(() => { - // v8.13 disabled for third-party agent alerts if the feature flag is not enabled - if (!isEndpointHost) { - switch (agentType) { - case 'sentinel_one': - // Disable it if feature flag is disabled - if (!isSentinelOneV1Enabled) { - return [true, undefined]; - } - // Event must have the property that identifies the agent id - if (!getSentinelOneAgentId(eventData ?? null)) { - return [true, SENTINEL_ONE_AGENT_ID_PROPERTY_MISSING]; - } - - return [false, undefined]; - case 'crowdstrike': - // Disable it if feature flag is disabled - if (!responseActionsCrowdstrikeManualHostIsolationEnabled) { - return [true, undefined]; - } - // Event must have the property that identifies the agent id - if (!getCrowdstrikeAgentId(eventData ?? null)) { - return [true, CROWDSTRIKE_AGENT_ID_PROPERTY_MISSING]; - } - - return [false, undefined]; - - default: - return [true, undefined]; - } - } - - if (!endpointId) { - return [true, HOST_ENDPOINT_UNENROLLED_TOOLTIP]; - } - - // Still loading host info - if (isFetching) { - return [true, LOADING_ENDPOINT_DATA_TOOLTIP]; - } - - // if we got an error, and it's a 404, it means the endpoint is not from the endpoint host - if (error && error.body?.statusCode === 404) { - return [true, NOT_FROM_ENDPOINT_HOST_TOOLTIP]; - } - - // if we got an error and, - // it's a 400 with unenrolled in the error message (alerts can exist for endpoint that are no longer around) - // or, - // the Host status is `unenrolled` - if ( - (error && error.body?.statusCode === 400 && error.body?.message.includes('unenrolled')) || - hostInfo?.host_status === HostStatus.UNENROLLED - ) { - return [true, HOST_ENDPOINT_UNENROLLED_TOOLTIP]; - } - - // return general error tooltip - if (error) { - return [true, METADATA_API_ERROR_TOOLTIP]; - } - - return [false, undefined]; - }, [ - isEndpointHost, - endpointId, - isFetching, - error, - hostInfo?.host_status, - agentType, - isSentinelOneV1Enabled, - eventData, - responseActionsCrowdstrikeManualHostIsolationEnabled, - ]); - - const handleResponseActionsClick = useCallback(() => { - if (!isEndpointHost && eventData != null) { - const agentInfoFromAlert = getExternalEdrAgentInfo(eventData, agentType); - showResponseActionsConsole({ - agentId: agentInfoFromAlert.agent.id, - agentType, - capabilities: ['isolation'], - hostName: agentInfoFromAlert.host.name, - platform: agentInfoFromAlert.host.os.family, - }); - } - if (isEndpointHost && hostInfo) { - showResponseActionsConsole({ - agentId: hostInfo.metadata.agent.id, - agentType, - capabilities: (hostInfo.metadata.Endpoint.capabilities as EndpointCapabilities[]) ?? [], - hostName: hostInfo.metadata.host.name, - platform: hostInfo.metadata.host.os.name.toLowerCase() as Platform, - }); - } - if (onClick) onClick(); - }, [isEndpointHost, hostInfo, onClick, eventData, showResponseActionsConsole, agentType]); - - return { - handleResponseActionsClick, - isDisabled, - tooltip, - }; -}; diff --git a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_item.test.tsx b/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_item.test.tsx deleted file mode 100644 index 750a627985192..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/endpoint_responder/use_responder_action_item.test.tsx +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { renderHook } from '@testing-library/react-hooks'; -import { useResponderActionItem } from './use_responder_action_item'; -import { useUserPrivileges } from '../../../common/components/user_privileges'; -import { isTimelineEventItemAnAlert } from '../../../common/utils/endpoint_alert_check'; -import { getFieldValue } from '../host_isolation/helpers'; -import { isAlertFromCrowdstrikeEvent } from '../../../common/utils/crowdstrike_alert_check'; -import { isAlertFromSentinelOneEvent } from '../../../common/utils/sentinelone_alert_check'; -import { useResponderActionData } from './use_responder_action_data'; - -jest.mock('../../../common/components/user_privileges'); -jest.mock('../../../common/utils/endpoint_alert_check'); -jest.mock('../host_isolation/helpers'); -jest.mock('../../../common/utils/crowdstrike_alert_check'); -jest.mock('../../../common/utils/sentinelone_alert_check'); -jest.mock('./use_responder_action_data'); - -describe('useResponderActionItem', () => { - const mockUseUserPrivileges = useUserPrivileges as jest.Mock; - const mockIsTimelineEventItemAnAlert = isTimelineEventItemAnAlert as jest.Mock; - const mockGetFieldValue = getFieldValue as jest.Mock; - const mockIsAlertFromCrowdstrikeEvent = isAlertFromCrowdstrikeEvent as jest.Mock; - const mockIsAlertFromSentinelOneEvent = isAlertFromSentinelOneEvent as jest.Mock; - const mockUseResponderActionData = useResponderActionData as jest.Mock; - - beforeEach(() => { - jest.clearAllMocks(); - mockUseResponderActionData.mockImplementation(() => ({ - handleResponseActionsClick: jest.fn(), - isDisabled: false, - tooltip: 'Tooltip text', - })); - }); - - it('should return an empty array if user privileges are loading', () => { - mockUseUserPrivileges.mockReturnValue({ - endpointPrivileges: { - loading: true, - canAccessResponseConsole: false, - }, - }); - - const { result } = renderHook(() => useResponderActionItem(null, jest.fn())); - expect(result.current).toEqual([]); - }); - - it('should return an empty array if user cannot access response console', () => { - mockUseUserPrivileges.mockReturnValue({ - endpointPrivileges: { - loading: false, - canAccessResponseConsole: false, - }, - }); - - const { result } = renderHook(() => useResponderActionItem(null, jest.fn())); - expect(result.current).toEqual([]); - }); - - it('should return an empty array if the event is not an alert', () => { - mockUseUserPrivileges.mockReturnValue({ - endpointPrivileges: { - loading: false, - canAccessResponseConsole: true, - }, - }); - mockIsTimelineEventItemAnAlert.mockReturnValue(false); - - const { result } = renderHook(() => useResponderActionItem(null, jest.fn())); - expect(result.current).toEqual([]); - }); - - it('should return the response action item if all conditions are met for a generic endpoint', () => { - mockUseUserPrivileges.mockReturnValue({ - endpointPrivileges: { - loading: false, - canAccessResponseConsole: true, - }, - }); - mockIsTimelineEventItemAnAlert.mockReturnValue(true); - mockGetFieldValue.mockReturnValue('endpoint-id'); - mockIsAlertFromCrowdstrikeEvent.mockReturnValue(false); - mockIsAlertFromSentinelOneEvent.mockReturnValue(false); - - renderHook(() => useResponderActionItem([], jest.fn())); - - expect(mockUseResponderActionData).toHaveBeenCalledWith({ - agentType: 'endpoint', - endpointId: 'endpoint-id', - eventData: null, - onClick: expect.any(Function), - }); - }); - - it('should return the response action item if all conditions are met for a Crowdstrike event', () => { - mockUseUserPrivileges.mockReturnValue({ - endpointPrivileges: { - loading: false, - canAccessResponseConsole: true, - }, - }); - mockIsTimelineEventItemAnAlert.mockReturnValue(true); - mockGetFieldValue.mockReturnValue('crowdstrike-id'); - mockIsAlertFromCrowdstrikeEvent.mockReturnValue(true); - mockIsAlertFromSentinelOneEvent.mockReturnValue(false); - - renderHook(() => useResponderActionItem([], jest.fn())); - - expect(mockUseResponderActionData).toHaveBeenCalledWith({ - agentType: 'crowdstrike', - endpointId: 'crowdstrike-id', - eventData: [], - onClick: expect.any(Function), - }); - }); - - it('should return the response action item if all conditions are met for a SentinelOne event', () => { - mockUseUserPrivileges.mockReturnValue({ - endpointPrivileges: { - loading: false, - canAccessResponseConsole: true, - }, - }); - - mockIsTimelineEventItemAnAlert.mockReturnValue(true); - mockGetFieldValue.mockReturnValue('sentinelone-id'); - mockIsAlertFromCrowdstrikeEvent.mockReturnValue(false); - mockIsAlertFromSentinelOneEvent.mockReturnValue(true); - - renderHook(() => useResponderActionItem([], jest.fn())); - - expect(mockUseResponderActionData).toHaveBeenCalledWith({ - agentType: 'sentinel_one', - endpointId: 'sentinelone-id', - eventData: [], - onClick: expect.any(Function), - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/index.tsx b/x-pack/plugins/security_solution/public/detections/components/host_isolation/index.tsx deleted file mode 100644 index 918de21b704f8..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/index.tsx +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useMemo } from 'react'; -import type { ResponseActionAgentType } from '../../../../common/endpoint/service/response_actions/constants'; -import { getSentinelOneAgentId } from '../../../common/utils/sentinelone_alert_check'; -import { getCrowdstrikeAgentId } from '../../../common/utils/crowdstrike_alert_check'; -import { useCasesFromAlerts } from '../../containers/detection_engine/alerts/use_cases_from_alerts'; -import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; -import { getFieldValue } from './helpers'; -import { IsolateHost } from './isolate'; -import { UnisolateHost } from './unisolate'; - -export const HostIsolationPanel = React.memo( - ({ - details, - cancelCallback, - successCallback, - isolateAction, - }: { - details: TimelineEventsDetailsItem[] | null; - cancelCallback: () => void; - successCallback?: () => void; - isolateAction: string; - }) => { - const elasticAgentId = useMemo( - () => getFieldValue({ category: 'agent', field: 'agent.id' }, details), - [details] - ); - - const sentinelOneAgentId = useMemo(() => getSentinelOneAgentId(details), [details]); - const crowdstrikeAgentId = useMemo(() => getCrowdstrikeAgentId(details), [details]); - - const alertId = useMemo( - () => getFieldValue({ category: '_id', field: '_id' }, details), - [details] - ); - - const { casesInfo } = useCasesFromAlerts({ alertId }); - - const agentType: ResponseActionAgentType = useMemo(() => { - if (sentinelOneAgentId) { - return 'sentinel_one'; - } else if (crowdstrikeAgentId) { - return 'crowdstrike'; - } else { - return 'endpoint'; - } - }, [sentinelOneAgentId, crowdstrikeAgentId]); - - const endpointId = useMemo( - () => sentinelOneAgentId ?? crowdstrikeAgentId ?? elasticAgentId, - [elasticAgentId, sentinelOneAgentId, crowdstrikeAgentId] - ); - - const hostName = useMemo(() => { - switch (agentType) { - case 'crowdstrike': - return getFieldValue( - { category: 'crowdstrike', field: 'crowdstrike.event.HostName' }, - details - ); - default: - return getFieldValue({ category: 'host', field: 'host.name' }, details); - } - }, [agentType, details]); - - return isolateAction === 'isolateHost' ? ( - - ) : ( - - ); - } -); - -HostIsolationPanel.displayName = 'HostIsolationContent'; diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.test.tsx b/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.test.tsx deleted file mode 100644 index 5adbbe58de167..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.test.tsx +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { FC, PropsWithChildren } from 'react'; -import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; -import { useHostIsolationAction } from './use_host_isolation_action'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { - useAgentStatusHook, - useGetAgentStatus, - useGetSentinelOneAgentStatus, -} from '../../../management/hooks/agents/use_get_agent_status'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; -import type { ResponseActionAgentType } from '../../../../common/endpoint/service/response_actions/constants'; - -jest.mock('../../../management/hooks/agents/use_get_agent_status'); -jest.mock('../../../common/hooks/use_experimental_features'); - -const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock; -const useGetSentinelOneAgentStatusMock = useGetSentinelOneAgentStatus as jest.Mock; -const useGetAgentStatusMock = useGetAgentStatus as jest.Mock; -const useAgentStatusHookMock = useAgentStatusHook as jest.Mock; - -describe('useHostIsolationAction', () => { - describe.each([ - ['useGetSentinelOneAgentStatus', useGetSentinelOneAgentStatusMock], - ['useGetAgentStatus', useGetAgentStatusMock], - ])('works with %s hook', (name, hook) => { - const createReactQueryWrapper = () => { - const queryClient = new QueryClient(); - const wrapper: FC> = ({ children }) => ( - {children} - ); - return wrapper; - }; - - const render = (agentTypeAlert: ResponseActionAgentType) => - renderHook( - () => - useHostIsolationAction({ - closePopover: jest.fn(), - detailsData: - agentTypeAlert === 'sentinel_one' - ? [ - { - category: 'kibana', - field: 'kibana.alert.rule.uuid', - isObjectArray: false, - values: ['ruleId'], - originalValue: ['ruleId'], - }, - { - category: 'event', - field: 'event.module', - values: ['sentinel_one'], - originalValue: ['sentinel_one'], - isObjectArray: false, - }, - { - category: 'observer', - field: 'observer.serial_number', - values: ['some-agent-id'], - originalValue: ['some-agent-id'], - isObjectArray: false, - }, - ] - : agentTypeAlert === 'crowdstrike' - ? [ - { - category: 'kibana', - field: 'kibana.alert.rule.uuid', - isObjectArray: false, - values: ['ruleId'], - originalValue: ['ruleId'], - }, - { - category: 'event', - field: 'event.module', - values: ['crowdstrike'], - originalValue: ['crowdstrike'], - isObjectArray: false, - }, - { - category: 'crowdstrike', - field: 'crowdstrike.event.DeviceId', - values: ['expectedCrowdstrikeAgentId'], - originalValue: ['expectedCrowdstrikeAgentId'], - isObjectArray: false, - }, - ] - : [ - { - category: 'agent', - field: 'agent.id', - values: ['some-agent-id'], - originalValue: ['some-agent-id'], - isObjectArray: false, - }, - ], - isHostIsolationPanelOpen: false, - onAddIsolationStatusClick: jest.fn(), - }), - { - wrapper: createReactQueryWrapper(), - } - ); - - beforeEach(() => { - useIsExperimentalFeatureEnabledMock.mockReturnValue(true); - useAgentStatusHookMock.mockImplementation(() => hook); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it(`${name} is invoked as 'enabled' when SentinelOne alert and FF enabled`, () => { - render('sentinel_one'); - - expect(hook).toHaveBeenCalledWith(['some-agent-id'], 'sentinel_one', { - enabled: true, - }); - }); - it(`${name} is invoked as 'enabled' when Crowdstrike alert and FF enabled`, () => { - render('crowdstrike'); - - expect(hook).toHaveBeenCalledWith(['expectedCrowdstrikeAgentId'], 'crowdstrike', { - enabled: true, - }); - }); - - it(`${name} is invoked as 'disabled' when SentinelOne alert and FF disabled`, () => { - useIsExperimentalFeatureEnabledMock.mockReturnValue(false); - render('sentinel_one'); - - expect(hook).toHaveBeenCalledWith(['some-agent-id'], 'sentinel_one', { - enabled: false, - }); - }); - - it(`${name} is invoked as 'disabled' when Crowdstrike alert and FF disabled`, () => { - useIsExperimentalFeatureEnabledMock.mockReturnValue(false); - render('crowdstrike'); - - expect(hook).toHaveBeenCalledWith(['expectedCrowdstrikeAgentId'], 'crowdstrike', { - enabled: false, - }); - }); - - it(`${name} is invoked as 'disabled' when endpoint alert`, () => { - render('endpoint'); - - expect(hook).toHaveBeenCalledWith([''], 'endpoint', { - enabled: false, - }); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.tsx b/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.tsx deleted file mode 100644 index abafc59bef195..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/use_host_isolation_action.tsx +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { useCallback, useMemo } from 'react'; -import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; -import { useKibana } from '../../../common/lib/kibana/kibana_react'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; -import { - getSentinelOneAgentId, - isAlertFromSentinelOneEvent, -} from '../../../common/utils/sentinelone_alert_check'; -import { - getCrowdstrikeAgentId, - isAlertFromCrowdstrikeEvent, -} from '../../../common/utils/crowdstrike_alert_check'; -import { isIsolationSupported } from '../../../../common/endpoint/service/host_isolation/utils'; -import type { AgentStatusInfo } from '../../../../common/endpoint/types'; -import { HostStatus } from '../../../../common/endpoint/types'; -import { isAlertFromEndpointEvent } from '../../../common/utils/endpoint_alert_check'; -import { useEndpointHostIsolationStatus } from '../../containers/detection_engine/alerts/use_host_isolation_status'; -import { ISOLATE_HOST, UNISOLATE_HOST } from './translations'; -import { getFieldValue } from './helpers'; -import { useUserPrivileges } from '../../../common/components/user_privileges'; -import type { AlertTableContextMenuItem } from '../alerts_table/types'; -import { useAgentStatusHook } from '../../../management/hooks/agents/use_get_agent_status'; - -interface UseHostIsolationActionProps { - closePopover: () => void; - detailsData: TimelineEventsDetailsItem[] | null; - isHostIsolationPanelOpen: boolean; - onAddIsolationStatusClick: (action: 'isolateHost' | 'unisolateHost') => void; -} - -export const useHostIsolationAction = ({ - closePopover, - detailsData, - isHostIsolationPanelOpen, - onAddIsolationStatusClick, -}: UseHostIsolationActionProps): AlertTableContextMenuItem[] => { - const useAgentStatus = useAgentStatusHook(); - - const hasActionsAllPrivileges = useKibana().services.application?.capabilities?.actions?.save; - - const agentStatusClientEnabled = useIsExperimentalFeatureEnabled('agentStatusClientEnabled'); - const sentinelOneManualHostActionsEnabled = useIsExperimentalFeatureEnabled( - 'sentinelOneManualHostActionsEnabled' - ); - const crowdstrikeManualHostActionsEnabled = useIsExperimentalFeatureEnabled( - 'responseActionsCrowdstrikeManualHostIsolationEnabled' - ); - - const { canIsolateHost, canUnIsolateHost } = useUserPrivileges().endpointPrivileges; - - const isEndpointAlert = useMemo( - () => isAlertFromEndpointEvent({ data: detailsData || [] }), - [detailsData] - ); - - const isSentinelOneAlert = useMemo( - () => isAlertFromSentinelOneEvent({ data: detailsData || [] }), - [detailsData] - ); - - const isCrowdstrikeAlert = useMemo( - () => isAlertFromCrowdstrikeEvent({ data: detailsData || [] }), - [detailsData] - ); - - const agentId = useMemo( - () => getFieldValue({ category: 'agent', field: 'agent.id' }, detailsData), - [detailsData] - ); - - const sentinelOneAgentId = useMemo(() => getSentinelOneAgentId(detailsData), [detailsData]); - const crowdstrikeAgentId = useMemo(() => getCrowdstrikeAgentId(detailsData), [detailsData]); - - const externalAgentId = sentinelOneAgentId ?? crowdstrikeAgentId ?? ''; - const hostOsFamily = useMemo( - () => getFieldValue({ category: 'host', field: 'host.os.name' }, detailsData), - [detailsData] - ); - - const agentVersion = useMemo( - () => getFieldValue({ category: 'agent', field: 'agent.version' }, detailsData), - [detailsData] - ); - - const agentType = useMemo(() => { - if (isSentinelOneAlert) { - return 'sentinel_one'; - } - if (isCrowdstrikeAlert) { - return 'crowdstrike'; - } - return 'endpoint'; - }, [isCrowdstrikeAlert, isSentinelOneAlert]); - - const { - loading: loadingHostIsolationStatus, - isIsolated, - agentStatus, - capabilities, - } = useEndpointHostIsolationStatus({ - agentId, - agentType, - }); - - const { data: externalAgentData } = useAgentStatus([externalAgentId], agentType, { - enabled: - (!!sentinelOneAgentId && sentinelOneManualHostActionsEnabled) || - (!!crowdstrikeAgentId && crowdstrikeManualHostActionsEnabled), - }); - - const externalAgentStatus = externalAgentData?.[externalAgentId]; - - const isHostIsolated = useMemo(() => { - if ( - (sentinelOneManualHostActionsEnabled && isSentinelOneAlert) || - (crowdstrikeManualHostActionsEnabled && isCrowdstrikeAlert) - ) { - return externalAgentStatus?.isolated; - } - - return isIsolated; - }, [ - isIsolated, - isSentinelOneAlert, - isCrowdstrikeAlert, - externalAgentStatus?.isolated, - sentinelOneManualHostActionsEnabled, - crowdstrikeManualHostActionsEnabled, - ]); - - const doesHostSupportIsolation = useMemo(() => { - if (isEndpointAlert) { - return isIsolationSupported({ - osName: hostOsFamily, - version: agentVersion, - capabilities, - }); - } - - if ( - (externalAgentStatus && sentinelOneManualHostActionsEnabled && isSentinelOneAlert) || - (externalAgentStatus && crowdstrikeManualHostActionsEnabled && isCrowdstrikeAlert) - ) { - return externalAgentStatus.status === 'healthy'; - } - - return false; - }, [ - isEndpointAlert, - sentinelOneManualHostActionsEnabled, - isSentinelOneAlert, - externalAgentStatus, - crowdstrikeManualHostActionsEnabled, - isCrowdstrikeAlert, - hostOsFamily, - agentVersion, - capabilities, - ]); - - const isolateHostHandler = useCallback(() => { - closePopover(); - if (!isHostIsolated) { - onAddIsolationStatusClick('isolateHost'); - } else { - onAddIsolationStatusClick('unisolateHost'); - } - }, [closePopover, isHostIsolated, onAddIsolationStatusClick]); - - const isIsolationActionDisabled = useMemo(() => { - if ( - (sentinelOneManualHostActionsEnabled && isSentinelOneAlert) || - (crowdstrikeManualHostActionsEnabled && isCrowdstrikeAlert) - ) { - // 8.15 use FF for computing if action is enabled - if (agentStatusClientEnabled) { - return externalAgentStatus?.status === HostStatus.UNENROLLED; - } - - // else use the old way - if (!externalAgentStatus) { - return true; - } - - const { isUninstalled, isPendingUninstall } = externalAgentStatus as AgentStatusInfo[string]; - - return isUninstalled || isPendingUninstall; - } - - return agentStatus === HostStatus.UNENROLLED; - }, [ - agentStatus, - agentStatusClientEnabled, - isSentinelOneAlert, - externalAgentStatus, - sentinelOneManualHostActionsEnabled, - crowdstrikeManualHostActionsEnabled, - isCrowdstrikeAlert, - ]); - - const menuItems = useMemo( - () => [ - { - key: 'isolate-host-action-item', - 'data-test-subj': 'isolate-host-action-item', - disabled: isIsolationActionDisabled, - onClick: isolateHostHandler, - name: isHostIsolated ? UNISOLATE_HOST : ISOLATE_HOST, - }, - ], - [isHostIsolated, isolateHostHandler, isIsolationActionDisabled] - ); - - return useMemo(() => { - if (isHostIsolationPanelOpen) { - return []; - } - - if ( - isSentinelOneAlert && - sentinelOneManualHostActionsEnabled && - sentinelOneAgentId && - externalAgentStatus && - hasActionsAllPrivileges - ) { - return menuItems; - } - - if ( - isCrowdstrikeAlert && - crowdstrikeManualHostActionsEnabled && - crowdstrikeAgentId && - externalAgentStatus && - hasActionsAllPrivileges - ) { - return menuItems; - } - - if ( - isEndpointAlert && - doesHostSupportIsolation && - !loadingHostIsolationStatus && - (canIsolateHost || (isHostIsolated && !canUnIsolateHost)) - ) { - return menuItems; - } - - return []; - }, [ - canIsolateHost, - canUnIsolateHost, - doesHostSupportIsolation, - hasActionsAllPrivileges, - isEndpointAlert, - isHostIsolated, - isHostIsolationPanelOpen, - isSentinelOneAlert, - loadingHostIsolationStatus, - menuItems, - externalAgentStatus, - sentinelOneAgentId, - sentinelOneManualHostActionsEnabled, - crowdstrikeAgentId, - isCrowdstrikeAlert, - crowdstrikeManualHostActionsEnabled, - ]); -}; diff --git a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx index 442b4b2d4ff62..d8150057dd44b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx @@ -22,26 +22,15 @@ import { useHttp, useKibana } from '../../../common/lib/kibana'; import { mockCasesContract } from '@kbn/cases-plugin/public/mocks'; import { initialUserPrivilegesState as mockInitialUserPrivilegesState } from '../../../common/components/user_privileges/user_privileges_context'; import { useUserPrivileges } from '../../../common/components/user_privileges'; -import { - HOST_ENDPOINT_UNENROLLED_TOOLTIP, - LOADING_ENDPOINT_DATA_TOOLTIP, - NOT_FROM_ENDPOINT_HOST_TOOLTIP, -} from '../endpoint_responder/translations'; -import { endpointMetadataHttpMocks } from '../../../management/pages/endpoint_hosts/mocks'; -import type { HttpSetup } from '@kbn/core/public'; -import { - isAlertFromEndpointAlert, - isAlertFromEndpointEvent, -} from '../../../common/utils/endpoint_alert_check'; import { getUserPrivilegesMockDefaultValue } from '../../../common/components/user_privileges/__mocks__'; import { allCasesPermissions } from '../../../cases_test_utils'; -import { HostStatus } from '../../../../common/endpoint/types'; -import { ENDPOINT_CAPABILITIES } from '../../../../common/endpoint/service/response_actions/constants'; import { ALERT_ASSIGNEES_CONTEXT_MENU_ITEM_TITLE, ALERT_TAGS_CONTEXT_MENU_ITEM_TITLE, } from '../../../common/components/toolbar/bulk_actions/translations'; +jest.mock('../../../common/components/endpoint/host_isolation'); +jest.mock('../../../common/components/endpoint/responder'); jest.mock('../../../common/components/user_privileges'); jest.mock('../user_info', () => ({ @@ -66,34 +55,18 @@ jest.mock('../../../common/hooks/use_license', () => ({ useLicense: jest.fn().mockReturnValue({ isPlatinumPlus: () => true, isEnterprise: () => false }), })); -jest.mock('../../../common/utils/endpoint_alert_check', () => { - const realEndpointAlertCheckUtils = jest.requireActual( - '../../../common/utils/endpoint_alert_check' - ); - return { - isTimelineEventItemAnAlert: realEndpointAlertCheckUtils.isTimelineEventItemAnAlert, - isAlertFromEndpointAlert: jest.fn().mockReturnValue(true), - isAlertFromEndpointEvent: jest.fn().mockReturnValue(true), - }; -}); - -jest.mock('../../../../common/endpoint/service/host_isolation/utils', () => { - return { - isIsolationSupported: jest.fn().mockReturnValue(true), - }; -}); - -jest.mock('../../containers/detection_engine/alerts/use_host_isolation_status', () => { - return { - useEndpointHostIsolationStatus: jest.fn().mockReturnValue({ - loading: false, - isIsolated: false, - agentStatus: 'healthy', - }), - }; -}); - -jest.mock('../../../common/components/user_privileges'); +jest.mock( + '../../../common/components/endpoint/host_isolation/from_alerts/use_host_isolation_status', + () => { + return { + useEndpointHostIsolationStatus: jest.fn().mockReturnValue({ + loading: false, + isIsolated: false, + agentStatus: 'healthy', + }), + }; + } +); describe('take action dropdown', () => { let defaultProps: TakeActionDropdownProps; @@ -292,15 +265,15 @@ describe('take action dropdown', () => { } }; - const setAlertDetailsDataMockToEndpointAgent = () => { + const setAgentTypeOnAlertDetailsDataMock = (agentType: string = 'endpoint') => { if (defaultProps.detailsData) { defaultProps.detailsData = defaultProps.detailsData.map((obj) => { if (obj.field === 'agent.type') { return { category: 'agent', field: 'agent.type', - values: ['endpoint'], - originalValue: ['endpoint'], + values: [agentType], + originalValue: [agentType], }; } if (obj.field === 'agent.id') { @@ -335,9 +308,32 @@ describe('take action dropdown', () => { } }; - describe('should correctly enable/disable the "Add Endpoint event filter" button', () => { - let wrapper: ReactWrapper; + let wrapper: ReactWrapper; + const render = (): ReactWrapper => { + wrapper = mount( + + + + ); + wrapper.find('button[data-test-subj="take-action-dropdown-btn"]').simulate('click'); + + return wrapper; + }; + + it('should include the Isolate/Release action', () => { + render(); + + expect(wrapper.exists('[data-test-subj="isolate-host-action-item"]')).toBe(true); + }); + + it('should include the Responder action', () => { + render(); + + expect(wrapper.exists('[data-test-subj="endpointResponseActions-action-item"]')).toBe(true); + }); + + describe('should correctly enable/disable the "Add Endpoint event filter" button', () => { beforeEach(() => { setTypeOnEcsDataWithAgentType(); setAlertDetailsDataMockToEvent(); @@ -348,12 +344,7 @@ describe('take action dropdown', () => { ...mockInitialUserPrivilegesState(), endpointPrivileges: { loading: false, canWriteEventFilters: true }, }); - wrapper = mount( - - - - ); - wrapper.find('button[data-test-subj="take-action-dropdown-btn"]').simulate('click'); + render(); await waitFor(() => { expect( wrapper.find('[data-test-subj="add-event-filter-menu-item"]').last().getDOMNode() @@ -366,204 +357,18 @@ describe('take action dropdown', () => { ...mockInitialUserPrivilegesState(), endpointPrivileges: { loading: false, canWriteEventFilters: false }, }); - wrapper = mount( - - - - ); - wrapper.find('button[data-test-subj="take-action-dropdown-btn"]').simulate('click'); + render(); await waitFor(() => { expect(wrapper.exists('[data-test-subj="add-event-filter-menu-item"]')).toBeFalsy(); }); }); test('should hide the "Add Endpoint event filter" button if provided no event from endpoint', async () => { + setAgentTypeOnAlertDetailsDataMock('filebeat'); setTypeOnEcsDataWithAgentType('filebeat'); - - wrapper = mount( - - - - ); - wrapper.find('button[data-test-subj="take-action-dropdown-btn"]').simulate('click'); - await waitFor(() => { - expect(wrapper.exists('[data-test-subj="add-event-filter-menu-item"]')).toBeFalsy(); - }); - }); - }); - - describe('should correctly enable/disable the "Isolate Host" button', () => { - let wrapper: ReactWrapper; - - const render = (): ReactWrapper => { - wrapper = mount( - - - - ); - wrapper.find('button[data-test-subj="take-action-dropdown-btn"]').simulate('click'); - - return wrapper; - }; - - const isolateHostButtonExists = (): ReturnType => { - return wrapper.exists('[data-test-subj="isolate-host-action-item"]'); - }; - - beforeEach(() => { - setTypeOnEcsDataWithAgentType(); - }); - - it('should show Isolate host button if user has "Host isolation" privileges set to all', async () => { - (useUserPrivileges as jest.Mock).mockReturnValue({ - ...mockInitialUserPrivilegesState(), - endpointPrivileges: { loading: false, canIsolateHost: true }, - }); - render(); - - await waitFor(() => { - expect(isolateHostButtonExists()).toBeTruthy(); - }); - }); - it('should hide Isolate host button if user has "Host isolation" privileges set to none', () => { - (useUserPrivileges as jest.Mock).mockReturnValue({ - ...mockInitialUserPrivilegesState(), - endpointPrivileges: { loading: false, canIsolateHost: false }, - }); - render(); - - expect(isolateHostButtonExists()).toBeFalsy(); - }); - }); - - describe('should correctly enable/disable the "Respond" button', () => { - let wrapper: ReactWrapper; - let apiMocks: ReturnType; - - const render = (): ReactWrapper => { - wrapper = mount( - - - - ); - wrapper.find('button[data-test-subj="take-action-dropdown-btn"]').simulate('click'); - - return wrapper; - }; - - const findLaunchResponderButton = (): ReturnType => { - return wrapper.find('[data-test-subj="endpointResponseActions-action-item"]'); - }; - - beforeAll(() => { - // Un-Mock endpoint alert check hooks - const actualChecks = jest.requireActual('../../../common/utils/endpoint_alert_check'); - (isAlertFromEndpointEvent as jest.Mock).mockImplementation( - actualChecks.isAlertFromEndpointEvent - ); - (isAlertFromEndpointAlert as jest.Mock).mockImplementation( - actualChecks.isAlertFromEndpointAlert - ); - }); - - afterAll(() => { - // Set the mock modules back to what they were - (isAlertFromEndpointEvent as jest.Mock).mockImplementation(() => true); - (isAlertFromEndpointAlert as jest.Mock).mockImplementation(() => true); - }); - - beforeEach(() => { - setTypeOnEcsDataWithAgentType(); - apiMocks = endpointMetadataHttpMocks(mockStartServicesMock.http as jest.Mocked); - }); - - it('should not display the button if user is not allowed to write event filters', async () => { - (useUserPrivileges as jest.Mock).mockReturnValue({ - ...mockInitialUserPrivilegesState(), - endpointPrivileges: { loading: false, canWriteEventFilters: false }, - }); - render(); - - expect(findLaunchResponderButton()).toHaveLength(0); - }); - - it('should not display the button for Events', async () => { - setAlertDetailsDataMockToEvent(); - render(); - - expect(findLaunchResponderButton()).toHaveLength(0); - }); - - it('should enable button for non endpoint event type when defend integration present', async () => { - setTypeOnEcsDataWithAgentType('filebeat'); - if (defaultProps.detailsData) { - defaultProps.detailsData = generateAlertDetailsDataMock() as TimelineEventsDetailsItem[]; - } - render(); - - expect(findLaunchResponderButton().first().prop('disabled')).toBe(true); - expect(findLaunchResponderButton().first().prop('toolTipContent')).toEqual( - LOADING_ENDPOINT_DATA_TOOLTIP - ); - - await waitFor(() => { - expect(apiMocks.responseProvider.metadataDetails).toHaveBeenCalled(); - wrapper.update(); - - expect(findLaunchResponderButton().first().prop('disabled')).toBe(false); - expect(findLaunchResponderButton().first().prop('toolTipContent')).toEqual(undefined); - }); - }); - - it('should disable the button for non endpoint event type when defend integration not present', async () => { - setAlertDetailsDataMockToEndpointAgent(); - apiMocks.responseProvider.metadataDetails.mockImplementation(() => { - const error: Error & { body?: { statusCode: number } } = new Error(); - error.body = { statusCode: 404 }; - throw error; - }); render(); - await waitFor(() => { - expect(apiMocks.responseProvider.metadataDetails).toThrow(); - wrapper.update(); - - expect(findLaunchResponderButton().first().prop('disabled')).toBe(true); - expect(findLaunchResponderButton().first().prop('toolTipContent')).toEqual( - NOT_FROM_ENDPOINT_HOST_TOOLTIP - ); - }); - }); - - it('should disable the button if host status is unenrolled', async () => { - setAlertDetailsDataMockToEndpointAgent(); - const getApiResponse = apiMocks.responseProvider.metadataDetails.getMockImplementation(); - apiMocks.responseProvider.metadataDetails.mockImplementation(() => { - if (getApiResponse) { - return { - ...getApiResponse(), - metadata: { - ...getApiResponse().metadata, - Endpoint: { - ...getApiResponse().metadata.Endpoint, - capabilities: [...ENDPOINT_CAPABILITIES], - }, - }, - host_status: HostStatus.UNENROLLED, - }; - } - throw new Error('some error'); - }); - render(); - - await waitFor(() => { - expect(apiMocks.responseProvider.metadataDetails).toHaveBeenCalled(); - wrapper.update(); - - expect(findLaunchResponderButton().first().prop('disabled')).toBe(true); - expect(findLaunchResponderButton().first().prop('toolTipContent')).toEqual( - HOST_ENDPOINT_UNENROLLED_TOOLTIP - ); + expect(wrapper.exists('[data-test-subj="add-event-filter-menu-item"]')).toBeFalsy(); }); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx index 2c769b81b0481..1f4baaeae8df5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx @@ -11,23 +11,21 @@ import type { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-typ import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import { TableId } from '@kbn/securitysolution-data-table'; import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; +import { getAlertDetailsFieldValue } from '../../../common/lib/endpoint/utils/get_event_details_field_values'; import { GuidedOnboardingTourStep } from '../../../common/components/guided_onboarding_tour/tour_step'; import { AlertsCasesTourSteps, SecurityStepId, } from '../../../common/components/guided_onboarding_tour/tour_config'; import { isActiveTimeline } from '../../../helpers'; -import { useResponderActionItem } from '../endpoint_responder'; import { TAKE_ACTION } from '../alerts_table/additional_filters_action/translations'; import { useAlertExceptionActions } from '../alerts_table/timeline_actions/use_add_exception_actions'; import { useAlertsActions } from '../alerts_table/timeline_actions/use_alerts_actions'; import { useInvestigateInTimeline } from '../alerts_table/timeline_actions/use_investigate_in_timeline'; - import { useEventFilterAction } from '../alerts_table/timeline_actions/use_event_filter_action'; -import { useHostIsolationAction } from '../host_isolation/use_host_isolation_action'; -import { getFieldValue } from '../host_isolation/helpers'; +import { useResponderActionItem } from '../../../common/components/endpoint/responder'; +import { useHostIsolationAction } from '../../../common/components/endpoint/host_isolation'; import type { Status } from '../../../../common/api/detection_engine'; -import { isAlertFromEndpointAlert } from '../../../common/utils/endpoint_alert_check'; import { useUserPrivileges } from '../../../common/components/user_privileges'; import { useAddToCaseActions } from '../alerts_table/timeline_actions/use_add_to_case_actions'; import { useKibana } from '../../../common/lib/kibana'; @@ -97,7 +95,10 @@ export const TakeActionDropdown = React.memo( ].reduce( (acc, curr) => ({ ...acc, - [curr.name]: getFieldValue({ category: curr.category, field: curr.field }, detailsData), + [curr.name]: getAlertDetailsFieldValue( + { category: curr.category, field: curr.field }, + detailsData + ), }), {} as ActionsData ), @@ -107,11 +108,10 @@ export const TakeActionDropdown = React.memo( const isEvent = actionsData.eventKind === 'event'; const isAgentEndpoint = useMemo(() => ecsData?.agent?.type?.includes('endpoint'), [ecsData]); - const isEndpointEvent = useMemo(() => isEvent && isAgentEndpoint, [isEvent, isAgentEndpoint]); - const agentId = useMemo( - () => getFieldValue({ category: 'agent', field: 'agent.id' }, detailsData), + const osqueryAgentId = useMemo( + () => getAlertDetailsFieldValue({ category: 'agent', field: 'agent.id' }, detailsData), [detailsData] ); @@ -157,7 +157,7 @@ export const TakeActionDropdown = React.memo( ); const { exceptionActionItems } = useAlertExceptionActions({ - isEndpointAlert: isAlertFromEndpointAlert({ ecsData }), + isEndpointAlert: Boolean(isAgentEndpoint), onAddExceptionTypeClick: handleOnAddExceptionTypeClick, }); @@ -208,13 +208,13 @@ export const TakeActionDropdown = React.memo( }); const osqueryAvailable = osquery?.isOsqueryAvailable({ - agentId, + agentId: osqueryAgentId, }); const handleOnOsqueryClick = useCallback(() => { - onOsqueryClick(agentId); + onOsqueryClick(osqueryAgentId); setIsPopoverOpen(false); - }, [onOsqueryClick, setIsPopoverOpen, agentId]); + }, [onOsqueryClick, setIsPopoverOpen, osqueryAgentId]); const osqueryActionItem = useMemo( () => diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts index 815628f8a3d4a..0b198793a31ff 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts @@ -30,7 +30,7 @@ import type { CheckSignalIndex, UpdateAlertStatusByIdsProps, } from './types'; -import { isolateHost, unIsolateHost } from '../../../../common/lib/endpoint_isolation'; +import { isolateHost, unIsolateHost } from '../../../../common/lib/endpoint/endpoint_isolation'; import { resolvePathVariables } from '../../../../common/utils/resolve_path_variables'; /** 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 9903f6eb2ef78..aa3b432533027 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 @@ -18,7 +18,7 @@ import type { RiskScoresEntityCalculationRequest, RiskScoresEntityCalculationResponse, } from '../../../common/api/entity_analytics/risk_engine/entity_calculation_route.gen'; -import type { AssetCriticalityCsvUploadResponse } from '../../../common/entity_analytics/asset_criticality/types'; +import type { AssetCriticalityBulkUploadResponse } from '../../../common/entity_analytics/asset_criticality/types'; import type { AssetCriticalityRecord, EntityAnalyticsPrivileges, @@ -31,12 +31,13 @@ import { RISK_ENGINE_DISABLE_URL, RISK_ENGINE_INIT_URL, RISK_ENGINE_PRIVILEGES_URL, - ASSET_CRITICALITY_PRIVILEGES_URL, - ASSET_CRITICALITY_URL, + ASSET_CRITICALITY_INTERNAL_PRIVILEGES_URL, + ASSET_CRITICALITY_PUBLIC_URL, RISK_SCORE_INDEX_STATUS_API_URL, RISK_ENGINE_SETTINGS_URL, - ASSET_CRITICALITY_CSV_UPLOAD_URL, + ASSET_CRITICALITY_PUBLIC_CSV_UPLOAD_URL, RISK_SCORE_ENTITY_CALCULATION_URL, + API_VERSIONS, } from '../../../common/constants'; import type { RiskEngineSettingsResponse } from '../../../common/api/entity_analytics/risk_engine'; import type { SnakeToCamelCase } from '../common/utils'; @@ -127,7 +128,7 @@ export const useEntityAnalyticsRoutes = () => { * Get asset criticality privileges */ const fetchAssetCriticalityPrivileges = () => - http.fetch(ASSET_CRITICALITY_PRIVILEGES_URL, { + http.fetch(ASSET_CRITICALITY_INTERNAL_PRIVILEGES_URL, { version: '1', method: 'GET', }); @@ -140,8 +141,8 @@ export const useEntityAnalyticsRoutes = () => { refresh?: 'wait_for'; } ): Promise => - http.fetch(ASSET_CRITICALITY_URL, { - version: '1', + http.fetch(ASSET_CRITICALITY_PUBLIC_URL, { + version: API_VERSIONS.public.v1, method: 'POST', body: JSON.stringify({ id_value: params.idValue, @@ -154,8 +155,8 @@ export const useEntityAnalyticsRoutes = () => { const deleteAssetCriticality = async ( params: Pick & { refresh?: 'wait_for' } ): Promise<{ deleted: true }> => { - await http.fetch(ASSET_CRITICALITY_URL, { - version: '1', + await http.fetch(ASSET_CRITICALITY_PUBLIC_URL, { + version: API_VERSIONS.public.v1, method: 'DELETE', query: { id_value: params.idValue, id_field: params.idField, refresh: params.refresh }, }); @@ -170,8 +171,8 @@ export const useEntityAnalyticsRoutes = () => { const fetchAssetCriticality = async ( params: Pick ): Promise => { - return http.fetch(ASSET_CRITICALITY_URL, { - version: '1', + return http.fetch(ASSET_CRITICALITY_PUBLIC_URL, { + version: API_VERSIONS.public.v1, method: 'GET', query: { id_value: params.idValue, id_field: params.idField }, }); @@ -180,19 +181,22 @@ export const useEntityAnalyticsRoutes = () => { const uploadAssetCriticalityFile = async ( fileContent: string, fileName: string - ): Promise => { + ): Promise => { const file = new File([new Blob([fileContent])], fileName, { type: 'text/csv' }); const body = new FormData(); body.append('file', file); - return http.fetch(ASSET_CRITICALITY_CSV_UPLOAD_URL, { - version: '1', - method: 'POST', - headers: { - 'Content-Type': undefined, // Lets the browser set the appropriate content type - }, - body, - }); + return http.fetch( + ASSET_CRITICALITY_PUBLIC_CSV_UPLOAD_URL, + { + version: API_VERSIONS.public.v1, + method: 'POST', + headers: { + 'Content-Type': undefined, // Lets the browser set the appropriate content type + }, + body, + } + ); }; const getRiskScoreIndexStatus = ({ diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/components/result_step.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/components/result_step.tsx index 30834f2fb8c32..1652c85eace1f 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/components/result_step.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/components/result_step.tsx @@ -18,11 +18,11 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { css } from '@emotion/react'; -import type { AssetCriticalityCsvUploadResponse } from '../../../../../common/entity_analytics/asset_criticality/types'; +import type { AssetCriticalityBulkUploadResponse } from '../../../../../common/entity_analytics/asset_criticality/types'; import { buildAnnotationsFromError } from '../helpers'; export const AssetCriticalityResultStep: React.FC<{ - result?: AssetCriticalityCsvUploadResponse; + result?: AssetCriticalityBulkUploadResponse; validLinesAsText: string; errorMessage?: string; onReturn: () => void; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/reducer.test.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/reducer.test.ts index 68f7814d3113a..60b6191a777d6 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/reducer.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { AssetCriticalityCsvUploadResponse } from '../../../../common/api/entity_analytics'; +import type { AssetCriticalityBulkUploadResponse } from '../../../../common/api/entity_analytics'; import type { ReducerAction, ReducerState, ValidationStepState } from './reducer'; import { reducer } from './reducer'; import { FileUploaderSteps } from './types'; @@ -43,7 +43,7 @@ describe('reducer', () => { }); it('should handle "fileUploaded" action with response', () => { - const response: AssetCriticalityCsvUploadResponse = { + const response: AssetCriticalityBulkUploadResponse = { errors: [], stats: { total: 10, diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/reducer.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/reducer.ts index 0868caf2d624b..e7f233015434f 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/reducer.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality_file_uploader/reducer.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { AssetCriticalityCsvUploadResponse } from '../../../../common/entity_analytics/asset_criticality/types'; +import type { AssetCriticalityBulkUploadResponse } from '../../../../common/entity_analytics/asset_criticality/types'; import { FileUploaderSteps } from './types'; import type { ValidatedFile } from './types'; import { isFilePickerStep, isValidationStep } from './helpers'; @@ -26,7 +26,7 @@ export interface ValidationStepState { export interface ResultStepState { step: FileUploaderSteps.RESULT; - fileUploadResponse?: AssetCriticalityCsvUploadResponse; + fileUploadResponse?: AssetCriticalityBulkUploadResponse; fileUploadError?: string; validLinesAsText: string; } @@ -46,7 +46,7 @@ export type ReducerAction = | { type: 'uploadingFile' } | { type: 'fileUploaded'; - payload: { response?: AssetCriticalityCsvUploadResponse; errorMessage?: string }; + payload: { response?: AssetCriticalityBulkUploadResponse; errorMessage?: string }; }; export const INITIAL_STATE: FilePickerState = { diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_preview_section.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_preview_section.tsx index 91383fcdfd658..b8f7870e23b56 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_preview_section.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/risk_score_preview_section.tsx @@ -227,6 +227,7 @@ const RiskEnginePreview = () => { showDatePicker={true} displayStyle={'inPage'} submitButtonStyle={'iconOnly'} + dataTestSubj="risk-score-preview-search-bar-input" /> )} diff --git a/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/columns.tsx b/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/columns.tsx index 270a10b52155a..6f8e3ad587a0d 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/columns.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/columns.tsx @@ -7,6 +7,8 @@ import { EuiIcon, EuiLink, EuiText, EuiToolTip } from '@elastic/eui'; import React from 'react'; +import type { CriticalityLevelWithUnassigned } from '../../../../../common/entity_analytics/asset_criticality/types'; +import { AssetCriticalityBadge } from '../../../../entity_analytics/components/asset_criticality'; import { SecurityCellActions, CellActionsMode, @@ -25,7 +27,8 @@ import { ENTITY_RISK_LEVEL } from '../../../../entity_analytics/components/risk_ export const getHostsColumns = ( showRiskColumn: boolean, - dispatchSeverityUpdate: (s: RiskSeverity) => void + dispatchSeverityUpdate: (s: RiskSeverity) => void, + isAssetCriticalityEnabled: boolean ): HostsTableColumns => { const columns: HostsTableColumns = [ { @@ -163,5 +166,24 @@ export const getHostsColumns = ( }); } + if (isAssetCriticalityEnabled) { + columns.push({ + field: 'node.criticality', + name: i18n.ASSET_CRITICALITY, + truncateText: false, + mobileOptions: { show: true }, + sortable: false, + render: (assetCriticality: CriticalityLevelWithUnassigned) => { + if (!assetCriticality) return getEmptyTagValue(); + return ( + + ); + }, + }); + } + return columns; }; diff --git a/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.test.tsx b/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.test.tsx index d46182ca7f4d9..8d20fed91a66a 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.test.tsx @@ -46,6 +46,16 @@ jest.mock('../../../../helper_hooks', () => ({ useHasSecurityCapability: () => mockUseHasSecurityCapability(), })); +const mockUseUiSetting = jest.fn().mockReturnValue([false]); + +jest.mock('@kbn/kibana-react-plugin/public', () => { + const original = jest.requireActual('@kbn/kibana-react-plugin/public'); + return { + ...original, + useUiSetting$: () => mockUseUiSetting(), + }; +}); + describe('Hosts Table', () => { const loadPage = jest.fn(); const store = createMockStore(); @@ -145,6 +155,56 @@ describe('Hosts Table', () => { expect(queryByTestId('tableHeaderCell_node.riskScore_4')).not.toBeInTheDocument(); }); + test('it renders "Asset Criticality" column when "isPlatinumOrTrialLicense" is truthy, user has risk-entity capability and Asset Criticality is enabled in Kibana settings', () => { + mockUseMlCapabilities.mockReturnValue({ isPlatinumOrTrialLicense: true }); + mockUseHasSecurityCapability.mockReturnValue(true); + mockUseUiSetting.mockReturnValue([true]); + + const { queryByTestId } = render( + + + + ); + + expect(queryByTestId('tableHeaderCell_node.criticality_5')).toBeInTheDocument(); + }); + + test('it does not render "Asset Criticality" column when Asset Criticality is not enabled in Kibana settings', () => { + mockUseMlCapabilities.mockReturnValue({ isPlatinumOrTrialLicense: true }); + mockUseHasSecurityCapability.mockReturnValue(true); + mockUseUiSetting.mockReturnValue([false]); + + const { queryByTestId } = render( + + + + ); + + expect(queryByTestId('tableHeaderCell_node.criticality_5')).not.toBeInTheDocument(); + }); + describe('Sorting on Table', () => { let wrapper: ReturnType; diff --git a/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.tsx b/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.tsx index 3c76bc5dcbf9c..ef9a368dac370 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/index.tsx @@ -9,6 +9,8 @@ import React, { useMemo, useCallback } from 'react'; import { useDispatch } from 'react-redux'; import type { HostEcs, OsEcs } from '@kbn/securitysolution-ecs'; +import { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; +import type { CriticalityLevelWithUnassigned } from '../../../../../common/entity_analytics/asset_criticality/types'; import { HostsFields } from '../../../../../common/api/search_strategy/hosts/model/sort'; import type { Columns, @@ -27,7 +29,10 @@ import type { HostsSortField, } from '../../../../../common/search_strategy/security_solution/hosts'; import type { Direction, RiskSeverity } from '../../../../../common/search_strategy'; -import { SecurityPageName } from '../../../../../common/constants'; +import { + ENABLE_ASSET_CRITICALITY_SETTING, + SecurityPageName, +} from '../../../../../common/constants'; import { HostsTableType } from '../../store/model'; import { useNavigateTo } from '../../../../common/lib/kibana/hooks'; import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml_capabilities'; @@ -53,7 +58,8 @@ export type HostsTableColumns = [ Columns, Columns, Columns, - Columns? + Columns?, + Columns? ]; const rowItems: ItemsPerRow[] = [ @@ -153,15 +159,22 @@ const HostsTableComponent: React.FC = ({ [dispatch, navigateTo, type] ); + const [isAssetCriticalityEnabled] = useUiSetting$(ENABLE_ASSET_CRITICALITY_SETTING); + const hostsColumns = useMemo( () => getHostsColumns( isPlatinumOrTrialLicense && hasEntityAnalyticsCapability, - dispatchSeverityUpdate + dispatchSeverityUpdate, + isAssetCriticalityEnabled ), - [dispatchSeverityUpdate, isPlatinumOrTrialLicense, hasEntityAnalyticsCapability] + [ + dispatchSeverityUpdate, + isPlatinumOrTrialLicense, + hasEntityAnalyticsCapability, + isAssetCriticalityEnabled, + ] ); - const sorting = useMemo(() => getSorting(sortField, direction), [sortField, direction]); return ( diff --git a/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/translations.ts b/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/translations.ts index 1f81968213b45..c6cc085a61b0b 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/translations.ts +++ b/x-pack/plugins/security_solution/public/explore/hosts/components/hosts_table/translations.ts @@ -60,3 +60,10 @@ export const ROWS_10 = i18n.translate('xpack.securitySolution.hostsTable.rows', values: { numRows: 10 }, defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', }); + +export const ASSET_CRITICALITY = i18n.translate( + 'xpack.securitySolution.hostsTable.assetCriticality', + { + defaultMessage: 'Asset criticality', + } +); diff --git a/x-pack/plugins/security_solution/public/explore/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/explore/hosts/pages/details/index.tsx index 2a6e51d5839a8..f60866be1e3ea 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/pages/details/index.tsx @@ -77,7 +77,7 @@ import { useSourcererDataView } from '../../../../sourcerer/containers'; import { EmptyPrompt } from '../../../../common/components/empty_prompt'; import { AlertCountByRuleByStatus } from '../../../../common/components/alert_count_by_status'; import { useLicense } from '../../../../common/hooks/use_license'; -import { ResponderActionButton } from '../../../../detections/components/endpoint_responder/responder_action_button'; +import { ResponderActionButton } from '../../../../common/components/endpoint/responder'; import { useRefetchOverviewPageRiskScore } from '../../../../entity_analytics/api/hooks/use_refetch_overview_page_risk_score'; const ES_HOST_FIELD = 'host.name'; @@ -226,7 +226,7 @@ const HostDetailsComponent: React.FC = ({ detailName, hostDeta rightSideItems={[ hostOverview.endpoint?.hostInfo?.metadata.elastic.agent.id && ( ), diff --git a/x-pack/plugins/security_solution/public/explore/users/components/all_users/index.tsx b/x-pack/plugins/security_solution/public/explore/users/components/all_users/index.tsx index 6948009ebb188..d05c9551a2518 100644 --- a/x-pack/plugins/security_solution/public/explore/users/components/all_users/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/users/components/all_users/index.tsx @@ -9,6 +9,9 @@ import React, { useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { EuiLink, EuiText } from '@elastic/eui'; +import { ENABLE_ASSET_CRITICALITY_SETTING } from '../../../../../common/constants'; +import { AssetCriticalityBadge } from '../../../../entity_analytics/components/asset_criticality'; +import type { CriticalityLevelWithUnassigned } from '../../../../../common/entity_analytics/asset_criticality/types'; import { FormattedRelativePreferenceDate } from '../../../../common/components/formatted_date'; import { UserDetailsLink } from '../../../../common/components/links'; import { @@ -32,7 +35,7 @@ import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml import { VIEW_USERS_BY_SEVERITY } from '../../../../entity_analytics/components/user_risk_score_table/translations'; import { SecurityPageName } from '../../../../app/types'; import { UsersTableType } from '../../store/model'; -import { useNavigateTo } from '../../../../common/lib/kibana'; +import { useNavigateTo, useUiSetting$ } from '../../../../common/lib/kibana'; const tableType = usersModel.UsersTableType.allUsers; @@ -53,7 +56,8 @@ export type UsersTableColumns = [ Columns, Columns, Columns, - Columns? + Columns?, + Columns? ]; const rowItems: ItemsPerRow[] = [ @@ -69,7 +73,8 @@ const rowItems: ItemsPerRow[] = [ const getUsersColumns = ( showRiskColumn: boolean, - dispatchSeverityUpdate: (s: RiskSeverity) => void + dispatchSeverityUpdate: (s: RiskSeverity) => void, + isAssetCriticalityEnabled: boolean ): UsersTableColumns => { const columns: UsersTableColumns = [ { @@ -138,6 +143,25 @@ const getUsersColumns = ( }); } + if (isAssetCriticalityEnabled) { + columns.push({ + field: 'criticality', + name: i18n.ASSET_CRITICALITY, + truncateText: false, + mobileOptions: { show: true }, + sortable: false, + render: (assetCriticality: CriticalityLevelWithUnassigned) => { + if (!assetCriticality) return getEmptyTagValue(); + return ( + + ); + }, + }); + } + return columns; }; @@ -217,9 +241,11 @@ const UsersTableComponent: React.FC = ({ [dispatch, navigateTo] ); + const [isAssetCriticalityEnabled] = useUiSetting$(ENABLE_ASSET_CRITICALITY_SETTING); const columns = useMemo( - () => getUsersColumns(isPlatinumOrTrialLicense, dispatchSeverityUpdate), - [isPlatinumOrTrialLicense, dispatchSeverityUpdate] + () => + getUsersColumns(isPlatinumOrTrialLicense, dispatchSeverityUpdate, isAssetCriticalityEnabled), + [isPlatinumOrTrialLicense, dispatchSeverityUpdate, isAssetCriticalityEnabled] ); return ( diff --git a/x-pack/plugins/security_solution/public/explore/users/components/all_users/translations.ts b/x-pack/plugins/security_solution/public/explore/users/components/all_users/translations.ts index e1e3f43c6af32..0a96f6ff717b8 100644 --- a/x-pack/plugins/security_solution/public/explore/users/components/all_users/translations.ts +++ b/x-pack/plugins/security_solution/public/explore/users/components/all_users/translations.ts @@ -42,3 +42,10 @@ export const UNIT = (totalCount: number) => export const USER_RISK = i18n.translate('xpack.securitySolution.usersTable.riskTitle', { defaultMessage: 'User risk level', }); + +export const ASSET_CRITICALITY = i18n.translate( + 'xpack.securitySolution.hostsTable.assetCriticality', + { + defaultMessage: 'Asset criticality', + } +); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/content.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/content.tsx index ae7707e971131..6c4aafa0f7bd4 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/content.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/content.tsx @@ -10,10 +10,12 @@ import React, { useCallback } from 'react'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import { DocumentDetailsRightPanelKey } from '../shared/constants/panel_keys'; import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers'; -import { EndpointIsolateSuccess } from '../../../common/components/endpoint/host_isolation'; +import { + EndpointIsolateSuccess, + HostIsolationPanel, +} from '../../../common/components/endpoint/host_isolation'; import { useHostIsolationTools } from '../../../timelines/components/side_panel/event_details/use_host_isolation_tools'; import { useIsolateHostPanelContext } from './context'; -import { HostIsolationPanel } from '../../../detections/components/host_isolation'; import { FlyoutBody } from '../../shared/components/flyout_body'; /** diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/header.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/header.test.tsx index 5df180ed58075..63fd7651f04aa 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/header.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/header.test.tsx @@ -6,128 +6,95 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; -import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import type { IsolateHostPanelContext } from './context'; import { useIsolateHostPanelContext } from './context'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { PanelHeader } from './header'; -import { FLYOUT_HEADER_TITLE_TEST_ID } from './test_ids'; -import { isAlertFromSentinelOneEvent } from '../../../common/utils/sentinelone_alert_check'; -import { isAlertFromCrowdstrikeEvent } from '../../../common/utils/crowdstrike_alert_check'; +import type { AppContextTestRender } from '../../../common/mock/endpoint'; +import { createAppRootMockRenderer, endpointAlertDataMock } from '../../../common/mock/endpoint'; +import type { ResponseActionAgentType } from '../../../../common/endpoint/service/response_actions/constants'; +import { RESPONSE_ACTION_AGENT_TYPE } from '../../../../common/endpoint/service/response_actions/constants'; +import { ISOLATE_HOST, UNISOLATE_HOST } from '../../../common/components/endpoint/host_isolation'; import { TECHNICAL_PREVIEW } from '../../../common/translations'; -jest.mock('../../../common/hooks/use_experimental_features'); -jest.mock('../../../common/utils/sentinelone_alert_check'); -jest.mock('../../../common/utils/crowdstrike_alert_check'); jest.mock('./context'); -const mockUseIsExperimentalFeatureEnabled = useIsExperimentalFeatureEnabled as jest.Mock; -const mockIsAlertFromSentinelOneEvent = isAlertFromSentinelOneEvent as jest.Mock; -const mockIsAlertFromCrowdstrike = isAlertFromCrowdstrikeEvent as jest.Mock; +describe('Isolation Flyout PanelHeader', () => { + let render: () => ReturnType; -const renderPanelHeader = () => - render( - - - - ); + const setUseIsolateHostPanelContext = (data: Partial = {}) => { + const panelContextMock: IsolateHostPanelContext = { + eventId: 'some-even-1', + indexName: 'some-index-name', + scopeId: 'some-scope-id', + dataFormattedForFieldBrowser: endpointAlertDataMock.generateEndpointAlertDetailsItemData(), + isolateAction: 'isolateHost', + ...data, + }; + + (useIsolateHostPanelContext as jest.Mock).mockReturnValue(panelContextMock); + }; -describe('', () => { beforeEach(() => { - mockUseIsExperimentalFeatureEnabled.mockReturnValue(false); - mockIsAlertFromSentinelOneEvent.mockReturnValue(false); - mockIsAlertFromCrowdstrike.mockReturnValue(false); - }); + const appContextMock = createAppRootMockRenderer(); - it.each([ - { - isolateAction: 'isolateHost', - title: 'Isolate host', - }, - { - isolateAction: 'unisolateHost', - title: 'Release host', - }, - ])('should display release host message', ({ isolateAction, title }) => { - (useIsolateHostPanelContext as jest.Mock).mockReturnValue({ isolateAction }); + appContextMock.setExperimentalFlag({ + responseActionsSentinelOneV1Enabled: true, + responseActionsCrowdstrikeManualHostIsolationEnabled: true, + }); - const { getByTestId } = renderPanelHeader(); + render = () => appContextMock.render(); - expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toHaveTextContent(title); + setUseIsolateHostPanelContext({ + isolateAction: 'isolateHost', + dataFormattedForFieldBrowser: endpointAlertDataMock.generateEndpointAlertDetailsItemData(), + }); }); - it.each([ - { - action: 'isolateHost', - alertCheck: mockIsAlertFromSentinelOneEvent, - description: 'SentinelOne', - }, - { - action: 'unisolateHost', - alertCheck: mockIsAlertFromSentinelOneEvent, - description: 'SentinelOne', - }, - { - action: 'isolateHost', - alertCheck: mockIsAlertFromCrowdstrike, - description: 'Crowdstrike', - }, - { - action: 'unisolateHost', - alertCheck: mockIsAlertFromCrowdstrike, - description: 'Crowdstrike', - }, - ])( - 'should display beta badge on $description alerts for %s host message', - ({ action, alertCheck }) => { - (useIsolateHostPanelContext as jest.Mock).mockReturnValue({ - isolateAction: action, - }); - mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); - alertCheck.mockReturnValue(true); + afterEach(() => { + jest.clearAllMocks(); + }); - const { getByTestId } = renderPanelHeader(); + const testConditions: Array<{ + action: IsolateHostPanelContext['isolateAction']; + agentType: ResponseActionAgentType; + title: string; + // if `expectedBadgeText` is `undefined`, then it validates that the badge is not displayed + expectedBadgeText: string | undefined; + }> = []; - expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toHaveTextContent(TECHNICAL_PREVIEW); - } - ); + for (const agentType of RESPONSE_ACTION_AGENT_TYPE) { + (['isolateHost', 'unisolateHost'] as Array).forEach( + (action) => { + testConditions.push({ + action, + agentType, + title: action === 'isolateHost' ? ISOLATE_HOST : UNISOLATE_HOST, + expectedBadgeText: + agentType === 'crowdstrike' || agentType === 'sentinel_one' + ? TECHNICAL_PREVIEW + : undefined, + }); + } + ); + } - it.each([ - { - action: 'isolateHost', - alertCheck: mockIsAlertFromSentinelOneEvent, - description: 'SentinelOne', - }, - { - action: 'unisolateHost', - alertCheck: mockIsAlertFromSentinelOneEvent, - description: 'SentinelOne', - }, - { - action: 'isolateHost', - alertCheck: mockIsAlertFromCrowdstrike, - description: 'Crowdstrike', - }, - { - action: 'unisolateHost', - alertCheck: mockIsAlertFromCrowdstrike, - description: 'Crowdstrike', - }, - ])( - 'should not display beta badge on $description alerts for %s host message', - ({ action, alertCheck }) => { - (useIsolateHostPanelContext as jest.Mock).mockReturnValue({ + it.each(testConditions)( + 'should display correct flyout header title for $action on agentType $agentType', + ({ action, agentType, title, expectedBadgeText }) => { + setUseIsolateHostPanelContext({ isolateAction: action, + dataFormattedForFieldBrowser: + endpointAlertDataMock.generateAlertDetailsItemDataForAgentType(agentType), }); - mockUseIsExperimentalFeatureEnabled.mockReturnValue(true); - alertCheck.mockReturnValue(false); + const { getByTestId, queryByTestId } = render(); - const { getByTestId } = renderPanelHeader(); + expect(getByTestId('flyoutHostIsolationHeaderTitle')).toHaveTextContent(title); - expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toBeInTheDocument(); - expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).not.toHaveTextContent(TECHNICAL_PREVIEW); + if (expectedBadgeText) { + expect(getByTestId('flyoutHostIsolationHeaderBadge')).toHaveTextContent(expectedBadgeText); + } else { + expect(queryByTestId('flyoutHostIsolationHeaderBadge')).toBeNull(); + } } ); }); diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/header.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/header.tsx index ead599d38e498..635d273b43237 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/header.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/isolate_host/header.tsx @@ -7,51 +7,40 @@ import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import type { FC } from 'react'; -import React from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { isAlertFromCrowdstrikeEvent } from '../../../common/utils/crowdstrike_alert_check'; +import React, { useMemo } from 'react'; +import { useAlertResponseActionsSupport } from '../../../common/hooks/endpoint/use_alert_response_actions_support'; import { TECHNICAL_PREVIEW, TECHNICAL_PREVIEW_TOOLTIP } from '../../../common/translations'; -import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; -import { isAlertFromSentinelOneEvent } from '../../../common/utils/sentinelone_alert_check'; import { useIsolateHostPanelContext } from './context'; import { FLYOUT_HEADER_TITLE_TEST_ID } from './test_ids'; import { FlyoutHeader } from '../../shared/components/flyout_header'; +import { ISOLATE_HOST, UNISOLATE_HOST } from '../../../common/components/endpoint'; /** * Document details expandable right section header for the isolate host panel */ export const PanelHeader: FC = () => { const { isolateAction, dataFormattedForFieldBrowser: data } = useIsolateHostPanelContext(); - const isSentinelOneAlert = isAlertFromSentinelOneEvent({ data }); - const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled( - 'responseActionsSentinelOneV1Enabled' - ); + const { + isSupported: supportsResponseActions, + details: { agentType }, + } = useAlertResponseActionsSupport(data); + + const showTechPreviewBadge: boolean = useMemo(() => { + return supportsResponseActions && (agentType === 'sentinel_one' || agentType === 'crowdstrike'); + }, [agentType, supportsResponseActions]); - const isAlertFromCrowdstrikeAlert = isAlertFromCrowdstrikeEvent({ data }); - const responseActionsCrowdstrikeManualHostIsolationEnabled = useIsExperimentalFeatureEnabled( - 'responseActionsCrowdstrikeManualHostIsolationEnabled' - ); - const showAsTechPreview = - (isSentinelOneV1Enabled && isSentinelOneAlert) || - (responseActionsCrowdstrikeManualHostIsolationEnabled && isAlertFromCrowdstrikeAlert); const title = ( - - {isolateAction === 'isolateHost' ? ( - - ) : ( - - )} + + {isolateAction === 'isolateHost' ? <>{ISOLATE_HOST} : <>{UNISOLATE_HOST}} - {showAsTechPreview && ( + {showTechPreviewBadge && ( - + )} diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/components/notes_details.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/notes_details.tsx new file mode 100644 index 0000000000000..7d7d370d4953c --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/components/notes_details.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo, useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import { fetchNotesByDocumentId } from '../../../../notes/store/notes.slice'; +import { useLeftPanelContext } from '../context'; + +/** + * List all the notes for a document id and allows to create new notes associated with that document. + * Displayed in the document details expandable flyout left section. + */ +export const NotesDetails = memo(() => { + const dispatch = useDispatch(); + const { eventId } = useLeftPanelContext(); + + useEffect(() => { + dispatch(fetchNotesByDocumentId({ documentId: eventId })); + }, [dispatch, eventId]); + + return <>; +}); + +NotesDetails.displayName = 'NotesDetails'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/index.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/index.tsx index 84e9d2553a422..a93b1e7e7a4fb 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/index.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/index.tsx @@ -9,6 +9,7 @@ import type { FC } from 'react'; import React, { memo, useMemo } from 'react'; import type { FlyoutPanelProps, PanelPath } from '@kbn/expandable-flyout'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; +import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; import { DocumentDetailsLeftPanelKey } from '../shared/constants/panel_keys'; import { useKibana } from '../../../common/lib/kibana'; import { PanelHeader } from './header'; @@ -20,11 +21,12 @@ import { EventKind } from '../shared/constants/event_kinds'; import { useLeftPanelContext } from './context'; import { LeftPanelTour } from './components/tour'; -export type LeftPanelPaths = 'visualize' | 'insights' | 'investigation' | 'response'; +export type LeftPanelPaths = 'visualize' | 'insights' | 'investigation' | 'response' | 'notes'; export const LeftPanelVisualizeTab: LeftPanelPaths = 'visualize'; export const LeftPanelInsightsTab: LeftPanelPaths = 'insights'; export const LeftPanelInvestigationTab: LeftPanelPaths = 'investigation'; export const LeftPanelResponseTab: LeftPanelPaths = 'response'; +export const LeftPanelNotesTab: LeftPanelPaths = 'notes'; export interface LeftPanelProps extends FlyoutPanelProps { key: typeof DocumentDetailsLeftPanelKey; @@ -41,14 +43,18 @@ export const LeftPanel: FC> = memo(({ path }) => { const { openLeftPanel } = useExpandableFlyoutApi(); const { eventId, indexName, scopeId, getFieldsData } = useLeftPanelContext(); const eventKind = getField(getFieldsData('event.kind')); + const notesEnabled = useIsExperimentalFeatureEnabled('notesEnabled'); - const tabsDisplayed = useMemo( - () => + const tabsDisplayed = useMemo(() => { + const tabList = eventKind === EventKind.signal ? [tabs.insightsTab, tabs.investigationTab, tabs.responseTab] - : [tabs.insightsTab], - [eventKind] - ); + : [tabs.insightsTab]; + if (notesEnabled) { + tabList.push(tabs.notesTab); + } + return tabList; + }, [eventKind, notesEnabled]); const selectedTabId = useMemo(() => { const defaultTab = tabsDisplayed[0].id; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs.tsx index 4851f4a455959..6a066ffb34648 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs.tsx @@ -8,6 +8,7 @@ import type { ReactElement } from 'react'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; +import { NotesTab } from './tabs/notes_tab'; import { VisualizeTab } from './tabs/visualize_tab'; import { InvestigationTab } from './tabs/investigation_tab'; import { InsightsTab } from './tabs/insights_tab'; @@ -15,6 +16,7 @@ import type { LeftPanelPaths } from '.'; import { INSIGHTS_TAB_TEST_ID, INVESTIGATION_TAB_TEST_ID, + NOTES_TAB_TEST_ID, RESPONSE_TAB_TEST_ID, VISUALIZE_TAB_TEST_ID, } from './test_ids'; @@ -74,3 +76,15 @@ export const responseTab: LeftPanelTabType = { ), content: , }; + +export const notesTab: LeftPanelTabType = { + id: 'notes', + 'data-test-subj': NOTES_TAB_TEST_ID, + name: ( + + ), + content: , +}; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/notes_tab.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/notes_tab.tsx new file mode 100644 index 0000000000000..5f95fc67478aa --- /dev/null +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/notes_tab.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo } from 'react'; +import { EuiPanel } from '@elastic/eui'; +import { NotesDetails } from '../components/notes_details'; +import { NOTES_TAB_CONTENT_TEST_ID } from './test_ids'; + +/** + * Notes view displayed in the document details expandable flyout left section + * // TODO to be implemented + */ +export const NotesTab = memo(() => { + return ( + + + + ); +}); + +NotesTab.displayName = 'NotesTab'; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/test_ids.ts index 4630a19d08c98..1e99fb63d18a5 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/tabs/test_ids.ts @@ -29,3 +29,4 @@ export const INSIGHTS_TAB_CORRELATIONS_BUTTON_TEST_ID = `${INSIGHTS_TAB_TEST_ID}CorrelationsButton` as const; export const INVESTIGATION_TAB_CONTENT_TEST_ID = `${PREFIX}InvestigationsTabContent` as const; export const RESPONSE_TAB_CONTENT_TEST_ID = `${PREFIX}ResponseTabContent` as const; +export const NOTES_TAB_CONTENT_TEST_ID = `${PREFIX}NotesTabContent` as const; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/left/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/document_details/left/test_ids.ts index beb7971b3a861..9f5eeb035786c 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/left/test_ids.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/left/test_ids.ts @@ -11,3 +11,4 @@ export const VISUALIZE_TAB_TEST_ID = `${PREFIX}VisualizeTab` as const; export const INSIGHTS_TAB_TEST_ID = `${PREFIX}InsightsTab` as const; export const INVESTIGATION_TAB_TEST_ID = `${PREFIX}InvestigationTab` as const; export const RESPONSE_TAB_TEST_ID = `${PREFIX}ResponseTab` as const; +export const NOTES_TAB_TEST_ID = `${PREFIX}NotesTab` as const; diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.tsx index be495a6b16df6..51e60b67ed8b0 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/right/components/highlighted_fields_cell.tsx @@ -11,12 +11,10 @@ import { EuiFlexItem, EuiLink } from '@elastic/eui'; import { useExpandableFlyoutApi } from '@kbn/expandable-flyout'; import type { ResponseActionAgentType } from '../../../../../common/endpoint/service/response_actions/constants'; import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; -import { SENTINEL_ONE_AGENT_ID_FIELD } from '../../../../common/utils/sentinelone_alert_check'; import { AgentStatus, EndpointAgentStatusById, -} from '../../../../common/components/agents/agent_status'; -import { CROWDSTRIKE_AGENT_ID_FIELD } from '../../../../common/utils/crowdstrike_alert_check'; +} from '../../../../common/components/endpoint/agents/agent_status'; import { useRightPanelContext } from '../context'; import { AGENT_STATUS_FIELD_NAME, @@ -32,6 +30,7 @@ import { HIGHLIGHTED_FIELDS_CELL_TEST_ID, HIGHLIGHTED_FIELDS_LINKED_CELL_TEST_ID, } from './test_ids'; +import { RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD } from '../../../../../common/endpoint/service/response_actions/constants'; interface LinkFieldCellProps { /** @@ -117,11 +116,11 @@ export const HighlightedFieldsCell: VFC = ({ originalField, }) => { const isSentinelOneAgentIdField = useMemo( - () => originalField === SENTINEL_ONE_AGENT_ID_FIELD, + () => originalField === RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD.sentinel_one, [originalField] ); const isCrowdstrikeAgentIdField = useMemo( - () => originalField === CROWDSTRIKE_AGENT_ID_FIELD, + () => originalField === RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD.crowdstrike, [originalField] ); const agentType: ResponseActionAgentType = useMemo(() => { diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_highlighted_fields.test.tsx b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_highlighted_fields.test.tsx index 3a18b49a0fc17..c78e7313792c8 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_highlighted_fields.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_highlighted_fields.test.tsx @@ -12,7 +12,9 @@ import { mockDataFormattedForFieldBrowserWithOverridenField, } from '../mocks/mock_data_formatted_for_field_browser'; import { useHighlightedFields } from './use_highlighted_fields'; -import { SENTINEL_ONE_AGENT_ID_FIELD } from '../../../../common/utils/sentinelone_alert_check'; +import { RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD } from '../../../../../common/endpoint/service/response_actions/constants'; + +jest.mock('../../../../common/experimental_features_service'); const dataFormattedForFieldBrowser = mockDataFormattedForFieldBrowser; @@ -104,7 +106,7 @@ describe('useHighlightedFields', () => { useHighlightedFields({ dataFormattedForFieldBrowser: dataFormattedForFieldBrowser.concat({ category: 'observer', - field: `observer.${SENTINEL_ONE_AGENT_ID_FIELD}`, + field: `observer.${RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD.sentinel_one}`, values: ['deb35a20-70f8-458e-a64a-c9e6f7575893'], originalValue: ['deb35a20-70f8-458e-a64a-c9e6f7575893'], isObjectArray: false, @@ -154,7 +156,7 @@ describe('useHighlightedFields', () => { }, { category: 'observer', - field: SENTINEL_ONE_AGENT_ID_FIELD, + field: RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD.sentinel_one, values: ['deb35a20-70f8-458e-a64a-c9e6f7575893'], originalValue: ['deb35a20-70f8-458e-a64a-c9e6f7575893'], isObjectArray: false, diff --git a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_highlighted_fields.ts b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_highlighted_fields.ts index c60bb96c77974..571e01b9a2e22 100644 --- a/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_highlighted_fields.ts +++ b/x-pack/plugins/security_solution/public/flyout/document_details/shared/hooks/use_highlighted_fields.ts @@ -8,15 +8,8 @@ import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; import { find, isEmpty } from 'lodash/fp'; import { ALERT_RULE_TYPE } from '@kbn/rule-data-utils'; -import { - CROWDSTRIKE_AGENT_ID_FIELD, - isAlertFromCrowdstrikeEvent, -} from '../../../../common/utils/crowdstrike_alert_check'; -import { - SENTINEL_ONE_AGENT_ID_FIELD, - isAlertFromSentinelOneEvent, -} from '../../../../common/utils/sentinelone_alert_check'; -import { isAlertFromEndpointEvent } from '../../../../common/utils/endpoint_alert_check'; +import { useAlertResponseActionsSupport } from '../../../../common/hooks/endpoint/use_alert_response_actions_support'; +import { isResponseActionsAlertAgentIdField } from '../../../../common/lib/endpoint'; import { getEventCategoriesFromData, getEventFieldsToDisplay, @@ -53,6 +46,7 @@ export const useHighlightedFields = ({ dataFormattedForFieldBrowser, investigationFields, }: UseHighlightedFieldsParams): UseHighlightedFieldsResult => { + const responseActionsSupport = useAlertResponseActionsSupport(dataFormattedForFieldBrowser); const eventCategories = getEventCategoriesFromData(dataFormattedForFieldBrowser); const eventCodeField = find( @@ -99,26 +93,12 @@ export const useHighlightedFields = ({ field.id = field.legacyId; } - // if the field is agent.id and the event is not an endpoint event we skip it - if ( - field.id === 'agent.id' && - !isAlertFromEndpointEvent({ data: dataFormattedForFieldBrowser }) - ) { - return acc; - } - - // if the field is observer.serial_number and the event is not a sentinel one event we skip it - if ( - field.id === SENTINEL_ONE_AGENT_ID_FIELD && - !isAlertFromSentinelOneEvent({ data: dataFormattedForFieldBrowser }) - ) { - return acc; - } - - // if the field is crowdstrike.event.DeviceId and the event is not a crowdstrike event we skip it + // If the field is one used by a supported Response Actions agentType, + // but the alert field is not the one that the agentType on the alert host uses, + // then exit and return accumulator if ( - field.id === CROWDSTRIKE_AGENT_ID_FIELD && - !isAlertFromCrowdstrikeEvent({ data: dataFormattedForFieldBrowser }) + isResponseActionsAlertAgentIdField(field.id) && + responseActionsSupport.details.agentIdField !== field.id ) { return acc; } diff --git a/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/fields/endpoint_policy_fields.tsx b/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/fields/endpoint_policy_fields.tsx index 7334cf9d7e8f6..1b8897c73271b 100644 --- a/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/fields/endpoint_policy_fields.tsx +++ b/x-pack/plugins/security_solution/public/flyout/entity_details/host_right/fields/endpoint_policy_fields.tsx @@ -10,7 +10,7 @@ import { EuiHealth } from '@elastic/eui'; import type { EntityTableRows } from '../../shared/components/entity_table/types'; import type { ObservedEntityData } from '../../shared/components/observed_entity/types'; -import { EndpointAgentStatus } from '../../../../common/components/agents/agent_status'; +import { EndpointAgentStatus } from '../../../../common/components/endpoint/agents/agent_status'; import { getEmptyTagValue } from '../../../../common/components/empty_value'; import type { HostItem } from '../../../../../common/search_strategy'; import { HostPolicyResponseActionStatus } from '../../../../../common/search_strategy'; diff --git a/x-pack/plugins/security_solution/public/management/components/effected_policy_select/utils.ts b/x-pack/plugins/security_solution/public/management/components/effected_policy_select/utils.ts deleted file mode 100644 index 7e5e27e90db7e..0000000000000 --- a/x-pack/plugins/security_solution/public/management/components/effected_policy_select/utils.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { PolicyData } from '../../../../common/endpoint/types'; -import type { EffectedPolicySelection } from './effected_policy_select'; -import { GLOBAL_ARTIFACT_TAG } from '../../../../common/endpoint/service/artifacts/constants'; - -/** - * Given a list of artifact tags, returns the tags that are not policy tags - * policy tags follow the format: `policy:id` - */ -export function getArtifactTagsWithoutPolicies(tags?: string[]): string[] { - return tags?.filter((tag) => !tag.startsWith('policy:')) || []; -} - -/** - * Return a list of artifact policy tags based on a current - * selection by the EffectedPolicySelection component. - */ -export function getArtifactTagsByEffectedPolicySelection( - selection: EffectedPolicySelection, - otherTags: string[] = [] -): string[] { - if (selection.isGlobal) { - return [GLOBAL_ARTIFACT_TAG, ...otherTags]; - } - const newTags = selection.selected.map((policy) => { - return `policy:${policy.id}`; - }); - - return newTags.concat(otherTags); -} - -/** - * Given a list of an Exception item tags it will return - * the parsed policies from it. - * - * Policy tags follow the pattern `policy:id` - * non policy tags will be ignored. - */ -export function getEffectedPolicySelectionByTags( - tags: string[], - policies: PolicyData[] -): EffectedPolicySelection { - if (tags.find((tag) => tag === GLOBAL_ARTIFACT_TAG)) { - return { - isGlobal: true, - selected: [], - }; - } - const selected: PolicyData[] = tags.reduce((acc, tag) => { - // edge case: a left over tag with a non-existed policy - // will be removed by veryfing the policy exists - const id = tag.split(':')[1]; - const foundPolicy = policies.find((policy) => policy.id === id); - if (foundPolicy !== undefined) { - acc.push(foundPolicy); - } - return acc; - }, [] as PolicyData[]); - - return { - isGlobal: false, - selected, - }; -} - -export function isGlobalPolicyEffected(tags?: string[]): boolean { - return tags !== undefined && tags.find((tag) => tag === GLOBAL_ARTIFACT_TAG) !== undefined; -} diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/status_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/status_action.tsx index 3f4b46b318b82..7ea0986a3e721 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/status_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/status_action.tsx @@ -19,7 +19,7 @@ import type { CommandExecutionComponentProps } from '../../console/types'; import { FormattedError } from '../../formatted_error'; import { ConsoleCodeBlock } from '../../console/components/console_code_block'; import { POLICY_STATUS_TO_TEXT } from '../../../pages/endpoint_hosts/view/host_constants'; -import { getAgentStatusText } from '../../../../common/components/agents/agent_status_text'; +import { getAgentStatusText } from '../../../../common/components/endpoint/agents/agent_status_text'; export const EndpointStatusActionResult = memo< CommandExecutionComponentProps< diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_info/agent_info/agent_info.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_info/agent_info/agent_info.tsx index 4c0bf3ca511a2..7845b9d8b3efd 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_info/agent_info/agent_info.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_info/agent_info/agent_info.tsx @@ -6,7 +6,7 @@ */ import React, { memo } from 'react'; -import { AgentStatus } from '../../../../../../common/components/agents/agent_status'; +import { AgentStatus } from '../../../../../../common/components/endpoint/agents/agent_status'; import { useAgentStatusHook } from '../../../../../hooks/agents/use_get_agent_status'; import type { ThirdPartyAgentInfo } from '../../../../../../../common/types'; import { HeaderAgentInfo } from '../header_agent_info'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_info/endpoint/header_endpoint_info.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_info/endpoint/header_endpoint_info.tsx index e9d73b3d62d63..28ac70e7c969d 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_info/endpoint/header_endpoint_info.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_info/endpoint/header_endpoint_info.tsx @@ -7,7 +7,7 @@ import React, { memo } from 'react'; import { EuiSkeletonText } from '@elastic/eui'; -import { EndpointAgentStatus } from '../../../../../../common/components/agents/agent_status'; +import { EndpointAgentStatus } from '../../../../../../common/components/endpoint/agents/agent_status'; import { HeaderAgentInfo } from '../header_agent_info'; import { useGetEndpointDetails } from '../../../../../hooks'; import type { Platform } from '../platforms'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_info/sentinel_one/header_sentinel_one_info.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_info/sentinel_one/header_sentinel_one_info.tsx index 212f66b6fe6e6..72a155833f967 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_info/sentinel_one/header_sentinel_one_info.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/header_info/sentinel_one/header_sentinel_one_info.tsx @@ -6,7 +6,7 @@ */ import React, { memo } from 'react'; -import { AgentStatus } from '../../../../../../common/components/agents/agent_status'; +import { AgentStatus } from '../../../../../../common/components/endpoint/agents/agent_status'; import { useAgentStatusHook } from '../../../../../hooks/agents/use_get_agent_status'; import { useIsExperimentalFeatureEnabled } from '../../../../../../common/hooks/use_experimental_features'; import type { ThirdPartyAgentInfo } from '../../../../../../../common/types'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/offline_callout.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/offline_callout.tsx index d18a8526fb3eb..727d30a6c75b4 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/offline_callout.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/components/offline_callout.tsx @@ -9,6 +9,7 @@ import React, { memo, useMemo } from 'react'; import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; +import { isAgentTypeAndActionSupported } from '../../../../common/lib/endpoint'; import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; import { useAgentStatusHook } from '../../../hooks/agents/use_get_agent_status'; import type { ResponseActionAgentType } from '../../../../../common/endpoint/service/response_actions/constants'; @@ -27,16 +28,10 @@ export const OfflineCallout = memo(({ agentType, endpointId const isCrowdstrikeAgent = agentType === 'crowdstrike'; const getAgentStatus = useAgentStatusHook(); const agentStatusClientEnabled = useIsExperimentalFeatureEnabled('agentStatusClientEnabled'); - const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled( - 'responseActionsSentinelOneV1Enabled' - ); - const sentinelOneManualHostActionsEnabled = useIsExperimentalFeatureEnabled( - 'sentinelOneManualHostActionsEnabled' - ); - const crowdstrikeManualHostActionsEnabled = useIsExperimentalFeatureEnabled( - 'responseActionsCrowdstrikeManualHostIsolationEnabled' - ); + const isAgentTypeEnabled = useMemo(() => { + return isAgentTypeAndActionSupported(agentType); + }, [agentType]); const { data: endpointDetails } = useGetEndpointDetails(endpointId, { refetchInterval: 10000, @@ -45,9 +40,7 @@ export const OfflineCallout = memo(({ agentType, endpointId const { data } = getAgentStatus([endpointId], agentType, { enabled: - (sentinelOneManualHostActionsEnabled && isSentinelOneAgent) || - (crowdstrikeManualHostActionsEnabled && isCrowdstrikeAgent) || - (isEndpointAgent && agentStatusClientEnabled), + (isEndpointAgent && agentStatusClientEnabled) || (!isEndpointAgent && isAgentTypeEnabled), }); const showOfflineCallout = useMemo( () => @@ -64,11 +57,7 @@ export const OfflineCallout = memo(({ agentType, endpointId ] ); - if ( - (isEndpointAgent && !endpointDetails) || - (isSentinelOneV1Enabled && isSentinelOneAgent && !data) || - (crowdstrikeManualHostActionsEnabled && isCrowdstrikeAgent && !data) - ) { + if ((isEndpointAgent && !endpointDetails) || (isAgentTypeEnabled && !data)) { return null; } diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/console_commands_definition.ts b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/console_commands_definition.ts index 22459383fde8f..84624c2ce595b 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/console_commands_definition.ts +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/console_commands_definition.ts @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import { isActionSupportedByAgentType } from '../../../../../common/endpoint/service/response_actions/is_response_action_supported'; +import { isAgentTypeAndActionSupported } from '../../../../common/lib/endpoint'; import { getRbacControl } from '../../../../../common/endpoint/service/response_actions/utils'; import { UploadActionResult } from '../command_render_components/upload_action'; import { ArgumentFileSelector } from '../../console_argument_selectors'; @@ -139,6 +139,7 @@ const COMMENT_ARG_ABOUT = i18n.translate( export interface GetEndpointConsoleCommandsOptions { endpointAgentId: string; agentType: ResponseActionAgentType; + /** Applicable only for Endpoint Agents */ endpointCapabilities: ImmutableArray; endpointPrivileges: EndpointPrivileges; } @@ -556,43 +557,30 @@ export const getEndpointConsoleCommands = ({ } }; +/** @private */ +const disableCommand = (command: CommandDefinition, agentType: ResponseActionAgentType) => { + command.helpDisabled = true; + command.helpHidden = true; + command.validate = () => + UPGRADE_AGENT_FOR_RESPONDER(agentType, command.name as ConsoleResponseActionCommands); +}; + /** @private */ const adjustCommandsForSentinelOne = ({ commandList, }: { commandList: CommandDefinition[]; }): CommandDefinition[] => { - const featureFlags = ExperimentalFeaturesService.get(); - const isHostIsolationEnabled = featureFlags.responseActionsSentinelOneV1Enabled; - const isGetFileFeatureEnabled = featureFlags.responseActionsSentinelOneGetFileEnabled; - - const disableCommand = (command: CommandDefinition) => { - command.helpDisabled = true; - command.helpHidden = true; - command.validate = () => - UPGRADE_AGENT_FOR_RESPONDER('sentinel_one', command.name as ConsoleResponseActionCommands); - }; - return commandList.map((command) => { - const agentSupportsResponseAction = - command.name === 'status' - ? false - : isActionSupportedByAgentType( - 'sentinel_one', - RESPONSE_CONSOLE_COMMAND_TO_API_COMMAND_MAP[ - command.name as ConsoleResponseActionCommands - ], - 'manual' - ); - - // If command is not supported by SentinelOne - disable it if ( - !agentSupportsResponseAction || - (command.name === 'get-file' && !isGetFileFeatureEnabled) || - (command.name === 'isolate' && !isHostIsolationEnabled) || - (command.name === 'release' && !isHostIsolationEnabled) + command.name === 'status' || + !isAgentTypeAndActionSupported( + 'sentinel_one', + RESPONSE_CONSOLE_COMMAND_TO_API_COMMAND_MAP[command.name as ConsoleResponseActionCommands], + 'manual' + ) ) { - disableCommand(command); + disableCommand(command, 'sentinel_one'); } return command; @@ -605,35 +593,16 @@ const adjustCommandsForCrowdstrike = ({ }: { commandList: CommandDefinition[]; }): CommandDefinition[] => { - const featureFlags = ExperimentalFeaturesService.get(); - const isHostIsolationEnabled = featureFlags.responseActionsCrowdstrikeManualHostIsolationEnabled; - - const disableCommand = (command: CommandDefinition) => { - command.helpDisabled = true; - command.helpHidden = true; - command.validate = () => - UPGRADE_AGENT_FOR_RESPONDER('crowdstrike', command.name as ConsoleResponseActionCommands); - }; - return commandList.map((command) => { - const agentSupportsResponseAction = - command.name === 'status' - ? false - : isActionSupportedByAgentType( - 'crowdstrike', - RESPONSE_CONSOLE_COMMAND_TO_API_COMMAND_MAP[ - command.name as ConsoleResponseActionCommands - ], - 'manual' - ); - - // If command is not supported by Crowdstrike - disable it if ( - !agentSupportsResponseAction || - (command.name === 'isolate' && !isHostIsolationEnabled) || - (command.name === 'release' && !isHostIsolationEnabled) + command.name === 'status' || + !isAgentTypeAndActionSupported( + 'crowdstrike', + RESPONSE_CONSOLE_COMMAND_TO_API_COMMAND_MAP[command.name as ConsoleResponseActionCommands], + 'manual' + ) ) { - disableCommand(command); + disableCommand(command, 'crowdstrike'); } return command; 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 4c6b2ceec4e5e..20bfa44d0f8f9 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,8 +20,7 @@ import { createEndpointHost } from '../../tasks/create_endpoint_host'; import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; -// Failing: See https://github.com/elastic/kibana/issues/168427 -describe.skip( +describe( 'Automated Response Actions', { tags: ['@ess', '@serverless'], @@ -74,8 +73,7 @@ describe.skip( login(); }); - // FLAKY: https://github.com/elastic/kibana/issues/168340 - describe.skip('From alerts', () => { + describe('From alerts', () => { let ruleId: string; let ruleName: string; diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_list/endpoints.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_list/endpoints.cy.ts index e1a291cc19a25..217b13f96f934 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_list/endpoints.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_list/endpoints.cy.ts @@ -28,9 +28,7 @@ import { createEndpointHost } from '../../tasks/create_endpoint_host'; import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data'; import { enableAllPolicyProtections } from '../../tasks/endpoint_policy'; -// Failing: See https://github.com/elastic/kibana/issues/168719 -// FLAKY: https://github.com/elastic/kibana/issues/168284 -describe.skip('Endpoints page', { tags: ['@ess', '@serverless'] }, () => { +describe('Endpoints page', { tags: ['@ess', '@serverless'] }, () => { let indexedPolicy: IndexedFleetEndpointPolicyResponse; let policy: PolicyData; let createdHost: CreateAndEnrollEndpointHostResponse; 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 b574ca98d5f44..042031b301185 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,14 +21,12 @@ import { enableAllPolicyProtections } from '../../../tasks/endpoint_policy'; import { createEndpointHost } from '../../../tasks/create_endpoint_host'; import { deleteAllLoadedEndpointData } from '../../../tasks/delete_all_endpoint_data'; -// Failing: See https://github.com/elastic/kibana/issues/171444 -describe.skip('Response console', { tags: ['@ess', '@serverless'] }, () => { +describe('Response console', { tags: ['@ess', '@serverless'] }, () => { beforeEach(() => { login(); }); - // FLAKY: https://github.com/elastic/kibana/issues/170373 - describe.skip('Execute operations:', () => { + describe('Execute operations:', () => { const homeFilePath = process.env.CI || true ? '/home/vagrant' : `/home/ubuntu`; let indexedPolicy: IndexedFleetEndpointPolicyResponse; diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/isolate.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/isolate.cy.ts index da54bc85acb68..f89f2a6f62ecf 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/isolate.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/isolate.cy.ts @@ -26,8 +26,7 @@ import { enableAllPolicyProtections } from '../../../tasks/endpoint_policy'; import { createEndpointHost } from '../../../tasks/create_endpoint_host'; import { deleteAllLoadedEndpointData } from '../../../tasks/delete_all_endpoint_data'; -// Failing: See https://github.com/elastic/kibana/issues/173465 -describe.skip('Response console', { tags: ['@ess', '@serverless'] }, () => { +describe('Response console', { tags: ['@ess', '@serverless'] }, () => { let indexedPolicy: IndexedFleetEndpointPolicyResponse; let policy: PolicyData; let createdHost: CreateAndEnrollEndpointHostResponse; @@ -62,8 +61,7 @@ describe.skip('Response console', { tags: ['@ess', '@serverless'] }, () => { } }); - // FLAKY: https://github.com/elastic/kibana/issues/173464 - describe.skip('Host Isolation:', () => { + describe('Host Isolation:', () => { beforeEach(() => { login(); }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/scan.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/scan.cy.ts index f05e0581332cf..59c6916f23fff 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/scan.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_console/scan.cy.ts @@ -33,7 +33,7 @@ describe( ], }, }, - tags: ['@ess', '@serverless'], + tags: ['@ess', '@serverless', '@skipInServerlessMKI'], }, () => { beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/index.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/index.tsx index fc345f1820a30..5de39e0a087d3 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/artifacts/index.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/index.tsx @@ -13,3 +13,4 @@ export { useDeleteArtifact } from './use_delete_artifact'; export { useSummaryArtifact } from './use_summary_artifact'; export { useBulkUpdateArtifact } from './use_bulk_update_artifact'; export { useBulkDeleteArtifact } from './use_bulk_delete_artifact'; +export { useGetUpdatedTags } from './use_get_updated_tags'; diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_get_updated_tags.test.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_get_updated_tags.test.tsx new file mode 100644 index 0000000000000..0fb481a489c2f --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_get_updated_tags.test.tsx @@ -0,0 +1,195 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useGetUpdatedTags } from '.'; +import { renderHook } from '@testing-library/react-hooks'; +import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import type { TagFilter } from '../../../../common/endpoint/service/artifacts/utils'; + +describe('useGetUpdatedTags hook', () => { + const firstFilter: TagFilter = (tag) => tag.startsWith('first:'); + const secondFilter: TagFilter = (tag) => tag.startsWith('second:') || tag === 'special_second'; + const thirdFilter: TagFilter = (tag) => tag.startsWith('third:'); + + const getFiltersInOrder = () => ({ + first: firstFilter, + second: secondFilter, + third: thirdFilter, + }); + + const getExampleException = (): Partial => ({ + tags: [ + 'first:mozzarella', + 'first:roquefort', + + 'second:cabernet', + 'second:shiraz', + 'special_second', + + 'third:tagliatelle', + 'third:penne', + ], + }); + + describe('getTagsUpdatedBy', () => { + describe('when `tags` is undefined', () => { + it('should return empty array when the update is empty', () => { + const { result } = renderHook(() => useGetUpdatedTags({}, getFiltersInOrder())); + + expect(result.current.getTagsUpdatedBy('second', [])).toStrictEqual([]); + }); + + it('should return new array with the update', () => { + const { result } = renderHook(() => useGetUpdatedTags({}, getFiltersInOrder())); + + expect(result.current.getTagsUpdatedBy('second', ['special_second'])).toStrictEqual([ + 'special_second', + ]); + }); + }); + + describe('when removing tags from a category', () => { + it('should be able to remove a tag category from the start', () => { + const { result } = renderHook(() => + useGetUpdatedTags(getExampleException(), getFiltersInOrder()) + ); + + expect(result.current.getTagsUpdatedBy('first', [])).toStrictEqual([ + 'second:cabernet', + 'second:shiraz', + 'special_second', + + 'third:tagliatelle', + 'third:penne', + ]); + }); + + it('should be able to remove a tag category from the middle', () => { + const { result } = renderHook(() => + useGetUpdatedTags(getExampleException(), getFiltersInOrder()) + ); + + expect(result.current.getTagsUpdatedBy('second', [])).toStrictEqual([ + 'first:mozzarella', + 'first:roquefort', + + 'third:tagliatelle', + 'third:penne', + ]); + }); + + it('should be able to remove a tag category from the end', () => { + const { result } = renderHook(() => + useGetUpdatedTags(getExampleException(), getFiltersInOrder()) + ); + + expect(result.current.getTagsUpdatedBy('third', [])).toStrictEqual([ + 'first:mozzarella', + 'first:roquefort', + + 'second:cabernet', + 'second:shiraz', + 'special_second', + ]); + }); + + it('should be able to remove all tags category by category and keeping the original order', () => { + const { result, rerender } = renderHook( + ({ exception, filters }) => useGetUpdatedTags(exception, filters), + { initialProps: { exception: getExampleException(), filters: getFiltersInOrder() } } + ); + + let tags = result.current.getTagsUpdatedBy('first', []); + expect(tags).toStrictEqual([ + 'second:cabernet', + 'second:shiraz', + 'special_second', + + 'third:tagliatelle', + 'third:penne', + ]); + + rerender({ exception: { tags }, filters: getFiltersInOrder() }); + tags = result.current.getTagsUpdatedBy('second', []); + expect(tags).toStrictEqual(['third:tagliatelle', 'third:penne']); + + rerender({ exception: { tags }, filters: getFiltersInOrder() }); + tags = result.current.getTagsUpdatedBy('third', []); + expect(tags).toStrictEqual([]); + }); + }); + + it('should keep original order of categories after multiple updates', () => { + let tags: string[] = []; + const { result, rerender } = renderHook( + ({ exception, filters }) => useGetUpdatedTags(exception, filters), + { initialProps: { exception: { tags }, filters: getFiltersInOrder() } } + ); + + // add middle + tags = result.current.getTagsUpdatedBy('second', ['special_second']); + expect(tags).toStrictEqual(['special_second']); + + // add last + rerender({ exception: { tags }, filters: getFiltersInOrder() }); + tags = result.current.getTagsUpdatedBy('third', ['third:spaghetti']); + expect(tags).toStrictEqual(['special_second', 'third:spaghetti']); + + // add first + rerender({ exception: { tags }, filters: getFiltersInOrder() }); + tags = result.current.getTagsUpdatedBy('first', ['first:brie']); + expect(tags).toStrictEqual(['first:brie', 'special_second', 'third:spaghetti']); + }); + + it('should update category order on any change if filter is changed (although it should not)', () => { + const { result, rerender } = renderHook( + ({ exception, filters }) => useGetUpdatedTags(exception, filters), + { initialProps: { exception: getExampleException(), filters: getFiltersInOrder() } } + ); + + let tags = result.current.getTagsUpdatedBy('second', ['second:shiraz']); + expect(tags).toStrictEqual([ + 'first:mozzarella', + 'first:roquefort', + + 'second:shiraz', + + 'third:tagliatelle', + 'third:penne', + ]); + + const newFilterOrder = { + third: thirdFilter, + first: firstFilter, + second: secondFilter, + }; + + rerender({ exception: { tags }, filters: newFilterOrder }); + tags = result.current.getTagsUpdatedBy('third', ['third:spaghetti']); + + expect(tags).toStrictEqual([ + 'third:spaghetti', + + 'first:mozzarella', + 'first:roquefort', + + 'second:shiraz', + ]); + }); + + it('should not mutate input parameters', () => { + const inputFilters = getFiltersInOrder(); + const inputException = getExampleException(); + + const { result } = renderHook(() => useGetUpdatedTags(inputException, inputFilters)); + result.current.getTagsUpdatedBy('third', []); + + expect(inputFilters).toStrictEqual(getFiltersInOrder()); + expect(inputException).toStrictEqual(getExampleException()); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_get_updated_tags.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_get_updated_tags.tsx new file mode 100644 index 0000000000000..0c651304c72d1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_get_updated_tags.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { useCallback } from 'react'; +import type { TagFilter } from '../../../../common/endpoint/service/artifacts/utils'; + +type TagFiltersType = { + [tagCategory in string]: TagFilter; +}; + +type GetTagsUpdatedBy = (tagsToUpdate: keyof TagFilters, newTags: string[]) => string[]; + +/** + * A hook to be used to generate a new `tags` array that contains multiple 'categories' of tags, + * e.g. policy assignment, some special settings, in a desired order. + * + * The hook excepts a `filter` object that contain a simple filter function for every tag + * category. The `filter` object should contain the filters in the same ORDER as the categories + * should appear in the `tags` array. + * + * ``` + * const FILTERS_IN_ORDER = { // preferably defined out of the component + * first: (tag) => tag.startsWith('1:'), + * second: (tag) => tag.startsWith('2:'), + * } + * ... + * const { getTagsUpdatedBy } = useGetUpdatedTags(exception, FILTERS_IN_ORDER) + * ``` + * + * The returned `getTagsUpdatedBy()` function can be used in event handlers of the given tag category + * without affecting other tags. + * ``` + * const newTags = getTagsUpdatedBy('second', ['2:new-tag-1', ...]) + * ``` + * + * @param exception + * @param filters + * @returns `getTagsUpdatedBy(tagCategory, ['new', 'tags'])` + */ +export const useGetUpdatedTags = ( + exception: Partial>, + filters: TagFilters +): { + getTagsUpdatedBy: GetTagsUpdatedBy; +} => { + const getTagsUpdatedBy: GetTagsUpdatedBy = useCallback( + (tagsToUpdate, newTags) => { + const tagCategories = Object.keys(filters); + + const arrayOfTagArrays: string[][] = tagCategories.map((category) => { + if (tagsToUpdate === category) { + return newTags; + } + + return (exception.tags ?? []).filter(filters[category]); + }); + + return arrayOfTagArrays.flat(); + }, + [exception, filters] + ); + + return { + /** + * @param tagsToUpdate The category of the tags to update, keys of the filter object. + * @param newTags + * @return a new tags array + */ + getTagsUpdatedBy, + }; +}; diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_get_endpoint_pending_actions_summary.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_get_endpoint_pending_actions_summary.ts index cee29041b0354..57f0150317f5d 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_get_endpoint_pending_actions_summary.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_get_endpoint_pending_actions_summary.ts @@ -9,7 +9,7 @@ import type { QueryObserverResult, UseQueryOptions } from '@tanstack/react-query import { useQuery } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; import type { PendingActionsResponse } from '../../../../common/endpoint/types'; -import { fetchPendingActionsByAgentId } from '../../../common/lib/endpoint_pending_actions'; +import { fetchPendingActionsByAgentId } from '../../../common/lib/endpoint/endpoint_pending_actions'; /** * Retrieves the pending actions against the given Endpoint `agent.id`'s diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_isolate_endpoint_request.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_isolate_endpoint_request.ts index b0fb5029ab15d..b4cb131f6ae9c 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_isolate_endpoint_request.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_isolate_endpoint_request.ts @@ -8,7 +8,7 @@ import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; import type { IHttpFetchError } from '@kbn/core-http-browser'; -import { isolateHost } from '../../../common/lib/endpoint_isolation'; +import { isolateHost } from '../../../common/lib/endpoint/endpoint_isolation'; import type { HostIsolationRequestBody, ResponseActionApiResponse, diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_release_endpoint_request.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_release_endpoint_request.ts index f6d45393ae885..b9e140dc3ba9d 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_release_endpoint_request.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_release_endpoint_request.ts @@ -12,7 +12,7 @@ import type { HostIsolationRequestBody, ResponseActionApiResponse, } from '../../../../common/endpoint/types'; -import { unIsolateHost } from '../../../common/lib/endpoint_isolation'; +import { unIsolateHost } from '../../../common/lib/endpoint/endpoint_isolation'; /** * Create host release requests diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index 91bf4e958f6fb..21a5fc69ca1d9 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -22,6 +22,7 @@ import { EVENT_FILTERS_PATH, HOST_ISOLATION_EXCEPTIONS_PATH, MANAGE_PATH, + NOTES_MANAGEMENT_PATH, POLICIES_PATH, RESPONSE_ACTIONS_HISTORY_PATH, SecurityPageName, @@ -39,6 +40,7 @@ import { TRUSTED_APPLICATIONS, ENTITY_ANALYTICS_RISK_SCORE, ASSET_CRITICALITY, + NOTES, } from '../app/translations'; import { licenseService } from '../common/hooks/use_license'; import type { LinkItem } from '../common/links/types'; @@ -85,6 +87,12 @@ const categories = [ }), linkIds: [SecurityPageName.cloudDefendPolicies], }, + { + label: i18n.translate('xpack.securitySolution.appLinks.category.investigations', { + defaultMessage: 'Investigations', + }), + linkIds: [SecurityPageName.notesManagement], + }, ]; export const links: LinkItem = { @@ -215,6 +223,18 @@ export const links: LinkItem = { hideTimeline: true, }, cloudDefendLink, + { + id: SecurityPageName.notesManagement, + title: NOTES, + description: i18n.translate('xpack.securitySolution.appLinks.notesManagementDescription', { + defaultMessage: 'Visualize and delete notes.', + }), + landingIcon: IconTool, // TODO get new icon + path: NOTES_MANAGEMENT_PATH, + skipUrlState: true, + hideTimeline: true, + experimentalKey: 'notesEnabled', + }, ], }; diff --git a/x-pack/plugins/security_solution/public/management/pages/blocklist/view/components/blocklist_form.tsx b/x-pack/plugins/security_solution/public/management/pages/blocklist/view/components/blocklist_form.tsx index f819a55690563..ee59fd26714e3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/blocklist/view/components/blocklist_form.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/blocklist/view/components/blocklist_form.tsx @@ -26,7 +26,6 @@ import { import type { BlocklistConditionEntryField } from '@kbn/securitysolution-utils'; import { OperatingSystem, isPathValid } from '@kbn/securitysolution-utils'; import { isOneOfOperator } from '@kbn/securitysolution-list-utils'; -import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import { uniq } from 'lodash'; import { OS_TITLES } from '../../../../common/translations'; @@ -50,15 +49,13 @@ import { } from '../../translations'; import type { EffectedPolicySelection } from '../../../../components/effected_policy_select'; import { EffectedPolicySelect } from '../../../../components/effected_policy_select'; -import { - GLOBAL_ARTIFACT_TAG, - BY_POLICY_ARTIFACT_TAG_PREFIX, -} from '../../../../../../common/endpoint/service/artifacts/constants'; import { useLicense } from '../../../../../common/hooks/use_license'; import { isValidHash } from '../../../../../../common/endpoint/service/artifacts/validations'; -import { isArtifactGlobal } from '../../../../../../common/endpoint/service/artifacts'; +import { + getArtifactTagsByPolicySelection, + isArtifactGlobal, +} from '../../../../../../common/endpoint/service/artifacts'; import type { PolicyData } from '../../../../../../common/endpoint/types'; -import { isGlobalPolicyEffected } from '../../../../components/effected_policy_select/utils'; import { useTestIdGenerator } from '../../../../hooks/use_test_id_generator'; const testIdPrefix = 'blocklist-form'; @@ -111,8 +108,8 @@ export const BlockListForm = memo( const errorsRef = useRef({ name: {}, value: {} }); const [selectedPolicies, setSelectedPolicies] = useState([]); const isPlatinumPlus = useLicense().isPlatinumPlus(); - const isGlobal = useMemo(() => isArtifactGlobal(item as ExceptionListItemSchema), [item]); - const [wasByPolicy, setWasByPolicy] = useState(!isGlobalPolicyEffected(item.tags)); + const isGlobal = useMemo(() => isArtifactGlobal(item), [item]); + const [wasByPolicy, setWasByPolicy] = useState(!isArtifactGlobal(item)); const [hasFormChanged, setHasFormChanged] = useState(false); const showAssignmentSection = useMemo(() => { @@ -125,7 +122,7 @@ export const BlockListForm = memo( // set initial state of `wasByPolicy` that checks if the initial state of the exception was by policy or not useEffect(() => { if (!hasFormChanged && item.tags) { - setWasByPolicy(!isGlobalPolicyEffected(item.tags)); + setWasByPolicy(!isArtifactGlobal({ tags: item.tags })); } }, [item.tags, hasFormChanged]); @@ -422,9 +419,7 @@ export const BlockListForm = memo( const handleOnPolicyChange = useCallback( (change: EffectedPolicySelection) => { - const tags = change.isGlobal - ? [GLOBAL_ARTIFACT_TAG] - : change.selected.map((policy) => `${BY_POLICY_ARTIFACT_TAG_PREFIX}${policy.id}`); + const tags = getArtifactTagsByPolicySelection(change); const nextItem = { ...item, tags }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts index 7030f13bdd0f9..c3cc8adc3b4fe 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts @@ -23,8 +23,8 @@ import { HOST_METADATA_LIST_ROUTE, METADATA_TRANSFORMS_STATUS_ROUTE, } from '../../../../common/endpoint/constants'; -import type { PendingActionsHttpMockInterface } from '../../../common/lib/endpoint_pending_actions/mocks'; -import { pendingActionsHttpMock } from '../../../common/lib/endpoint_pending_actions/mocks'; +import type { PendingActionsHttpMockInterface } from '../../../common/lib/endpoint/endpoint_pending_actions/mocks'; +import { pendingActionsHttpMock } from '../../../common/lib/endpoint/endpoint_pending_actions/mocks'; import { TRANSFORM_STATES } from '../../../../common/constants'; import type { TransformStatsResponse } from './types'; import type { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts index ca1b0dfb56fea..763bc15dace43 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts @@ -39,7 +39,7 @@ import { hostIsolationHttpMocks, hostIsolationRequestBodyMock, hostIsolationResponseMock, -} from '../../../../common/lib/endpoint_isolation/mocks'; +} from '../../../../common/lib/endpoint/endpoint_isolation/mocks'; import { endpointPageHttpMock, failedTransformStateMock } from '../mocks'; import { HOST_METADATA_LIST_ROUTE } from '../../../../../common/endpoint/constants'; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index f8ffb703fb72e..215a789a8b4ed 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -29,8 +29,8 @@ import type { MetadataListResponse, ResponseActionApiResponse, } from '../../../../../common/endpoint/types'; -import { isolateHost, unIsolateHost } from '../../../../common/lib/endpoint_isolation'; -import { fetchPendingActionsByAgentId } from '../../../../common/lib/endpoint_pending_actions'; +import { isolateHost, unIsolateHost } from '../../../../common/lib/endpoint/endpoint_isolation'; +import { fetchPendingActionsByAgentId } from '../../../../common/lib/endpoint/endpoint_pending_actions'; import type { ImmutableMiddlewareAPI, ImmutableMiddlewareFactory } from '../../../../common/store'; import type { AppAction } from '../../../../common/store/actions'; import { sendGetEndpointSpecificPackagePolicies } from '../../../services/policies/policies'; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts index 1bc156d0c5a37..a851d2273907d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts @@ -29,7 +29,7 @@ import { INGEST_API_PACKAGE_POLICIES, } from '../../../services/policies/ingest'; import type { GetPolicyListResponse } from '../../policy/types'; -import { pendingActionsResponseMock } from '../../../../common/lib/endpoint_pending_actions/mocks'; +import { pendingActionsResponseMock } from '../../../../common/lib/endpoint/endpoint_pending_actions/mocks'; import { ACTION_STATUS_ROUTE, ENDPOINT_DEFAULT_SORT_DIRECTION, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx index 60ccec200d3c8..0140cba0780ed 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details_content.tsx @@ -21,7 +21,7 @@ import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use import { AgentStatus, EndpointAgentStatus, -} from '../../../../../common/components/agents/agent_status'; +} from '../../../../../common/components/endpoint/agents/agent_status'; import { isPolicyOutOfDate } from '../../utils'; import type { HostInfo } from '../../../../../../common/endpoint/types'; import { useEndpointSelector } from '../hooks'; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx index 75aeaf45a9a8a..abda617e2a779 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx @@ -20,7 +20,6 @@ import { agentPolicies, uiQueryParams } from '../../store/selectors'; import { useAppUrl } from '../../../../../common/lib/kibana/hooks'; import type { ContextMenuItemNavByRouterProps } from '../../../../components/context_menu_with_router_support/context_menu_item_nav_by_router'; import { isEndpointHostIsolated } from '../../../../../common/utils/validators'; -import { isIsolationSupported } from '../../../../../../common/endpoint/service/host_isolation/utils'; interface Options { isEndpointList: boolean; @@ -59,11 +58,6 @@ export const useEndpointActionItems = ( const endpointPolicyId = endpointMetadata.Endpoint.policy.applied.id; const endpointHostName = endpointMetadata.host.hostname; const fleetAgentId = endpointMetadata.elastic.agent.id; - const isolationSupported = isIsolationSupported({ - osName: endpointMetadata.host.os.name, - version: endpointMetadata.agent.version, - capabilities: endpointMetadata.Endpoint.capabilities, - }); const { show, selected_endpoint: _selectedEndpoint, ...currentUrlParams } = allCurrentUrlParams; const endpointActionsPath = getEndpointDetailsPath({ name: 'endpointActivityLog', @@ -101,7 +95,7 @@ export const useEndpointActionItems = ( /> ), }); - } else if (isolationSupported && canIsolateHost) { + } else if (canIsolateHost) { // For Platinum++ licenses, users also have ability to isolate isolationActions.push({ 'data-test-subj': 'isolateLink', diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index 06029b8a64668..ceb9f1ce10e86 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -25,7 +25,7 @@ import { POLICY_STATUS_TO_HEALTH_COLOR, POLICY_STATUS_TO_TEXT } from './host_con import { mockPolicyResultList } from '../../policy/store/test_mock_utils'; import { getEndpointDetailsPath } from '../../../common/routing'; import { KibanaServices, useKibana, useToasts, useUiSetting$ } from '../../../../common/lib/kibana'; -import { hostIsolationHttpMocks } from '../../../../common/lib/endpoint_isolation/mocks'; +import { hostIsolationHttpMocks } from '../../../../common/lib/endpoint/endpoint_isolation/mocks'; import { isFailedResourceState, isLoadedResourceState, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index 440ad6a7560a6..89473ece661a3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -39,7 +39,7 @@ import { EndpointListNavLink } from './components/endpoint_list_nav_link'; import { AgentStatus, EndpointAgentStatus, -} from '../../../../common/components/agents/agent_status'; +} from '../../../../common/components/endpoint/agents/agent_status'; import { EndpointDetailsFlyout } from './details'; import * as selectors from '../store/selectors'; import { getEndpointPendingActionsCallback } from '../store/selectors'; 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 60442644f4c9d..9ea16a071a72b 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 @@ -26,6 +26,11 @@ 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'; +import { + BY_POLICY_ARTIFACT_TAG_PREFIX, + FILTER_PROCESS_DESCENDANTS_TAG, + GLOBAL_ARTIFACT_TAG, +} from '../../../../../../common/endpoint/service/artifacts/constants'; jest.mock('../../../../../common/lib/kibana'); jest.mock('../../../../../common/containers/source'); @@ -88,7 +93,7 @@ describe('Event filter form', () => { os_types: [OperatingSystem.WINDOWS], entries: [createEntry()], type: 'simple', - tags: ['policy:all'], + tags: [GLOBAL_ARTIFACT_TAG, FILTER_PROCESS_DESCENDANTS_TAG], }; return { ...defaults, @@ -333,9 +338,9 @@ describe('Event filter form', () => { // move back to global userEvent.click(renderResult.getByTestId('eventFilters-form-effectedPolicies-global')); - formProps.item.tags = ['policy:all']; + formProps.item.tags = [GLOBAL_ARTIFACT_TAG]; rerenderWithLatestProps(); - expect(formProps.item.tags).toEqual(['policy:all']); + expect(formProps.item.tags).toEqual([GLOBAL_ARTIFACT_TAG]); expect( renderResult.queryByTestId(`${formPrefix}-effectedPolicies-policiesSelectable`) ).toBeFalsy(); @@ -366,7 +371,7 @@ describe('Event filter form', () => { it('should hide assignment section when no license', () => { render(); - formProps.item.tags = ['policy:all']; + formProps.item.tags = [GLOBAL_ARTIFACT_TAG]; rerender(); expect(renderResult.queryByTestId(`${formPrefix}-effectedPolicies`)).toBeNull(); }); @@ -395,12 +400,12 @@ describe('Event filter form', () => { 'eventFilters-form-effectedPolicies-global' ) as HTMLButtonElement; userEvent.click(globalButtonInput); - formProps.item.tags = ['policy:all']; + formProps.item.tags = [GLOBAL_ARTIFACT_TAG]; rerender(); const expected = createOnChangeArgs({ item: { ...formProps.item, - tags: ['policy:all'], + tags: [GLOBAL_ARTIFACT_TAG], }, }); expect(formProps.onChange).toHaveBeenCalledWith(expected); @@ -409,7 +414,134 @@ describe('Event filter form', () => { ? formProps.onChange.mock.calls[0][0].item.tags[0] : ''; - expect(policyItem).toBe('policy:all'); + expect(policyItem).toBe(GLOBAL_ARTIFACT_TAG); + }); + }); + + describe('Filter process descendants', () => { + beforeEach(() => { + mockedContext.setExperimentalFlag({ filterProcessDescendantsForEventFiltersEnabled: true }); + }); + + it('should not display selector when feature flag is disabled', () => { + mockedContext.setExperimentalFlag({ + filterProcessDescendantsForEventFiltersEnabled: false, + }); + render(); + + expect( + renderResult.queryByTestId(`${formPrefix}-filterProcessDescendantsButton`) + ).not.toBeInTheDocument(); + }); + + it('should show `Events` filter selected when tags are missing', () => { + delete formProps.item.tags; + render(); + + expect(renderResult.getByTestId(`${formPrefix}-filterEventsButton`)).toHaveAttribute( + 'aria-pressed', + 'true' + ); + expect( + renderResult.getByTestId(`${formPrefix}-filterProcessDescendantsButton`) + ).toHaveAttribute('aria-pressed', 'false'); + }); + + it('should show `Events` filter selected when filtering process descendants is disabled in config', () => { + formProps.item.tags = []; + render(); + + expect(renderResult.getByTestId(`${formPrefix}-filterEventsButton`)).toHaveAttribute( + 'aria-pressed', + 'true' + ); + expect( + renderResult.getByTestId(`${formPrefix}-filterProcessDescendantsButton`) + ).toHaveAttribute('aria-pressed', 'false'); + }); + + it('should show `Process descendants` filter selected when enabled in config', () => { + formProps.item.tags = [FILTER_PROCESS_DESCENDANTS_TAG]; + render(); + + expect(renderResult.getByTestId(`${formPrefix}-filterEventsButton`)).toHaveAttribute( + 'aria-pressed', + 'false' + ); + expect( + renderResult.getByTestId(`${formPrefix}-filterProcessDescendantsButton`) + ).toHaveAttribute('aria-pressed', 'true'); + }); + + it('should add process tree filtering tag to tags when filtering descendants enabled', () => { + formProps.item.tags = []; + render(); + + userEvent.click(renderResult.getByTestId(`${formPrefix}-filterProcessDescendantsButton`)); + + expect(latestUpdatedItem.tags).toStrictEqual([FILTER_PROCESS_DESCENDANTS_TAG]); + }); + + it('should remove process tree filtering tag from tags when filtering descendants disabled', () => { + formProps.item.tags = [FILTER_PROCESS_DESCENDANTS_TAG]; + render(); + + userEvent.click(renderResult.getByTestId(`${formPrefix}-filterEventsButton`)); + + expect(latestUpdatedItem.tags).toStrictEqual([]); + }); + + it('should add the tag always after policy assignment tags', () => { + formProps.policies = createPolicies(); + const perPolicyTags = formProps.policies.map( + (p) => `${BY_POLICY_ARTIFACT_TAG_PREFIX}${p.id}` + ); + formProps.item.tags = perPolicyTags; + render(); + + userEvent.click(renderResult.getByTestId(`${formPrefix}-filterProcessDescendantsButton`)); + expect(latestUpdatedItem.tags).toStrictEqual([ + ...perPolicyTags, + FILTER_PROCESS_DESCENDANTS_TAG, + ]); + + rerenderWithLatestProps(); + userEvent.click(renderResult.getByTestId(`${formPrefix}-effectedPolicies-global`)); + expect(latestUpdatedItem.tags).toStrictEqual([ + GLOBAL_ARTIFACT_TAG, + FILTER_PROCESS_DESCENDANTS_TAG, + ]); + + rerenderWithLatestProps(); + userEvent.click(renderResult.getByTestId(`${formPrefix}-filterEventsButton`)); + expect(latestUpdatedItem.tags).toStrictEqual([GLOBAL_ARTIFACT_TAG]); + + rerenderWithLatestProps(); + userEvent.click(renderResult.getByTestId(`${formPrefix}-filterProcessDescendantsButton`)); + expect(latestUpdatedItem.tags).toStrictEqual([ + GLOBAL_ARTIFACT_TAG, + FILTER_PROCESS_DESCENDANTS_TAG, + ]); + + rerenderWithLatestProps(); + userEvent.click(renderResult.getByTestId('eventFilters-form-effectedPolicies-perPolicy')); + expect(latestUpdatedItem.tags).toStrictEqual([ + ...perPolicyTags, + FILTER_PROCESS_DESCENDANTS_TAG, + ]); + }); + + it('should display a tooltip to the user', async () => { + const tooltipIconSelector = `${formPrefix}-filterProcessDescendants-tooltipIcon`; + const tooltipTextSelector = `${formPrefix}-filterProcessDescendants-tooltipText`; + render(); + + expect(renderResult.getByTestId(tooltipIconSelector)).toBeInTheDocument(); + expect(renderResult.queryByTestId(tooltipTextSelector)).not.toBeInTheDocument(); + + userEvent.hover(renderResult.getByTestId(tooltipIconSelector)); + + expect(await renderResult.findByTestId(tooltipTextSelector)).toBeInTheDocument(); }); }); 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 ee2ae2d5c6b6e..2413007fd693a 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 @@ -8,10 +8,15 @@ import React, { memo, useMemo, useCallback, useState, useEffect } from 'react'; import { isEqual } from 'lodash'; -import type { EuiSuperSelectOption } from '@elastic/eui'; +import type { EuiButtonGroupOptionProps, EuiSuperSelectOption } from '@elastic/eui'; import { EuiFieldText, EuiSpacer, + EuiFlexGroup, + EuiButtonGroup, + EuiToolTip, + EuiIcon, + useEuiTheme, EuiForm, EuiFormRow, EuiSuperSelect, @@ -21,6 +26,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { css } from '@emotion/react'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import { @@ -33,6 +39,14 @@ import { OperatingSystem } from '@kbn/securitysolution-utils'; import { getExceptionBuilderComponentLazy } from '@kbn/lists-plugin/public'; import type { OnChangeProps } from '@kbn/lists-plugin/public'; import type { ValueSuggestionsGetFn } from '@kbn/unified-search-plugin/public/autocomplete/providers/value_suggestion_provider'; +import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; +import { useGetUpdatedTags } from '../../../../hooks/artifacts'; +import { FILTER_PROCESS_DESCENDANTS_TAG } from '../../../../../../common/endpoint/service/artifacts/constants'; +import { + isFilterProcessDescendantsEnabled, + isFilterProcessDescendantsTag, + isPolicySelectionTag, +} from '../../../../../../common/endpoint/service/artifacts/utils'; import { ENDPOINT_FIELDS_SEARCH_STRATEGY, eventsIndexPattern, @@ -48,8 +62,7 @@ import type { ArtifactFormComponentProps } from '../../../../components/artifact import { isArtifactGlobal, getPolicyIdsFromArtifact, - GLOBAL_ARTIFACT_TAG, - BY_POLICY_ARTIFACT_TAG_PREFIX, + getArtifactTagsByPolicySelection, } from '../../../../../../common/endpoint/service/artifacts'; import { @@ -65,7 +78,6 @@ import { ENDPOINT_EVENT_FILTERS_LIST_ID, EVENT_FILTER_LIST_TYPE } from '../../co import type { EffectedPolicySelection } from '../../../../components/effected_policy_select'; import { EffectedPolicySelect } from '../../../../components/effected_policy_select'; -import { isGlobalPolicyEffected } from '../../../../components/effected_policy_select/utils'; import { ExceptionItemComments } from '../../../../../detection_engine/rule_exceptions/components/item_comments'; import { EventFiltersApiClient } from '../../service/api_client'; import { ShowValueListModal } from '../../../../../value_list/components/show_value_list_modal'; @@ -82,6 +94,12 @@ const osOptions: Array> = OPERATING_SYSTEM inputDisplay: OS_TITLES[os], })); +// Defines the tag categories for Event Filters, using the given order. +const TAG_FILTERS = Object.freeze({ + policySelection: isPolicySelectionTag, + processDescendantsFiltering: isFilterProcessDescendantsTag, +}); + const getAddedFieldsCounts = (formFields: string[]): { [k: string]: number } => formFields.reduce<{ [k: string]: number }>((allFields, field) => { if (field in allFields) { @@ -143,11 +161,8 @@ export const EventFiltersForm: React.FC([]); const isPlatinumPlus = useLicense().isPlatinumPlus(); - const isGlobal = useMemo( - () => isArtifactGlobal(exception as ExceptionListItemSchema), - [exception] - ); - const [wasByPolicy, setWasByPolicy] = useState(!isGlobalPolicyEffected(exception?.tags)); + const isGlobal = useMemo(() => isArtifactGlobal(exception), [exception]); + const [wasByPolicy, setWasByPolicy] = useState(!isArtifactGlobal(exception)); const [hasDuplicateFields, setHasDuplicateFields] = useState(false); const [hasWildcardWithWrongOperator, setHasWildcardWithWrongOperator] = useState( hasWrongOperatorWithWildcard([exception]) @@ -160,6 +175,17 @@ export const EventFiltersForm: React.FC isFilterProcessDescendantsEnabled(exception), + [exception] + ); const [areConditionsValid, setAreConditionsValid] = useState( !!exception.entries.length || false @@ -217,7 +243,7 @@ export const EventFiltersForm: React.FC { if (!hasFormChanged && exception.tags) { - setWasByPolicy(!isGlobalPolicyEffected(exception.tags)); + setWasByPolicy(!isArtifactGlobal({ tags: exception.tags })); } }, [exception.tags, hasFormChanged]); @@ -416,6 +442,131 @@ export const EventFiltersForm: React.FC { + const newTagsForDescendants = id === 'descendants' ? [FILTER_PROCESS_DESCENDANTS_TAG] : []; + + const tags = getTagsUpdatedBy('processDescendantsFiltering', newTagsForDescendants); + + processChanged({ tags }); + if (!hasFormChanged) setHasFormChanged(true); + }, + [getTagsUpdatedBy, hasFormChanged, processChanged] + ); + + const filterTypeOptions: EuiButtonGroupOptionProps[] = useMemo(() => { + const descendantsTooltip = ( + +

+ +

+

+ +

+ + } + data-test-subj={getTestId('filterProcessDescendants-tooltipText')} + > + +
+ ); + + return [ + { + id: 'events', + label: ( + + + + ), + iconType: isFilterProcessDescendantsSelected ? 'empty' : 'checkInCircleFilled', + 'data-test-subj': getTestId('filterEventsButton'), + }, + { + id: 'descendants', + label: ( + + + + + {descendantsTooltip} + + ), + iconType: isFilterProcessDescendantsSelected ? 'checkInCircleFilled' : 'empty', + 'data-test-subj': getTestId('filterProcessDescendantsButton'), + }, + ]; + }, [getTestId, isFilterProcessDescendantsSelected]); + + const filterTypeSubsection = useMemo(() => { + if (!isFilterProcessDescendantsFeatureEnabled) return null; + + return ( + <> + + + + {isFilterProcessDescendantsSelected && ( + <> + + + + + + + + + )} + + ); + }, [ + isFilterProcessDescendantsFeatureEnabled, + handleFilterTypeOnChange, + euiTheme.euiTheme.size.l, + filterTypeOptions, + isFilterProcessDescendantsSelected, + getTestId, + ]); + // conditions and handler const handleOnBuilderChange = useCallback( (arg: OnChangeProps) => { @@ -523,27 +674,28 @@ export const EventFiltersForm: React.FC ) : null} + {filterTypeSubsection} {exceptionBuilderComponentMemo} ), - [allowSelectOs, exceptionBuilderComponentMemo, osInputMemo] + [allowSelectOs, exceptionBuilderComponentMemo, osInputMemo, filterTypeSubsection] ); // policy and handler const handleOnPolicyChange = useCallback( (change: EffectedPolicySelection) => { - const tags = change.isGlobal - ? [GLOBAL_ARTIFACT_TAG] - : change.selected.map((policy) => `${BY_POLICY_ARTIFACT_TAG_PREFIX}${policy.id}`); + const policySelectionTags = getArtifactTagsByPolicySelection(change); // Preserve old selected policies when switching to global if (!change.isGlobal) { setSelectedPolicies(change.selected); } + + const tags = getTagsUpdatedBy('policySelection', policySelectionTags); processChanged({ tags }); if (!hasFormChanged) setHasFormChanged(true); }, - [processChanged, hasFormChanged, setSelectedPolicies] + [processChanged, getTagsUpdatedBy, hasFormChanged] ); const policiesSection = useMemo( diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.tsx index ef501b7cf9362..62e0683c4c44e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/components/form.tsx @@ -25,11 +25,10 @@ import type { } from '../../../../components/effected_policy_select'; import { EffectedPolicySelect } from '../../../../components/effected_policy_select'; import { - getArtifactTagsByEffectedPolicySelection, - getArtifactTagsWithoutPolicies, + getArtifactTagsByPolicySelection, getEffectedPolicySelectionByTags, - isGlobalPolicyEffected, -} from '../../../../components/effected_policy_select/utils'; + isArtifactGlobal, +} from '../../../../../../common/endpoint/service/artifacts'; import { DESCRIPTION_LABEL, DESCRIPTION_PLACEHOLDER, @@ -71,7 +70,7 @@ export const HostIsolationExceptionsForm = memo( const getTestId = useTestIdGenerator(testIdPrefix); const [selectedPolicies, setSelectedPolicies] = useState({ - isGlobal: isGlobalPolicyEffected(exception.tags), + isGlobal: isArtifactGlobal(exception), selected: [], }); @@ -139,13 +138,10 @@ export const HostIsolationExceptionsForm = memo( } notifyOfChange({ - tags: getArtifactTagsByEffectedPolicySelection( - selection, - getArtifactTagsWithoutPolicies(exception.tags) - ), + tags: getArtifactTagsByPolicySelection(selection), }); }, - [exception.tags, notifyOfChange] + [notifyOfChange] ); const handleOnDescriptionChange = useCallback( diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.tsx index 861e407e80824..497caf30d4020 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.tsx @@ -21,7 +21,7 @@ import type { ArtifactCardGridProps } from '../../../../../components/artifact_c import { ArtifactCardGrid } from '../../../../../components/artifact_card_grid'; import { usePolicyDetailsArtifactsNavigateCallback } from '../../policy_hooks'; import type { ImmutableObject, PolicyData } from '../../../../../../../common/endpoint/types'; -import { isGlobalPolicyEffected } from '../../../../../components/effected_policy_select/utils'; +import { isArtifactGlobal } from '../../../../../../../common/endpoint/service/artifacts'; import { useUserPrivileges } from '../../../../../../common/components/user_privileges'; import { useGetLinkTo } from '../empty/use_policy_artifacts_empty_hooks'; import type { ExceptionsListApiClient } from '../../../../../services/exceptions_list/exceptions_list_api_client'; @@ -135,7 +135,7 @@ export const PolicyArtifactsList = React.memo( }; const item = artifact as ExceptionListItemSchema; - const isGlobal = isGlobalPolicyEffected(item.tags); + const isGlobal = isArtifactGlobal(item); const deleteAction = { icon: 'trash', children: labels.listRemoveActionTitle, diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_agent_tamper_protection_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_agent_tamper_protection_extension.tsx index 144ecbd4ab157..93e6536977ba0 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_agent_tamper_protection_extension.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_agent_tamper_protection_extension.tsx @@ -6,7 +6,7 @@ */ import { lazy } from 'react'; -import type { FleetUiExtensionGetterOptions } from './types'; +import type { FleetUiExtensionGetterOptions } from '../../../../../common/types'; export const getLazyEndpointAgentTamperProtectionExtension = ({ coreStart, @@ -16,7 +16,7 @@ export const getLazyEndpointAgentTamperProtectionExtension = ({ lazy(async () => { const [{ withSecurityContext }, { EndpointAgentTamperProtectionExtension }] = await Promise.all( [ - import('./components/with_security_context/with_security_context'), + import('../../../../../common/components/with_security_context/with_security_context'), import('./endpoint_agent_tamper_protection_extension'), ] ); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_generic_errors_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_generic_errors_list.tsx index 07f13e6759891..873c3a6d9aca3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_generic_errors_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_generic_errors_list.tsx @@ -10,7 +10,7 @@ import type { PackageGenericErrorsListComponent, PackageGenericErrorsListProps, } from '@kbn/fleet-plugin/public'; -import type { FleetUiExtensionGetterOptions } from './types'; +import type { FleetUiExtensionGetterOptions } from '../../../../../common/types'; export const getLazyEndpointGenericErrorsListExtension = ({ coreStart, @@ -19,7 +19,7 @@ export const getLazyEndpointGenericErrorsListExtension = ({ }: FleetUiExtensionGetterOptions) => { return lazy(async () => { const [{ withSecurityContext }, { EndpointGenericErrorsList }] = await Promise.all([ - import('./components/with_security_context/with_security_context'), + import('../../../../../common/components/with_security_context/with_security_context'), import('./endpoint_generic_errors_list'), ]); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_package_custom_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_package_custom_extension.tsx index e122ed4c4b26f..81c8ff33ec45a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_package_custom_extension.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_package_custom_extension.tsx @@ -7,7 +7,7 @@ import { lazy } from 'react'; import type { PackageCustomExtensionComponent } from '@kbn/fleet-plugin/public'; -import type { FleetUiExtensionGetterOptions } from './types'; +import type { FleetUiExtensionGetterOptions } from '../../../../../common/types'; export const getLazyEndpointPackageCustomExtension = ({ coreStart, @@ -16,7 +16,7 @@ export const getLazyEndpointPackageCustomExtension = ({ }: FleetUiExtensionGetterOptions) => { return lazy(async () => { const [{ withSecurityContext }, { EndpointPackageCustomExtension }] = await Promise.all([ - import('./components/with_security_context/with_security_context'), + import('../../../../../common/components/with_security_context/with_security_context'), import('./endpoint_package_custom_extension'), ]); return { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension.tsx index 5955661c82864..51325e74f7570 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension.tsx @@ -7,7 +7,7 @@ import { lazy } from 'react'; import type { PackagePolicyCreateExtensionComponent } from '@kbn/fleet-plugin/public'; -import type { FleetUiExtensionGetterOptions } from './types'; +import type { FleetUiExtensionGetterOptions } from '../../../../../common/types'; export const getLazyEndpointPolicyCreateExtension = ({ coreStart, @@ -16,7 +16,7 @@ export const getLazyEndpointPolicyCreateExtension = ({ }: FleetUiExtensionGetterOptions) => { return lazy(async () => { const [{ withSecurityContext }, { EndpointPolicyCreateExtension }] = await Promise.all([ - import('./components/with_security_context/with_security_context'), + import('../../../../../common/components/with_security_context/with_security_context'), import('./endpoint_policy_create_extension'), ]); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension.tsx index 57b1c5b23c15e..80d36a7803733 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension.tsx @@ -10,7 +10,7 @@ import type { PackagePolicyEditExtensionComponent, PackagePolicyEditExtensionComponentProps, } from '@kbn/fleet-plugin/public'; -import type { FleetUiExtensionGetterOptions } from './types'; +import type { FleetUiExtensionGetterOptions } from '../../../../../common/types'; export const getLazyEndpointPolicyEditExtension = ({ coreStart, @@ -19,7 +19,7 @@ export const getLazyEndpointPolicyEditExtension = ({ }: FleetUiExtensionGetterOptions) => { return lazy(async () => { const [{ withSecurityContext }, { EndpointPolicyEditExtension }] = await Promise.all([ - import('./components/with_security_context/with_security_context'), + import('../../../../../common/components/with_security_context/with_security_context'), import('./endpoint_policy_edit_extension/endpoint_policy_edit_extension'), ]); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_response_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_response_extension.tsx index 88a49965dd89a..3a596bd4c6f92 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_response_extension.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_response_extension.tsx @@ -10,7 +10,7 @@ import type { PackagePolicyResponseExtensionComponent, PackagePolicyResponseExtensionComponentProps, } from '@kbn/fleet-plugin/public'; -import type { FleetUiExtensionGetterOptions } from './types'; +import type { FleetUiExtensionGetterOptions } from '../../../../../common/types'; export const getLazyEndpointPolicyResponseExtension = ({ coreStart, @@ -19,7 +19,7 @@ export const getLazyEndpointPolicyResponseExtension = ({ }: FleetUiExtensionGetterOptions) => { return lazy(async () => { const [{ withSecurityContext }, { EndpointPolicyResponseExtension }] = await Promise.all([ - import('./components/with_security_context/with_security_context'), + import('../../../../../common/components/with_security_context/with_security_context'), import('./endpoint_policy_response_extension'), ]); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/mocks.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/mocks.tsx index 7bad12cc0be9d..945a841c132fd 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/mocks.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/mocks.tsx @@ -16,9 +16,9 @@ import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { deepFreeze } from '@kbn/std'; +import { createFleetContextReduxStore } from '../../../../../common/components/with_security_context/store'; import type { AppContextTestRender, UiRender } from '../../../../../common/mock/endpoint'; import { createAppRootMockRenderer } from '../../../../../common/mock/endpoint'; -import { createFleetContextReduxStore } from './components/with_security_context/store'; import type { ExperimentalFeatures } from '../../../../../../common/experimental_features'; import { allowedExperimentalValues } from '../../../../../../common/experimental_features'; import type { State } from '../../../../../common/store'; @@ -26,7 +26,7 @@ import { mockGlobalState } from '../../../../../common/mock'; import { managementReducer } from '../../../../store/reducer'; import { appReducer } from '../../../../../common/store/app'; import { ExperimentalFeaturesService } from '../../../../../common/experimental_features_service'; -import { RenderContextProviders } from './components/with_security_context/render_context_providers'; +import { RenderContextProviders } from '../../../../../common/components/with_security_context/render_context_providers'; import type { AppAction } from '../../../../../common/store/actions'; // Defined a private custom reducer that reacts to an action that enables us to update the diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/types.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/types.ts deleted file mode 100644 index 1e5b4260b2148..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { CoreStart } from '@kbn/core-lifecycle-browser'; -import type { UpsellingService } from '@kbn/security-solution-upselling/service'; -import type { StartPlugins } from '../../../../../types'; - -export interface FleetUiExtensionGetterOptions { - coreStart: CoreStart; - depsStart: Pick; - services: { - upsellingService: UpsellingService; - }; -} diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/form.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/form.tsx index 3f7664b1e58dd..ce168c5c06467 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/form.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/form.tsx @@ -28,7 +28,6 @@ import { ConditionEntryField, OperatingSystem, } from '@kbn/securitysolution-utils'; -import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import { WildCardWithWrongOperatorCallout } from '@kbn/securitysolution-exception-list-components'; import type { TrustedAppConditionEntry, @@ -43,6 +42,7 @@ import { import { isArtifactGlobal, getPolicyIdsFromArtifact, + getArtifactTagsByPolicySelection, } from '../../../../../../common/endpoint/service/artifacts'; import { isMacosLinuxTrustedAppCondition, @@ -67,12 +67,7 @@ import { useTestIdGenerator } from '../../../../hooks/use_test_id_generator'; import { useLicense } from '../../../../../common/hooks/use_license'; import type { EffectedPolicySelection } from '../../../../components/effected_policy_select'; import { EffectedPolicySelect } from '../../../../components/effected_policy_select'; -import { - GLOBAL_ARTIFACT_TAG, - BY_POLICY_ARTIFACT_TAG_PREFIX, -} from '../../../../../../common/endpoint/service/artifacts/constants'; import type { ArtifactFormComponentProps } from '../../../../components/artifact_list_page'; -import { isGlobalPolicyEffected } from '../../../../components/effected_policy_select/utils'; import { TrustedAppsArtifactsDocsLink } from './artifacts_docs_link'; interface FieldValidationState { @@ -253,13 +248,13 @@ export const TrustedAppsForm = memo( const [selectedPolicies, setSelectedPolicies] = useState([]); const isPlatinumPlus = useLicense().isPlatinumPlus(); - const isGlobal = useMemo(() => isArtifactGlobal(item as ExceptionListItemSchema), [item]); - const [wasByPolicy, setWasByPolicy] = useState(!isGlobalPolicyEffected(item.tags)); + const isGlobal = useMemo(() => isArtifactGlobal(item), [item]); + const [wasByPolicy, setWasByPolicy] = useState(!isArtifactGlobal(item)); const [hasFormChanged, setHasFormChanged] = useState(false); useEffect(() => { if (!hasFormChanged && item.tags) { - setWasByPolicy(!isGlobalPolicyEffected(item.tags)); + setWasByPolicy(!isArtifactGlobal({ tags: item.tags })); } }, [item.tags, hasFormChanged]); @@ -309,9 +304,7 @@ export const TrustedAppsForm = memo( const handleOnPolicyChange = useCallback( (change: EffectedPolicySelection) => { - const tags = change.isGlobal - ? [GLOBAL_ARTIFACT_TAG] - : change.selected.map((policy) => `${BY_POLICY_ARTIFACT_TAG_PREFIX}${policy.id}`); + const tags = getArtifactTagsByPolicySelection(change); const nextItem = { ...item, tags }; // Preserve old selected policies when switching to global diff --git a/x-pack/plugins/security_solution/public/notes/api/api.ts b/x-pack/plugins/security_solution/public/notes/api/api.ts new file mode 100644 index 0000000000000..d9cef8cf997ab --- /dev/null +++ b/x-pack/plugins/security_solution/public/notes/api/api.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as uuid from 'uuid'; + +// TODO point to the correct API when it is available +/** + * Fetches all the notes for a document id + */ +export const fetchNotesByDocumentId = async (documentId: string) => { + const response = { + totalCount: 1, + notes: [generateNoteMock(documentId)], + }; + return response.notes; +}; + +// TODO remove when the API is available +const generateNoteMock = (documentId: string) => ({ + noteId: uuid.v4(), + version: 'WzU1MDEsMV0=', + timelineId: '', + eventId: documentId, + note: 'This is a mocked note', + created: new Date().getTime(), + createdBy: 'elastic', + updated: new Date().getTime(), + updatedBy: 'elastic', +}); diff --git a/x-pack/plugins/security_solution/public/notes/pages/note_management_page.tsx b/x-pack/plugins/security_solution/public/notes/pages/note_management_page.tsx new file mode 100644 index 0000000000000..1964fa65fd96f --- /dev/null +++ b/x-pack/plugins/security_solution/public/notes/pages/note_management_page.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +/** + * Page to allow users to manage notes. The page is accessible via the Investigations section within the Manage page. + * // TODO to be implemented + */ +export const NoteManagementPage = () => { + return <>; +}; + +NoteManagementPage.displayName = 'NoteManagementPage'; diff --git a/x-pack/plugins/security_solution/public/notes/store/normalize.ts b/x-pack/plugins/security_solution/public/notes/store/normalize.ts new file mode 100644 index 0000000000000..2b433a35d7c86 --- /dev/null +++ b/x-pack/plugins/security_solution/public/notes/store/normalize.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 { Note } from '../../../common/api/timeline'; + +/** + * Interface to represent a normalized entity + */ +export interface NormalizedEntity { + entities: { + [entity: string]: { + [id: string]: T; + }; + }; + result: string; +} + +/** + * Interface to represent normalized entities + */ +export interface NormalizedEntities { + entities: { + [entity: string]: { + [id: string]: T; + }; + }; + result: string[]; +} + +/** + * Normalizes a single note + */ +export const normalizeEntity = (res: Note): NormalizedEntity => ({ + entities: { + notes: { + [res.noteId]: res, + }, + }, + result: res.noteId, +}); + +/** + * Normalizes an array of notes + */ +export const normalizeEntities = (res: Note[]): NormalizedEntities => ({ + entities: { + notes: res.reduce((obj, item) => Object.assign(obj, { [item.noteId]: item }), {}), + }, + result: res.map((note) => note.noteId), +}); diff --git a/x-pack/plugins/security_solution/public/notes/store/notes.slice.ts b/x-pack/plugins/security_solution/public/notes/store/notes.slice.ts new file mode 100644 index 0000000000000..015bd78efd435 --- /dev/null +++ b/x-pack/plugins/security_solution/public/notes/store/notes.slice.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EntityState, SerializedError } from '@reduxjs/toolkit'; +import { createAsyncThunk, createEntityAdapter, createSlice } from '@reduxjs/toolkit'; +import type { State } from '../../common/store'; +import { fetchNotesByDocumentId as fetchNotesByDocumentIdApi } from '../api/api'; +import type { NormalizedEntities } from './normalize'; +import { normalizeEntities } from './normalize'; +import type { Note } from '../../../common/api/timeline'; + +export enum ReqStatus { + Idle = 'idle', + Loading = 'loading', + Succeeded = 'succeeded', + Failed = 'failed', +} + +interface HttpError { + type: 'http'; + status: number; +} + +export interface NotesState extends EntityState { + status: { + fetchNotesByDocumentId: ReqStatus; + }; + error: { + fetchNotesByDocumentId: SerializedError | HttpError | null; + }; +} + +const notesAdapter = createEntityAdapter({ + selectId: (note: Note) => note.noteId, +}); + +export const initialNotesState: NotesState = notesAdapter.getInitialState({ + status: { + fetchNotesByDocumentId: ReqStatus.Idle, + }, + error: { + fetchNotesByDocumentId: null, + }, +}); + +export const fetchNotesByDocumentId = createAsyncThunk< + NormalizedEntities, + { documentId: string }, + {} +>('notes/fetchNotesByDocumentId', async (args) => { + const { documentId } = args; + const res = await fetchNotesByDocumentIdApi(documentId); + return normalizeEntities(res); +}); + +const notesSlice = createSlice({ + name: 'notes', + initialState: initialNotesState, + reducers: {}, + extraReducers(builder) { + builder + .addCase(fetchNotesByDocumentId.pending, (state, action) => { + state.status.fetchNotesByDocumentId = ReqStatus.Loading; + }) + .addCase(fetchNotesByDocumentId.fulfilled, (state, action) => { + notesAdapter.upsertMany(state, action.payload.entities.notes); + state.status.fetchNotesByDocumentId = ReqStatus.Succeeded; + }) + .addCase(fetchNotesByDocumentId.rejected, (state, action) => { + state.status.fetchNotesByDocumentId = ReqStatus.Failed; + state.error.fetchNotesByDocumentId = action.payload ?? action.error; + }); + }, +}); + +export const notesReducer = notesSlice.reducer; + +export const { + selectAll: selectAllNotes, + selectById: selectNoteById, + selectIds: selectNoteIds, +} = notesAdapter.getSelectors((state: State) => state.notes); diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.tsx b/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.tsx index ea6639ac8bcbf..99eb2ed17e6b6 100644 --- a/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/endpoint_overview/index.tsx @@ -13,7 +13,7 @@ import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_ex import { AgentStatus, EndpointAgentStatus, -} from '../../../../common/components/agents/agent_status'; +} from '../../../../common/components/endpoint/agents/agent_status'; import { OverviewDescriptionList } from '../../../../common/components/overview_description_list'; import type { DescriptionList } from '../../../../../common/utility_types'; import { getEmptyTagValue } from '../../../../common/components/empty_value'; diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 7b0ff954371c0..991f591773aa7 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -20,8 +20,8 @@ import type { import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import type { TriggersAndActionsUIPublicPluginSetup } from '@kbn/triggers-actions-ui-plugin/public'; +import { getLazyCloudSecurityPosturePliAuthBlockExtension } from './cloud_security_posture/lazy_cloud_security_posture_pli_auth_block_extension'; import { getLazyEndpointAgentTamperProtectionExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_agent_tamper_protection_extension'; -import type { FleetUiExtensionGetterOptions } from './management/pages/policy/view/ingest_manager_integration/types'; import type { PluginSetup, PluginStart, @@ -39,7 +39,7 @@ import { APP_ID, APP_UI_ID, APP_PATH, APP_ICON_SOLUTION } from '../common/consta import type { AppLinkItems } from './common/links'; import { updateAppLinks, type LinksPermissions } from './common/links'; import { registerDeepLinksUpdater } from './common/links/deep_links'; -import type { SecuritySolutionUiConfigType } from './common/types'; +import type { FleetUiExtensionGetterOptions, SecuritySolutionUiConfigType } from './common/types'; import { getLazyEndpointPolicyEditExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension'; import { getLazyEndpointPolicyCreateExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension'; @@ -263,6 +263,12 @@ export class Plugin implements IPlugin { }); }); - it('should update the title of the data view according to the selected patterns', async () => { + it('should update the title and name of the data view according to the selected patterns', async () => { const { result, rerender } = renderHook( () => useSourcererDataView(), { @@ -690,11 +690,12 @@ describe('Sourcerer Hooks', () => { selectedPatterns: testPatterns, }) ); + }); - await rerender(); + await rerender(); - expect(result.current.sourcererDataView?.title).toBe(testPatterns.join(',')); - }); + expect(result.current.sourcererDataView?.title).toBe(testPatterns.join(',')); + expect(result.current.sourcererDataView?.name).toBe(testPatterns.join(',')); }); }); }); diff --git a/x-pack/plugins/security_solution/public/sourcerer/containers/index.tsx b/x-pack/plugins/security_solution/public/sourcerer/containers/index.tsx index a8dc888541f33..01dff26d7fdfc 100644 --- a/x-pack/plugins/security_solution/public/sourcerer/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/sourcerer/containers/index.tsx @@ -82,6 +82,7 @@ export const useSourcererDataView = ( dataView: { ..._dv.dataView, title: selectedPatterns.join(','), + name: selectedPatterns.join(','), }, }; }, [legacyDataView, missingPatterns.length, selectedDataView, selectedPatterns]); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/back_to_alert_details_link.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/back_to_alert_details_link.tsx index 6ae4207a46f78..d14622cc0e7d0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/back_to_alert_details_link.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/back_to_alert_details_link.tsx @@ -18,7 +18,7 @@ import React from 'react'; import { ISOLATE_HOST, UNISOLATE_HOST, -} from '../../../../../detections/components/host_isolation/translations'; +} from '../../../../../common/components/endpoint/host_isolation'; import { ALERT_DETAILS, TECHNICAL_PREVIEW, TECHNICAL_PREVIEW_DESCRIPTION } from '../translations'; const BackToAlertDetailsLinkComponent = ({ diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/body.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/body.tsx index 446aa27f5a3a5..5aea7657f8d61 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/body.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/body.tsx @@ -9,8 +9,10 @@ import { EuiFlyoutBody } from '@elastic/eui'; import styled from 'styled-components'; import React from 'react'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; -import { EndpointIsolateSuccess } from '../../../../../common/components/endpoint/host_isolation'; -import { HostIsolationPanel } from '../../../../../detections/components/host_isolation'; +import { + HostIsolationPanel, + EndpointIsolateSuccess, +} from '../../../../../common/components/endpoint/host_isolation'; import type { BrowserFields, TimelineEventsDetailsItem, diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx index e48dbe5215f39..a269dd422ca38 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx @@ -37,14 +37,11 @@ const mockAlertDetailsDataWithIsObject = mockAlertDetailsData.map((detail) => { }; }) as TimelineEventsDetailsItem[]; -jest.mock('../../../../../../common/endpoint/service/host_isolation/utils', () => { - return { - isIsolationSupported: jest.fn().mockReturnValue(true), - }; -}); +jest.mock('../../../../../common/components/endpoint/host_isolation'); +jest.mock('../../../../../common/components/endpoint/responder'); jest.mock( - '../../../../../detections/containers/detection_engine/alerts/use_host_isolation_status', + '../../../../../common/components/endpoint/host_isolation/from_alerts/use_host_isolation_status', () => { return { useEndpointHostIsolationStatus: jest.fn().mockReturnValue({ @@ -74,16 +71,6 @@ jest.mock( ); jest.mock('../../../../../cases/components/use_insert_timeline'); -jest.mock('../../../../../common/utils/endpoint_alert_check', () => { - const realEndpointAlertCheckUtils = jest.requireActual( - '../../../../../common/utils/endpoint_alert_check' - ); - return { - isTimelineEventItemAnAlert: realEndpointAlertCheckUtils.isTimelineEventItemAnAlert, - isAlertFromEndpointAlert: jest.fn().mockReturnValue(true), - isAlertFromEndpointEvent: jest.fn().mockReturnValue(true), - }; -}); jest.mock( '../../../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx index ea0df8fd3c684..61dde196c6eda 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx @@ -9,6 +9,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import { EuiFlyoutFooter, EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui'; import { find } from 'lodash/fp'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; +import { getAlertDetailsFieldValue } from '../../../../../common/lib/endpoint/utils/get_event_details_field_values'; import { isActiveTimeline } from '../../../../../helpers'; import { TakeActionDropdown } from '../../../../../detections/components/take_action_dropdown'; import type { TimelineEventsDetailsItem } from '../../../../../../common/search_strategy'; @@ -16,7 +17,6 @@ import { useExceptionFlyout } from '../../../../../detections/components/alerts_ import { AddExceptionFlyoutWrapper } from '../../../../../detections/components/alerts_table/timeline_actions/alert_context_menu'; import { EventFiltersFlyout } from '../../../../../management/pages/event_filters/view/components/event_filters_flyout'; import { useEventFilterModal } from '../../../../../detections/components/alerts_table/timeline_actions/use_event_filter_modal'; -import { getFieldValue } from '../../../../../detections/components/host_isolation/helpers'; import type { Status } from '../../../../../../common/api/detection_engine'; import { OsqueryFlyout } from '../../../../../detections/components/osquery/osquery_flyout'; import { useRefetchByScope } from './use_refetch_by_scope'; @@ -93,7 +93,10 @@ export const FlyoutFooterComponent = ({ ].reduce( (acc, curr) => ({ ...acc, - [curr.name]: getFieldValue({ category: curr.category, field: curr.field }, detailsData), + [curr.name]: getAlertDetailsFieldValue( + { category: curr.category, field: curr.field }, + detailsData + ), }), {} as AddExceptionModalWrapperData ), diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/header.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/header.tsx index 0d139449b1ca2..206d35e187cf6 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/header.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/header.tsx @@ -8,10 +8,10 @@ import { EuiFlyoutHeader } from '@elastic/eui'; import React, { useMemo } from 'react'; -import { SENTINEL_ONE_AGENT_ID_FIELD } from '../../../../../common/utils/sentinelone_alert_check'; import type { GetFieldsData } from '../../../../../common/hooks/use_get_fields_data'; import { ExpandableEventTitle } from '../expandable_event'; import { BackToAlertDetailsLink } from './back_to_alert_details_link'; +import { RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD } from '../../../../../../common/endpoint/service/response_actions/constants'; interface FlyoutHeaderComponentProps { eventId: string; @@ -45,7 +45,7 @@ const FlyoutHeaderContentComponent = ({ getFieldsData, }: FlyoutHeaderComponentProps) => { const isSentinelOneAlert = useMemo( - () => !!(isAlert && getFieldsData(SENTINEL_ONE_AGENT_ID_FIELD)?.length), + () => !!(isAlert && getFieldsData(RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD.sentinel_one)?.length), [getFieldsData, isAlert] ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx index 31491730adc83..acfdde85b6826 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/helpers.tsx @@ -7,8 +7,8 @@ import { some } from 'lodash/fp'; import { useMemo } from 'react'; +import { getAlertDetailsFieldValue } from '../../../../common/lib/endpoint/utils/get_event_details_field_values'; import type { TimelineEventsDetailsItem } from '../../../../../common/search_strategy'; -import { getFieldValue } from '../../../../detections/components/host_isolation/helpers'; import { DEFAULT_ALERTS_INDEX, DEFAULT_PREVIEW_INDEX } from '../../../../../common/constants'; export interface GetBasicDataFromDetailsData { @@ -34,50 +34,57 @@ export const useBasicDataFromDetailsData = ( const ruleId = useMemo( () => isAlert - ? getFieldValue({ category: 'kibana', field: 'kibana.alert.rule.uuid' }, data) - : getFieldValue({ category: 'signal', field: 'signal.rule.id' }, data), + ? getAlertDetailsFieldValue({ category: 'kibana', field: 'kibana.alert.rule.uuid' }, data) + : getAlertDetailsFieldValue({ category: 'signal', field: 'signal.rule.id' }, data), [isAlert, data] ); const ruleName = useMemo( - () => getFieldValue({ category: 'kibana', field: 'kibana.alert.rule.name' }, data), + () => getAlertDetailsFieldValue({ category: 'kibana', field: 'kibana.alert.rule.name' }, data), [data] ); const ruleDescription = useMemo( - () => getFieldValue({ category: 'kibana', field: 'kibana.alert.rule.description' }, data), + () => + getAlertDetailsFieldValue( + { category: 'kibana', field: 'kibana.alert.rule.description' }, + data + ), [data] ); - const alertId = useMemo(() => getFieldValue({ category: '_id', field: '_id' }, data), [data]); + const alertId = useMemo( + () => getAlertDetailsFieldValue({ category: '_id', field: '_id' }, data), + [data] + ); const indexName = useMemo( - () => getFieldValue({ category: '_index', field: '_index' }, data), + () => getAlertDetailsFieldValue({ category: '_index', field: '_index' }, data), [data] ); const alertUrl = useMemo( - () => getFieldValue({ category: 'kibana', field: 'kibana.alert.url' }, data), + () => getAlertDetailsFieldValue({ category: 'kibana', field: 'kibana.alert.url' }, data), [data] ); const agentId = useMemo( - () => getFieldValue({ category: 'agent', field: 'agent.id' }, data), + () => getAlertDetailsFieldValue({ category: 'agent', field: 'agent.id' }, data), [data] ); const hostName = useMemo( - () => getFieldValue({ category: 'host', field: 'host.name' }, data), + () => getAlertDetailsFieldValue({ category: 'host', field: 'host.name' }, data), [data] ); const userName = useMemo( - () => getFieldValue({ category: 'user', field: 'user.name' }, data), + () => getAlertDetailsFieldValue({ category: 'user', field: 'user.name' }, data), [data] ); const timestamp = useMemo( - () => getFieldValue({ category: 'base', field: '@timestamp' }, data), + () => getAlertDetailsFieldValue({ category: 'base', field: '@timestamp' }, data), [data] ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.test.tsx index 0f186fdcf39d9..edf2da964b2b3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.test.tsx @@ -46,18 +46,12 @@ jest.mock('react-router-dom', () => { }; }); -jest.mock('../../../../../common/endpoint/service/host_isolation/utils', () => { - return { - isIsolationSupported: jest.fn().mockReturnValue(true), - }; -}); - jest.mock('../../../../common/hooks/use_space_id', () => ({ useSpaceId: jest.fn().mockReturnValue('testSpace'), })); jest.mock( - '../../../../detections/containers/detection_engine/alerts/use_host_isolation_status', + '../../../../common/components/endpoint/host_isolation/from_alerts/use_host_isolation_status', () => { return { useEndpointHostIsolationStatus: jest.fn().mockReturnValue({ @@ -97,12 +91,6 @@ jest.mock( ); jest.mock('../../../../cases/components/use_insert_timeline'); -jest.mock('../../../../common/utils/endpoint_alert_check', () => { - return { - isAlertFromEndpointAlert: jest.fn().mockReturnValue(true), - isAlertFromEndpointEvent: jest.fn().mockReturnValue(true), - }; -}); jest.mock( '../../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx index 49945e07796fa..0982d829e8886 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx @@ -25,8 +25,10 @@ import { useHostIsolationTools } from './use_host_isolation_tools'; import { FlyoutBody, FlyoutHeader, FlyoutFooter } from './flyout'; import { useBasicDataFromDetailsData, getAlertIndexAlias } from './helpers'; import { useSpaceId } from '../../../../common/hooks/use_space_id'; -import { EndpointIsolateSuccess } from '../../../../common/components/endpoint/host_isolation'; -import { HostIsolationPanel } from '../../../../detections/components/host_isolation'; +import { + EndpointIsolateSuccess, + HostIsolationPanel, +} from '../../../../common/components/endpoint/host_isolation'; import { ALERT_SUMMARY_CONVERSATION_ID, ALERT_SUMMARY_CONTEXT_DESCRIPTION, diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/use_host_isolation_tools.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/use_host_isolation_tools.tsx index 5667c4e0a6156..7885e1cd542dc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/use_host_isolation_tools.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/use_host_isolation_tools.tsx @@ -7,7 +7,7 @@ import { useCallback, useMemo, useReducer } from 'react'; -import { useWithCaseDetailsRefresh } from '../../../../common/components/endpoint/host_isolation/endpoint_host_isolation_cases_context'; +import { useWithCaseDetailsRefresh } from '../../../../common/components/endpoint/host_isolation/from_cases/endpoint_host_isolation_cases_context'; interface HostIsolationStateReducer { isolateAction: 'isolateHost' | 'unisolateHost'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx index 9d4264dd5b079..34578db7e5a15 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx @@ -145,14 +145,16 @@ describe('EventColumnView', () => { }); test('it renders correct tooltip for NotesButton - timeline template', () => { - (useShallowEqualSelector as jest.Mock).mockReturnValue(TimelineType.template); + (useShallowEqualSelector as jest.Mock).mockReturnValue({ + timelineType: TimelineType.template, + }); const wrapper = mount(, { wrappingComponent: TestProviders }); expect(wrapper.find('[data-test-subj="add-note"]').prop('toolTip')).toEqual( NOTES_DISABLE_TOOLTIP ); - (useShallowEqualSelector as jest.Mock).mockReturnValue(TimelineType.default); + (useShallowEqualSelector as jest.Mock).mockReturnValue({ timelineType: TimelineType.default }); }); test('it does NOT render a pin button when isEventViewer is true', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx index 46a3301d52569..28b60305b3f23 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx @@ -19,11 +19,10 @@ import { ALERT_HOST_CRITICALITY, ALERT_USER_CRITICALITY, } from '../../../../../../common/field_maps/field_names'; -import { SENTINEL_ONE_AGENT_ID_FIELD } from '../../../../../common/utils/sentinelone_alert_check'; import { AgentStatus, EndpointAgentStatusById, -} from '../../../../../common/components/agents/agent_status'; +} from '../../../../../common/components/endpoint/agents/agent_status'; import { INDICATOR_REFERENCE } from '../../../../../../common/cti/constants'; import { DefaultDraggable } from '../../../../../common/components/draggables'; import { Bytes, BYTES_FORMAT } from './bytes'; @@ -54,6 +53,7 @@ import { RuleStatus } from './rule_status'; import { HostName } from './host_name'; import { UserName } from './user_name'; import { AssetCriticalityLevel } from './asset_criticality_level'; +import { RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD } from '../../../../../../common/endpoint/service/response_actions/constants'; // simple black-list to prevent dragging and dropping fields such as message name const columnNamesNotDraggable = [MESSAGE_FIELD_NAME]; @@ -276,7 +276,7 @@ const FormattedFieldValueComponent: React.FC<{ ); } else if ( fieldName === AGENT_STATUS_FIELD_NAME && - fieldFromBrowserField?.name === SENTINEL_ONE_AGENT_ID_FIELD + fieldFromBrowserField?.name === RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELD.sentinel_one ) { return ; } else if (fieldName === ALERT_HOST_CRITICALITY || fieldName === ALERT_USER_CRITICALITY) { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/unified_timeline_body.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/unified_timeline_body.test.tsx index 401fe8763ada5..21a923653237a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/unified_timeline_body.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/unified_timeline_body.test.tsx @@ -46,6 +46,8 @@ const defaultProps: UnifiedTimelineBodyProps = { activePage: 0, querySize: 0, }, + eventIdToNoteIds: {} as Record, + pinnedEventIds: {} as Record, }; const renderTestComponents = (props?: UnifiedTimelineBodyProps) => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.tsx index 99952f170f13c..b9c8fd9edada5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/eql/index.tsx @@ -92,8 +92,6 @@ export const EqlTabContentComponent: React.FC = ({ } = useSourcererDataView(SourcererScopeName.timeline); const { augmentedColumnHeaders, timelineQueryFieldsFromColumns } = useTimelineColumns(columns); - const leadingControlColumns = useTimelineControlColumn(columns, TIMELINE_NO_SORTING); - const unifiedComponentsInTimelineEnabled = useIsExperimentalFeatureEnabled( 'unifiedComponentsInTimelineEnabled' ); @@ -137,6 +135,14 @@ export const EqlTabContentComponent: React.FC = ({ timerangeKind, }); + const leadingControlColumns = useTimelineControlColumn({ + columns, + sort: TIMELINE_NO_SORTING, + timelineId, + activeTab: TimelineTabs.eql, + refetch, + }); + const isQueryLoading = useMemo( () => dataLoadingState === DataLoadingState.loading || diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/pinned/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/pinned/index.tsx index 7a4242178f027..27074cceb45b1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/pinned/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/pinned/index.tsx @@ -171,7 +171,13 @@ export const PinnedTabContentComponent: React.FC = ({ timerangeKind: undefined, }); - const leadingControlColumns = useTimelineControlColumn(columns, sort); + const leadingControlColumns = useTimelineControlColumn({ + columns, + sort, + timelineId, + activeTab: TimelineTabs.pinned, + refetch, + }); const isQueryLoading = useMemo( () => [DataLoadingState.loading, DataLoadingState.loadingMore].includes(queryLoadingState), diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.tsx index 8a2c42998e3ec..745cd04daaee5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/index.tsx @@ -203,7 +203,13 @@ export const QueryTabContentComponent: React.FC = ({ timerangeKind, }); - const leadingControlColumns = useTimelineControlColumn(columns, sort); + const leadingControlColumns = useTimelineControlColumn({ + columns, + sort, + timelineId, + activeTab: TimelineTabs.query, + refetch, + }); useEffect(() => { dispatch( @@ -259,41 +265,52 @@ export const QueryTabContentComponent: React.FC = ({ if (unifiedComponentsInTimelineEnabled) { return ( - - } - columns={augmentedColumnHeaders} - rowRenderers={rowRenderers} - timelineId={timelineId} - itemsPerPage={itemsPerPage} - itemsPerPageOptions={itemsPerPageOptions} - sort={sort} - events={events} - refetch={refetch} - dataLoadingState={dataLoadingState} - totalCount={isBlankTimeline ? 0 : totalCount} - onEventClosed={onEventClosed} - expandedDetail={expandedDetail} - showExpandedDetails={showExpandedDetails} - leadingControlColumns={leadingControlColumns as EuiDataGridControlColumn[]} - eventIdToNoteIds={eventIdToNoteIds} - pinnedEventIds={pinnedEventIds} - onChangePage={loadPage} - activeTab={activeTab} - updatedAt={refreshedAt} - isTextBasedQuery={false} - pageInfo={pageInfo} - /> + <> + + + + } + columns={augmentedColumnHeaders} + rowRenderers={rowRenderers} + timelineId={timelineId} + itemsPerPage={itemsPerPage} + itemsPerPageOptions={itemsPerPageOptions} + sort={sort} + events={events} + refetch={refetch} + dataLoadingState={dataLoadingState} + totalCount={isBlankTimeline ? 0 : totalCount} + onEventClosed={onEventClosed} + expandedDetail={expandedDetail} + showExpandedDetails={showExpandedDetails} + leadingControlColumns={leadingControlColumns as EuiDataGridControlColumn[]} + eventIdToNoteIds={eventIdToNoteIds} + pinnedEventIds={pinnedEventIds} + onChangePage={loadPage} + activeTab={activeTab} + updatedAt={refreshedAt} + isTextBasedQuery={false} + pageInfo={pageInfo} + /> + ); } diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/query_tab_unified_components.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/query_tab_unified_components.test.tsx index 36284be9dad18..1edb154db295c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/query_tab_unified_components.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/query/query_tab_unified_components.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import type { ComponentProps } from 'react'; +import type { ComponentProps, FunctionComponent } from 'react'; import React, { useEffect } from 'react'; import QueryTabContent from '.'; import { defaultRowRenderers } from '../../body/renderers'; @@ -15,7 +15,9 @@ import { useTimelineEventsDetails } from '../../../../containers/details'; import { useSourcererDataView } from '../../../../../sourcerer/containers'; import { mockSourcererScope } from '../../../../../sourcerer/containers/mocks'; import { + createMockStore, createSecuritySolutionStorageMock, + mockGlobalState, mockTimelineData, TestProviders, } from '../../../../../common/mock'; @@ -29,7 +31,13 @@ import { timelineActions } from '../../../../store'; import type { ExperimentalFeatures } from '../../../../../../common'; import { allowedExperimentalValues } from '../../../../../../common'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; -import { cloneDeep, flatten } from 'lodash'; +import { defaultUdtHeaders } from '../../unified_components/default_headers'; +import { defaultColumnHeaderType } from '../../body/column_headers/default_headers'; +import { useUserPrivileges } from '../../../../../common/components/user_privileges'; +import { getEndpointPrivilegesInitialStateMock } from '../../../../../common/components/user_privileges/endpoint/mocks'; +import userEvent from '@testing-library/user-event'; + +jest.mock('../../../../../common/components/user_privileges'); jest.mock('../../../../containers', () => ({ useTimelineEvents: jest.fn(), @@ -54,9 +62,17 @@ jest.mock('../../../../../common/lib/kuery'); jest.mock('../../../../../common/hooks/use_experimental_features'); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: jest.fn(() => ({ + pathname: '', + search: '', + })), +})); + // These tests can take more than standard timeout of 5s -// that is why we are setting it to 15s -const SPECIAL_TEST_TIMEOUT = 15000; +// that is why we are increasing it. +const SPECIAL_TEST_TIMEOUT = 50000; const useIsExperimentalFeatureEnabledMock = jest.fn((feature: keyof ExperimentalFeatures) => { if (feature === 'unifiedComponentsInTimelineEnabled') { @@ -67,8 +83,8 @@ const useIsExperimentalFeatureEnabledMock = jest.fn((feature: keyof Experimental jest.mock('../../../../../common/lib/kibana'); -// unified-field-list is is reporiting multiple analytics events -jest.mock(`@kbn/analytics-client`); +// unified-field-list is reporting multiple analytics events +jest.mock(`@kbn/ebt/client`); const TestComponent = (props: Partial>) => { const testComponentDefaultProps: ComponentProps = { @@ -98,44 +114,41 @@ const TestComponent = (props: Partial>) = return ; }; +const customColumnOrder = [ + ...defaultUdtHeaders, + { + columnHeaderType: defaultColumnHeaderType, + id: 'event.severity', + }, +]; + +const mockState = { + ...structuredClone(mockGlobalState), +}; + +mockState.timeline.timelineById[TimelineId.test].columns = customColumnOrder; + +const TestWrapper: FunctionComponent = ({ children }) => { + return {children}; +}; + const renderTestComponents = (props?: Partial>) => { return render(, { - wrapper: TestProviders, + wrapper: TestWrapper, }); }; -const changeItemsPerPageTo = (newItemsPerPage: number) => { - fireEvent.click(screen.getByTestId('tablePaginationPopoverButton')); - fireEvent.click(screen.getByTestId(`tablePagination-${newItemsPerPage}-rows`)); - expect(screen.getByTestId('tablePaginationPopoverButton')).toHaveTextContent( - `Rows per page: ${newItemsPerPage}` - ); -}; - const loadPageMock = jest.fn(); -const useTimelineEventsMock = jest.fn(() => [ - false, - { - events: cloneDeep(mockTimelineData), - pageInfo: { - activePage: 0, - totalPages: 10, - }, - refreshedAt: Date.now(), - totalCount: 70, - loadPage: loadPageMock, - }, -]); - const useSourcererDataViewMocked = jest.fn().mockReturnValue({ ...mockSourcererScope, }); const { storage: storageMock } = createSecuritySolutionStorageMock(); -// Flaky : See https://github.com/elastic/kibana/issues/179831 -describe.skip('query tab with unified timeline', () => { +let useTimelineEventsMock = jest.fn(); + +describe('query tab with unified timeline', () => { const kibanaServiceMock: StartServices = { ...createStartServicesMock(), storage: storageMock, @@ -149,9 +162,20 @@ describe.skip('query tab with unified timeline', () => { }); beforeEach(() => { - // increase timeout for these tests as they are rendering a complete table with ~30 rows which can take time. - const ONE_SECOND = 1000; - jest.setTimeout(10 * ONE_SECOND); + useTimelineEventsMock = jest.fn(() => [ + false, + { + events: structuredClone(mockTimelineData.slice(0, 1)), + pageInfo: { + activePage: 0, + totalPages: 3, + }, + refreshedAt: Date.now(), + totalCount: 3, + loadPage: loadPageMock, + }, + ]); + HTMLElement.prototype.getBoundingClientRect = jest.fn(() => { return { width: 1000, @@ -176,6 +200,12 @@ describe.skip('query tab with unified timeline', () => { (useIsExperimentalFeatureEnabled as jest.Mock).mockImplementation( useIsExperimentalFeatureEnabledMock ); + + (useUserPrivileges as jest.Mock).mockReturnValue({ + kibanaSecuritySolutionsPrivileges: { crud: true, read: true }, + endpointPrivileges: getEndpointPrivilegesInitialStateMock(), + detectionEnginePrivileges: { loading: false, error: undefined, result: undefined }, + }); }); describe('render', () => { @@ -235,15 +265,39 @@ describe.skip('query tab with unified timeline', () => { fireEvent.click(screen.getByLabelText('Closes this modal window')); - expect(screen.queryByTestId('row-renderers-modal')).toBeFalsy(); + expect(screen.queryByTestId('row-renderers-modal')).not.toBeInTheDocument(); - expect(screen.queryByTestId('timeline-row-renderer-0')).toBeFalsy(); + expect(screen.queryByTestId('timeline-row-renderer-0')).not.toBeInTheDocument(); }, SPECIAL_TEST_TIMEOUT ); }); describe('pagination', () => { + beforeEach(() => { + // should return all the records instead just 3 + // as the case in the default mock + useTimelineEventsMock = jest.fn(() => [ + false, + { + events: structuredClone(mockTimelineData), + pageInfo: { + activePage: 0, + totalPages: 10, + }, + refreshedAt: Date.now(), + totalCount: 70, + loadPage: loadPageMock, + }, + ]); + + (useTimelineEvents as jest.Mock).mockImplementation(useTimelineEventsMock); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + it( 'should paginate correctly', async () => { @@ -296,9 +350,14 @@ describe.skip('query tab with unified timeline', () => { await waitFor(() => { expect(screen.getByTestId('discoverDocTable')).toBeVisible(); }); + + const messageColumnIndex = + customColumnOrder.findIndex((header) => header.id === 'message') + 3; + // 3 is the offset for additional leading columns on left + expect(container.querySelector('[data-gridcell-column-id="message"]')).toHaveAttribute( 'data-gridcell-column-index', - '12' + String(messageColumnIndex) ); expect(container.querySelector('[data-gridcell-column-id="message"]')).toBeInTheDocument(); @@ -318,7 +377,7 @@ describe.skip('query tab with unified timeline', () => { await waitFor(() => { expect(container.querySelector('[data-gridcell-column-id="message"]')).toHaveAttribute( 'data-gridcell-column-index', - '11' + String(messageColumnIndex - 1) ); }); }, @@ -391,7 +450,7 @@ describe.skip('query tab with unified timeline', () => { sort: [ { direction: 'asc', - esTypes: [], + esTypes: ['date'], field: '@timestamp', type: 'date', }, @@ -439,7 +498,7 @@ describe.skip('query tab with unified timeline', () => { sort: [ { direction: 'desc', - esTypes: [], + esTypes: ['date'], field: '@timestamp', type: 'date', }, @@ -498,7 +557,7 @@ describe.skip('query tab with unified timeline', () => { sort: [ { direction: 'desc', - esTypes: [], + esTypes: ['date'], field: '@timestamp', type: 'date', }, @@ -547,7 +606,6 @@ describe.skip('query tab with unified timeline', () => { SPECIAL_TEST_TIMEOUT ); - // Failing: See https://github.com/elastic/kibana/issues/179831 it( 'should be able to sort by multiple columns', async () => { @@ -608,7 +666,7 @@ describe.skip('query tab with unified timeline', () => { await waitFor(() => { expect(screen.getByTestId('fieldListGroupedSelectedFields-count')).toHaveTextContent( - '11' + String(customColumnOrder.length) ); }); @@ -620,7 +678,7 @@ describe.skip('query tab with unified timeline', () => { // column not longer exists in the table await waitFor(() => { expect(screen.getByTestId('fieldListGroupedSelectedFields-count')).toHaveTextContent( - '10' + String(customColumnOrder.length - 1) ); }); expect(screen.queryAllByTestId(`dataGridHeaderCell-${field.name}`)).toHaveLength(0); @@ -640,7 +698,7 @@ describe.skip('query tab with unified timeline', () => { await waitFor(() => { expect(screen.getByTestId('fieldListGroupedSelectedFields-count')).toHaveTextContent( - '11' + String(customColumnOrder.length) ); }); @@ -653,7 +711,7 @@ describe.skip('query tab with unified timeline', () => { await waitFor(() => { expect(screen.getByTestId('fieldListGroupedSelectedFields-count')).toHaveTextContent( - '12' + String(customColumnOrder.length + 1) ); }); expect(screen.queryAllByTestId(`dataGridHeaderCell-${field.name}`)).toHaveLength(1); @@ -693,50 +751,64 @@ describe.skip('query tab with unified timeline', () => { async () => { renderTestComponents(); expect(await screen.findByTestId('timeline-sidebar')).toBeVisible(); - await waitFor(() => { - expect(screen.getByTestId('fieldListGroupedAvailableFields-count')).toHaveTextContent( - '37' - ); - }); + + expect(screen.getByTestId('fieldListGroupedFieldGroups')).toBeVisible(); fireEvent.click(screen.getByTitle('Hide sidebar')); await waitFor(() => { - expect(screen.queryAllByTestId('fieldListGroupedAvailableFields-count')).toHaveLength(0); + expect(screen.queryByTestId('fieldListGroupedFieldGroups')).not.toBeInTheDocument(); }); }, SPECIAL_TEST_TIMEOUT ); + }); + describe('row leading actions', () => { it( - 'should have all populated fields in Available fields section', + 'should be able to add notes', async () => { - const listOfPopulatedFields = new Set( - flatten( - mockTimelineData.map((dataItem) => - dataItem.data.map((item) => - item.value && item.value.length > 0 ? item.field : undefined - ) - ) - ).filter((item) => typeof item !== 'undefined') - ); - renderTestComponents(); + expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); - expect(await screen.findByTestId('timeline-sidebar')).toBeVisible(); + await waitFor(() => { + expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled(); + }); + + fireEvent.click(screen.getByTestId('timeline-notes-button-small')); + + await waitFor(() => { + expect(screen.getByTestId('add-note-container')).toBeVisible(); + }); + }, + SPECIAL_TEST_TIMEOUT + ); + + it( + 'should be cancel adding notes', + async () => { + renderTestComponents(); expect(await screen.findByTestId('discoverDocTable')).toBeVisible(); - changeItemsPerPageTo(100); + await waitFor(() => { + expect(screen.getByTestId('timeline-notes-button-small')).not.toBeDisabled(); + }); - const availableFields = screen.getByTestId('fieldListGroupedAvailableFields'); + fireEvent.click(screen.getByTestId('timeline-notes-button-small')); + + await waitFor(() => { + expect(screen.getByTestId('add-note-container')).toBeVisible(); + }); - for (const field of listOfPopulatedFields) { - fireEvent.change(screen.getByTestId('fieldListFiltersFieldSearch'), { - target: { value: field }, - }); + userEvent.type(screen.getByTestId('euiMarkdownEditorTextArea'), 'Test Note 1'); - expect(within(availableFields).getByTestId(`field-${field}`)); - } + expect(screen.getByTestId('cancel')).not.toBeDisabled(); + + fireEvent.click(screen.getByTestId('cancel')); + + await waitFor(() => { + expect(screen.queryByTestId('add-note-container')).not.toBeInTheDocument(); + }); }, SPECIAL_TEST_TIMEOUT ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/__snapshots__/use_timeline_control_columns.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/__snapshots__/use_timeline_control_columns.test.tsx.snap index a85af556d5f4c..e96dd27082bd0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/__snapshots__/use_timeline_control_columns.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/__snapshots__/use_timeline_control_columns.test.tsx.snap @@ -5,11 +5,7 @@ Array [ Object { "headerCellRender": [Function], "id": "default-timeline-control-column", - "rowCellRender": Object { - "$$typeof": Symbol(react.memo), - "compare": null, - "type": [Function], - }, + "rowCellRender": [Function], "width": 152, }, ] diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.test.tsx index b2958cb0339cb..7befceacb5449 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.test.tsx @@ -10,6 +10,8 @@ import { renderHook } from '@testing-library/react-hooks'; import { useLicense } from '../../../../../common/hooks/use_license'; import { useTimelineControlColumn } from './use_timeline_control_columns'; import type { ColumnHeaderOptions } from '../../../../../../common/types/timeline/columns'; +import { TimelineId } from '@kbn/timelines-plugin/public/store/timeline'; +import { TimelineTabs } from '../../../../../../common/types'; jest.mock('../../../../../common/hooks/use_experimental_features', () => ({ useIsExperimentalFeatureEnabled: jest.fn().mockReturnValue(true), @@ -37,20 +39,42 @@ describe('useTimelineColumns', () => { }, ]; + const refetchMock = jest.fn(); + describe('leadingControlColumns', () => { it('should return the leading control columns', () => { - const { result } = renderHook(() => useTimelineControlColumn(mockColumns, []), { - wrapper: TestProviders, - }); + const { result } = renderHook( + () => + useTimelineControlColumn({ + columns: mockColumns, + sort: [], + timelineId: TimelineId.test, + activeTab: TimelineTabs.query, + refetch: refetchMock, + }), + { + wrapper: TestProviders, + } + ); expect(result.current).toMatchSnapshot(); }); it('should have a width of 124 for 5 actions', () => { useLicenseMock.mockReturnValue({ isEnterprise: () => false, }); - const { result } = renderHook(() => useTimelineControlColumn(mockColumns, []), { - wrapper: TestProviders, - }); + const { result } = renderHook( + () => + useTimelineControlColumn({ + columns: mockColumns, + sort: [], + timelineId: TimelineId.test, + activeTab: TimelineTabs.query, + refetch: refetchMock, + }), + { + wrapper: TestProviders, + } + ); const controlColumn = result.current[0] as EuiDataGridControlColumn; expect(controlColumn.width).toBe(124); }); @@ -58,9 +82,19 @@ describe('useTimelineColumns', () => { useLicenseMock.mockReturnValue({ isEnterprise: () => true, }); - const { result } = renderHook(() => useTimelineControlColumn(mockColumns, []), { - wrapper: TestProviders, - }); + const { result } = renderHook( + () => + useTimelineControlColumn({ + columns: mockColumns, + sort: [], + timelineId: TimelineId.test, + activeTab: TimelineTabs.query, + refetch: refetchMock, + }), + { + wrapper: TestProviders, + } + ); const controlColumn = result.current[0] as EuiDataGridControlColumn; expect(controlColumn.width).toBe(152); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.tsx index 736afdbbec916..02791d85de022 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs/shared/use_timeline_control_columns.tsx @@ -6,7 +6,7 @@ */ import React, { useMemo } from 'react'; -import type { EuiDataGridControlColumn } from '@elastic/eui'; +import type { EuiDataGridCellValueElementProps } from '@elastic/eui'; import type { SortColumnTable } from '@kbn/securitysolution-data-table'; import { useLicense } from '../../../../../common/hooks/use_license'; import { SourcererScopeName } from '../../../../../sourcerer/store/model'; @@ -14,17 +14,32 @@ import { useSourcererDataView } from '../../../../../sourcerer/containers'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; import { getDefaultControlColumn } from '../../body/control_columns'; import type { UnifiedActionProps } from '../../unified_components/data_table/control_column_cell_render'; -import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline'; +import type { TimelineTabs } from '../../../../../../common/types/timeline'; import { HeaderActions } from '../../../../../common/components/header_actions/header_actions'; import { ControlColumnCellRender } from '../../unified_components/data_table/control_column_cell_render'; import type { ColumnHeaderOptions } from '../../../../../../common/types'; import { useTimelineColumns } from './use_timeline_columns'; +import type { TimelineDataGridCellContext } from '../../types'; +interface UseTimelineControlColumnArgs { + columns: ColumnHeaderOptions[]; + sort: SortColumnTable[]; + timelineId: string; + activeTab: TimelineTabs; + refetch: () => void; +} + +const EMPTY_STRING_ARRAY: string[] = []; + +const noOp = () => {}; const noSelectAll = ({ isSelected }: { isSelected: boolean }) => {}; -export const useTimelineControlColumn = ( - columns: ColumnHeaderOptions[], - sort: SortColumnTable[] -) => { +export const useTimelineControlColumn = ({ + columns, + sort, + timelineId, + activeTab, + refetch, +}: UseTimelineControlColumnArgs) => { const { browserFields } = useSourcererDataView(SourcererScopeName.timeline); const unifiedComponentsInTimelineEnabled = useIsExperimentalFeatureEnabled( @@ -55,14 +70,35 @@ export const useTimelineControlColumn = ( showSelectAllCheckbox={false} showFullScreenToggle={false} sort={sort} - tabType={TimelineTabs.pinned} + tabType={activeTab} + {...props} + timelineId={timelineId} + /> + ); + }, + rowCellRender: (props: EuiDataGridCellValueElementProps & TimelineDataGridCellContext) => { + return ( + ); }, - rowCellRender: ControlColumnCellRender, - })) as unknown as EuiDataGridControlColumn[]; + })); } else { return getDefaultControlColumn(ACTION_BUTTON_COUNT).map((x) => ({ ...x, @@ -76,5 +112,8 @@ export const useTimelineControlColumn = ( localColumns, sort, unifiedComponentsInTimelineEnabled, + timelineId, + activeTab, + refetch, ]); }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/types.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/types.ts new file mode 100644 index 0000000000000..e7dedcfa9aad3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/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. + */ + +import type { TimelineItem } from '@kbn/timelines-plugin/common'; +import type { TimelineModel } from '../../store/model'; + +export interface TimelineDataGridCellContext { + events: TimelineItem[]; + pinnedEventIds: TimelineModel['pinnedEventIds']; + eventIdsAddingNotes: Set; + onToggleShowNotes: (eventId?: string) => void; + eventIdToNoteIds: Record; + refetch: () => void; +} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/control_column_cell_render.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/control_column_cell_render.tsx index 48b8eaa4fc74f..c662a06cb0dc1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/control_column_cell_render.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/control_column_cell_render.tsx @@ -9,7 +9,6 @@ import React, { memo, useMemo } from 'react'; import type { TimelineItem } from '@kbn/timelines-plugin/common'; import { eventIsPinned } from '../../body/helpers'; import { Actions } from '../../../../../common/components/header_actions'; -import { TimelineId } from '../../../../../../common/types'; import type { TimelineModel } from '../../../../store/model'; import type { ActionProps } from '../../../../../../common/types'; @@ -20,8 +19,12 @@ export interface UnifiedActionProps extends ActionProps { pinnedEventIds: TimelineModel['pinnedEventIds']; } -export const ControlColumnCellRender = memo(function RowCellRender(props: UnifiedActionProps) { - const { rowIndex, events, ecsData, pinnedEventIds, onToggleShowNotes, eventIdToNoteIds } = props; +export const ControlColumnCellRender = memo(function ControlColumnCellRender( + props: UnifiedActionProps +) { + const { rowIndex, events, pinnedEventIds, onToggleShowNotes, eventIdToNoteIds, timelineId } = + props; + const event = useMemo(() => events && events[rowIndex], [events, rowIndex]); const isPinned = useMemo( () => eventIsPinned({ eventId: event?._id, pinnedEventIds }), @@ -32,17 +35,14 @@ export const ControlColumnCellRender = memo(function RowCellRender(props: Unifie {...props} ariaRowindex={rowIndex} columnValues="columnValues" - ecsData={ecsData ?? event.ecs} - eventId={event?._id} eventIdToNoteIds={eventIdToNoteIds} isEventPinned={isPinned} isEventViewer={false} onEventDetailsPanelOpened={noOp} onRuleChange={noOp} showNotes={true} - timelineId={TimelineId.active} + timelineId={timelineId} toggleShowNotes={onToggleShowNotes} - refetch={noOp} rowIndex={rowIndex} /> ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.test.tsx index a03c95341a07b..52cc643cbf2e4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.test.tsx @@ -17,15 +17,29 @@ import type { ComponentProps } from 'react'; import { getColumnHeaders } from '../../body/column_headers/helpers'; import { mockSourcererScope } from '../../../../../sourcerer/containers/mocks'; import { timelineActions } from '../../../../store'; -import type { ExpandedDetailTimeline } from '../../../../../../common/types'; +import { useUnifiedTableExpandableFlyout } from '../hooks/use_unified_timeline_expandable_flyout'; jest.mock('../../../../../sourcerer/containers'); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: jest.fn(() => ({ + pathname: '', + search: '', + })), +})); + const onFieldEditedMock = jest.fn(); const refetchMock = jest.fn(); const onEventClosedMock = jest.fn(); const onChangePageMock = jest.fn(); +const openFlyoutMock = jest.fn(); +const closeFlyoutMock = jest.fn(); +const isExpandableFlyoutDisabled = false; + +jest.mock('../hooks/use_unified_timeline_expandable_flyout'); + const initialEnrichedColumns = getColumnHeaders( defaultUdtHeaders, mockSourcererScope.browserFields @@ -39,7 +53,7 @@ type TestComponentProps = Partial> & { // These tests can take more than standard timeout of 5s // that is why we are setting it to 10s -const SPECIAL_TEST_TIMEOUT = 10000; +const SPECIAL_TEST_TIMEOUT = 50000; const TestComponent = (props: TestComponentProps) => { const { store = createMockStore(), ...restProps } = props; @@ -81,10 +95,17 @@ const getTimelineFromStore = ( return store.getState().timeline.timelineById[timelineId]; }; -// FLAKY: https://github.com/elastic/kibana/issues/179843 -describe.skip('unified data table', () => { +describe('unified data table', () => { beforeEach(() => { (useSourcererDataView as jest.Mock).mockReturnValue(mockSourcererScope); + (useUnifiedTableExpandableFlyout as jest.Mock).mockReturnValue({ + isExpandableFlyoutDisabled, + openFlyout: openFlyoutMock, + closeFlyout: closeFlyoutMock, + }); + }); + afterEach(() => { + jest.clearAllMocks(); }); it( @@ -269,86 +290,11 @@ describe.skip('unified data table', () => { fireEvent.click(screen.getAllByTestId('docTableExpandToggleColumn')[0]); await waitFor(() => { - expect(screen.getByTestId('timeline:details-panel:flyout')).toBeVisible(); + expect(openFlyoutMock).toHaveBeenCalledTimes(1); }); }, SPECIAL_TEST_TIMEOUT ); - - it( - 'should show details flyout when expandedDetails state is set', - async () => { - const customMockStore = createMockStore(); - const mockExpandedDetail: ExpandedDetailTimeline = { - query: { - params: { - eventId: 'some_id', - indexName: 'security-*', - }, - panelView: 'eventDetail', - }, - }; - customMockStore.dispatch( - timelineActions.toggleDetailPanel({ - id: TimelineId.test, - tabType: TimelineTabs.query, - ...mockExpandedDetail.query, - }) - ); - - render( - - ); - - await waitFor(() => { - expect(screen.getByTestId('timeline:details-panel:flyout')).toBeVisible(); - }); - }, - SPECIAL_TEST_TIMEOUT - ); - it( - 'should close details flyout when close icon is clicked', - async () => { - const customMockStore = createMockStore(); - const mockExpandedDetail: ExpandedDetailTimeline = { - query: { - params: { - eventId: 'some_id', - indexName: 'security-*', - }, - panelView: 'eventDetail', - }, - }; - - customMockStore.dispatch( - timelineActions.toggleDetailPanel({ - id: TimelineId.test, - tabType: TimelineTabs.query, - ...mockExpandedDetail.query, - }) - ); - - render( - - ); - - await waitFor(() => { - expect(screen.getByTestId('euiFlyoutCloseButton')).toBeVisible(); - }); - - fireEvent.click(screen.getByTestId('euiFlyoutCloseButton')); - expect(onEventClosedMock).toHaveBeenCalledTimes(1); - }, - SPECIAL_TEST_TIMEOUT - ); }); describe('pagination', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.test.tsx index f425c72880521..4cb56cdeba012 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.test.tsx @@ -58,6 +58,14 @@ jest.mock('../../../../common/lib/kuery'); jest.mock('../../../../common/hooks/use_experimental_features'); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: jest.fn(() => ({ + pathname: '', + search: '', + })), +})); + const useIsExperimentalFeatureEnabledMock = jest.fn((feature: keyof ExperimentalFeatures) => { if (feature === 'unifiedComponentsInTimelineEnabled') { return true; @@ -67,8 +75,8 @@ const useIsExperimentalFeatureEnabledMock = jest.fn((feature: keyof Experimental jest.mock('../../../../common/lib/kibana'); -// unified-field-list is is reporiting multiple analytics events -jest.mock(`@kbn/analytics-client`); +// unified-field-list is reporting multiple analytics events +jest.mock(`@kbn/ebt/client`); const columnsToDisplay = [ ...defaultUdtHeaders, @@ -79,8 +87,8 @@ const columnsToDisplay = [ ]; // These tests can take more than standard timeout of 5s -// that is why we are setting it to 10s -const SPECIAL_TEST_TIMEOUT = 10000; +// that is why we are increasing the timeout +const SPECIAL_TEST_TIMEOUT = 50000; const localMockedTimelineData = structuredClone(mockTimelineData); @@ -110,6 +118,8 @@ const TestComponent = (props: Partial>) = dataLoadingState: DataLoadingState.loaded, updatedAt: Date.now(), isTextBasedQuery: false, + eventIdToNoteIds: {} as Record, + pinnedEventIds: {} as Record, }; const dispatch = useDispatch(); @@ -188,8 +198,6 @@ describe('unified timeline', () => { }); beforeEach(() => { - const ONE_SECOND = 1000; - jest.setTimeout(10 * ONE_SECOND); HTMLElement.prototype.getBoundingClientRect = jest.fn(() => { return { width: 1000, @@ -216,9 +224,7 @@ describe('unified timeline', () => { ); }); - // Flaky : See https://github.com/elastic/kibana/issues/179831 - // removing/moving column current leads to infitinite loop, will be fixed in further PRs. - describe.skip('columns', () => { + describe('columns', () => { it( 'should move column left correctly ', async () => { @@ -297,7 +303,7 @@ describe('unified timeline', () => { SPECIAL_TEST_TIMEOUT ); - it.skip( + it( 'should remove column ', async () => { const field = { @@ -539,9 +545,7 @@ describe('unified timeline', () => { ); }); - // FLAKY: https://github.com/elastic/kibana/issues/180937 - // FLAKY: https://github.com/elastic/kibana/issues/180956 - describe.skip('unified field list', () => { + describe('unified field list', () => { it( 'should be able to add filters', async () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.tsx index b4daa07f11c40..eaa85e635e4cc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/index.tsx @@ -50,6 +50,7 @@ import { timelineActions } from '../../../store'; import type { TimelineModel } from '../../../store/model'; import { getFieldsListCreationOptions } from './get_fields_list_creation_options'; import { defaultUdtHeaders } from './default_headers'; +import type { TimelineDataGridCellContext } from '../types'; const TimelineBodyContainer = styled.div.attrs(({ className = '' }) => ({ className: `${className}`, @@ -119,8 +120,8 @@ interface Props { dataView: DataView; trailingControlColumns?: EuiDataGridProps['trailingControlColumns']; leadingControlColumns?: EuiDataGridProps['leadingControlColumns']; - pinnedEventIds?: TimelineModel['pinnedEventIds']; - eventIdToNoteIds?: TimelineModel['eventIdToNoteIds']; + pinnedEventIds: TimelineModel['pinnedEventIds']; + eventIdToNoteIds: TimelineModel['eventIdToNoteIds']; } const UnifiedTimelineComponent: React.FC = ({ @@ -170,8 +171,10 @@ const UnifiedTimelineComponent: React.FC = ({ } = timelineDataService; const [eventIdsAddingNotes, setEventIdsAddingNotes] = useState>(new Set()); + const onToggleShowNotes = useCallback( - (eventId: string) => { + (eventId?: string) => { + if (!eventId) return; const newSet = new Set(eventIdsAddingNotes); if (newSet.has(eventId)) { newSet.delete(eventId); @@ -370,7 +373,7 @@ const UnifiedTimelineComponent: React.FC = ({ onFieldEdited(); }, [onFieldEdited]); - const cellContext = useMemo(() => { + const cellContext: TimelineDataGridCellContext = useMemo(() => { return { events, pinnedEventIds, diff --git a/x-pack/plugins/security_solution/public/timelines/routes.tsx b/x-pack/plugins/security_solution/public/timelines/routes.tsx index b64c80cd23036..a7d6373007192 100644 --- a/x-pack/plugins/security_solution/public/timelines/routes.tsx +++ b/x-pack/plugins/security_solution/public/timelines/routes.tsx @@ -5,14 +5,38 @@ * 2.0. */ -import React from 'react'; +import React, { memo } from 'react'; import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; -import { Timelines } from './pages'; -import { TIMELINES_PATH } from '../../common/constants'; -import type { SecuritySubPluginRoutes } from '../app/types'; -import { SecurityPageName } from '../app/types'; +import { Switch } from 'react-router-dom'; +import { Route } from '@kbn/shared-ux-router'; +import { SpyRoute } from '../common/utils/route/spy_routes'; +import { NotFoundPage } from '../app/404'; +import { NoteManagementPage } from '../notes/pages/note_management_page'; import { PluginTemplateWrapper } from '../common/components/plugin_template_wrapper'; +import { SecurityPageName } from '../app/types'; +import type { SecuritySubPluginRoutes } from '../app/types'; +import { NOTES_MANAGEMENT_PATH, TIMELINES_PATH } from '../../common/constants'; +import { Timelines } from './pages'; + +const NoteManagementTelemetry = () => ( + + + + + + +); + +const NoteManagementContainer = memo(() => { + return ( + + + + + ); +}); +NoteManagementContainer.displayName = 'NoteManagementContainer'; const TimelinesRoutes = () => ( @@ -27,4 +51,8 @@ export const routes: SecuritySubPluginRoutes = [ path: TIMELINES_PATH, component: TimelinesRoutes, }, + { + path: NOTES_MANAGEMENT_PATH, + component: NoteManagementContainer, + }, ]; diff --git a/x-pack/plugins/security_solution/scripts/openapi/bundle.js b/x-pack/plugins/security_solution/scripts/openapi/bundle.js index cba548cfd2903..e2df0d47f5b47 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/bundle.js +++ b/x-pack/plugins/security_solution/scripts/openapi/bundle.js @@ -9,26 +9,36 @@ require('../../../../../src/setup_node_env'); const { bundle } = require('@kbn/openapi-bundler'); const { join, resolve } = require('path'); -const SECURITY_SOLUTION_ROOT = resolve(__dirname, '../..'); +const ROOT = resolve(__dirname, '../..'); bundle({ - sourceGlob: join(SECURITY_SOLUTION_ROOT, 'common/api/**/*.schema.yaml'), + sourceGlob: join(ROOT, 'common/api/detection_engine/**/*.schema.yaml'), outputFilePath: join( - SECURITY_SOLUTION_ROOT, - 'target/openapi/serverless/security_solution-{version}.bundled.schema.yaml' + ROOT, + 'docs/openapi/serverless/security_solution_detections_api_{version}.bundled.schema.yaml' ), options: { includeLabels: ['serverless'], + specInfo: { + title: 'Security Solution Detections API (Elastic Cloud Serverless)', + description: + 'You can create rules that automatically turn events and external alerts sent to Elastic Security into detection alerts. These alerts are displayed on the Detections page.', + }, }, }); bundle({ - sourceGlob: join(SECURITY_SOLUTION_ROOT, 'common/api/**/*.schema.yaml'), + sourceGlob: join(ROOT, 'common/api/detection_engine/**/*.schema.yaml'), outputFilePath: join( - SECURITY_SOLUTION_ROOT, - 'target/openapi/ess/security_solution-{version}.bundled.schema.yaml' + ROOT, + 'docs/openapi/ess/security_solution_detections_api_{version}.bundled.schema.yaml' ), options: { includeLabels: ['ess'], + specInfo: { + title: 'Security Solution Detections API (Elastic Cloud and self-hosted)', + description: + 'You can create rules that automatically turn events and external alerts sent to Elastic Security into detection alerts. These alerts are displayed on the Detections page.', + }, }, }); diff --git a/x-pack/plugins/security_solution/scripts/openapi/generate.js b/x-pack/plugins/security_solution/scripts/openapi/generate.js index d4484c1f71461..38eb0fe06f95a 100644 --- a/x-pack/plugins/security_solution/scripts/openapi/generate.js +++ b/x-pack/plugins/security_solution/scripts/openapi/generate.js @@ -16,7 +16,7 @@ const SECURITY_SOLUTION_ROOT = resolve(__dirname, '../..'); await generate({ title: 'API route schemas', rootDir: SECURITY_SOLUTION_ROOT, - sourceGlob: './**/*.schema.yaml', + sourceGlob: './common/**/*.schema.yaml', templateName: 'zod_operation_schema', skipLinting: true, }); @@ -24,7 +24,7 @@ const SECURITY_SOLUTION_ROOT = resolve(__dirname, '../..'); await generate({ title: 'API client for tests', rootDir: SECURITY_SOLUTION_ROOT, - sourceGlob: './**/*.schema.yaml', + sourceGlob: './common/**/*.schema.yaml', templateName: 'api_client_supertest', skipLinting: true, bundle: { diff --git a/x-pack/plugins/security_solution/server/assistant/tools/alert_counts/alert_counts_tool.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/alert_counts/alert_counts_tool.test.ts index e2c3036477875..752f8e472a755 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/alert_counts/alert_counts_tool.test.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/alert_counts/alert_counts_tool.test.ts @@ -8,6 +8,7 @@ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { KibanaRequest } from '@kbn/core-http-server'; import type { DynamicTool } from '@langchain/core/tools'; +import { loggerMock } from '@kbn/logging-mocks'; import { ALERT_COUNTS_TOOL } from './alert_counts_tool'; import type { RetrievalQAChain } from 'langchain/chains'; import type { ExecuteConnectorRequestBody } from '@kbn/elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.gen'; @@ -29,9 +30,11 @@ describe('AlertCountsTool', () => { const isEnabledKnowledgeBase = true; const chain = {} as unknown as RetrievalQAChain; const modelExists = true; + const logger = loggerMock.create(); const rest = { isEnabledKnowledgeBase, chain, + logger, modelExists, }; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts index a608673adf661..5d8fb0b51739a 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/attack_discovery/attack_discovery_tool.test.ts @@ -11,6 +11,8 @@ import type { AttackDiscoveryPostRequestBody } from '@kbn/elastic-assistant-comm import type { ActionsClientLlm } from '@kbn/langchain/server'; import type { DynamicTool } from '@langchain/core/tools'; +import { loggerMock } from '@kbn/logging-mocks'; + import { ATTACK_DISCOVERY_TOOL } from './attack_discovery_tool'; import { mockAnonymizationFields } from '../mock/mock_anonymization_fields'; import { mockEmptyOpenAndAcknowledgedAlertsQueryResults } from '../mock/mock_empty_open_and_acknowledged_alerts_qery_results'; @@ -66,11 +68,13 @@ describe('AttackDiscoveryTool', () => { search: jest.fn(), } as unknown as ElasticsearchClient; const llm = jest.fn() as unknown as ActionsClientLlm; + const logger = loggerMock.create(); const rest = { anonymizationFields: mockAnonymizationFields, isEnabledKnowledgeBase: false, llm, + logger, modelExists: false, onNewReplacements: jest.fn(), size, diff --git a/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.test.ts index 2db0575de8dce..29b10e9fb0275 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.test.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/esql_language_knowledge_base/esql_language_knowledge_base_tool.test.ts @@ -11,6 +11,7 @@ import { ESQL_KNOWLEDGE_BASE_TOOL } from './esql_language_knowledge_base_tool'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { KibanaRequest } from '@kbn/core-http-server'; import type { ExecuteConnectorRequestBody } from '@kbn/elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.gen'; +import { loggerMock } from '@kbn/logging-mocks'; describe('EsqlLanguageKnowledgeBaseTool', () => { const chain = {} as RetrievalQAChain; @@ -27,9 +28,11 @@ describe('EsqlLanguageKnowledgeBaseTool', () => { size: 20, }, } as unknown as KibanaRequest; + const logger = loggerMock.create(); const rest = { chain, esClient, + logger, request, }; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/index.ts b/x-pack/plugins/security_solution/server/assistant/tools/index.ts index b99c1f6e0cd38..0e5ea3a8f69d1 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/index.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/index.ts @@ -11,10 +11,14 @@ import { ALERT_COUNTS_TOOL } from './alert_counts/alert_counts_tool'; import { ESQL_KNOWLEDGE_BASE_TOOL } from './esql_language_knowledge_base/esql_language_knowledge_base_tool'; import { OPEN_AND_ACKNOWLEDGED_ALERTS_TOOL } from './open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool'; import { ATTACK_DISCOVERY_TOOL } from './attack_discovery/attack_discovery_tool'; +import { KNOWLEDGE_BASE_RETRIEVAL_TOOL } from './knowledge_base/knowledge_base_retrieval_tool'; +import { KNOWLEDGE_BASE_WRITE_TOOL } from './knowledge_base/knowledge_base_write_tool'; export const getAssistantTools = (): AssistantTool[] => [ ALERT_COUNTS_TOOL, ATTACK_DISCOVERY_TOOL, ESQL_KNOWLEDGE_BASE_TOOL, + KNOWLEDGE_BASE_RETRIEVAL_TOOL, + KNOWLEDGE_BASE_WRITE_TOOL, OPEN_AND_ACKNOWLEDGED_ALERTS_TOOL, ]; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/knowledge_base/knowledge_base_retrieval_tool.ts b/x-pack/plugins/security_solution/server/assistant/tools/knowledge_base/knowledge_base_retrieval_tool.ts new file mode 100644 index 0000000000000..47cb35e244d51 --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/knowledge_base/knowledge_base_retrieval_tool.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { z } from 'zod'; +import type { AssistantTool, AssistantToolParams } from '@kbn/elastic-assistant-plugin/server'; +import type { AIAssistantKnowledgeBaseDataClient } from '@kbn/elastic-assistant-plugin/server/ai_assistant_data_clients/knowledge_base'; +import { APP_UI_ID } from '../../../../common'; + +export interface KnowledgeBaseRetrievalToolParams extends AssistantToolParams { + kbDataClient: AIAssistantKnowledgeBaseDataClient; +} + +const toolDetails = { + description: + "Call this for fetching details from the user's knowledge base. The knowledge base contains useful information the user wants to store between conversation contexts. Call this function when the user asks for information about themself, like 'what is my favorite...' or 'using my saved....'. Input must always be the free-text query on a single line, with no other text. You are welcome to re-write the query to be a summary of items/things to search for in the knowledge base, as a vector search will be performed to return similar results when requested. If the results returned do not look relevant, disregard and tell the user you were unable to find the information they were looking for. All requests include a `knowledge history` section which includes some existing knowledge of the user. DO NOT CALL THIS FUNCTION if the `knowledge history` sections appears to be able to answer the user's query.", + id: 'knowledge-base-retrieval-tool', + name: 'KnowledgeBaseRetrievalTool', +}; +export const KNOWLEDGE_BASE_RETRIEVAL_TOOL: AssistantTool = { + ...toolDetails, + sourceRegister: APP_UI_ID, + isSupported: (params: AssistantToolParams): params is KnowledgeBaseRetrievalToolParams => { + const { kbDataClient, isEnabledKnowledgeBase, modelExists } = params; + return isEnabledKnowledgeBase && modelExists && kbDataClient != null; + }, + getTool(params: AssistantToolParams) { + if (!this.isSupported(params)) return null; + + const { kbDataClient, logger } = params as KnowledgeBaseRetrievalToolParams; + if (kbDataClient == null) return null; + + return new DynamicStructuredTool({ + name: toolDetails.name, + description: toolDetails.description, + schema: z.object({ + query: z.string().describe(`Summary of items/things to search for in the knowledge base`), + }), + func: async (input, _, cbManager) => { + logger.debug(`KnowledgeBaseRetrievalToolParams:input\n ${JSON.stringify(input, null, 2)}`); + + const docs = await kbDataClient.getKnowledgeBaseDocuments({ + query: input.query, + kbResource: 'user', + required: false, + }); + + return JSON.stringify(docs); + }, + tags: ['knowledge-base'], + }); + }, +}; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/knowledge_base/knowledge_base_write_tool.ts b/x-pack/plugins/security_solution/server/assistant/tools/knowledge_base/knowledge_base_write_tool.ts new file mode 100644 index 0000000000000..addb2a5580dfc --- /dev/null +++ b/x-pack/plugins/security_solution/server/assistant/tools/knowledge_base/knowledge_base_write_tool.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DynamicStructuredTool } from '@langchain/core/tools'; +import { z } from 'zod'; +import type { AssistantTool, AssistantToolParams } from '@kbn/elastic-assistant-plugin/server'; +import type { AIAssistantKnowledgeBaseDataClient } from '@kbn/elastic-assistant-plugin/server/ai_assistant_data_clients/knowledge_base'; +import type { KnowledgeBaseEntryCreateProps } from '@kbn/elastic-assistant-common'; +import { APP_UI_ID } from '../../../../common'; + +export interface KnowledgeBaseWriteToolParams extends AssistantToolParams { + kbDataClient: AIAssistantKnowledgeBaseDataClient; +} + +const toolDetails = { + description: + "Call this for writing details to the user's knowledge base. The knowledge base contains useful information the user wants to store between conversation contexts. Input will be the summarized knowledge base entry to store, with no other text, and whether or not the entry is required.", + id: 'knowledge-base-write-tool', + name: 'KnowledgeBaseWriteTool', +}; +export const KNOWLEDGE_BASE_WRITE_TOOL: AssistantTool = { + ...toolDetails, + sourceRegister: APP_UI_ID, + isSupported: (params: AssistantToolParams): params is KnowledgeBaseWriteToolParams => { + const { isEnabledKnowledgeBase, kbDataClient, modelExists } = params; + return isEnabledKnowledgeBase && modelExists && kbDataClient != null; + }, + getTool(params: AssistantToolParams) { + if (!this.isSupported(params)) return null; + + const { kbDataClient, logger } = params as KnowledgeBaseWriteToolParams; + if (kbDataClient == null) return null; + + return new DynamicStructuredTool({ + name: toolDetails.name, + description: toolDetails.description, + schema: z.object({ + query: z.string().describe(`Summary of items/things to save in the knowledge base`), + required: z + .boolean() + .describe( + `Whether or not the entry is required to always be included in conversations. Is only true if the user explicitly asks for it to be required or always included in conversations, otherwise this is always false.` + ), + }), + func: async (input, _, cbManager) => { + logger.debug(`KnowledgeBaseWriteToolParams:input\n ${JSON.stringify(input, null, 2)}`); + + const knowledgeBaseEntry: KnowledgeBaseEntryCreateProps = { + metadata: { kbResource: 'user', source: 'conversation', required: input.required }, + text: input.query, + }; + + logger.debug(`knowledgeBaseEntry\n ${JSON.stringify(knowledgeBaseEntry, null, 2)}`); + + const resp = await kbDataClient.createKnowledgeBaseEntry({ knowledgeBaseEntry }); + + if (resp == null) { + return "I'm sorry, but I was unable to add this entry to your knowledge base."; + } + return "I've successfully saved this entry to your knowledge base. You can ask me to recall this information at any time."; + }, + tags: ['knowledge-base'], + }); + }, +}; diff --git a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts index b083e3b8754bc..2b134dfd86335 100644 --- a/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts +++ b/x-pack/plugins/security_solution/server/assistant/tools/open_and_acknowledged_alerts/open_and_acknowledged_alerts_tool.test.ts @@ -14,6 +14,7 @@ import { MAX_SIZE } from './helpers'; import type { RetrievalQAChain } from 'langchain/chains'; import { mockAlertsFieldsApi } from '@kbn/elastic-assistant-plugin/server/__mocks__/alerts'; import type { ExecuteConnectorRequestBody } from '@kbn/elastic-assistant-common/impl/schemas/actions_connector/post_actions_connector_execute_route.gen'; +import { loggerMock } from '@kbn/logging-mocks'; describe('OpenAndAcknowledgedAlertsTool', () => { const alertsIndexPattern = 'alerts-index'; @@ -32,10 +33,12 @@ describe('OpenAndAcknowledgedAlertsTool', () => { const isEnabledKnowledgeBase = true; const chain = {} as unknown as RetrievalQAChain; const modelExists = true; + const logger = loggerMock.create(); const rest = { isEnabledKnowledgeBase, esClient, chain, + logger, modelExists, }; diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.test.ts index c5b641853e4db..29a9d5e547825 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.test.ts @@ -18,6 +18,7 @@ import { coreMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import type { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server'; import { TaskStatus } from '@kbn/task-manager-plugin/server'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { CoreSetup } from '@kbn/core/server'; import type { ElasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { TRANSFORM_STATES } from '../../../../common/constants'; @@ -126,6 +127,16 @@ describe('check metadata transforms task', () => { }, } as unknown as TransportResult); + it('should not run if task is outdated', async () => { + const result = await runTask({ ...MOCK_TASK_INSTANCE, id: 'old-id' }); + + expect(esClient.transform.getTransformStats).not.toHaveBeenCalled(); + expect(esClient.transform.stopTransform).not.toHaveBeenCalled(); + expect(esClient.transform.startTransform).not.toHaveBeenCalled(); + + expect(result).toEqual(getDeleteTaskRunResult()); + }); + describe('transforms restart', () => { it('should stop task if transform stats response fails', async () => { esClient.transform.getTransformStats.mockRejectedValue({}); diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.ts b/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.ts index 048db4b4c6c37..cf5f19ab9741f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/metadata/check_metadata_transforms_task.ts @@ -16,7 +16,7 @@ import type { TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; -import { throwUnrecoverableError } from '@kbn/task-manager-plugin/server'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import { ElasticsearchAssetType, FLEET_ENDPOINT_PACKAGE } from '@kbn/fleet-plugin/common'; import type { EndpointAppContext } from '../../types'; import { METADATA_TRANSFORMS_PATTERN } from '../../../../common/endpoint/constants'; @@ -105,7 +105,12 @@ export class CheckMetadataTransformsTask { // Check that this task is current if (taskInstance.id !== this.getTaskId()) { // old task, die - throwUnrecoverableError(new Error('Outdated task version')); + this.logger.info( + `Outdated task version: Got [${ + taskInstance.id + }] from task instance. Current version is [${this.getTaskId()}]` + ); + return getDeleteTaskRunResult(); } const [{ elasticsearch }] = await core.getStartServices(); diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task.ts b/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task.ts index fd823928b6631..f7d2f58720743 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task.ts @@ -11,7 +11,6 @@ import type { } from '@kbn/task-manager-plugin/server'; import type { Logger } from '@kbn/logging'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { throwUnrecoverableError } from '@kbn/task-manager-plugin/server'; import { EndpointError } from '../../../../common/endpoint/errors'; import { CompleteExternalActionsTaskRunner } from './complete_external_actions_task_runner'; import type { EndpointAppContext } from '../../types'; @@ -90,14 +89,6 @@ export class CompleteExternalResponseActionsTask { ); } - if (taskInstance.id !== this.taskId) { - throwUnrecoverableError( - new EndpointError( - `Outdated task version. Got [${taskInstance.id}] from task instance. Current version is [${this.taskId}]` - ) - ); - } - const { id: taskId, taskType } = taskInstance; return new CompleteExternalActionsTaskRunner( diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.test.ts index 9497f24c8fc42..d37929c15a3f4 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.test.ts @@ -14,6 +14,11 @@ import { EndpointActionGenerator } from '../../../../common/endpoint/data_genera import { ENDPOINT_ACTION_RESPONSES_INDEX } from '../../../../common/endpoint/constants'; import { waitFor } from '@testing-library/react'; import { ResponseActionsConnectorNotConfiguredError } from '../../services/actions/clients/errors'; +import { + COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_TYPE, + COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_VERSION, +} from './complete_external_actions_task'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; describe('CompleteExternalTaskRunner class', () => { let endpointContextServicesMock: ReturnType; @@ -25,7 +30,9 @@ describe('CompleteExternalTaskRunner class', () => { esClientMock = elasticsearchServiceMock.createElasticsearchClient(); runnerInstance = new CompleteExternalActionsTaskRunner( endpointContextServicesMock, - esClientMock + esClientMock, + '60s', + `${COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_TYPE}-${COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_VERSION}` ); const actionGenerator = new EndpointActionGenerator('seed'); @@ -53,6 +60,22 @@ describe('CompleteExternalTaskRunner class', () => { ); }); + it('should do nothing if task instance id is outdated', async () => { + runnerInstance = new CompleteExternalActionsTaskRunner( + endpointContextServicesMock, + esClientMock, + '60s', + 'old-id' + ); + const result = await runnerInstance.run(); + + expect(result).toEqual(getDeleteTaskRunResult()); + + expect(endpointContextServicesMock.createLogger().info).toHaveBeenCalledWith( + `Outdated task version. Got [old-id] from task instance. Current version is [endpoint:complete-external-response-actions-1.0.0]` + ); + }); + it('should NOT log an error if agentType is not configured with a connector', async () => { (endpointContextServicesMock.getInternalResponseActionsClient as jest.Mock).mockImplementation( () => { diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.ts b/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.ts index bb6fcc0c897d7..dbc57c2a55a84 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/response_actions/complete_external_actions_task_runner.ts @@ -6,6 +6,7 @@ */ import type { CancellableTask, RunContext, RunResult } from '@kbn/task-manager-plugin/server/task'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { Logger, ElasticsearchClient } from '@kbn/core/server'; import type { BulkRequest } from '@elastic/elasticsearch/lib/api/types'; import { ResponseActionsConnectorNotConfiguredError } from '../../services/actions/clients/errors'; @@ -17,6 +18,10 @@ import { QueueProcessor } from '../../utils/queue_processor'; import type { LogsEndpointActionResponse } from '../../../../common/endpoint/types'; import type { EndpointAppContextService } from '../../endpoint_app_context_services'; import { ENDPOINT_ACTION_RESPONSES_INDEX } from '../../../../common/endpoint/constants'; +import { + COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_TYPE, + COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_VERSION, +} from './complete_external_actions_task'; /** * A task manager runner responsible for checking the status of and completing pending actions @@ -34,7 +39,7 @@ export class CompleteExternalActionsTaskRunner private readonly endpointContextServices: EndpointAppContextService, private readonly esClient: ElasticsearchClient, private readonly nextRunInterval: string = '60s', - private readonly taskId?: string, + private readonly taskInstanceId?: string, private readonly taskType?: string ) { this.log = this.endpointContextServices.createLogger( @@ -49,6 +54,10 @@ export class CompleteExternalActionsTaskRunner }); } + private get taskId(): string { + return `${COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_TYPE}-${COMPLETE_EXTERNAL_RESPONSE_ACTIONS_TASK_VERSION}`; + } + private async queueBatchProcessor({ batch, data, @@ -94,6 +103,13 @@ export class CompleteExternalActionsTaskRunner } public async run(): Promise { + if (this.taskInstanceId !== this.taskId) { + this.log.info( + `Outdated task version. Got [${this.taskInstanceId}] from task instance. Current version is [${this.taskId}]` + ); + return getDeleteTaskRunResult(); + } + this.log.debug(`Started: Checking status of external response actions`); this.abortController = new AbortController(); @@ -118,7 +134,7 @@ export class CompleteExternalActionsTaskRunner this.endpointContextServices.getInternalResponseActionsClient({ agentType, taskType: this.taskType, - taskId: this.taskId, + taskId: this.taskInstanceId, }); return agentTypeActionsClient diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/create_migration.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/create_migration.ts index da49cda43507a..8bc38b1ecdf94 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/create_migration.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/create_migration.ts @@ -6,7 +6,7 @@ */ import type { ElasticsearchClient } from '@kbn/core/server'; -import type { SignalsReindexOptions } from '../../../../common/api/detection_engine/signals_migration'; +import type { AlertsReindexOptions } from '../../../../common/api/detection_engine/signals_migration'; import { createMigrationIndex } from './create_migration_index'; export interface CreatedMigration { @@ -24,7 +24,7 @@ export interface CreatedMigration { * @param esClient An {@link ElasticsearchClient} * @param index name of the concrete signals index to be migrated * @param version version of the current signals template/mappings - * @param reindexOptions object containing reindex options {@link SignalsReindexOptions} + * @param reindexOptions object containing reindex options {@link AlertsReindexOptions} * * @returns identifying information representing the {@link MigrationInfo} * @throws if elasticsearch returns an error @@ -37,7 +37,7 @@ export const createMigration = async ({ }: { esClient: ElasticsearchClient; index: string; - reindexOptions: SignalsReindexOptions; + reindexOptions: AlertsReindexOptions; version: number; }): Promise => { const migrationIndex = await createMigrationIndex({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/migration_service.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/migration_service.ts index e0c82f70549fd..5a4399bd6389c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/migration_service.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/migrations/migration_service.ts @@ -6,7 +6,7 @@ */ import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; -import type { SignalsReindexOptions } from '../../../../common/api/detection_engine/signals_migration'; +import type { AlertsReindexOptions } from '../../../../common/api/detection_engine/signals_migration'; import type { SignalsMigrationSO } from './saved_objects_schema'; import { createMigrationSavedObject } from './create_migration_saved_object'; import { createMigration } from './create_migration'; @@ -16,7 +16,7 @@ import { deleteMigration } from './delete_migration'; export interface CreateParams { index: string; version: number; - reindexOptions: SignalsReindexOptions; + reindexOptions: AlertsReindexOptions; } export interface FinalizeParams { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_installation/perform_rule_installation_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_installation/perform_rule_installation_route.ts index 0399d3312de2f..8ffec60a26c11 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_installation/perform_rule_installation_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/perform_rule_installation/perform_rule_installation_route.ts @@ -19,7 +19,6 @@ import type { SecuritySolutionPluginRouter } from '../../../../../types'; import { buildRouteValidation } from '../../../../../utils/build_validation/route_validation'; import type { PromisePoolError } from '../../../../../utils/promise_pool'; import { buildSiemResponse } from '../../../routes/utils'; -import { internalRuleToAPIResponse } from '../../../rule_management/normalization/rule_converters'; import { aggregatePrebuiltRuleErrors } from '../../logic/aggregate_prebuilt_rule_errors'; import { ensureLatestRulesPackageInstalled } from '../../logic/ensure_latest_rules_package_installed'; import { createPrebuiltRuleAssetsClient } from '../../logic/rule_assets/prebuilt_rule_assets_client'; @@ -135,7 +134,7 @@ export const performRuleInstallationRoute = (router: SecuritySolutionPluginRoute failed: ruleErrors.length, }, results: { - created: installedRules.map(({ result }) => internalRuleToAPIResponse(result)), + created: installedRules.map(({ result }) => result), skipped: skippedRules, }, errors: allErrors, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts index 2270c6fea7396..de7db929790de 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/review_rule_upgrade/review_rule_upgrade_route.ts @@ -7,7 +7,10 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import { pickBy } from 'lodash'; -import { REVIEW_RULE_UPGRADE_URL } from '../../../../../../common/api/detection_engine/prebuilt_rules'; +import { + REVIEW_RULE_UPGRADE_URL, + ThreeWayDiffOutcome, +} from '../../../../../../common/api/detection_engine/prebuilt_rules'; import type { ReviewRuleUpgradeResponseBody, RuleUpgradeInfoForReview, @@ -120,7 +123,7 @@ const calculateRuleInfos = (results: CalculateRuleDiffResult[]): RuleUpgradeInfo diff: { fields: pickBy>( ruleDiff.fields, - (fieldDiff) => fieldDiff.has_update || fieldDiff.has_conflict + (fieldDiff) => fieldDiff.diff_outcome !== ThreeWayDiffOutcome.StockValueNoUpdate ), has_conflict: ruleDiff.has_conflict, }, diff --git a/x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/index.ts similarity index 54% rename from x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/index.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/index.ts index 18453665cb4f2..57dd3553cb4c7 100644 --- a/x-pack/packages/ml/aiops_components/src/log_rate_analysis_state_provider/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/index.ts @@ -5,8 +5,6 @@ * 2.0. */ -export { - useLogRateAnalysisStateContext, - LogRateAnalysisStateProvider, -} from './log_rate_analysis_state_provider'; -export type { GroupTableItem, GroupTableItemGroup, TableItemAction } from './types'; +export { numberDiffAlgorithm } from './number_diff_algorithm'; +export { singleLineStringDiffAlgorithm } from './single_line_string_diff_algorithm'; +export { simpleDiffAlgorithm } from './simple_diff_algorithm'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.ts index 513d9047c7a5c..30c32a475ecfb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/number_diff_algorithm.ts @@ -5,6 +5,9 @@ * 2.0. */ +import type { ThreeVersionsOf } from '../../../../../../../../common/api/detection_engine'; import { simpleDiffAlgorithm } from './simple_diff_algorithm'; -export const numberDiffAlgorithm = simpleDiffAlgorithm; +export const numberDiffAlgorithm = ( + versions: ThreeVersionsOf +) => simpleDiffAlgorithm(versions); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.ts index 901bb6c050e51..f80d8b63c8da8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/algorithms/single_line_string_diff_algorithm.ts @@ -5,6 +5,9 @@ * 2.0. */ +import type { ThreeVersionsOf } from '../../../../../../../../common/api/detection_engine'; import { simpleDiffAlgorithm } from './simple_diff_algorithm'; -export const singleLineStringDiffAlgorithm = simpleDiffAlgorithm; +export const singleLineStringDiffAlgorithm = ( + versions: ThreeVersionsOf +) => simpleDiffAlgorithm(versions); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts index 5639cc07ebedd..ea482858650fb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/logic/diff/calculation/calculate_rule_fields_diff.ts @@ -37,7 +37,11 @@ import type { FieldsDiffAlgorithmsFor } from '../../../../../../../common/api/de import type { ThreeVersionsOf } from '../../../../../../../common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff'; import { MissingVersion } from '../../../../../../../common/api/detection_engine/prebuilt_rules/model/diff/three_way_diff/three_way_diff'; import { calculateFieldsDiffFor } from './diff_calculation_helpers'; -import { simpleDiffAlgorithm } from './algorithms/simple_diff_algorithm'; +import { + numberDiffAlgorithm, + simpleDiffAlgorithm, + singleLineStringDiffAlgorithm, +} from './algorithms'; const BASE_TYPE_ERROR = `Base version can't be of different rule type`; const TARGET_TYPE_ERROR = `Target version can't be of different rule type`; @@ -168,14 +172,14 @@ const calculateCommonFieldsDiff = ( const commonFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { rule_id: simpleDiffAlgorithm, - version: simpleDiffAlgorithm, + version: numberDiffAlgorithm, meta: simpleDiffAlgorithm, - name: simpleDiffAlgorithm, + name: singleLineStringDiffAlgorithm, tags: simpleDiffAlgorithm, description: simpleDiffAlgorithm, - severity: simpleDiffAlgorithm, + severity: singleLineStringDiffAlgorithm, severity_mapping: simpleDiffAlgorithm, - risk_score: simpleDiffAlgorithm, + risk_score: numberDiffAlgorithm, risk_score_mapping: simpleDiffAlgorithm, references: simpleDiffAlgorithm, false_positives: simpleDiffAlgorithm, @@ -185,12 +189,12 @@ const commonFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor related_integrations: simpleDiffAlgorithm, required_fields: simpleDiffAlgorithm, author: simpleDiffAlgorithm, - license: simpleDiffAlgorithm, + license: singleLineStringDiffAlgorithm, rule_schedule: simpleDiffAlgorithm, actions: simpleDiffAlgorithm, throttle: simpleDiffAlgorithm, exceptions_list: simpleDiffAlgorithm, - max_signals: simpleDiffAlgorithm, + max_signals: numberDiffAlgorithm, rule_name_override: simpleDiffAlgorithm, timestamp_override: simpleDiffAlgorithm, timeline_template: simpleDiffAlgorithm, @@ -233,9 +237,9 @@ const eqlFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor = { type: simpleDiffAlgorithm, eql_query: simpleDiffAlgorithm, data_source: simpleDiffAlgorithm, - event_category_override: simpleDiffAlgorithm, - timestamp_field: simpleDiffAlgorithm, - tiebreaker_field: simpleDiffAlgorithm, + event_category_override: singleLineStringDiffAlgorithm, + timestamp_field: singleLineStringDiffAlgorithm, + tiebreaker_field: singleLineStringDiffAlgorithm, }; const calculateEsqlFieldsDiff = ( @@ -262,7 +266,7 @@ const threatMatchFieldsDiffAlgorithms: FieldsDiffAlgorithmsFor { return detectionRulesClient.createPrebuiltRule({ - ruleAsset: rule, + params: rule, }); }, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 82a40cbf71a41..2bd7efdbbdcf2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -11,6 +11,12 @@ import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils'; import { ruleTypeMappings } from '@kbn/securitysolution-rules'; import type { SanitizedRule, ResolvedSanitizedRule } from '@kbn/alerting-plugin/common'; +import type { + SetAlertsStatusByIds, + SetAlertsStatusByQuery, + SetAlertsStatusRequestBodyInput, + SearchAlertsRequestBody, +} from '../../../../../common/api/detection_engine/signals'; import { DETECTION_ENGINE_RULES_URL, DETECTION_ENGINE_SIGNALS_STATUS_URL, @@ -36,10 +42,7 @@ import { } from '../../../../../common/api/detection_engine/rule_management/mocks'; import { getCreateRulesSchemaMock } from '../../../../../common/api/detection_engine/model/rule_schema/mocks'; -import type { - QuerySignalsSchemaDecoded, - SetSignalsStatusSchemaDecoded, -} from '../../../../../common/api/detection_engine/signals'; + import { getFinalizeSignalsMigrationSchemaMock, getSignalsMigrationStatusSchemaMock, @@ -53,26 +56,28 @@ import { getQueryRuleParams } from '../../rule_schema/mocks'; import { requestMock } from './request'; import type { HapiReadableStream } from '../../../../types'; -export const typicalSetStatusSignalByIdsPayload = (): SetSignalsStatusSchemaDecoded => ({ +export const typicalSetStatusSignalByIdsPayload = (): SetAlertsStatusByIds => ({ signal_ids: ['somefakeid1', 'somefakeid2'], status: 'closed', }); -export const typicalSetStatusSignalByQueryPayload = (): SetSignalsStatusSchemaDecoded => ({ +export const typicalSetStatusSignalByQueryPayload = (): SetAlertsStatusByQuery => ({ query: { bool: { filter: { range: { '@timestamp': { gte: 'now-2M', lte: 'now/M' } } } } }, status: 'closed', + conflicts: 'abort', }); -export const typicalSignalsQuery = (): QuerySignalsSchemaDecoded => ({ +export const typicalSignalsQuery = (): SearchAlertsRequestBody => ({ aggs: {}, query: { match_all: {} }, }); -export const typicalSignalsQueryAggs = (): QuerySignalsSchemaDecoded => ({ +export const typicalSignalsQueryAggs = (): SearchAlertsRequestBody => ({ aggs: { statuses: { terms: { field: ALERT_WORKFLOW_STATUS, size: 10 } } }, }); -export const setStatusSignalMissingIdsAndQueryPayload = (): SetSignalsStatusSchemaDecoded => ({ +// @ts-expect-error data with missing required fields +export const setStatusSignalMissingIdsAndQueryPayload = (): SetAlertsStatusRequestBodyInput => ({ status: 'closed', }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.test.ts index e48cd6ff2dab1..5d81134325c50 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.test.ts @@ -7,7 +7,6 @@ import { requestMock, serverMock } from '../__mocks__'; import type { SetupPlugins } from '../../../../plugin'; -import type { SignalsReindexOptions } from '../../../../../common/api/detection_engine/signals_migration'; import { DETECTION_ENGINE_SIGNALS_MIGRATION_URL } from '../../../../../common/constants'; import { getCreateSignalsMigrationSchemaMock } from '../../../../../common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration_route.mock'; import { getIndexVersionsByIndex } from '../../migrations/get_index_versions_by_index'; @@ -17,6 +16,7 @@ import { getIndexAliases } from '@kbn/securitysolution-es-utils'; import { getTemplateVersion } from '../index/check_template_version'; import { createSignalsMigrationRoute } from './create_signals_migration_route'; import { SIGNALS_TEMPLATE_VERSION } from '../index/get_signals_template'; +import type { AlertsReindexOptions } from '../../../../../common/api/detection_engine/signals_migration'; jest.mock('../index/check_template_version'); jest.mock('@kbn/securitysolution-es-utils', () => { @@ -53,7 +53,7 @@ describe('creating signals migrations route', () => { }); it('passes options to the createMigration', async () => { - const reindexOptions: SignalsReindexOptions = { requests_per_second: 4, size: 10, slices: 2 }; + const reindexOptions: AlertsReindexOptions = { requests_per_second: 4, size: 10, slices: 2 }; const request = requestMock.create({ method: 'post', path: DETECTION_ENGINE_SIGNALS_MIGRATION_URL, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts index 198a1992058fa..b6e7ae696b755 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/create_signals_migration_route.ts @@ -6,11 +6,11 @@ */ import { transformError, BadRequestError, getIndexAliases } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { CreateAlertsMigrationRequestBody } from '../../../../../common/api/detection_engine/signals_migration'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import type { SetupPlugins } from '../../../../plugin'; import { DETECTION_ENGINE_SIGNALS_MIGRATION_URL } from '../../../../../common/constants'; -import { createSignalsMigrationSchema } from '../../../../../common/api/detection_engine/signals_migration'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { buildSiemResponse } from '../utils'; import { getTemplateVersion } from '../index/check_template_version'; @@ -35,7 +35,9 @@ export const createSignalsMigrationRoute = ( .addVersion( { version: '2023-10-31', - validate: { request: { body: buildRouteValidation(createSignalsMigrationSchema) } }, + validate: { + request: { body: buildRouteValidationWithZod(CreateAlertsMigrationRequestBody) }, + }, }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts index 4a4a38e074c0b..f4452c73eaf78 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/delete_signals_migration_route.ts @@ -6,11 +6,11 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { AlertsMigrationCleanupRequestBody } from '../../../../../common/api/detection_engine/signals_migration'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import type { SetupPlugins } from '../../../../plugin'; import { DETECTION_ENGINE_SIGNALS_MIGRATION_URL } from '../../../../../common/constants'; -import { deleteSignalsMigrationSchema } from '../../../../../common/api/detection_engine/signals_migration'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { buildSiemResponse } from '../utils'; import { signalsMigrationService } from '../../migrations/migration_service'; @@ -31,7 +31,9 @@ export const deleteSignalsMigrationRoute = ( .addVersion( { version: '2023-10-31', - validate: { request: { body: buildRouteValidation(deleteSignalsMigrationSchema) } }, + validate: { + request: { body: buildRouteValidationWithZod(AlertsMigrationCleanupRequestBody) }, + }, }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts index 12d6520fa52d1..468baae7fdcad 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/finalize_signals_migration_route.ts @@ -7,11 +7,11 @@ import { transformError, BadRequestError } from '@kbn/securitysolution-es-utils'; import type { RuleDataPluginService } from '@kbn/rule-registry-plugin/server'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { FinalizeAlertsMigrationRequestBody } from '../../../../../common/api/detection_engine/signals_migration'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import type { SetupPlugins } from '../../../../plugin'; import { DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL } from '../../../../../common/constants'; -import { finalizeSignalsMigrationSchema } from '../../../../../common/api/detection_engine/signals_migration'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { isMigrationFailed, isMigrationPending } from '../../migrations/helpers'; import { signalsMigrationService } from '../../migrations/migration_service'; import { buildSiemResponse } from '../utils'; @@ -34,7 +34,9 @@ export const finalizeSignalsMigrationRoute = ( .addVersion( { version: '2023-10-31', - validate: { request: { body: buildRouteValidation(finalizeSignalsMigrationSchema) } }, + validate: { + request: { body: buildRouteValidationWithZod(FinalizeAlertsMigrationRequestBody) }, + }, }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/get_signals_migration_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/get_signals_migration_status_route.ts index dbf6d97d4b72b..418db17b566c5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/get_signals_migration_status_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/get_signals_migration_status_route.ts @@ -6,10 +6,10 @@ */ import { transformError, getIndexAliases } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { GetAlertsMigrationStatusRequestQuery } from '../../../../../common/api/detection_engine/signals_migration'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_SIGNALS_MIGRATION_STATUS_URL } from '../../../../../common/constants'; -import { getSignalsMigrationStatusSchema } from '../../../../../common/api/detection_engine/signals_migration'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { getIndexVersionsByIndex } from '../../migrations/get_index_versions_by_index'; import { getMigrationSavedObjectsByIndex } from '../../migrations/get_migration_saved_objects_by_index'; import { getSignalsIndicesInRange } from '../../migrations/get_signals_indices_in_range'; @@ -30,7 +30,9 @@ export const getSignalsMigrationStatusRoute = (router: SecuritySolutionPluginRou .addVersion( { version: '2023-10-31', - validate: { request: { query: buildRouteValidation(getSignalsMigrationStatusSchema) } }, + validate: { + request: { query: buildRouteValidationWithZod(GetAlertsMigrationStatusRequestQuery) }, + }, }, async (context, request, response) => { const siemResponse = buildSiemResponse(response); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts index ece3b64ad2fbf..1b2bdb6ca1ef4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts @@ -150,12 +150,10 @@ describe('set signal status', () => { path: DETECTION_ENGINE_SIGNALS_STATUS_URL, body: setStatusSignalMissingIdsAndQueryPayload(), }); - const response = await server.inject(request, requestContextMock.convertContext(context)); - expect(response.status).toEqual(400); - expect(response.body).toEqual({ - message: ['either "signal_ids" or "query" must be set'], - status_code: 400, - }); + + const result = server.validate(request); + + expect(result.badRequest).toHaveBeenCalled(); }); test('rejects if signal_ids but no status', async () => { @@ -167,9 +165,7 @@ describe('set signal status', () => { }); const result = server.validate(request); - expect(result.badRequest).toHaveBeenCalledWith( - 'Invalid value "undefined" supplied to "status"' - ); + expect(result.badRequest).toHaveBeenCalled(); }); test('rejects if query but no status', async () => { @@ -181,9 +177,7 @@ describe('set signal status', () => { }); const result = server.validate(request); - expect(result.badRequest).toHaveBeenCalledWith( - 'Invalid value "undefined" supplied to "status"' - ); + expect(result.badRequest).toHaveBeenCalled(); }); test('rejects if query and signal_ids but no status', async () => { @@ -199,9 +193,7 @@ describe('set signal status', () => { }); const result = server.validate(request); - expect(result.badRequest).toHaveBeenCalledWith( - 'Invalid value "undefined" supplied to "status"' - ); + expect(result.badRequest).toHaveBeenCalled(); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts index 43a1951bea4c5..5db7cc16b05ec 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts @@ -14,11 +14,8 @@ import { } from '@kbn/rule-data-utils'; import type { ElasticsearchClient, Logger, StartServicesAccessor } from '@kbn/core/server'; import type { AuthenticatedUser } from '@kbn/security-plugin/common'; -import { - setSignalStatusValidateTypeDependents, - setSignalsStatusSchema, -} from '../../../../../common/api/detection_engine/signals'; -import type { SetSignalsStatusSchemaDecoded } from '../../../../../common/api/detection_engine/signals'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { SetAlertsStatusRequestBody } from '../../../../../common/api/detection_engine/signals'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DEFAULT_ALERTS_INDEX, @@ -28,7 +25,6 @@ import { buildSiemResponse } from '../utils'; import type { ITelemetryEventsSender } from '../../../telemetry/sender'; import { INSIGHTS_CHANNEL } from '../../../telemetry/constants'; import type { StartPlugins } from '../../../../plugin'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { getSessionIDfromKibanaRequest, createAlertStatusPayloads, @@ -53,27 +49,19 @@ export const setSignalsStatusRoute = ( version: '2023-10-31', validate: { request: { - body: buildRouteValidation< - typeof setSignalsStatusSchema, - SetSignalsStatusSchemaDecoded - >(setSignalsStatusSchema), + body: buildRouteValidationWithZod(SetAlertsStatusRequestBody), }, }, }, async (context, request, response) => { - const { conflicts, signal_ids: signalIds, query, status } = request.body; + const { status } = request.body; const core = await context.core; const securitySolution = await context.securitySolution; const esClient = core.elasticsearch.client.asCurrentUser; const siemClient = securitySolution?.getAppClient(); const siemResponse = buildSiemResponse(response); - const validationErrors = setSignalStatusValidateTypeDependents(request.body); const spaceId = securitySolution?.getSpaceId() ?? 'default'; - if (validationErrors.length) { - return siemResponse.error({ statusCode: 400, body: validationErrors }); - } - if (!siemClient) { return siemResponse.error({ statusCode: 404 }); } @@ -85,9 +73,13 @@ export const setSignalsStatusRoute = ( sender.isTelemetryOptedIn(), security?.authc.getCurrentUser(request)?.username, ]); + if (isTelemetryOptedIn && clusterId) { // Sometimes the ids are in the query not passed in the request? - const toSendAlertIds = get(query, 'bool.filter.terms._id') || signalIds; + const toSendAlertIds = + 'signal_ids' in request.body + ? request.body.signal_ids + : (get(request.body.query, 'bool.filter.terms._id') as string[]); // Get Context for Insights Payloads const sessionId = getSessionIDfromKibanaRequest(clusterId, request); if (username && toSendAlertIds && sessionId && status) { @@ -105,10 +97,15 @@ export const setSignalsStatusRoute = ( } try { - if (signalIds) { + if ('signal_ids' in request.body) { + const { signal_ids: signalIds } = request.body; + const body = await updateSignalsStatusByIds(status, signalIds, spaceId, esClient, user); + return response.ok({ body }); } else { + const { conflicts, query } = request.body; + const body = await updateSignalsStatusByQuery( status, query, @@ -117,6 +114,7 @@ export const setSignalsStatusRoute = ( esClient, user ); + return response.ok({ body }); } } catch (err) { @@ -132,7 +130,7 @@ export const setSignalsStatusRoute = ( }; const updateSignalsStatusByIds = async ( - status: SetSignalsStatusSchemaDecoded['status'], + status: SetAlertsStatusRequestBody['status'], signalsId: string[], spaceId: string, esClient: ElasticsearchClient, @@ -158,7 +156,7 @@ const updateSignalsStatusByIds = async ( * This method calls `updateByQuery` with `refresh: true` which is expensive on serverless. */ const updateSignalsStatusByQuery = async ( - status: SetSignalsStatusSchemaDecoded['status'], + status: SetAlertsStatusRequestBody['status'], query: object | undefined, options: { conflicts: 'abort' | 'proceed' }, spaceId: string, @@ -181,7 +179,7 @@ const updateSignalsStatusByQuery = async ( }); const getUpdateSignalStatusScript = ( - status: SetSignalsStatusSchemaDecoded['status'], + status: SetAlertsStatusRequestBody['status'], user: AuthenticatedUser | null ) => ({ source: `if (ctx._source['${ALERT_WORKFLOW_STATUS}'] != null && ctx._source['${ALERT_WORKFLOW_STATUS}'] != '${status}') { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts index 3bd52ebdaacda..60e0bde69c590 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts @@ -8,13 +8,12 @@ import type { MappingRuntimeFields, Sort } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { transformError } from '@kbn/securitysolution-es-utils'; import type { IRuleDataClient } from '@kbn/rule-registry-plugin/server'; +import type { AggregationsAggregationContainer } from '@elastic/elasticsearch/lib/api/types'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { SearchAlertsRequestBody } from '../../../../../common/api/detection_engine/signals'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '../../../../../common/constants'; import { buildSiemResponse } from '../utils'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; - -import type { QuerySignalsSchemaDecoded } from '../../../../../common/api/detection_engine/signals'; -import { querySignalsSchema } from '../../../../../common/api/detection_engine/signals'; export const querySignalsRoute = ( router: SecuritySolutionPluginRouter, @@ -33,9 +32,7 @@ export const querySignalsRoute = ( version: '2023-10-31', validate: { request: { - body: buildRouteValidation( - querySignalsSchema - ), + body: buildRouteValidationWithZod(SearchAlertsRequestBody), }, }, }, @@ -67,8 +64,7 @@ export const querySignalsRoute = ( index: indexPattern, body: { query, - // Note: I use a spread operator to please TypeScript with aggs: { ...aggs } - aggs: { ...aggs }, + aggs: aggs as Record, _source, fields, track_total_hits, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_assignees_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_assignees_route.ts index f15342a36f46c..1ce791143705b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_assignees_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_assignees_route.ts @@ -7,6 +7,7 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import { uniq } from 'lodash/fp'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { SetAlertAssigneesRequestBody } from '../../../../../common/api/detection_engine/alert_assignees'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { @@ -14,7 +15,6 @@ import { DETECTION_ENGINE_ALERT_ASSIGNEES_URL, } from '../../../../../common/constants'; import { buildSiemResponse } from '../utils'; -import { buildRouteValidationWithZod } from '../../../../utils/build_validation/route_validation'; import { validateAlertAssigneesArrays } from './helpers'; export const setAlertAssigneesRoute = (router: SecuritySolutionPluginRouter) => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_tags_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_tags_route.test.ts index ab9d79c53fc3f..eaeae10d26471 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_tags_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_tags_route.test.ts @@ -75,20 +75,9 @@ describe('setAlertTagsRoute', () => { body: getSetAlertTagsRequestMock(['tag-1'], ['tag-2']), }); - context.core.elasticsearch.client.asCurrentUser.updateByQuery.mockResponse( - getSuccessfulSignalUpdateResponse() - ); - - const response = await server.inject(request, requestContextMock.convertContext(context)); - - context.core.elasticsearch.client.asCurrentUser.updateByQuery.mockRejectedValue( - new Error('Test error') - ); + const result = server.validate(request); - expect(response.body).toEqual({ - message: [`No alert ids were provided`], - status_code: 400, - }); + expect(result.badRequest).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_tags_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_tags_route.ts index 8b7e81f9bf812..c6997cc9a9bed 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_tags_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/set_alert_tags_route.ts @@ -7,15 +7,14 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import { uniq } from 'lodash/fp'; -import type { SetAlertTagsRequestBodyDecoded } from '../../../../../common/api/detection_engine/alert_tags'; -import { setAlertTagsRequestBody } from '../../../../../common/api/detection_engine/alert_tags'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { ManageAlertTagsRequestBody } from '../../../../../common/api/detection_engine/alert_tags'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DEFAULT_ALERTS_INDEX, DETECTION_ENGINE_ALERT_TAGS_URL, } from '../../../../../common/constants'; import { buildSiemResponse } from '../utils'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { validateAlertTagsArrays } from './helpers'; export const setAlertTagsRoute = (router: SecuritySolutionPluginRouter) => { @@ -32,10 +31,7 @@ export const setAlertTagsRoute = (router: SecuritySolutionPluginRouter) => { version: '2023-10-31', validate: { request: { - body: buildRouteValidation< - typeof setAlertTagsRequestBody, - SetAlertTagsRequestBodyDecoded - >(setAlertTagsRequestBody), + body: buildRouteValidationWithZod(ManageAlertTagsRequestBody), }, }, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/users/suggest_user_profiles_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/users/suggest_user_profiles_route.ts index 76787a82b7799..1b1aeada05660 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/users/suggest_user_profiles_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/users/suggest_user_profiles_route.ts @@ -8,11 +8,11 @@ import type { IKibanaResponse, StartServicesAccessor } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; import type { UserProfileWithAvatar } from '@kbn/user-profile-components'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_ALERT_SUGGEST_USERS_URL } from '../../../../../common/constants'; import { buildSiemResponse } from '../utils'; import type { StartPlugins } from '../../../../plugin'; -import { buildRouteValidationWithZod } from '../../../../utils/build_validation/route_validation'; import { SuggestUserProfilesRequestQuery } from '../../../../../common/api/detection_engine/users'; export const suggestUserProfilesRoute = ( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts index da0bc90c9e4df..cbc3e006a4fea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts @@ -10,6 +10,7 @@ import type { BulkError } from './utils'; import { transformBulkError, convertToSnakeCase, SiemResponseFactory } from './utils'; import { responseMock } from './__mocks__'; import { CustomHttpRequestError } from '../../../utils/custom_http_request_error'; +import { RuleResponseValidationError } from '../rule_management/logic/detection_rules_client/utils'; describe('utils', () => { describe('transformBulkError', () => { @@ -59,6 +60,24 @@ describe('utils', () => { }; expect(transformed).toEqual(expected); }); + + test('it detects a RuleResponseValidationError and returns an error status of 500', () => { + const error = new RuleResponseValidationError({ + ruleId: 'rule-1', + message: 'name: Required', + }); + + const expected: BulkError = { + rule_id: 'rule-1', + error: { message: 'name: Required', status_code: 500 }, + }; + + /* Works when the ruleId is passed in. For example, when creating a rule with a user-set ruleId */ + expect(transformBulkError('rule-1', error)).toEqual(expected); + + /* Works when the ruleId is not passed in. For example, when creating a rule with a generated ruleId */ + expect(transformBulkError(undefined, error)).toEqual(expected); + }); }); describe('convertToSnakeCase', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts index 5b0e859052500..0e08374b1f927 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts @@ -17,6 +17,7 @@ import type { } from '@kbn/core/server'; import { CustomHttpRequestError } from '../../../utils/custom_http_request_error'; +import { RuleResponseValidationError } from '../rule_management/logic/detection_rules_client/utils'; export interface OutputError { message: string; @@ -116,6 +117,12 @@ export const transformBulkError = ( statusCode: 400, message: err.message, }); + } else if (err instanceof RuleResponseValidationError) { + return createBulkErrorObject({ + ruleId: err.ruleId, + statusCode: 500, + message: err.message, + }); } else { return createBulkErrorObject({ ruleId, 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 ff608a6344057..1177d4363fe29 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 @@ -8,6 +8,7 @@ import type { IKibanaResponse, Logger } from '@kbn/core/server'; import { AbortError } from '@kbn/kibana-utils-plugin/common'; import { transformError } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { ConfigType } from '../../../../../../config'; import type { PerformBulkActionResponse } from '../../../../../../../common/api/detection_engine/rule_management'; import { @@ -22,7 +23,6 @@ import { } from '../../../../../../../common/constants'; import type { SetupPlugins } from '../../../../../../plugin'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; -import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import { initPromisePool } from '../../../../../../utils/promise_pool'; import { routeLimitedConcurrencyTag } from '../../../../../../utils/route_limited_concurrency_tag'; import { buildMlAuthz } from '../../../../../machine_learning/authz'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.test.ts index 21ee4116edc2c..d6403f0d1553d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.test.ts @@ -22,6 +22,7 @@ import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-m import { getQueryRuleParams } from '../../../../rule_schema/mocks'; import { loggingSystemMock } from '@kbn/core/server/mocks'; import { HttpAuthzError } from '../../../../../machine_learning/validation'; +import { getRulesSchemaMock } from '../../../../../../../common/api/detection_engine/model/rule_schema/rule_response_schema.mock'; describe('Bulk create rules route', () => { let server: ReturnType; @@ -34,9 +35,7 @@ describe('Bulk create rules route', () => { clients.rulesClient.find.mockResolvedValue(getEmptyFindResult()); // no existing rules clients.rulesClient.create.mockResolvedValue(getRuleMock(getQueryRuleParams())); // successful creation - clients.detectionRulesClient.createCustomRule.mockResolvedValue( - getRuleMock(getQueryRuleParams()) - ); + clients.detectionRulesClient.createCustomRule.mockResolvedValue(getRulesSchemaMock()); context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse()) ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts index a8e5a0446c6e3..98910ea337630 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_create_rules/route.ts @@ -8,6 +8,7 @@ import type { IKibanaResponse, Logger } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { DETECTION_ENGINE_RULES_BULK_CREATE } from '../../../../../../../common/constants'; import { BulkCreateRulesRequestBody, @@ -18,8 +19,6 @@ import { import type { SecuritySolutionPluginRouter } from '../../../../../../types'; import { readRules } from '../../../logic/detection_rules_client/read_rules'; import { getDuplicates } from './get_duplicates'; -import { transformValidateBulkError } from '../../../utils/validate'; -import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import { validateRuleDefaultExceptionList } from '../../../logic/exceptions/validate_rule_default_exception_list'; import { validateRulesWithDuplicatedDefaultExceptionsList } from '../../../logic/exceptions/validate_rules_with_duplicated_default_exceptions_list'; import { RULE_MANAGEMENT_BULK_ACTION_SOCKET_TIMEOUT_MS } from '../../timeouts'; @@ -113,7 +112,7 @@ export const bulkCreateRulesRoute = (router: SecuritySolutionPluginRouter, logge params: payloadRule, }); - return transformValidateBulkError(createdRule.params.ruleId, createdRule); + return createdRule; } catch (err) { return transformBulkError( payloadRule.rule_id, @@ -122,6 +121,7 @@ export const bulkCreateRulesRoute = (router: SecuritySolutionPluginRouter, logge } }) ); + const rulesBulk = [ ...rules, ...dupes.map((ruleId) => diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts index 234439eb49052..2f1ef59d0971b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_delete_rules/route.ts @@ -8,6 +8,7 @@ import type { VersionedRouteConfig } from '@kbn/core-http-server'; import type { IKibanaResponse, Logger, RequestHandler } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { BulkCrudRulesResponse, BulkDeleteRulesRequestBody, @@ -18,7 +19,6 @@ import type { SecuritySolutionPluginRouter, SecuritySolutionRequestHandlerContext, } from '../../../../../../types'; -import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import { buildSiemResponse, createBulkErrorObject, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts index a3752d421380b..aa06c04db0619 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_patch_rules/route.ts @@ -8,13 +8,12 @@ import type { IKibanaResponse, Logger } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../../../../common/constants'; import { BulkPatchRulesRequestBody, BulkCrudRulesResponse, } from '../../../../../../../common/api/detection_engine/rule_management'; - -import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; import { transformBulkError, buildSiemResponse } from '../../../../routes/utils'; import { getIdBulkError } from '../../../utils/utils'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts index e08d781cd74d8..bae89a74ab0b1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_update_rules/route.ts @@ -6,15 +6,13 @@ */ import type { IKibanaResponse, Logger } from '@kbn/core/server'; - +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { transformError } from '@kbn/securitysolution-es-utils'; import { BulkUpdateRulesRequestBody, validateUpdateRuleProps, BulkCrudRulesResponse, } from '../../../../../../../common/api/detection_engine/rule_management'; - -import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../../../../common/constants'; import { getIdBulkError } from '../../../utils/utils'; 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 08a9f4e6c4769..1402518103e64 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 @@ -20,6 +20,7 @@ import { getCreateRulesSchemaMock } from '../../../../../../../common/api/detect import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { getQueryRuleParams } from '../../../../rule_schema/mocks'; import { HttpAuthzError } from '../../../../../machine_learning/validation'; +import { getRulesSchemaMock } from '../../../../../../../common/api/detection_engine/model/rule_schema/rule_response_schema.mock'; describe('Create rule route', () => { let server: ReturnType; @@ -31,9 +32,7 @@ describe('Create rule route', () => { clients.rulesClient.find.mockResolvedValue(getEmptyFindResult()); // no current rules clients.rulesClient.create.mockResolvedValue(getRuleMock(getQueryRuleParams())); // creation succeeds - clients.detectionRulesClient.createCustomRule.mockResolvedValue( - getRuleMock(getQueryRuleParams()) - ); + clients.detectionRulesClient.createCustomRule.mockResolvedValue(getRulesSchemaMock()); context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue( elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse()) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts index 03642aa12266b..aa6425b2e673c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts @@ -7,6 +7,7 @@ import type { IKibanaResponse } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { CreateRuleResponse } from '../../../../../../../common/api/detection_engine/rule_management'; import { CreateRuleRequestBody, @@ -14,12 +15,11 @@ import { } from '../../../../../../../common/api/detection_engine/rule_management'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; -import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import { buildSiemResponse } from '../../../../routes/utils'; import { readRules } from '../../../logic/detection_rules_client/read_rules'; import { checkDefaultRuleExceptionListReferences } from '../../../logic/exceptions/check_for_default_rule_exception_list'; import { validateRuleDefaultExceptionList } from '../../../logic/exceptions/validate_rule_default_exception_list'; -import { transformValidate, validateResponseActionsPermissions } from '../../../utils/validate'; +import { validateResponseActionsPermissions } from '../../../utils/validate'; export const createRuleRoute = (router: SecuritySolutionPluginRouter): void => { router.versioned @@ -94,7 +94,7 @@ export const createRuleRoute = (router: SecuritySolutionPluginRouter): void => { }); return response.ok({ - body: transformValidate(createdRule), + body: createdRule, }); } catch (err) { const error = transformError(err as Error); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts index 3cf9aa6a2fb55..42a6a1c47544f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/delete_rule/route.ts @@ -7,6 +7,7 @@ import type { IKibanaResponse } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { DeleteRuleResponse } from '../../../../../../../common/api/detection_engine/rule_management'; import { DeleteRuleRequestQuery, @@ -14,7 +15,6 @@ import { } from '../../../../../../../common/api/detection_engine/rule_management'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; -import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import { buildSiemResponse } from '../../../../routes/utils'; import { readRules } from '../../../logic/detection_rules_client/read_rules'; import { getIdError, transform } from '../../../utils/utils'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/export_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/export_rules/route.ts index 301bb62452f28..478a0ce02cc96 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/export_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/export_rules/route.ts @@ -7,14 +7,12 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import type { Logger } from '@kbn/core/server'; - +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; import { ExportRulesRequestBody, ExportRulesRequestQuery, } from '../../../../../../../common/api/detection_engine/rule_management'; - -import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; import type { ConfigType } from '../../../../../../config'; import { getNonPackagedRulesCount } from '../../../logic/search/get_existing_prepackaged_rules'; 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 3cbd164586a9d..02ff637ab6f10 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 @@ -7,7 +7,7 @@ import type { IKibanaResponse, Logger } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; - +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { DETECTION_ENGINE_RULES_URL_FIND } from '../../../../../../../common/constants'; import type { FindRulesResponse } from '../../../../../../../common/api/detection_engine/rule_management'; import { @@ -18,7 +18,6 @@ import { import type { SecuritySolutionPluginRouter } from '../../../../../../types'; import { findRules } from '../../../logic/search/find_rules'; import { buildSiemResponse } from '../../../../routes/utils'; -import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import { transformFindAlerts } from '../../../utils/utils'; export const findRulesRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts index 45379d3d17555..297a4cb587352 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts @@ -11,6 +11,7 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import { createPromiseFromStreams } from '@kbn/utils'; import { chunk } from 'lodash/fp'; import { extname } from 'path'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { ImportRulesRequestQuery, ImportRulesResponse, @@ -18,7 +19,6 @@ import { import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; import type { ConfigType } from '../../../../../../config'; import type { HapiReadableStream, SecuritySolutionPluginRouter } from '../../../../../../types'; -import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import type { BulkError, ImportRuleResponse } from '../../../../routes/utils'; import { buildSiemResponse, isBulkError, isImportRegular } from '../../../../routes/utils'; import { importRuleActionConnectors } from '../../../logic/import/action_connectors/import_rule_action_connectors'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts index 38b283b2c5fb6..506b36d4441dc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts @@ -7,6 +7,7 @@ import type { IKibanaResponse } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { PatchRuleResponse } from '../../../../../../../common/api/detection_engine/rule_management'; import { PatchRuleRequestBody, @@ -14,7 +15,6 @@ import { } from '../../../../../../../common/api/detection_engine/rule_management'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; -import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import { buildSiemResponse } from '../../../../routes/utils'; import { readRules } from '../../../logic/detection_rules_client/read_rules'; import { checkDefaultRuleExceptionListReferences } from '../../../logic/exceptions/check_for_default_rule_exception_list'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts index 95992618b0625..a119d1afae912 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/read_rule/route.ts @@ -7,6 +7,7 @@ import type { IKibanaResponse, Logger } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { ReadRuleResponse } from '../../../../../../../common/api/detection_engine/rule_management'; import { ReadRuleRequestQuery, @@ -14,7 +15,6 @@ import { } from '../../../../../../../common/api/detection_engine/rule_management'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; -import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import { buildSiemResponse } from '../../../../routes/utils'; import { readRules } from '../../../logic/detection_rules_client/read_rules'; import { getIdError, transform } from '../../../utils/utils'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts index 5ac2df600908c..5e77fa64e1fb9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts @@ -7,6 +7,7 @@ import type { IKibanaResponse } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { UpdateRuleResponse } from '../../../../../../../common/api/detection_engine/rule_management'; import { UpdateRuleRequestBody, @@ -14,7 +15,6 @@ import { } from '../../../../../../../common/api/detection_engine/rule_management'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../../../common/constants'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; -import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import { buildSiemResponse } from '../../../../routes/utils'; import { readRules } from '../../../logic/detection_rules_client/read_rules'; import { checkDefaultRuleExceptionListReferences } from '../../../logic/exceptions/check_for_default_rule_exception_list'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.create_custom_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.create_custom_rule.test.ts index a8335108d7cbe..1cf4afecedb26 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.create_custom_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.create_custom_rule.test.ts @@ -12,11 +12,15 @@ import { getCreateMachineLearningRulesSchemaMock, getCreateThreatMatchRulesSchemaMock, } from '../../../../../../common/api/detection_engine/model/rule_schema/mocks'; +import { getRuleMock } from '../../../routes/__mocks__/request_responses'; +import { getQueryRuleParams } from '../../../rule_schema/mocks'; import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../../../common/constants'; import { buildMlAuthz } from '../../../../machine_learning/authz'; import { throwAuthzError } from '../../../../machine_learning/validation'; import { createDetectionRulesClient } from './detection_rules_client'; import type { IDetectionRulesClient } from './detection_rules_client_interface'; +import { RuleResponseValidationError } from './utils'; +import type { RuleAlertType } from '../../../rule_schema'; jest.mock('../../../../machine_learning/authz'); jest.mock('../../../../machine_learning/validation'); @@ -29,7 +33,10 @@ describe('DetectionRulesClient.createCustomRule', () => { beforeEach(() => { jest.resetAllMocks(); + rulesClient = rulesClientMock.create(); + rulesClient.create.mockResolvedValue(getRuleMock(getQueryRuleParams())); + detectionRulesClient = createDetectionRulesClient(rulesClient, mlAuthz); }); @@ -63,6 +70,20 @@ describe('DetectionRulesClient.createCustomRule', () => { expect(rulesClient.create).not.toHaveBeenCalled(); }); + it('throws if RuleResponse validation fails', async () => { + const internalRuleMock: RuleAlertType = getRuleMock({ + ...getQueryRuleParams(), + /* Casting as 'query' suppress to TS error */ + type: 'fake-non-existent-type' as 'query', + }); + + rulesClient.create.mockResolvedValueOnce(internalRuleMock); + + await expect( + detectionRulesClient.createCustomRule({ params: getCreateMachineLearningRulesSchemaMock() }) + ).rejects.toThrow(RuleResponseValidationError); + }); + it('calls the rulesClient with legacy ML params', async () => { await detectionRulesClient.createCustomRule({ params: getCreateMachineLearningRulesSchemaMock(), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.create_prebuilt_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.create_prebuilt_rule.test.ts index d2a61dcc65e45..fd3ac991a968f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.create_prebuilt_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.create_prebuilt_rule.test.ts @@ -12,6 +12,8 @@ import { getCreateMachineLearningRulesSchemaMock, getCreateThreatMatchRulesSchemaMock, } from '../../../../../../common/api/detection_engine/model/rule_schema/mocks'; +import { getRuleMock } from '../../../routes/__mocks__/request_responses'; +import { getQueryRuleParams } from '../../../rule_schema/mocks'; import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../../../common/constants'; import { buildMlAuthz } from '../../../../machine_learning/authz'; import { throwAuthzError } from '../../../../machine_learning/validation'; @@ -29,22 +31,25 @@ describe('DetectionRulesClient.createPrebuiltRule', () => { beforeEach(() => { jest.resetAllMocks(); + rulesClient = rulesClientMock.create(); + rulesClient.create.mockResolvedValue(getRuleMock(getQueryRuleParams())); + detectionRulesClient = createDetectionRulesClient(rulesClient, mlAuthz); }); it('creates a rule with the correct parameters and options', async () => { - const ruleAsset = { ...getCreateRulesSchemaMock(), version: 1, rule_id: 'rule-id' }; + const params = { ...getCreateRulesSchemaMock(), version: 1, rule_id: 'rule-id' }; - await detectionRulesClient.createPrebuiltRule({ ruleAsset }); + await detectionRulesClient.createPrebuiltRule({ params }); expect(rulesClient.create).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ enabled: false, - name: ruleAsset.name, + name: params.name, params: expect.objectContaining({ - ruleId: ruleAsset.rule_id, + ruleId: params.rule_id, immutable: true, }), }), @@ -53,12 +58,12 @@ describe('DetectionRulesClient.createPrebuiltRule', () => { }); it('throws if mlAuth fails', async () => { - const ruleAsset = { ...getCreateRulesSchemaMock(), version: 1, rule_id: 'rule-id' }; + const params = { ...getCreateRulesSchemaMock(), version: 1, rule_id: 'rule-id' }; (throwAuthzError as jest.Mock).mockImplementationOnce(() => { throw new Error('mocked MLAuth error'); }); - await expect(detectionRulesClient.createPrebuiltRule({ ruleAsset })).rejects.toThrow( + await expect(detectionRulesClient.createPrebuiltRule({ params })).rejects.toThrow( 'mocked MLAuth error' ); @@ -66,13 +71,13 @@ describe('DetectionRulesClient.createPrebuiltRule', () => { }); it('calls the rulesClient with legacy ML params', async () => { - const ruleAsset = { + const params = { ...getCreateMachineLearningRulesSchemaMock(), version: 1, rule_id: 'rule-id', }; await detectionRulesClient.createPrebuiltRule({ - ruleAsset, + params, }); expect(rulesClient.create).toHaveBeenCalledWith( @@ -80,8 +85,8 @@ describe('DetectionRulesClient.createPrebuiltRule', () => { data: expect.objectContaining({ enabled: false, params: expect.objectContaining({ - anomalyThreshold: ruleAsset.anomaly_threshold, - machineLearningJobId: [ruleAsset.machine_learning_job_id], + anomalyThreshold: params.anomaly_threshold, + machineLearningJobId: [params.machine_learning_job_id], immutable: true, }), }), @@ -90,14 +95,14 @@ describe('DetectionRulesClient.createPrebuiltRule', () => { }); it('calls the rulesClient with ML params', async () => { - const ruleAsset = { + const params = { ...getCreateMachineLearningRulesSchemaMock(), machine_learning_job_id: ['new_job_1', 'new_job_2'], version: 1, rule_id: 'rule-id', }; await detectionRulesClient.createPrebuiltRule({ - ruleAsset, + params, }); expect(rulesClient.create).toHaveBeenCalledWith( @@ -115,11 +120,11 @@ describe('DetectionRulesClient.createPrebuiltRule', () => { }); it('populates a threatIndicatorPath value for threat_match rule if empty', async () => { - const ruleAsset = { ...getCreateThreatMatchRulesSchemaMock(), version: 1, rule_id: 'rule-id' }; - delete ruleAsset.threat_indicator_path; + const params = { ...getCreateThreatMatchRulesSchemaMock(), version: 1, rule_id: 'rule-id' }; + delete params.threat_indicator_path; await detectionRulesClient.createPrebuiltRule({ - ruleAsset, + params, }); expect(rulesClient.create).toHaveBeenCalledWith( @@ -136,8 +141,8 @@ describe('DetectionRulesClient.createPrebuiltRule', () => { }); it('does not populate a threatIndicatorPath value for other rules if empty', async () => { - const ruleAsset = { ...getCreateRulesSchemaMock(), version: 1, rule_id: 'rule-id' }; - await detectionRulesClient.createPrebuiltRule({ ruleAsset }); + const params = { ...getCreateRulesSchemaMock(), version: 1, rule_id: 'rule-id' }; + await detectionRulesClient.createPrebuiltRule({ params }); expect(rulesClient.create).not.toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.ts index 7256441697e65..c26649604b282 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client.ts @@ -9,6 +9,7 @@ import type { RulesClient } from '@kbn/alerting-plugin/server'; import type { MlAuthz } from '../../../../machine_learning/authz'; import type { RuleAlertType } from '../../../rule_schema'; +import type { RuleResponse } from '../../../../../../common/api/detection_engine/model/rule_schema'; import type { IDetectionRulesClient, CreateCustomRuleArgs, @@ -34,13 +35,13 @@ export const createDetectionRulesClient = ( rulesClient: RulesClient, mlAuthz: MlAuthz ): IDetectionRulesClient => ({ - async createCustomRule(args: CreateCustomRuleArgs): Promise { + async createCustomRule(args: CreateCustomRuleArgs): Promise { return withSecuritySpan('DetectionRulesClient.createCustomRule', async () => { return createCustomRule(rulesClient, args, mlAuthz); }); }, - async createPrebuiltRule(args: CreatePrebuiltRuleArgs): Promise { + async createPrebuiltRule(args: CreatePrebuiltRuleArgs): Promise { return withSecuritySpan('DetectionRulesClient.createPrebuiltRule', async () => { return createPrebuiltRule(rulesClient, args, mlAuthz); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client_interface.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client_interface.ts index d99b0b9c2cbd0..2d9c787119cdf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client_interface.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/detection_rules_client_interface.ts @@ -5,26 +5,25 @@ * 2.0. */ -import type { RuleCreateProps } from '../../../../../../common/api/detection_engine/model/rule_schema'; -import type { PrebuiltRuleAsset } from '../../../prebuilt_rules'; import type { + RuleCreateProps, RuleUpdateProps, RulePatchProps, RuleObjectId, RuleToImport, + RuleResponse, } from '../../../../../../common/api/detection_engine'; import type { RuleAlertType } from '../../../rule_schema'; +import type { PrebuiltRuleAsset } from '../../../prebuilt_rules'; export interface IDetectionRulesClient { - createCustomRule: (createCustomRulePayload: CreateCustomRuleArgs) => Promise; - createPrebuiltRule: (createPrebuiltRulePayload: CreatePrebuiltRuleArgs) => Promise; - updateRule: (updateRulePayload: UpdateRuleArgs) => Promise; - patchRule: (patchRulePayload: PatchRuleArgs) => Promise; - deleteRule: (deleteRulePayload: DeleteRuleArgs) => Promise; - upgradePrebuiltRule: ( - upgradePrebuiltRulePayload: UpgradePrebuiltRuleArgs - ) => Promise; - importRule: (importRulePayload: ImportRuleArgs) => Promise; + createCustomRule: (args: CreateCustomRuleArgs) => Promise; + createPrebuiltRule: (args: CreatePrebuiltRuleArgs) => Promise; + updateRule: (args: UpdateRuleArgs) => Promise; + patchRule: (args: PatchRuleArgs) => Promise; + deleteRule: (args: DeleteRuleArgs) => Promise; + upgradePrebuiltRule: (args: UpgradePrebuiltRuleArgs) => Promise; + importRule: (args: ImportRuleArgs) => Promise; } export interface CreateCustomRuleArgs { @@ -32,7 +31,7 @@ export interface CreateCustomRuleArgs { } export interface CreatePrebuiltRuleArgs { - ruleAsset: PrebuiltRuleAsset; + params: RuleCreateProps; } export interface UpdateRuleArgs { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/create_custom_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/create_custom_rule.ts index c9b3b7b542a17..c77446f5baf63 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/create_custom_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/create_custom_rule.ts @@ -6,18 +6,20 @@ */ import type { RulesClient } from '@kbn/alerting-plugin/server'; +import { stringifyZodError } from '@kbn/zod-helpers'; import type { CreateCustomRuleArgs } from '../detection_rules_client_interface'; import type { MlAuthz } from '../../../../../machine_learning/authz'; -import type { RuleAlertType, RuleParams } from '../../../../rule_schema'; +import type { RuleParams } from '../../../../rule_schema'; +import { RuleResponse } from '../../../../../../../common/api/detection_engine/model/rule_schema'; import { convertCreateAPIToInternalSchema } from '../../../normalization/rule_converters'; - -import { validateMlAuth } from '../utils'; +import { transform } from '../../../utils/utils'; +import { validateMlAuth, RuleResponseValidationError } from '../utils'; export const createCustomRule = async ( rulesClient: RulesClient, args: CreateCustomRuleArgs, mlAuthz: MlAuthz -): Promise => { +): Promise => { const { params } = args; await validateMlAuth(mlAuthz, params.type); @@ -26,5 +28,15 @@ export const createCustomRule = async ( data: internalRule, }); - return rule; + /* Trying to convert the rule to a RuleResponse object */ + const parseResult = RuleResponse.safeParse(transform(rule)); + + if (!parseResult.success) { + throw new RuleResponseValidationError({ + message: stringifyZodError(parseResult.error), + ruleId: rule.params.ruleId, + }); + } + + return parseResult.data; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/create_prebuilt_rule.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/create_prebuilt_rule.ts index 0ada0197137a4..db510d9071c4f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/create_prebuilt_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/methods/create_prebuilt_rule.ts @@ -6,23 +6,25 @@ */ import type { RulesClient } from '@kbn/alerting-plugin/server'; +import { stringifyZodError } from '@kbn/zod-helpers'; import type { CreatePrebuiltRuleArgs } from '../detection_rules_client_interface'; import type { MlAuthz } from '../../../../../machine_learning/authz'; -import type { RuleAlertType, RuleParams } from '../../../../rule_schema'; +import { RuleResponse } from '../../../../../../../common/api/detection_engine/model/rule_schema'; +import type { RuleParams } from '../../../../rule_schema'; import { convertCreateAPIToInternalSchema } from '../../../normalization/rule_converters'; - -import { validateMlAuth } from '../utils'; +import { transform } from '../../../utils/utils'; +import { validateMlAuth, RuleResponseValidationError } from '../utils'; export const createPrebuiltRule = async ( rulesClient: RulesClient, args: CreatePrebuiltRuleArgs, mlAuthz: MlAuthz -): Promise => { - const { ruleAsset } = args; +): Promise => { + const { params } = args; - await validateMlAuth(mlAuthz, ruleAsset.type); + await validateMlAuth(mlAuthz, params.type); - const internalRule = convertCreateAPIToInternalSchema(ruleAsset, { + const internalRule = convertCreateAPIToInternalSchema(params, { immutable: true, defaultEnabled: false, }); @@ -31,5 +33,15 @@ export const createPrebuiltRule = async ( data: internalRule, }); - return rule; + /* Trying to convert the rule to a RuleResponse object */ + const parseResult = RuleResponse.safeParse(transform(rule)); + + if (!parseResult.success) { + throw new RuleResponseValidationError({ + message: stringifyZodError(parseResult.error), + ruleId: rule.params.ruleId, + }); + } + + return parseResult.data; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/utils.ts index 0173ba5aea370..624f86a49422a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/detection_rules_client/utils.ts @@ -5,12 +5,15 @@ * 2.0. */ +/* eslint-disable max-classes-per-file */ + import type { RulesClient } from '@kbn/alerting-plugin/server'; import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import type { MlAuthz } from '../../../../machine_learning/authz'; import type { RuleAlertType } from '../../../rule_schema'; +import type { RuleSignatureId } from '../../../../../../common/api/detection_engine/model/rule_schema/common_attributes.gen'; import { throwAuthzError } from '../../../../machine_learning/validation'; export const toggleRuleEnabledOnUpdate = async ( @@ -36,3 +39,19 @@ export class ClientError extends Error { this.statusCode = statusCode; } } + +/** + * Represents an error that occurred while validating a RuleResponse object. + * Includes the ruleId (rule signature id) of the rule that failed validation. + * Thrown when a rule does not match the RuleResponse schema. + * @param message - The error message + * @param ruleId - The rule signature id of the rule that failed validation + * @extends Error + */ +export class RuleResponseValidationError extends Error { + public readonly ruleId: RuleSignatureId; + constructor({ message, ruleId }: { message: string; ruleId: RuleSignatureId }) { + super(message); + this.ruleId = ruleId; + } +} 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 4a01a6550cabc..4e8001193b5c5 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 { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { buildSiemResponse } from '../../../../routes/utils'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.ts index 6c71652d27e34..bf3a9864260ac 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_monitoring/api/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.ts @@ -7,8 +7,8 @@ import type { IKibanaResponse } from '@kbn/core/server'; import { transformError } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { SecuritySolutionPluginRouter } from '../../../../../../types'; -import { buildRouteValidationWithZod } from '../../../../../../utils/build_validation/route_validation'; import { buildSiemResponse } from '../../../../routes/utils'; import type { GetRuleExecutionResultsResponse } from '../../../../../../../common/api/detection_engine/rule_monitoring'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts index 8c4137bf3d386..c2faa464b75da 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/route.ts @@ -19,7 +19,7 @@ import type { import { parseDuration, DISABLE_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common'; import type { ExecutorType } from '@kbn/alerting-plugin/server/types'; import type { Alert } from '@kbn/alerting-plugin/server'; - +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { DEFAULT_PREVIEW_INDEX, DETECTION_ENGINE_RULES_PREVIEW, @@ -40,7 +40,6 @@ import { createPreviewRuleExecutionLogger } from './preview_rule_execution_logge import { parseInterval } from '../../../rule_types/utils/utils'; import { buildMlAuthz } from '../../../../machine_learning/authz'; import { throwAuthzError } from '../../../../machine_learning/validation'; -import { buildRouteValidationWithZod } from '../../../../../utils/build_validation/route_validation'; import { routeLimitedConcurrencyTag } from '../../../../../utils/route_limited_concurrency_tag'; import type { SecuritySolutionPluginRouter } from '../../../../../types'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_scoped_cluster_client.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_scoped_cluster_client.ts index e813f1caf16ae..2327ea438189f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_scoped_cluster_client.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_preview/api/preview_rules/wrap_scoped_cluster_client.ts @@ -31,18 +31,47 @@ type WrapEsClientOpts = Omit esClient: ElasticsearchClient; }; +class WrappedScopedClusterClient implements IScopedClusterClient { + #asInternalUser?: ElasticsearchClient; + #asCurrentUser?: ElasticsearchClient; + #asSecondaryAuthUser?: ElasticsearchClient; + + constructor(private readonly opts: WrapScopedClusterClientOpts) {} + + public get asInternalUser() { + if (this.#asInternalUser === undefined) { + const { scopedClusterClient, ...rest } = this.opts; + this.#asInternalUser = wrapEsClient({ + ...rest, + esClient: scopedClusterClient.asInternalUser, + }); + } + return this.#asInternalUser; + } + public get asCurrentUser() { + if (this.#asCurrentUser === undefined) { + const { scopedClusterClient, ...rest } = this.opts; + this.#asCurrentUser = wrapEsClient({ + ...rest, + esClient: scopedClusterClient.asCurrentUser, + }); + } + return this.#asCurrentUser; + } + public get asSecondaryAuthUser() { + if (this.#asSecondaryAuthUser === undefined) { + const { scopedClusterClient, ...rest } = this.opts; + this.#asSecondaryAuthUser = wrapEsClient({ + ...rest, + esClient: scopedClusterClient.asSecondaryAuthUser, + }); + } + return this.#asSecondaryAuthUser; + } +} + export function wrapScopedClusterClient(opts: WrapScopedClusterClientOpts): IScopedClusterClient { - const { scopedClusterClient, ...rest } = opts; - return { - asInternalUser: wrapEsClient({ - ...rest, - esClient: scopedClusterClient.asInternalUser, - }), - asCurrentUser: wrapEsClient({ - ...rest, - esClient: scopedClusterClient.asCurrentUser, - }), - }; + return new WrappedScopedClusterClient(opts); } function wrapEsClient(opts: WrapEsClientOpts): ElasticsearchClient { 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 6fac2725754d5..4dccc9ad0aae7 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 @@ -14,7 +14,7 @@ import { responseActionsClientMock } from '../../../endpoint/services/actions/cl describe('ScheduleNotificationResponseActions', () => { const signalOne = { - agent: { id: 'agent-id-1' }, + agent: { id: 'agent-id-1', type: 'endpoint' }, _id: 'alert-id-1', user: { id: 'S-1-5-20' }, process: { @@ -23,7 +23,7 @@ describe('ScheduleNotificationResponseActions', () => { [ALERT_RULE_UUID]: 'rule-id-1', [ALERT_RULE_NAME]: 'rule-name-1', }; - const signalTwo = { agent: { id: 'agent-id-2' }, _id: 'alert-id-2' }; + const signalTwo = { agent: { id: 'agent-id-2', type: 'endpoint' }, _id: 'alert-id-2' }; const getSignals = () => [signalOne, signalTwo]; const osqueryActionMock = { @@ -210,5 +210,25 @@ describe('ScheduleNotificationResponseActions', () => { } ); }); + + it('should only attempt to send response actions to alerts from endpoint', async () => { + const signals = getSignals(); + signals.push({ agent: { id: '123-432', type: 'filebeat' }, _id: '1' }); + const responseActions: RuleResponseAction[] = [ + { + actionTypeId: ResponseActionTypesEnum['.endpoint'], + params: { + command: 'isolate', + comment: 'test process comment', + }, + }, + ]; + await scheduleNotificationResponseActions({ + signals, + responseActions, + }); + + expect(mockedResponseActionsClient.isolate).toHaveBeenCalledTimes(signals.length - 1); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/schedule_notification_response_actions.ts index d20995291b5f1..2fcf09d6cfbb4 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 @@ -37,9 +37,17 @@ export const getScheduleNotificationResponseActionsService = }); } if (responseAction.actionTypeId === ResponseActionTypesEnum['.endpoint']) { - await endpointResponseAction(responseAction, endpointAppContextService, { - alerts, - }); + // We currently support only automated response actions for Elastic Defend. This will + // need to be updated once we introduce support for other EDR systems. + // For an explanation of why this is needed, see this comment here: + // https://github.com/elastic/kibana/issues/180774#issuecomment-2139526239 + const alertsFromElasticDefend = alerts.filter((alert) => alert.agent.type === 'endpoint'); + + if (alertsFromElasticDefend.length > 0) { + await endpointResponseAction(responseAction, endpointAppContextService, { + alerts: alertsFromElasticDefend, + }); + } } }) ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/types.ts index d34d2eba9e5ab..e7317acfd7ca1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_response_actions/types.ts @@ -24,6 +24,7 @@ export type Alert = ParsedTechnicalFields & { export interface AlertAgent { id: string; name: string; + type: string; } export interface AlertWithAgent extends Alert { diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts index fd5c2921972f3..c5abf75b71478 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts @@ -10,7 +10,7 @@ import type { Logger, ElasticsearchClient } from '@kbn/core/server'; import { mappingFromFieldMap } from '@kbn/alerting-plugin/common'; import type { AuditLogger } from '@kbn/security-plugin-types-server'; import type { - AssetCriticalityCsvUploadResponse, + AssetCriticalityBulkUploadResponse, AssetCriticalityUpsert, } from '../../../../common/entity_analytics/asset_criticality/types'; import type { AssetCriticalityRecord } from '../../../../common/api/entity_analytics'; @@ -183,9 +183,9 @@ export class AssetCriticalityDataClient { recordsStream, flushBytes, retries, - }: BulkUpsertFromStreamOptions): Promise => { - const errors: AssetCriticalityCsvUploadResponse['errors'] = []; - const stats: AssetCriticalityCsvUploadResponse['stats'] = { + }: BulkUpsertFromStreamOptions): Promise => { + const errors: AssetCriticalityBulkUploadResponse['errors'] = []; + const stats: AssetCriticalityBulkUploadResponse['stats'] = { successful: 0, failed: 0, total: 0, diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/bulk_upload.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/bulk_upload.ts new file mode 100644 index 0000000000000..efc03a4adacd0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/bulk_upload.ts @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { Logger } from '@kbn/core/server'; +import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; +import { transformError } from '@kbn/securitysolution-es-utils'; +import { Readable } from 'node:stream'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import type { AssetCriticalityBulkUploadResponse } from '../../../../../common/api/entity_analytics'; +import { AssetCriticalityBulkUploadRequest } from '../../../../../common/api/entity_analytics'; +import type { ConfigType } from '../../../../config'; +import { + ASSET_CRITICALITY_PUBLIC_BULK_UPLOAD_URL, + APP_ID, + ENABLE_ASSET_CRITICALITY_SETTING, + API_VERSIONS, +} from '../../../../../common/constants'; +import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources'; +import { assertAdvancedSettingsEnabled } from '../../utils/assert_advanced_setting_enabled'; +import type { EntityAnalyticsRoutesDeps } from '../../types'; +import { AssetCriticalityAuditActions } from '../audit'; +import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; + +export const assetCriticalityPublicBulkUploadRoute = ( + router: EntityAnalyticsRoutesDeps['router'], + logger: Logger, + config: ConfigType +) => { + router.versioned + .post({ + access: 'public', + path: ASSET_CRITICALITY_PUBLIC_BULK_UPLOAD_URL, + options: { + tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + }, + }) + .addVersion( + { + version: API_VERSIONS.public.v1, + validate: { + request: { + body: buildRouteValidationWithZod(AssetCriticalityBulkUploadRequest), + }, + }, + }, + async (context, request, response) => { + const { errorRetries, maxBulkRequestBodySizeBytes } = + config.entityAnalytics.assetCriticality.csvUpload; + const { records } = request.body; + + const securitySolution = await context.securitySolution; + securitySolution.getAuditLogger()?.log({ + message: 'User attempted to assign many asset criticalities via bulk upload', + event: { + action: AssetCriticalityAuditActions.ASSET_CRITICALITY_BULK_UPDATE, + category: AUDIT_CATEGORY.DATABASE, + type: AUDIT_TYPE.CREATION, + outcome: AUDIT_OUTCOME.UNKNOWN, + }, + }); + + const start = new Date(); + const siemResponse = buildSiemResponse(response); + + try { + await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); + await checkAndInitAssetCriticalityResources(context, logger); + const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); + + const formattedRecords = records.map((record) => ({ + idField: record.id_field, + idValue: record.id_value, + criticalityLevel: record.criticality_level, + })); + + const recordsStream = Readable.from(formattedRecords, { objectMode: true }); + + const { errors, stats } = await assetCriticalityClient.bulkUpsertFromStream({ + recordsStream, + retries: errorRetries, + flushBytes: maxBulkRequestBodySizeBytes, + }); + const end = new Date(); + + const tookMs = end.getTime() - start.getTime(); + logger.debug( + `Asset criticality Bulk upload completed in ${tookMs}ms ${JSON.stringify(stats)}` + ); + + const resBody: AssetCriticalityBulkUploadResponse = { errors, stats }; + + return response.ok({ body: resBody }); + } catch (e) { + logger.error(`Error during asset criticality bulk upload: ${e}`); + const error = transformError(e); + return siemResponse.error({ + statusCode: error.statusCode, + body: error.message, + }); + } + } + ); +}; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts index 6f1d677d414c5..c7a0f07400cc8 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts @@ -4,79 +4,119 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { Logger } from '@kbn/core/server'; +import type { IKibanaResponse, KibanaResponseFactory, Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import type { SecuritySolutionRequestHandlerContext } from '../../../../types'; import { - ASSET_CRITICALITY_URL, + ASSET_CRITICALITY_PUBLIC_URL, + ASSET_CRITICALITY_INTERNAL_URL, APP_ID, ENABLE_ASSET_CRITICALITY_SETTING, + API_VERSIONS, } from '../../../../../common/constants'; import { DeleteAssetCriticalityRecord } from '../../../../../common/api/entity_analytics/asset_criticality'; -import { buildRouteValidationWithZod } from '../../../../utils/build_validation/route_validation'; import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources'; import { assertAdvancedSettingsEnabled } from '../../utils/assert_advanced_setting_enabled'; import type { EntityAnalyticsRoutesDeps } from '../../types'; import { AssetCriticalityAuditActions } from '../audit'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; -export const assetCriticalityDeleteRoute = ( + +type DeleteHandler = ( + context: SecuritySolutionRequestHandlerContext, + request: { + query: DeleteAssetCriticalityRecord; + }, + response: KibanaResponseFactory +) => Promise; + +const handler: (logger: Logger) => DeleteHandler = + (logger) => async (context, request, response) => { + const securitySolution = await context.securitySolution; + + securitySolution.getAuditLogger()?.log({ + message: 'User attempted to un-assign asset criticality from an entity', + event: { + action: AssetCriticalityAuditActions.ASSET_CRITICALITY_UNASSIGN, + category: AUDIT_CATEGORY.DATABASE, + type: AUDIT_TYPE.DELETION, + outcome: AUDIT_OUTCOME.UNKNOWN, + }, + }); + + const siemResponse = buildSiemResponse(response); + try { + await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); + await checkAndInitAssetCriticalityResources(context, logger); + + const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); + await assetCriticalityClient.delete( + { + idField: request.query.id_field, + idValue: request.query.id_value, + }, + request.query.refresh + ); + + return response.ok(); + } catch (e) { + const error = transformError(e); + + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, + }); + } + }; + +export const assetCriticalityInternalDeleteRoute = ( router: EntityAnalyticsRoutesDeps['router'], logger: Logger ) => { router.versioned .delete({ access: 'internal', - path: ASSET_CRITICALITY_URL, + path: ASSET_CRITICALITY_INTERNAL_URL, options: { tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], }, }) .addVersion( { - version: '1', + version: API_VERSIONS.internal.v1, validate: { request: { query: buildRouteValidationWithZod(DeleteAssetCriticalityRecord), }, }, }, - async (context, request, response) => { - const securitySolution = await context.securitySolution; + handler(logger) + ); +}; - securitySolution.getAuditLogger()?.log({ - message: 'User attempted to un-assign asset criticality from an entity', - event: { - action: AssetCriticalityAuditActions.ASSET_CRITICALITY_UNASSIGN, - category: AUDIT_CATEGORY.DATABASE, - type: AUDIT_TYPE.DELETION, - outcome: AUDIT_OUTCOME.UNKNOWN, +export const assetCriticalityPublicDeleteRoute = ( + router: EntityAnalyticsRoutesDeps['router'], + logger: Logger +) => { + router.versioned + .delete({ + access: 'public', + path: ASSET_CRITICALITY_PUBLIC_URL, + options: { + tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + }, + }) + .addVersion( + { + version: API_VERSIONS.public.v1, + validate: { + request: { + query: buildRouteValidationWithZod(DeleteAssetCriticalityRecord), }, - }); - - const siemResponse = buildSiemResponse(response); - try { - await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); - await checkAndInitAssetCriticalityResources(context, logger); - - const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); - await assetCriticalityClient.delete( - { - idField: request.query.id_field, - idValue: request.query.id_value, - }, - request.query.refresh - ); - - return response.ok(); - } catch (e) { - const error = transformError(e); - - return siemResponse.error({ - statusCode: error.statusCode, - body: { message: error.message, full_error: JSON.stringify(e) }, - bypassErrorFormat: true, - }); - } - } + }, + }, + handler(logger) ); }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts index fd2a826df117a..07d0cb3098dbc 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/get.ts @@ -4,79 +4,117 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { Logger } from '@kbn/core/server'; +import type { IKibanaResponse, KibanaResponseFactory, Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import type { SecuritySolutionRequestHandlerContext } from '../../../../types'; import { - ASSET_CRITICALITY_URL, + ASSET_CRITICALITY_INTERNAL_URL, + ASSET_CRITICALITY_PUBLIC_URL, APP_ID, ENABLE_ASSET_CRITICALITY_SETTING, + API_VERSIONS, } from '../../../../../common/constants'; import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources'; -import { buildRouteValidationWithZod } from '../../../../utils/build_validation/route_validation'; import { AssetCriticalityRecordIdParts } from '../../../../../common/api/entity_analytics/asset_criticality'; import { assertAdvancedSettingsEnabled } from '../../utils/assert_advanced_setting_enabled'; import type { EntityAnalyticsRoutesDeps } from '../../types'; import { AssetCriticalityAuditActions } from '../audit'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; -export const assetCriticalityGetRoute = ( +type GetHandler = ( + context: SecuritySolutionRequestHandlerContext, + request: { + query: AssetCriticalityRecordIdParts; + }, + response: KibanaResponseFactory +) => Promise; + +const handler: (logger: Logger) => GetHandler = (logger) => async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); + await checkAndInitAssetCriticalityResources(context, logger); + + const securitySolution = await context.securitySolution; + const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); + const record = await assetCriticalityClient.get({ + idField: request.query.id_field, + idValue: request.query.id_value, + }); + + if (!record) { + return response.notFound(); + } + + securitySolution.getAuditLogger()?.log({ + message: 'User accessed the criticality level for an entity', + event: { + action: AssetCriticalityAuditActions.ASSET_CRITICALITY_GET, + category: AUDIT_CATEGORY.DATABASE, + type: AUDIT_TYPE.ACCESS, + outcome: AUDIT_OUTCOME.SUCCESS, + }, + }); + + return response.ok({ body: record }); + } catch (e) { + const error = transformError(e); + + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, + }); + } +}; + +export const assetCriticalityInternalGetRoute = ( router: EntityAnalyticsRoutesDeps['router'], logger: Logger ) => { router.versioned .get({ access: 'internal', - path: ASSET_CRITICALITY_URL, + path: ASSET_CRITICALITY_INTERNAL_URL, options: { tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], }, }) .addVersion( { - version: '1', + version: API_VERSIONS.internal.v1, validate: { request: { query: buildRouteValidationWithZod(AssetCriticalityRecordIdParts), }, }, }, - async (context, request, response) => { - const siemResponse = buildSiemResponse(response); - try { - await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); - await checkAndInitAssetCriticalityResources(context, logger); - - const securitySolution = await context.securitySolution; - const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); - const record = await assetCriticalityClient.get({ - idField: request.query.id_field, - idValue: request.query.id_value, - }); - - if (!record) { - return response.notFound(); - } - - securitySolution.getAuditLogger()?.log({ - message: 'User accessed the criticality level for an entity', - event: { - action: AssetCriticalityAuditActions.ASSET_CRITICALITY_GET, - category: AUDIT_CATEGORY.DATABASE, - type: AUDIT_TYPE.ACCESS, - outcome: AUDIT_OUTCOME.SUCCESS, - }, - }); - - return response.ok({ body: record }); - } catch (e) { - const error = transformError(e); + handler(logger) + ); +}; - return siemResponse.error({ - statusCode: error.statusCode, - body: { message: error.message, full_error: JSON.stringify(e) }, - bypassErrorFormat: true, - }); - } - } +export const assetCriticalityPublicGetRoute = ( + router: EntityAnalyticsRoutesDeps['router'], + logger: Logger +) => { + router.versioned + .get({ + access: 'public', + path: ASSET_CRITICALITY_PUBLIC_URL, + options: { + tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + }, + }) + .addVersion( + { + version: API_VERSIONS.public.v1, + validate: { + request: { + query: buildRouteValidationWithZod(AssetCriticalityRecordIdParts), + }, + }, + }, + handler(logger) ); }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/privileges.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/privileges.ts index a339ee994c8c2..a3b4c48d828df 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/privileges.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/privileges.ts @@ -8,9 +8,10 @@ import type { Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { - ASSET_CRITICALITY_PRIVILEGES_URL, + ASSET_CRITICALITY_INTERNAL_PRIVILEGES_URL, APP_ID, ENABLE_ASSET_CRITICALITY_SETTING, + API_VERSIONS, } from '../../../../../common/constants'; import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources'; import { getUserAssetCriticalityPrivileges } from '../get_user_asset_criticality_privileges'; @@ -19,7 +20,7 @@ import type { EntityAnalyticsRoutesDeps } from '../../types'; import { AssetCriticalityAuditActions } from '../audit'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; -export const assetCriticalityPrivilegesRoute = ( +export const assetCriticalityInternalPrivilegesRoute = ( router: EntityAnalyticsRoutesDeps['router'], logger: Logger, getStartServices: EntityAnalyticsRoutesDeps['getStartServices'] @@ -27,14 +28,14 @@ export const assetCriticalityPrivilegesRoute = ( router.versioned .get({ access: 'internal', - path: ASSET_CRITICALITY_PRIVILEGES_URL, + path: ASSET_CRITICALITY_INTERNAL_PRIVILEGES_URL, options: { tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], }, }) .addVersion( { - version: '1', + version: API_VERSIONS.internal.v1, validate: false, }, async (context, request, response) => { diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/register_asset_criticality_routes.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/register_asset_criticality_routes.ts index 518602a4bc9b7..1f380de7df300 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/register_asset_criticality_routes.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/register_asset_criticality_routes.ts @@ -4,13 +4,17 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { assetCriticalityStatusRoute } from './status'; -import { assetCriticalityUpsertRoute } from './upsert'; -import { assetCriticalityGetRoute } from './get'; -import { assetCriticalityDeleteRoute } from './delete'; -import { assetCriticalityPrivilegesRoute } from './privileges'; -import { assetCriticalityCSVUploadRoute } from './upload_csv'; +import { assetCriticalityInternalStatusRoute } from './status'; +import { assetCriticalityPublicUpsertRoute, assetCriticalityInternalUpsertRoute } from './upsert'; +import { assetCriticalityInternalGetRoute, assetCriticalityPublicGetRoute } from './get'; +import { assetCriticalityPublicDeleteRoute, assetCriticalityInternalDeleteRoute } from './delete'; +import { assetCriticalityInternalPrivilegesRoute } from './privileges'; +import { + assetCriticalityInternalCSVUploadRoute, + assetCriticalityPublicCSVUploadRoute, +} from './upload_csv'; import type { EntityAnalyticsRoutesDeps } from '../../types'; +import { assetCriticalityPublicBulkUploadRoute } from './bulk_upload'; export const registerAssetCriticalityRoutes = ({ router, @@ -18,10 +22,18 @@ export const registerAssetCriticalityRoutes = ({ config, getStartServices, }: EntityAnalyticsRoutesDeps) => { - assetCriticalityStatusRoute(router, logger); - assetCriticalityUpsertRoute(router, logger); - assetCriticalityGetRoute(router, logger); - assetCriticalityDeleteRoute(router, logger); - assetCriticalityPrivilegesRoute(router, logger, getStartServices); - assetCriticalityCSVUploadRoute(router, logger, config, getStartServices); + // Internal routes + assetCriticalityInternalCSVUploadRoute(router, logger, config, getStartServices); + assetCriticalityInternalDeleteRoute(router, logger); + assetCriticalityInternalGetRoute(router, logger); + assetCriticalityInternalPrivilegesRoute(router, logger, getStartServices); + assetCriticalityInternalStatusRoute(router, logger); + assetCriticalityInternalUpsertRoute(router, logger); + + // Public routes + assetCriticalityPublicCSVUploadRoute(router, logger, config, getStartServices); + assetCriticalityPublicBulkUploadRoute(router, logger, config); + assetCriticalityPublicDeleteRoute(router, logger); + assetCriticalityPublicGetRoute(router, logger); + assetCriticalityPublicUpsertRoute(router, logger); }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts index 27910cfd40631..2afa73ed5a059 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/status.ts @@ -9,9 +9,10 @@ import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import type { AssetCriticalityStatusResponse } from '../../../../../common/api/entity_analytics/asset_criticality'; import { - ASSET_CRITICALITY_STATUS_URL, + ASSET_CRITICALITY_INTERNAL_STATUS_URL, APP_ID, ENABLE_ASSET_CRITICALITY_SETTING, + API_VERSIONS, } from '../../../../../common/constants'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; import type { EntityAnalyticsRoutesDeps } from '../../types'; @@ -19,53 +20,56 @@ import { assertAdvancedSettingsEnabled } from '../../utils/assert_advanced_setti import { AssetCriticalityAuditActions } from '../audit'; import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources'; -export const assetCriticalityStatusRoute = ( +export const assetCriticalityInternalStatusRoute = ( router: EntityAnalyticsRoutesDeps['router'], logger: Logger ) => { router.versioned .get({ access: 'internal', - path: ASSET_CRITICALITY_STATUS_URL, + path: ASSET_CRITICALITY_INTERNAL_STATUS_URL, options: { tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], }, }) - .addVersion({ version: '1', validate: {} }, async (context, request, response) => { - const siemResponse = buildSiemResponse(response); - try { - await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); - await checkAndInitAssetCriticalityResources(context, logger); + .addVersion( + { version: API_VERSIONS.internal.v1, validate: {} }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); + await checkAndInitAssetCriticalityResources(context, logger); - const securitySolution = await context.securitySolution; - const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); + const securitySolution = await context.securitySolution; + const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); - const result = await assetCriticalityClient.getStatus(); + const result = await assetCriticalityClient.getStatus(); - securitySolution.getAuditLogger()?.log({ - message: 'User checked the status of the asset criticality service', - event: { - action: AssetCriticalityAuditActions.ASSET_CRITICALITY_STATUS_GET, - category: AUDIT_CATEGORY.DATABASE, - type: AUDIT_TYPE.ACCESS, - outcome: AUDIT_OUTCOME.UNKNOWN, - }, - }); + securitySolution.getAuditLogger()?.log({ + message: 'User checked the status of the asset criticality service', + event: { + action: AssetCriticalityAuditActions.ASSET_CRITICALITY_STATUS_GET, + category: AUDIT_CATEGORY.DATABASE, + type: AUDIT_TYPE.ACCESS, + outcome: AUDIT_OUTCOME.UNKNOWN, + }, + }); - const body: AssetCriticalityStatusResponse = { - asset_criticality_resources_installed: result.isAssetCriticalityResourcesInstalled, - }; - return response.ok({ - body, - }); - } catch (e) { - const error = transformError(e); + const body: AssetCriticalityStatusResponse = { + asset_criticality_resources_installed: result.isAssetCriticalityResourcesInstalled, + }; + return response.ok({ + body, + }); + } catch (e) { + const error = transformError(e); - return siemResponse.error({ - statusCode: error.statusCode, - body: { message: error.message, full_error: JSON.stringify(e) }, - bypassErrorFormat: true, - }); + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, + }); + } } - }); + ); }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts index cf4123db487a4..9c9fa19ca3cbd 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upload_csv.ts @@ -4,19 +4,22 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { Logger } from '@kbn/core/server'; +import type { IKibanaResponse, KibanaResponseFactory, Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { schema } from '@kbn/config-schema'; import Papa from 'papaparse'; import { transformError } from '@kbn/securitysolution-es-utils'; -import type { AssetCriticalityCsvUploadResponse } from '../../../../../common/api/entity_analytics'; +import type internal from 'stream'; +import type { AssetCriticalityBulkUploadResponse } from '../../../../../common/api/entity_analytics'; import { CRITICALITY_CSV_MAX_SIZE_BYTES_WITH_TOLERANCE } from '../../../../../common/entity_analytics/asset_criticality'; import type { ConfigType } from '../../../../config'; -import type { HapiReadableStream } from '../../../../types'; +import type { HapiReadableStream, SecuritySolutionRequestHandlerContext } from '../../../../types'; import { - ASSET_CRITICALITY_CSV_UPLOAD_URL, + ASSET_CRITICALITY_INTERNAL_CSV_UPLOAD_URL, + ASSET_CRITICALITY_PUBLIC_CSV_UPLOAD_URL, APP_ID, ENABLE_ASSET_CRITICALITY_SETTING, + API_VERSIONS, } from '../../../../../common/constants'; import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources'; import { transformCSVToUpsertRecords } from '../transform_csv_to_upsert_records'; @@ -26,18 +29,109 @@ import type { EntityAnalyticsRoutesDeps } from '../../types'; import { AssetCriticalityAuditActions } from '../audit'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; -export const assetCriticalityCSVUploadRoute = ( +type CSVUploadHandler = ( + context: SecuritySolutionRequestHandlerContext, + request: { + body: { file: internal.Stream }; + }, + response: KibanaResponseFactory +) => Promise; + +const handler: ( + logger: Logger, + getStartServices: EntityAnalyticsRoutesDeps['getStartServices'], + config: ConfigType +) => CSVUploadHandler = (logger, getStartServices, config) => async (context, request, response) => { + const { errorRetries, maxBulkRequestBodySizeBytes } = + config.entityAnalytics.assetCriticality.csvUpload; + + const securitySolution = await context.securitySolution; + securitySolution.getAuditLogger()?.log({ + message: 'User attempted to assign many asset criticalities via file upload', + event: { + action: AssetCriticalityAuditActions.ASSET_CRITICALITY_BULK_UPDATE, + category: AUDIT_CATEGORY.DATABASE, + type: AUDIT_TYPE.CREATION, + outcome: AUDIT_OUTCOME.UNKNOWN, + }, + }); + + const start = new Date(); + const siemResponse = buildSiemResponse(response); + const [coreStart] = await getStartServices(); + const telemetry = coreStart.analytics; + + try { + await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); + await checkAndInitAssetCriticalityResources(context, logger); + const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); + const fileStream = request.body.file as HapiReadableStream; + + logger.debug(`Parsing asset criticality CSV file ${fileStream.hapi.filename}`); + + const csvStream = Papa.parse(Papa.NODE_STREAM_INPUT, { + header: false, + dynamicTyping: true, + skipEmptyLines: true, + }); + + const recordsStream = fileStream.pipe(csvStream).pipe(transformCSVToUpsertRecords()); + + const { errors, stats } = await assetCriticalityClient.bulkUpsertFromStream({ + recordsStream, + retries: errorRetries, + flushBytes: maxBulkRequestBodySizeBytes, + }); + const end = new Date(); + + const tookMs = end.getTime() - start.getTime(); + logger.debug(`Asset criticality CSV upload completed in ${tookMs}ms ${JSON.stringify(stats)}`); + + // type assignment here to ensure that the response body stays in sync with the API schema + const resBody: AssetCriticalityBulkUploadResponse = { errors, stats }; + + const [eventType, event] = createAssetCriticalityProcessedFileEvent({ + startTime: start, + endTime: end, + result: stats, + }); + + telemetry.reportEvent(eventType, event); + + return response.ok({ body: resBody }); + } catch (e) { + logger.error(`Error during asset criticality csv upload: ${e}`); + try { + const end = new Date(); + + const [eventType, event] = createAssetCriticalityProcessedFileEvent({ + startTime: start, + endTime: end, + }); + + telemetry.reportEvent(eventType, event); + } catch (error) { + logger.error(`Error reporting telemetry event: ${error}`); + } + + const error = transformError(e); + return siemResponse.error({ + statusCode: error.statusCode, + body: error.message, + }); + } +}; + +export const assetCriticalityInternalCSVUploadRoute = ( router: EntityAnalyticsRoutesDeps['router'], logger: Logger, config: ConfigType, getStartServices: EntityAnalyticsRoutesDeps['getStartServices'] ) => { - const { errorRetries, maxBulkRequestBodySizeBytes } = - config.entityAnalytics.assetCriticality.csvUpload; router.versioned .post({ access: 'internal', - path: ASSET_CRITICALITY_CSV_UPLOAD_URL, + path: ASSET_CRITICALITY_INTERNAL_CSV_UPLOAD_URL, options: { tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], body: { @@ -49,7 +143,7 @@ export const assetCriticalityCSVUploadRoute = ( }) .addVersion( { - version: '1', + version: API_VERSIONS.internal.v1, validate: { request: { body: schema.object({ @@ -58,84 +152,39 @@ export const assetCriticalityCSVUploadRoute = ( }, }, }, - async (context, request, response) => { - const securitySolution = await context.securitySolution; - securitySolution.getAuditLogger()?.log({ - message: 'User attempted to assign many asset criticalities via file upload', - event: { - action: AssetCriticalityAuditActions.ASSET_CRITICALITY_BULK_UPDATE, - category: AUDIT_CATEGORY.DATABASE, - type: AUDIT_TYPE.CREATION, - outcome: AUDIT_OUTCOME.UNKNOWN, + handler(logger, getStartServices, config) + ); +}; +export const assetCriticalityPublicCSVUploadRoute = ( + router: EntityAnalyticsRoutesDeps['router'], + logger: Logger, + config: ConfigType, + getStartServices: EntityAnalyticsRoutesDeps['getStartServices'] +) => { + router.versioned + .post({ + access: 'public', + path: ASSET_CRITICALITY_PUBLIC_CSV_UPLOAD_URL, + options: { + tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + body: { + output: 'stream', + accepts: 'multipart/form-data', + maxBytes: CRITICALITY_CSV_MAX_SIZE_BYTES_WITH_TOLERANCE, + }, + }, + }) + .addVersion( + { + version: API_VERSIONS.public.v1, + validate: { + request: { + body: schema.object({ + file: schema.stream(), + }), }, - }); - - const start = new Date(); - const siemResponse = buildSiemResponse(response); - const [coreStart] = await getStartServices(); - const telemetry = coreStart.analytics; - - try { - await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); - await checkAndInitAssetCriticalityResources(context, logger); - const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); - const fileStream = request.body.file as HapiReadableStream; - - logger.debug(`Parsing asset criticality CSV file ${fileStream.hapi.filename}`); - - const csvStream = Papa.parse(Papa.NODE_STREAM_INPUT, { - header: false, - dynamicTyping: true, - skipEmptyLines: true, - }); - - const recordsStream = fileStream.pipe(csvStream).pipe(transformCSVToUpsertRecords()); - - const { errors, stats } = await assetCriticalityClient.bulkUpsertFromStream({ - recordsStream, - retries: errorRetries, - flushBytes: maxBulkRequestBodySizeBytes, - }); - const end = new Date(); - - const tookMs = end.getTime() - start.getTime(); - logger.debug( - `Asset criticality CSV upload completed in ${tookMs}ms ${JSON.stringify(stats)}` - ); - - // type assignment here to ensure that the response body stays in sync with the API schema - const resBody: AssetCriticalityCsvUploadResponse = { errors, stats }; - - const [eventType, event] = createAssetCriticalityProcessedFileEvent({ - startTime: start, - endTime: end, - result: stats, - }); - - telemetry.reportEvent(eventType, event); - - return response.ok({ body: resBody }); - } catch (e) { - logger.error(`Error during asset criticality csv upload: ${e}`); - try { - const end = new Date(); - - const [eventType, event] = createAssetCriticalityProcessedFileEvent({ - startTime: start, - endTime: end, - }); - - telemetry.reportEvent(eventType, event); - } catch (error) { - logger.error(`Error reporting telemetry event: ${error}`); - } - - const error = transformError(e); - return siemResponse.error({ - statusCode: error.statusCode, - body: error.message, - }); - } - } + }, + }, + handler(logger, getStartServices, config) ); }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts index 8a6d475695962..cb3c36f450e43 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/upsert.ts @@ -4,84 +4,124 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { Logger } from '@kbn/core/server'; +import type { IKibanaResponse, KibanaResponseFactory, Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import type { SecuritySolutionRequestHandlerContext } from '../../../../types'; import { - ASSET_CRITICALITY_URL, + ASSET_CRITICALITY_PUBLIC_URL, + ASSET_CRITICALITY_INTERNAL_URL, APP_ID, ENABLE_ASSET_CRITICALITY_SETTING, + API_VERSIONS, } from '../../../../../common/constants'; import { checkAndInitAssetCriticalityResources } from '../check_and_init_asset_criticality_resources'; -import { buildRouteValidationWithZod } from '../../../../utils/build_validation/route_validation'; -import { CreateAssetCriticalityRecord } from '../../../../../common/api/entity_analytics'; +import { CreateSingleAssetCriticalityRequest } from '../../../../../common/api/entity_analytics'; import type { EntityAnalyticsRoutesDeps } from '../../types'; import { AssetCriticalityAuditActions } from '../audit'; import { AUDIT_CATEGORY, AUDIT_OUTCOME, AUDIT_TYPE } from '../../audit'; import { assertAdvancedSettingsEnabled } from '../../utils/assert_advanced_setting_enabled'; -export const assetCriticalityUpsertRoute = ( + +type UpsertHandler = ( + context: SecuritySolutionRequestHandlerContext, + request: { + body: CreateSingleAssetCriticalityRequest; + }, + response: KibanaResponseFactory +) => Promise; + +const handler: (logger: Logger) => UpsertHandler = + (logger) => async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); + await checkAndInitAssetCriticalityResources(context, logger); + + const securitySolution = await context.securitySolution; + const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); + + const assetCriticalityRecord = { + idField: request.body.id_field, + idValue: request.body.id_value, + criticalityLevel: request.body.criticality_level, + }; + + const result = await assetCriticalityClient.upsert( + assetCriticalityRecord, + request.body.refresh + ); + + securitySolution.getAuditLogger()?.log({ + message: 'User attempted to assign the asset criticality level for an entity', + event: { + action: AssetCriticalityAuditActions.ASSET_CRITICALITY_UPDATE, + category: AUDIT_CATEGORY.DATABASE, + type: AUDIT_TYPE.CREATION, + outcome: AUDIT_OUTCOME.UNKNOWN, + }, + }); + + return response.ok({ + body: result, + }); + } catch (e) { + const error = transformError(e); + + return siemResponse.error({ + statusCode: error.statusCode, + body: { message: error.message, full_error: JSON.stringify(e) }, + bypassErrorFormat: true, + }); + } + }; + +export const assetCriticalityInternalUpsertRoute = ( router: EntityAnalyticsRoutesDeps['router'], logger: Logger ) => { router.versioned .post({ access: 'internal', - path: ASSET_CRITICALITY_URL, + path: ASSET_CRITICALITY_INTERNAL_URL, options: { tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], }, }) .addVersion( { - version: '1', + version: API_VERSIONS.internal.v1, validate: { request: { - body: buildRouteValidationWithZod(CreateAssetCriticalityRecord), + body: buildRouteValidationWithZod(CreateSingleAssetCriticalityRequest), }, }, }, - async (context, request, response) => { - const siemResponse = buildSiemResponse(response); - try { - await assertAdvancedSettingsEnabled(await context.core, ENABLE_ASSET_CRITICALITY_SETTING); - await checkAndInitAssetCriticalityResources(context, logger); - - const securitySolution = await context.securitySolution; - const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); - - const assetCriticalityRecord = { - idField: request.body.id_field, - idValue: request.body.id_value, - criticalityLevel: request.body.criticality_level, - }; - - const result = await assetCriticalityClient.upsert( - assetCriticalityRecord, - request.body.refresh - ); - - securitySolution.getAuditLogger()?.log({ - message: 'User attempted to assign the asset criticality level for an entity', - event: { - action: AssetCriticalityAuditActions.ASSET_CRITICALITY_UPDATE, - category: AUDIT_CATEGORY.DATABASE, - type: AUDIT_TYPE.CREATION, - outcome: AUDIT_OUTCOME.UNKNOWN, - }, - }); - - return response.ok({ - body: result, - }); - } catch (e) { - const error = transformError(e); + handler(logger) + ); +}; - return siemResponse.error({ - statusCode: error.statusCode, - body: { message: error.message, full_error: JSON.stringify(e) }, - bypassErrorFormat: true, - }); - } - } +export const assetCriticalityPublicUpsertRoute = ( + router: EntityAnalyticsRoutesDeps['router'], + logger: Logger +) => { + router.versioned + .post({ + access: 'public', + path: ASSET_CRITICALITY_PUBLIC_URL, + options: { + tags: ['access:securitySolution', `access:${APP_ID}-entity-analytics`], + }, + }) + .addVersion( + { + version: API_VERSIONS.public.v1, + validate: { + request: { + body: buildRouteValidationWithZod(CreateSingleAssetCriticalityRequest), + }, + }, + }, + handler(logger) ); }; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/calculation.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/calculation.ts index be4875d7dee04..1602e724db227 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/calculation.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/calculation.ts @@ -8,13 +8,13 @@ import type { Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { RiskScoresCalculationRequest } from '../../../../../common/api/entity_analytics/risk_engine/calculation_route.gen'; import { APP_ID, DEFAULT_RISK_SCORE_PAGE_SIZE, RISK_SCORE_CALCULATION_URL, } from '../../../../../common/constants'; -import { buildRouteValidationWithZod } from '../../../../utils/build_validation/route_validation'; import { getRiskInputsIndex } from '../get_risk_inputs_index'; import type { EntityAnalyticsRoutesDeps } from '../../types'; import { RiskScoreAuditActions } from '../audit'; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/entity_calculation.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/entity_calculation.ts index fe6e404e9e96d..eeb773b41a180 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/entity_calculation.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/entity_calculation.ts @@ -5,16 +5,15 @@ * 2.0. */ +import { isEmpty } from 'lodash/fp'; import type { Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; - -import { isEmpty } from 'lodash/fp'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import type { RiskScoresCalculationResponse } from '../../../../../common/api/entity_analytics/risk_engine/calculation_route.gen'; import type { AfterKeys } from '../../../../../common/api/entity_analytics/common'; import { RiskScoresEntityCalculationRequest } from '../../../../../common/api/entity_analytics/risk_engine/entity_calculation_route.gen'; import { APP_ID, RISK_SCORE_ENTITY_CALCULATION_URL } from '../../../../../common/constants'; -import { buildRouteValidationWithZod } from '../../../../utils/build_validation/route_validation'; import { getRiskInputsIndex } from '../get_risk_inputs_index'; import type { EntityAnalyticsRoutesDeps } from '../../types'; import { RiskScoreAuditActions } from '../audit'; diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.ts index 0979b5900737a..d17dc8c99e19e 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.ts @@ -8,14 +8,13 @@ import type { Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; - +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { RiskScoresPreviewRequest } from '../../../../../common/api/entity_analytics/risk_engine/preview_route.gen'; import { APP_ID, DEFAULT_RISK_SCORE_PAGE_SIZE, RISK_SCORE_PREVIEW_URL, } from '../../../../../common/constants'; -import { buildRouteValidationWithZod } from '../../../../utils/build_validation/route_validation'; import { getRiskInputsIndex } from '../get_risk_inputs_index'; import type { EntityAnalyticsRoutesDeps } from '../../types'; import { RiskScoreAuditActions } from '../audit'; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/event_based/events.ts b/x-pack/plugins/security_solution/server/lib/telemetry/event_based/events.ts index af165617bf37c..97a4d44fcd594 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/event_based/events.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/event_based/events.ts @@ -4,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { EventTypeOpts } from '@kbn/analytics-client'; -import type { AssetCriticalityCsvUploadResponse } from '../../../../common/api/entity_analytics'; +import type { EventTypeOpts } from '@kbn/core/server'; +import type { AssetCriticalityBulkUploadResponse } from '../../../../common/api/entity_analytics'; export const RISK_SCORE_EXECUTION_SUCCESS_EVENT: EventTypeOpts<{ scoresWritten: number; @@ -88,7 +88,7 @@ interface AssetCriticalitySystemProcessedAssignmentFileEvent { endTime: string; tookMs: number; }; - result?: AssetCriticalityCsvUploadResponse['stats']; + result?: AssetCriticalityBulkUploadResponse['stats']; status: 'success' | 'partial_success' | 'fail'; } @@ -124,7 +124,7 @@ export const ASSET_CRITICALITY_SYSTEM_PROCESSED_ASSIGNMENT_FILE_EVENT: EventType }; interface CreateAssetCriticalityProcessedFileEvent { - result?: AssetCriticalityCsvUploadResponse['stats']; + result?: AssetCriticalityBulkUploadResponse['stats']; startTime: Date; endTime: Date; } @@ -154,7 +154,7 @@ export const createAssetCriticalityProcessedFileEvent = ({ ]; }; -const getUploadStatus = (stats?: AssetCriticalityCsvUploadResponse['stats']) => { +const getUploadStatus = (stats?: AssetCriticalityBulkUploadResponse['stats']) => { if (!stats) { return 'fail'; } diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/asset_criticality/query.asset_criticality.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/asset_criticality/query.asset_criticality.dsl.ts new file mode 100644 index 0000000000000..8b081ecd951d7 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/asset_criticality/query.asset_criticality.dsl.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { AssetCriticalityRequestOptions } from '../../../../../common/api/search_strategy/asset_criticality/all'; +import { createQueryFilterClauses } from '../../../../utils/build_query'; + +export const buildAssetCriticalityQuery = ({ + timerange, + filterQuery, + defaultIndex, + pagination: { querySize, cursorStart } = { + querySize: QUERY_SIZE, + cursorStart: 0, + }, +}: AssetCriticalityRequestOptions) => { + const filter = createQueryFilterClauses(filterQuery); + + if (timerange) { + filter.push({ + range: { + '@timestamp': { + gte: timerange.from, + lte: timerange.to, + format: 'strict_date_optional_time', + }, + }, + }); + } + + const dslQuery = { + index: defaultIndex, + allow_no_indices: false, + ignore_unavailable: true, + track_total_hits: true, + size: querySize, + from: cursorStart, + body: { + query: { bool: { filter } }, + }, + }; + + return dslQuery; +}; + +export const QUERY_SIZE = 10; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts index 1e04b6910601c..ad60ec516cb53 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts @@ -9,6 +9,9 @@ import { getOr } from 'lodash/fp'; import type { IEsSearchResponse } from '@kbn/search-types'; import type { IScopedClusterClient } from '@kbn/core/server'; +import type { AggregationsAggregate, SearchResponse } from '@elastic/elasticsearch/lib/api/types'; +import _ from 'lodash'; +import type { AssetCriticalityRecord } from '../../../../../../common/api/entity_analytics'; import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; import type { HostAggEsItem, @@ -32,6 +35,8 @@ import { formatHostEdgesData, HOSTS_FIELDS } from './helpers'; import type { EndpointAppContext } from '../../../../../endpoint/types'; import { buildRiskScoreQuery } from '../../risk_score/all/query.risk_score.dsl'; +import { buildAssetCriticalityQuery } from '../../asset_criticality/query.asset_criticality.dsl'; +import { getAssetCriticalityIndex } from '../../../../../../common/entity_analytics/asset_criticality'; export const allHosts: SecuritySolutionFactory = { buildDsl: (options) => { @@ -97,29 +102,23 @@ async function enhanceEdges( esClient: IScopedClusterClient, isNewRiskScoreModuleInstalled: boolean ): Promise { - const hostRiskData = await getHostRiskData( - esClient, - spaceId, - hostNames, - isNewRiskScoreModuleInstalled - ); - const hostsRiskByHostName: Record | undefined = hostRiskData?.hits.hits.reduce( - (acc, hit) => ({ - ...acc, - [hit._source?.host.name ?? '']: hit._source?.host?.risk?.calculated_level, - }), - {} - ); + const [riskByHostName, criticalityByHostName] = await Promise.all([ + getHostRiskData(esClient, spaceId, hostNames, isNewRiskScoreModuleInstalled).then( + buildRecordFromAggs('host.name', 'host.risk.calculated_level') + ), + getHostCriticalityData(esClient, hostNames).then( + buildRecordFromAggs('id_value', 'criticality_level') + ), + ]); - return hostsRiskByHostName - ? edges.map(({ node, cursor }) => ({ - node: { - ...node, - risk: hostsRiskByHostName[node._id ?? ''], - }, - cursor, - })) - : edges; + return edges.map(({ node, cursor }) => ({ + node: { + ...node, + risk: riskByHostName?.[node._id ?? ''], + criticality: criticalityByHostName?.[node._id ?? ''], + }, + cursor, + })); } export async function getHostRiskData( @@ -145,3 +144,33 @@ export async function getHostRiskData( return undefined; } } + +export async function getHostCriticalityData(esClient: IScopedClusterClient, hostNames: string[]) { + try { + const criticalityResponse = await esClient.asCurrentUser.search( + buildAssetCriticalityQuery({ + defaultIndex: [getAssetCriticalityIndex('default')], // TODO:(@tiansivive) move to constant or import from somewhere else + filterQuery: { terms: { id_value: hostNames } }, + }) + ); + return criticalityResponse; + } catch (error) { + if (error?.meta?.body?.error?.type !== 'index_not_found_exception') { + throw error; + } + return undefined; + } +} + +const buildRecordFromAggs = + (key: string, path: string) => + ( + data: SearchResponse> | undefined + ): Record | undefined => + data?.hits.hits.reduce( + (acc, hit) => ({ + ...acc, + [_.get(hit._source, key) || '']: _.get(hit._source, path), + }), + {} + ); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts index 18d20b5080352..7ae8371152dde 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/all/index.ts @@ -9,6 +9,8 @@ import { getOr } from 'lodash/fp'; import type { IEsSearchResponse } from '@kbn/search-types'; import type { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; +import type { AggregationsAggregate, SearchResponse } from '@elastic/elasticsearch/lib/api/types'; +import _ from 'lodash'; import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; import { inspectStringifyObject } from '../../../../../utils/build_query'; @@ -28,6 +30,9 @@ import { RiskScoreEntity, RiskQueries, } from '../../../../../../common/search_strategy'; +import { buildAssetCriticalityQuery } from '../../asset_criticality/query.asset_criticality.dsl'; +import { getAssetCriticalityIndex } from '../../../../../../common/entity_analytics/asset_criticality'; +import type { AssetCriticalityRecord } from '../../../../../../common/api/entity_analytics'; export const allUsers: SecuritySolutionFactory = { buildDsl: (options) => { @@ -103,29 +108,22 @@ async function enhanceEdges( esClient: IScopedClusterClient, isNewRiskScoreModuleInstalled: boolean ): Promise { - const userRiskData = await getUserRiskData( - esClient, - spaceId, - userNames, - isNewRiskScoreModuleInstalled - ); - const usersRiskByUserName: Record | undefined = - userRiskData?.hits.hits.reduce( - (acc, hit) => ({ - ...acc, - [hit._source?.user.name ?? '']: hit._source?.user?.risk?.calculated_level, - }), - {} - ); + const [riskByUserName, criticalityByUserName] = await Promise.all([ + getUserRiskData(esClient, spaceId, userNames, isNewRiskScoreModuleInstalled).then( + buildRecordFromAggs('user.name', 'user.risk.calculated_level') + ), + getUserCriticalityData(esClient, userNames).then( + buildRecordFromAggs('id_value', 'criticality_level') + ), + ]); - return usersRiskByUserName - ? edges.map(({ name, lastSeen, domain }) => ({ - name, - lastSeen, - domain, - risk: usersRiskByUserName[name ?? ''], - })) - : edges; + return edges.map(({ name, lastSeen, domain }) => ({ + name, + lastSeen, + domain, + risk: riskByUserName?.[name ?? ''] as RiskSeverity, + criticality: criticalityByUserName?.[name ?? ''], + })); } export async function getUserRiskData( @@ -151,3 +149,33 @@ export async function getUserRiskData( return undefined; } } + +export async function getUserCriticalityData(esClient: IScopedClusterClient, hostNames: string[]) { + try { + const criticalityResponse = await esClient.asCurrentUser.search( + buildAssetCriticalityQuery({ + defaultIndex: [getAssetCriticalityIndex('default')], // TODO:(@tiansivive) move to constant or import from somewhere else + filterQuery: { terms: { id_value: hostNames } }, + }) + ); + return criticalityResponse; + } catch (error) { + if (error?.meta?.body?.error?.type !== 'index_not_found_exception') { + throw error; + } + return undefined; + } +} + +const buildRecordFromAggs = + (key: string, path: string) => + ( + data: SearchResponse> | undefined + ): Record | undefined => + data?.hits.hits.reduce( + (acc, hit) => ({ + ...acc, + [_.get(hit._source, key) || '']: _.get(hit._source, path), + }), + {} + ); diff --git a/x-pack/plugins/security_solution/server/utils/build_validation/route_validation.ts b/x-pack/plugins/security_solution/server/utils/build_validation/route_validation.ts index 7775186cde97f..57692b515a230 100644 --- a/x-pack/plugins/security_solution/server/utils/build_validation/route_validation.ts +++ b/x-pack/plugins/security_solution/server/utils/build_validation/route_validation.ts @@ -14,8 +14,6 @@ import type { RouteValidationResultFactory, RouteValidationError, } from '@kbn/core/server'; -import type { TypeOf, ZodType } from 'zod'; -import { stringifyZodError } from '@kbn/zod-helpers'; import type { GenericIntersectionC } from '../runtime_types'; import { excess } from '../runtime_types'; @@ -67,14 +65,3 @@ export const buildRouteValidationWithExcess = (validatedInput: A) => validationResult.ok(validatedInput) ) ); - -export const buildRouteValidationWithZod = - >(schema: T): RouteValidationFunction => - (inputValue: unknown, validationResult: RouteValidationResultFactory) => { - const decoded = schema.safeParse(inputValue); - if (decoded.success) { - return validationResult.ok(decoded.data); - } else { - return validationResult.badRequest(stringifyZodError(decoded.error)); - } - }; diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index 9bc37db6612a0..f1320dc3205be 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -15,7 +15,11 @@ "public/**/*.json", "../../../typings/**/*" ], - "exclude": ["target/**/*", "**/cypress/**", "public/management/cypress.config.ts"], + "exclude": [ + "target/**/*", + "**/cypress/**", + "public/management/cypress.config.ts" + ], "kbn_references": [ "@kbn/core", { @@ -145,7 +149,6 @@ "@kbn/grouping", "@kbn/securitysolution-data-table", "@kbn/core-analytics-server", - "@kbn/analytics-client", "@kbn/security-solution-side-nav", "@kbn/ml-anomaly-utils", "@kbn/discover-plugin", diff --git a/x-pack/plugins/security_solution_serverless/common/pli/pli_config.ts b/x-pack/plugins/security_solution_serverless/common/pli/pli_config.ts index 4798b4923de96..f4365787b9189 100644 --- a/x-pack/plugins/security_solution_serverless/common/pli/pli_config.ts +++ b/x-pack/plugins/security_solution_serverless/common/pli/pli_config.ts @@ -44,7 +44,7 @@ export const PLI_PRODUCT_FEATURES: PliProductFeatures = { ], }, cloud: { - essentials: [], + essentials: [ProductFeatureKey.cloudSecurityPosture], complete: [], }, } as const; diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx index 23c5faf1551cc..ef4574424b42c 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx +++ b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx @@ -20,6 +20,13 @@ import type { UpsellingSectionId, } from '@kbn/security-solution-upselling/service/types'; import React from 'react'; +import { CloudSecurityPostureIntegrationPliBlockLazy } from './sections/cloud_security_posture'; +import { + EndpointAgentTamperProtectionLazy, + EndpointPolicyProtectionsLazy, + EndpointProtectionUpdatesLazy, + RuleDetailsEndpointExceptionsLazy, +} from './sections/endpoint_management'; import type { SecurityProductTypes } from '../../common/config'; import { getProductProductFeatures } from '../../common/pli/pli_features'; import type { Services } from '../common/services'; @@ -32,12 +39,6 @@ import { OsqueryResponseActionsUpsellingSectionLazy, ThreatIntelligencePaywallLazy, } from './lazy_upselling'; -import { - EndpointAgentTamperProtectionLazy, - EndpointPolicyProtectionsLazy, - EndpointProtectionUpdatesLazy, - RuleDetailsEndpointExceptionsLazy, -} from './sections/endpoint_management'; import * as i18n from './translations'; interface UpsellingsConfig { @@ -160,6 +161,11 @@ export const upsellingSections: UpsellingSections = [ pli: ProductFeatureKey.endpointProtectionUpdates, component: EndpointProtectionUpdatesLazy, }, + { + id: 'cloud_security_posture_integration_installation', + pli: ProductFeatureKey.cloudSecurityPosture, + component: CloudSecurityPostureIntegrationPliBlockLazy, + }, { id: 'entity_analytics_panel', pli: ProductFeatureKey.advancedInsights, diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/sections/cloud_security_posture/cloud_security_posture_integration_pli_block.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/sections/cloud_security_posture/cloud_security_posture_integration_pli_block.tsx new file mode 100644 index 0000000000000..dd742aa3d5d9f --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/upselling/sections/cloud_security_posture/cloud_security_posture_integration_pli_block.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo } from 'react'; +import { EuiCard, EuiIcon, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +/** + * Component displayed when a given product tier is not allowed to use Cloud Security Posture Integrations installation forms. + */ +export const CloudSecurityPostureIntegrationPliBlock = memo(() => { + // TODO: prefer to use getProductTypeByPLI(ProductFeatureKey.cloudSecurityPosture) after we change returned text to include "Protection" + const requiredPLI = 'Cloud Protection Essentials'; + + return ( + <> + + } + betaBadgeProps={{ + label: i18n.translate( + 'xpack.securitySolutionServerless.cloudSecurityPostureIntegrationPliBlock.badgeText', + { + defaultMessage: 'Cloud Protection Essentials', + } + ), + }} + title={ +

+ + {i18n.translate( + 'xpack.securitySolutionServerless.cloudSecurityPostureIntegrationPliBlock.cardTitle', + { + defaultMessage: 'Protection updates', + } + )} + +

+ } + > +
+ {i18n.translate( + 'xpack.securitySolutionServerless.cloudSecurityPostureIntegrationPliBlock.cardMessage', + { + defaultMessage: + 'To turn on CSPM, KSPM or CNVM, view your Cloud Posture Dashboards and generate findings of misconfiguration or vulnerabilities in your cloud environment, you must add {requiredPLI} under Manage --> Project features.', + values: { + requiredPLI, + }, + } + )} +
+
+ + ); +}); +CloudSecurityPostureIntegrationPliBlock.displayName = 'CloudSecurityPostureIntegrationPliBlock'; diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/sections/cloud_security_posture/index.ts b/x-pack/plugins/security_solution_serverless/public/upselling/sections/cloud_security_posture/index.ts new file mode 100644 index 0000000000000..b5c5794e00185 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/upselling/sections/cloud_security_posture/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 { lazy } from 'react'; + +export const CloudSecurityPostureIntegrationPliBlockLazy = lazy(() => + import('./cloud_security_posture_integration_pli_block').then( + ({ CloudSecurityPostureIntegrationPliBlock }) => ({ + default: CloudSecurityPostureIntegrationPliBlock, + }) + ) +); diff --git a/x-pack/plugins/security_solution_serverless/server/task_manager/nlp_cleanup_task/nlp_cleanup_task.ts b/x-pack/plugins/security_solution_serverless/server/task_manager/nlp_cleanup_task/nlp_cleanup_task.ts index 5a3ba158d6194..011e80bf02824 100644 --- a/x-pack/plugins/security_solution_serverless/server/task_manager/nlp_cleanup_task/nlp_cleanup_task.ts +++ b/x-pack/plugins/security_solution_serverless/server/task_manager/nlp_cleanup_task/nlp_cleanup_task.ts @@ -11,7 +11,7 @@ import type { TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; -import { throwUnrecoverableError } from '@kbn/task-manager-plugin/server'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { Tier } from '../../types'; import { ProductTier } from '../../../common/product'; import { NLP_CLEANUP_TASK_EVENT } from '../../telemetry/event_based_telemetry'; @@ -78,9 +78,10 @@ export class NLPCleanupTask { return { run: async () => { if (this.productTier === ProductTier.complete) { - throwUnrecoverableError( - new Error('Task no longer needed for current productTier, disabling...') + this.logger.info( + `Task ${taskInstance.id} no longer needed for current productTier, disabling...` ); + return getDeleteTaskRunResult(); } return this.runTask(taskInstance, core); }, @@ -134,7 +135,7 @@ export class NLPCleanupTask { // Check that this task is current if (taskInstance.id !== this.taskId) { // old task, return - throwUnrecoverableError(new Error('Outdated task version')); + return getDeleteTaskRunResult(); } const [{ elasticsearch }] = await core.getStartServices(); diff --git a/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.test.ts b/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.test.ts index 1b5871e6f2a88..5d5a8524b5733 100644 --- a/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.test.ts +++ b/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.test.ts @@ -14,6 +14,7 @@ import type { } from '@kbn/task-manager-plugin/server'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; import { TaskStatus } from '@kbn/task-manager-plugin/server'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import { coreMock } from '@kbn/core/server/mocks'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; @@ -201,6 +202,15 @@ describe('SecurityUsageReportingTask', () => { ); }); + it('should do nothing if task instance id is outdated', async () => { + const result = await runTask({ ...buildMockTaskInstance(), id: 'old-id' }); + + expect(result).toEqual(getDeleteTaskRunResult()); + + expect(reportUsageSpy).not.toHaveBeenCalled(); + expect(meteringCallbackMock).not.toHaveBeenCalled(); + }); + describe('lastSuccessfulReport', () => { it('should set lastSuccessfulReport correctly if report success', async () => { reportUsageSpy.mockResolvedValueOnce({ status: 201 }); diff --git a/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.ts b/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.ts index 2d612708f5bf3..eb36adc77874e 100644 --- a/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.ts +++ b/x-pack/plugins/security_solution_serverless/server/task_manager/usage_reporting_task.ts @@ -8,8 +8,8 @@ import type { Response } from 'node-fetch'; import type { CoreSetup, Logger } from '@kbn/core/server'; import type { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server'; +import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; -import { throwUnrecoverableError } from '@kbn/task-manager-plugin/server'; import { usageReportingService } from '../common/services'; import type { @@ -114,7 +114,10 @@ export class SecurityUsageReportingTask { // Check that this task is current if (taskInstance.id !== this.taskId) { // old task, die - throwUnrecoverableError(new Error('Outdated task version')); + this.logger.info( + `Outdated task version: Got [${taskInstance.id}] from task instance. Current version is [${this.taskId}]` + ); + return getDeleteTaskRunResult(); } const [{ elasticsearch }] = await core.getStartServices(); diff --git a/x-pack/plugins/security_solution_serverless/server/telemetry/event_based_telemetry.ts b/x-pack/plugins/security_solution_serverless/server/telemetry/event_based_telemetry.ts index 919338437643f..578b37f389cb3 100644 --- a/x-pack/plugins/security_solution_serverless/server/telemetry/event_based_telemetry.ts +++ b/x-pack/plugins/security_solution_serverless/server/telemetry/event_based_telemetry.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { EventTypeOpts } from '@kbn/analytics-client'; +import type { EventTypeOpts } from '@kbn/core/server'; export const NLP_CLEANUP_TASK_EVENT: EventTypeOpts<{ failedToDeleteCount: number; diff --git a/x-pack/plugins/security_solution_serverless/tsconfig.json b/x-pack/plugins/security_solution_serverless/tsconfig.json index 5cf1fcc8431a4..a1c6cefd396ca 100644 --- a/x-pack/plugins/security_solution_serverless/tsconfig.json +++ b/x-pack/plugins/security_solution_serverless/tsconfig.json @@ -44,6 +44,5 @@ "@kbn/management-cards-navigation", "@kbn/discover-plugin", "@kbn/logging", - "@kbn/analytics-client", ] } diff --git a/x-pack/plugins/serverless_search/kibana.jsonc b/x-pack/plugins/serverless_search/kibana.jsonc index fc298a895174e..04002ee897cf4 100644 --- a/x-pack/plugins/serverless_search/kibana.jsonc +++ b/x-pack/plugins/serverless_search/kibana.jsonc @@ -29,6 +29,8 @@ "optionalPlugins": [ "indexManagement", "searchConnectors", + "searchHomepage", + "searchInferenceEndpoints", "usageCollection" ], "requiredBundles": [ diff --git a/x-pack/plugins/serverless_search/public/application/components/overview.test.tsx b/x-pack/plugins/serverless_search/public/application/components/overview.test.tsx index 5c6d18fbf743d..902dc7e32abc0 100644 --- a/x-pack/plugins/serverless_search/public/application/components/overview.test.tsx +++ b/x-pack/plugins/serverless_search/public/application/components/overview.test.tsx @@ -30,6 +30,9 @@ describe('', () => { case '/internal/serverless_search/connectors': resolve({}); return; + case '/internal/serverless_search/ingest_pipelines': + resolve({ pipelines: [] }); + return; default: return reject(`unknown path requested ${fetchedUrl}`); } diff --git a/x-pack/plugins/serverless_search/public/application/hooks/api/use_ingest_pipelines.tsx b/x-pack/plugins/serverless_search/public/application/hooks/api/use_ingest_pipelines.tsx index adb3bb55c8bbe..bd7d4292cfe18 100644 --- a/x-pack/plugins/serverless_search/public/application/hooks/api/use_ingest_pipelines.tsx +++ b/x-pack/plugins/serverless_search/public/application/hooks/api/use_ingest_pipelines.tsx @@ -15,7 +15,7 @@ export const useIngestPipelines = () => { queryKey: ['fetchIngestPipelines'], queryFn: async () => http.fetch>( - `/internal/serverless_search/ingest_pipelines/` + `/internal/serverless_search/ingest_pipelines` ), }); }; diff --git a/x-pack/plugins/serverless_search/public/navigation_tree.ts b/x-pack/plugins/serverless_search/public/navigation_tree.ts index 5878031d99dc3..f5618e8b83e05 100644 --- a/x-pack/plugins/serverless_search/public/navigation_tree.ts +++ b/x-pack/plugins/serverless_search/public/navigation_tree.ts @@ -9,7 +9,7 @@ import type { NavigationTreeDefinition } from '@kbn/core-chrome-browser'; import { i18n } from '@kbn/i18n'; import { CONNECTORS_LABEL } from '../common/i18n_string'; -export const navigationTree: NavigationTreeDefinition = { +export const navigationTree = (useSearchHomepage: boolean = false): NavigationTreeDefinition => ({ body: [ { type: 'navGroup', @@ -25,7 +25,7 @@ export const navigationTree: NavigationTreeDefinition = { title: i18n.translate('xpack.serverlessSearch.nav.home', { defaultMessage: 'Home', }), - link: 'serverlessElasticsearch', + link: useSearchHomepage ? 'searchHomepage' : 'serverlessElasticsearch', spaceBefore: 'm', }, { @@ -93,6 +93,25 @@ export const navigationTree: NavigationTreeDefinition = { }, ], }, + { + id: 'relevance', + title: i18n.translate('xpack.serverlessSearch.nav.relevance', { + defaultMessage: 'Relevance', + }), + spaceBefore: 'm', + children: [ + { + id: 'searchInferenceEndpoints', + title: i18n.translate( + 'xpack.serverlessSearch.nav.relevance.searchInferenceEndpoints', + { + defaultMessage: 'Inference Endpoints', + } + ), + link: 'searchInferenceEndpoints', + }, + ], + }, ], }, ], @@ -130,4 +149,4 @@ export const navigationTree: NavigationTreeDefinition = { ], }, ], -}; +}); diff --git a/x-pack/plugins/serverless_search/public/plugin.ts b/x-pack/plugins/serverless_search/public/plugin.ts index d9019f911444a..e72e1a4575079 100644 --- a/x-pack/plugins/serverless_search/public/plugin.ts +++ b/x-pack/plugins/serverless_search/public/plugin.ts @@ -43,6 +43,9 @@ export class ServerlessSearchPlugin core: CoreSetup, setupDeps: ServerlessSearchPluginSetupDependencies ): ServerlessSearchPluginSetup { + const { searchHomepage } = setupDeps; + const useSearchHomepage = searchHomepage && searchHomepage.isHomepageFeatureEnabled(); + const queryClient = new QueryClient({ mutationCache: new MutationCache({ onError: (error) => { @@ -69,6 +72,24 @@ export class ServerlessSearchPlugin }, }), }); + if (useSearchHomepage) { + core.application.register({ + id: 'serverlessHomeRedirect', + title: i18n.translate('xpack.serverlessSearch.app.home.title', { + defaultMessage: 'Home', + }), + appRoute: '/app/elasticsearch', + euiIconType: 'logoElastic', + category: DEFAULT_APP_CATEGORIES.enterpriseSearch, + visibleIn: [], + async mount({}: AppMountParameters) { + const [coreStart] = await core.getStartServices(); + coreStart.application.navigateToApp('searchHomepage'); + return () => {}; + }, + }); + } + core.application.register({ id: 'serverlessElasticsearch', title: i18n.translate('xpack.serverlessSearch.app.elasticsearch.title', { @@ -76,7 +97,7 @@ export class ServerlessSearchPlugin }), euiIconType: 'logoElastic', category: DEFAULT_APP_CATEGORIES.enterpriseSearch, - appRoute: '/app/elasticsearch', + appRoute: useSearchHomepage ? '/app/elasticsearch/getting_started' : '/app/elasticsearch', async mount({ element, history }: AppMountParameters) { const { renderApp } = await import('./application/elasticsearch'); const [coreStart, services] = await core.getStartServices(); @@ -121,10 +142,12 @@ export class ServerlessSearchPlugin core: CoreStart, services: ServerlessSearchPluginStartDependencies ): ServerlessSearchPluginStart { - const { serverless, management, indexManagement, security } = services; - serverless.setProjectHome('/app/elasticsearch'); + const { serverless, management, indexManagement, security, searchHomepage } = services; + const useSearchHomepage = searchHomepage && searchHomepage.isHomepageFeatureEnabled(); + + serverless.setProjectHome(useSearchHomepage ? '/app/elasticsearch/home' : '/app/elasticsearch'); - const navigationTree$ = of(navigationTree); + const navigationTree$ = of(navigationTree(searchHomepage?.isHomepageFeatureEnabled() ?? false)); serverless.initNavigation('search', navigationTree$, { dataTestSubj: 'svlSearchSideNav' }); const extendCardNavDefinitions = serverless.getNavigationCards( diff --git a/x-pack/plugins/serverless_search/public/types.ts b/x-pack/plugins/serverless_search/public/types.ts index 5b502eb4f0d9e..d3011210c524f 100644 --- a/x-pack/plugins/serverless_search/public/types.ts +++ b/x-pack/plugins/serverless_search/public/types.ts @@ -5,15 +5,20 @@ * 2.0. */ -import { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; -import { ConsolePluginStart } from '@kbn/console-plugin/public'; -import { SearchPlaygroundPluginStart } from '@kbn/search-playground/public'; -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'; +import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; +import type { ConsolePluginStart } from '@kbn/console-plugin/public'; +import type { SearchInferenceEndpointsPluginStart } from '@kbn/search-inference-endpoints/public'; +import type { SearchPlaygroundPluginStart } from '@kbn/search-playground/public'; +import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public'; +import type { SecurityPluginStart } from '@kbn/security-plugin/public'; +import type { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public'; +import type { SharePluginStart } from '@kbn/share-plugin/public'; +import type { IndexManagementPluginStart } from '@kbn/index-management-plugin/public'; import type { DiscoverSetup } from '@kbn/discover-plugin/public'; +import type { + SearchHomepagePluginSetup, + SearchHomepagePluginStart, +} from '@kbn/search-homepage/public'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ServerlessSearchPluginSetup {} @@ -26,15 +31,18 @@ export interface ServerlessSearchPluginSetupDependencies { management: ManagementSetup; serverless: ServerlessPluginSetup; discover: DiscoverSetup; + searchHomepage?: SearchHomepagePluginSetup; } export interface ServerlessSearchPluginStartDependencies { cloud: CloudStart; console: ConsolePluginStart; searchPlayground: SearchPlaygroundPluginStart; + searchInferenceEndpoints?: SearchInferenceEndpointsPluginStart; management: ManagementStart; security: SecurityPluginStart; serverless: ServerlessPluginStart; share: SharePluginStart; indexManagement?: IndexManagementPluginStart; + searchHomepage?: SearchHomepagePluginStart; } diff --git a/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts b/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts index 397145ac647b2..3d89d104eaa1c 100644 --- a/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts +++ b/x-pack/plugins/serverless_search/server/routes/api_key_routes.ts @@ -15,9 +15,9 @@ export const registerApiKeyRoutes = ({ logger, router, getSecurity }: RouteDepen validate: {}, }, async (context, request, response) => { - const { client } = (await context.core).elasticsearch; - const security = await getSecurity(); - const user = security.authc.getCurrentUser(request); + const core = await context.core; + const { client } = core.elasticsearch; + const user = core.security.authc.getCurrentUser(); if (user) { const apiKeys = await client.asCurrentUser.security.getApiKey({ username: user.username }); const validKeys = apiKeys.api_keys.filter(({ invalidated }) => !invalidated); @@ -39,6 +39,7 @@ export const registerApiKeyRoutes = ({ logger, router, getSecurity }: RouteDepen }, async (context, request, response) => { const security = await getSecurity(); + const result = await security.authc.apiKeys.create(request, request.body); if (result) { const apiKey = { ...result, beats_logstash_format: `${result.id}:${result.api_key}` }; diff --git a/x-pack/plugins/serverless_search/server/routes/indices_routes.ts b/x-pack/plugins/serverless_search/server/routes/indices_routes.ts index 0cf303b677454..5c3e2187b1333 100644 --- a/x-pack/plugins/serverless_search/server/routes/indices_routes.ts +++ b/x-pack/plugins/serverless_search/server/routes/indices_routes.ts @@ -27,9 +27,9 @@ export const registerIndicesRoutes = ({ router, getSecurity }: RouteDependencies }, }, async (context, request, response) => { - const client = (await context.core).elasticsearch.client.asCurrentUser; - const security = await getSecurity(); - const user = security.authc.getCurrentUser(request); + const core = await context.core; + const client = core.elasticsearch.client.asCurrentUser; + const user = core.security.authc.getCurrentUser(); if (!user) { return response.customError({ diff --git a/x-pack/plugins/serverless_search/server/routes/ingest_pipeline_routes.ts b/x-pack/plugins/serverless_search/server/routes/ingest_pipeline_routes.ts index 4a2720c8712d9..0853540c66f04 100644 --- a/x-pack/plugins/serverless_search/server/routes/ingest_pipeline_routes.ts +++ b/x-pack/plugins/serverless_search/server/routes/ingest_pipeline_routes.ts @@ -10,7 +10,7 @@ import { RouteDependencies } from '../plugin'; export const registerIngestPipelineRoutes = ({ router }: RouteDependencies) => { router.get( { - path: '/internal/serverless_search/ingest_pipelines/', + path: '/internal/serverless_search/ingest_pipelines', validate: {}, }, async (context, request, response) => { diff --git a/x-pack/plugins/serverless_search/tsconfig.json b/x-pack/plugins/serverless_search/tsconfig.json index f7d7fb3444554..418dcb5fc6f5c 100644 --- a/x-pack/plugins/serverless_search/tsconfig.json +++ b/x-pack/plugins/serverless_search/tsconfig.json @@ -49,5 +49,7 @@ "@kbn/index-management", "@kbn/react-kibana-context-render", "@kbn/search-playground", + "@kbn/search-inference-endpoints", + "@kbn/search-homepage", ] } diff --git a/x-pack/plugins/spaces/common/types/space/v1.ts b/x-pack/plugins/spaces/common/types/space/v1.ts index e18ad5e656efc..9f110dc3098e3 100644 --- a/x-pack/plugins/spaces/common/types/space/v1.ts +++ b/x-pack/plugins/spaces/common/types/space/v1.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { OnBoardingDefaultSolution } from '@kbn/cloud-plugin/common'; + /** * A Space. */ @@ -62,7 +64,7 @@ export interface Space { /** * Solution selected for this space. */ - solution?: 'security' | 'observability' | 'search' | 'classic'; + solution?: OnBoardingDefaultSolution | 'classic'; } /** diff --git a/x-pack/plugins/spaces/kibana.jsonc b/x-pack/plugins/spaces/kibana.jsonc index e46747a401ea4..3c32314168417 100644 --- a/x-pack/plugins/spaces/kibana.jsonc +++ b/x-pack/plugins/spaces/kibana.jsonc @@ -18,7 +18,9 @@ "optionalPlugins": [ "home", "management", - "usageCollection" + "usageCollection", + "cloud", + "cloudExperiments" ], "requiredBundles": [ "esUiShared", diff --git a/x-pack/plugins/spaces/public/constants.ts b/x-pack/plugins/spaces/public/constants.ts index 64781228d4f43..27c6f04225d4f 100644 --- a/x-pack/plugins/spaces/public/constants.ts +++ b/x-pack/plugins/spaces/public/constants.ts @@ -22,3 +22,5 @@ export const getSpacesFeatureDescription = () => { export const DEFAULT_OBJECT_NOUN = i18n.translate('xpack.spaces.shareToSpace.objectNoun', { defaultMessage: 'object', }); + +export const SOLUTION_NAV_FEATURE_FLAG_NAME = 'solutionNavEnabled'; diff --git a/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/customize_space.test.tsx.snap b/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/customize_space.test.tsx.snap index c799b56e7f7ae..31de1c9ea55e9 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/customize_space.test.tsx.snap +++ b/x-pack/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/customize_space.test.tsx.snap @@ -2,6 +2,7 @@ exports[`renders correctly 1`] = ` { }); return ( - + diff --git a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx index c95e4e4363dee..68e8421449cf8 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx @@ -10,12 +10,13 @@ import { EuiButton } from '@elastic/eui'; import { waitFor } from '@testing-library/react'; import type { ReactWrapper } from 'enzyme'; import React from 'react'; +import { of } from 'rxjs'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public'; import { notificationServiceMock, scopedHistoryMock } from '@kbn/core/public/mocks'; import { KibanaFeature } from '@kbn/features-plugin/public'; import { featuresPluginMock } from '@kbn/features-plugin/public/mocks'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import { ConfirmAlterActiveSpaceModal } from './confirm_alter_active_space_modal'; import { EnabledFeatures } from './enabled_features'; @@ -105,6 +106,93 @@ describe('ManageSpacePage', () => { }); }); + it('shows solution view select when enabled', async () => { + const spacesManager = spacesManagerMock.create(); + spacesManager.createSpace = jest.fn(spacesManager.createSpace); + spacesManager.getActiveSpace = jest.fn().mockResolvedValue(space); + + const wrapper = mountWithIntl( + + ); + + await waitFor(() => { + wrapper.update(); + expect(wrapper.find('input[name="name"]')).toHaveLength(1); + }); + + expect(findTestSubject(wrapper, 'navigationPanel')).toHaveLength(1); + }); + + it('hides solution view select when not enabled or undefined', async () => { + const spacesManager = spacesManagerMock.create(); + spacesManager.createSpace = jest.fn(spacesManager.createSpace); + spacesManager.getActiveSpace = jest.fn().mockResolvedValue(space); + + { + const wrapper = mountWithIntl( + + ); + + await waitFor(() => { + wrapper.update(); + expect(wrapper.find('input[name="name"]')).toHaveLength(1); + }); + + expect(findTestSubject(wrapper, 'navigationPanel')).toHaveLength(0); + } + + { + const wrapper = mountWithIntl( + + ); + + await waitFor(() => { + wrapper.update(); + expect(wrapper.find('input[name="name"]')).toHaveLength(1); + }); + + expect(findTestSubject(wrapper, 'navigationPanel')).toHaveLength(0); + } + }); + it('shows feature visibility controls when allowed', async () => { const spacesManager = spacesManagerMock.create(); spacesManager.createSpace = jest.fn(spacesManager.createSpace); diff --git a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx index fbba203f8e060..8ea38d4fcc316 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.tsx @@ -18,6 +18,7 @@ import { } from '@elastic/eui'; import { difference } from 'lodash'; import React, { Component } from 'react'; +import type { Observable, Subscription } from 'rxjs'; import type { Capabilities, NotificationsStart, ScopedHistory } from '@kbn/core/public'; import { SectionLoading } from '@kbn/es-ui-shared-plugin/public'; @@ -29,6 +30,7 @@ import { ConfirmAlterActiveSpaceModal } from './confirm_alter_active_space_modal import { CustomizeSpace } from './customize_space'; import { DeleteSpacesButton } from './delete_spaces_button'; import { EnabledFeatures } from './enabled_features'; +import { SolutionView } from './solution_view'; import type { Space } from '../../../common'; import { isReservedSpace } from '../../../common'; import { getSpacesFeatureDescription } from '../../constants'; @@ -54,6 +56,7 @@ interface Props { capabilities: Capabilities; history: ScopedHistory; allowFeatureVisibility: boolean; + isSolutionNavEnabled$?: Observable; } interface State { @@ -67,10 +70,13 @@ interface State { isInvalid: boolean; error?: string; }; + isSolutionNavEnabled: boolean; } export class ManageSpacePage extends Component { private readonly validator: SpaceValidator; + private initialSpaceState: State['space'] | null = null; + private subscription: Subscription | null = null; constructor(props: Props) { super(props); @@ -83,6 +89,7 @@ export class ManageSpacePage extends Component { color: getSpaceColor({}), }, features: [], + isSolutionNavEnabled: false, }; } @@ -107,6 +114,12 @@ export class ManageSpacePage extends Component { }), }); } + + if (this.props.isSolutionNavEnabled$) { + this.subscription = this.props.isSolutionNavEnabled$.subscribe((isEnabled) => { + this.setState({ isSolutionNavEnabled: isEnabled }); + }); + } } public async componentDidUpdate(previousProps: Props) { @@ -115,6 +128,12 @@ export class ManageSpacePage extends Component { } } + public componentWillUnmount() { + if (this.subscription) { + this.subscription.unsubscribe(); + } + } + public render() { if (!this.props.capabilities.spaces.manage) { return ( @@ -161,6 +180,13 @@ export class ManageSpacePage extends Component { validator={this.validator} /> + {this.state.isSolutionNavEnabled && ( + <> + + + + )} + {this.props.allowFeatureVisibility && ( <> @@ -298,8 +324,10 @@ export class ManageSpacePage extends Component { const haveDisabledFeaturesChanged = space.disabledFeatures.length !== originalSpace.disabledFeatures.length || difference(space.disabledFeatures, originalSpace.disabledFeatures).length > 0; + const hasSolutionViewChanged = + this.state.space.solution !== this.initialSpaceState?.solution; - if (editingActiveSpace && haveDisabledFeaturesChanged) { + if (editingActiveSpace && (haveDisabledFeaturesChanged || hasSolutionViewChanged)) { this.setState({ showAlteringActiveSpaceDialog: true, }); @@ -326,17 +354,19 @@ export class ManageSpacePage extends Component { onLoadSpace(space); } + this.initialSpaceState = { + ...space, + avatarType: space.imageUrl ? 'image' : 'initials', + initials: space.initials || getSpaceInitials(space), + color: space.color || getSpaceColor(space), + customIdentifier: false, + customAvatarInitials: + !!space.initials && getSpaceInitials({ name: space.name }) !== space.initials, + customAvatarColor: !!space.color && getSpaceColor({ name: space.name }) !== space.color, + }; + this.setState({ - space: { - ...space, - avatarType: space.imageUrl ? 'image' : 'initials', - initials: space.initials || getSpaceInitials(space), - color: space.color || getSpaceColor(space), - customIdentifier: false, - customAvatarInitials: - !!space.initials && getSpaceInitials({ name: space.name }) !== space.initials, - customAvatarColor: !!space.color && getSpaceColor({ name: space.name }) !== space.color, - }, + space: { ...this.initialSpaceState }, features, originalSpace: space, isLoading: false, @@ -369,6 +399,7 @@ export class ManageSpacePage extends Component { disabledFeatures = [], imageUrl, avatarType, + solution, } = this.state.space; const params = { @@ -379,6 +410,7 @@ export class ManageSpacePage extends Component { color: color ? hsvToHex(hexToHsv(color)).toUpperCase() : color, // Convert 3 digit hex codes to 6 digits since Spaces API requires 6 digits disabledFeatures, imageUrl: avatarType === 'image' ? imageUrl : '', + solution, }; let action; diff --git a/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx b/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx index 4ffa5ae9de2ea..3d07aaabfd6b3 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx @@ -13,12 +13,13 @@ import React, { Component, Fragment } from 'react'; interface Props { iconType?: IconType; title: string | ReactNode; + dataTestSubj?: string; } export class SectionPanel extends Component { public render() { return ( - + {this.getTitle()} {this.getForm()} diff --git a/x-pack/plugins/spaces/public/management/edit_space/solution_view/index.ts b/x-pack/plugins/spaces/public/management/edit_space/solution_view/index.ts new file mode 100644 index 0000000000000..0aabb34537273 --- /dev/null +++ b/x-pack/plugins/spaces/public/management/edit_space/solution_view/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 { SolutionView } from './solution_view'; diff --git a/x-pack/plugins/spaces/public/management/edit_space/solution_view/solution_view.tsx b/x-pack/plugins/spaces/public/management/edit_space/solution_view/solution_view.tsx new file mode 100644 index 0000000000000..0a6dc317161f2 --- /dev/null +++ b/x-pack/plugins/spaces/public/management/edit_space/solution_view/solution_view.tsx @@ -0,0 +1,153 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EuiSuperSelectOption, EuiThemeComputed } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiIcon, + EuiSpacer, + EuiSuperSelect, + EuiText, + EuiTitle, + useEuiTheme, +} from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React from 'react'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import type { Space } from '../../../../common'; +import { SectionPanel } from '../section_panel'; + +type SolutionView = Space['solution']; + +const getOptions = ({ size }: EuiThemeComputed): Array> => { + const iconCss = { marginRight: size.m }; + + return [ + { + value: 'es', + inputDisplay: ( + <> + + {i18n.translate( + 'xpack.spaces.management.manageSpacePage.solutionViewSelect.searchOptionLabel', + { + defaultMessage: 'Search', + } + )} + + ), + 'data-test-subj': 'solutionViewEsOption', + }, + { + value: 'oblt', + inputDisplay: ( + <> + + {i18n.translate( + 'xpack.spaces.management.manageSpacePage.solutionViewSelect.obltOptionLabel', + { + defaultMessage: 'Observability', + } + )} + + ), + 'data-test-subj': 'solutionViewObltOption', + }, + { + value: 'security', + inputDisplay: ( + <> + + {i18n.translate( + 'xpack.spaces.management.manageSpacePage.solutionViewSelect.securityOptionLabel', + { + defaultMessage: 'Security', + } + )} + + ), + 'data-test-subj': 'solutionViewSecurityOption', + }, + { + value: 'classic', + inputDisplay: ( + <> + + {i18n.translate( + 'xpack.spaces.management.manageSpacePage.solutionViewSelect.classicOptionLabel', + { + defaultMessage: 'Classic', + } + )} + + ), + 'data-test-subj': 'solutionViewClassicOption', + }, + ]; +}; + +interface Props { + space: Partial; + onChange: (space: Partial) => void; +} + +export const SolutionView: FunctionComponent = ({ space, onChange }) => { + const { euiTheme } = useEuiTheme(); + + return ( + + + + +

+ +

+
+ + +

+ +

+
+
+ + + { + onChange({ ...space, solution }); + }} + /> + + +
+
+ ); +}; diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx index 89e1fc01d493e..b29d13bbd51a2 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx @@ -125,7 +125,7 @@ describe('spacesManagementApp', () => { css="You have tried to stringify object returned from \`css\` function. It isn't supposed to be used directly (e.g. as value of the \`className\` prop), but rather handed to emotion so it can handle it (e.g. as value of \`css\` prop)." data-test-subj="kbnRedirectAppLink" > - Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/create","search":"","hash":""}},"allowFeatureVisibility":true} + Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{}},"history":{"action":"PUSH","length":1,"location":{"pathname":"/create","search":"","hash":""}},"allowFeatureVisibility":true,"isSolutionNavEnabled$":{}}
`); @@ -158,7 +158,7 @@ describe('spacesManagementApp', () => { css="You have tried to stringify object returned from \`css\` function. It isn't supposed to be used directly (e.g. as value of the \`className\` prop), but rather handed to emotion so it can handle it (e.g. as value of \`css\` prop)." data-test-subj="kbnRedirectAppLink" > - Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{}},"spaceId":"some-space","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/some-space","search":"","hash":""}},"allowFeatureVisibility":true} + Spaces Edit Page: {"capabilities":{"catalogue":{},"management":{},"navLinks":{}},"notifications":{"toasts":{}},"spacesManager":{"onActiveSpaceChange$":{}},"spaceId":"some-space","history":{"action":"PUSH","length":1,"location":{"pathname":"/edit/some-space","search":"","hash":""}},"allowFeatureVisibility":true,"isSolutionNavEnabled$":{}}
`); diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx index fe6776b33f920..eafefd93f4464 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { useParams } from 'react-router-dom'; +import { from, of, shareReplay } from 'rxjs'; import type { StartServicesAccessor } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; @@ -20,6 +21,7 @@ import { Route, Router, Routes } from '@kbn/shared-ux-router'; import type { Space } from '../../common'; import type { ConfigType } from '../config'; +import { SOLUTION_NAV_FEATURE_FLAG_NAME } from '../constants'; import type { PluginsStart } from '../plugin'; import type { SpacesManager } from '../spaces_manager'; @@ -43,8 +45,15 @@ export const spacesManagementApp = Object.freeze({ title, async mount({ element, setBreadcrumbs, history }) { - const [[coreStart, { features }], { SpacesGridPage }, { ManageSpacePage }] = - await Promise.all([getStartServices(), import('./spaces_grid'), import('./edit_space')]); + const [ + [coreStart, { features, cloud, cloudExperiments }], + { SpacesGridPage }, + { ManageSpacePage }, + ] = await Promise.all([ + getStartServices(), + import('./spaces_grid'), + import('./edit_space'), + ]); const spacesFirstBreadcrumb = { text: title, @@ -54,6 +63,17 @@ export const spacesManagementApp = Object.freeze({ chrome.docTitle.change(title); + const onCloud = Boolean(cloud?.isCloudEnabled); + const isSolutionNavEnabled$ = + // Only available on Cloud and if the Launch Darkly flag is turned on + onCloud && cloudExperiments + ? from( + cloudExperiments + .getVariation(SOLUTION_NAV_FEATURE_FLAG_NAME, false) + .catch(() => false) + ).pipe(shareReplay(1)) + : of(false); + const SpacesGridPageWithBreadcrumbs = () => { setBreadcrumbs([{ ...spacesFirstBreadcrumb, href: undefined }]); return ( @@ -87,6 +107,7 @@ export const spacesManagementApp = Object.freeze({ spacesManager={spacesManager} history={history} allowFeatureVisibility={config.allowFeatureVisibility} + isSolutionNavEnabled$={isSolutionNavEnabled$} /> ); }; @@ -113,6 +134,7 @@ export const spacesManagementApp = Object.freeze({ onLoadSpace={onLoadSpace} history={history} allowFeatureVisibility={config.allowFeatureVisibility} + isSolutionNavEnabled$={isSolutionNavEnabled$} /> ); }; diff --git a/x-pack/plugins/spaces/public/plugin.tsx b/x-pack/plugins/spaces/public/plugin.tsx index 14d816739b4c9..01e9eacaddaea 100644 --- a/x-pack/plugins/spaces/public/plugin.tsx +++ b/x-pack/plugins/spaces/public/plugin.tsx @@ -5,6 +5,8 @@ * 2.0. */ +import type { CloudExperimentsPluginStart } from '@kbn/cloud-experiments-plugin/common'; +import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; import type { FeaturesPluginStart } from '@kbn/features-plugin/public'; import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; @@ -23,11 +25,14 @@ import { getUiApi } from './ui_api'; export interface PluginsSetup { home?: HomePublicPluginSetup; management?: ManagementSetup; + cloud?: CloudSetup; } export interface PluginsStart { features: FeaturesPluginStart; management?: ManagementStart; + cloud?: CloudStart; + cloudExperiments?: CloudExperimentsPluginStart; } /** diff --git a/x-pack/plugins/spaces/server/default_space/create_default_space.test.ts b/x-pack/plugins/spaces/server/default_space/create_default_space.test.ts index e7a7b51b278d1..7a401defe0aad 100644 --- a/x-pack/plugins/spaces/server/default_space/create_default_space.test.ts +++ b/x-pack/plugins/spaces/server/default_space/create_default_space.test.ts @@ -87,6 +87,31 @@ test(`it creates the default space when one does not exist`, async () => { ); }); +test(`it creates the default space when one does not exist with defined solution`, async () => { + const deps = createMockDeps({ + defaultExists: false, + }); + + await createDefaultSpace({ ...deps, solution: 'security' }); + + const repository = (await deps.getSavedObjects()).createInternalRepository(); + + expect(repository.get).toHaveBeenCalledTimes(1); + expect(repository.create).toHaveBeenCalledTimes(1); + expect(repository.create).toHaveBeenCalledWith( + 'space', + { + _reserved: true, + description: 'This is your default space!', + disabledFeatures: [], + name: 'Default', + color: '#00bfb3', + solution: 'security', + }, + { id: 'default' } + ); +}); + test(`it does not attempt to recreate the default space if it already exists`, async () => { const deps = createMockDeps({ defaultExists: true, diff --git a/x-pack/plugins/spaces/server/default_space/create_default_space.ts b/x-pack/plugins/spaces/server/default_space/create_default_space.ts index a7aee5a6d0ea1..53658fd68a0f7 100644 --- a/x-pack/plugins/spaces/server/default_space/create_default_space.ts +++ b/x-pack/plugins/spaces/server/default_space/create_default_space.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { OnBoardingDefaultSolution } from '@kbn/cloud-plugin/common'; import type { Logger, SavedObjectsRepository, SavedObjectsServiceStart } from '@kbn/core/server'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import { i18n } from '@kbn/i18n'; @@ -14,9 +15,10 @@ import { DEFAULT_SPACE_ID } from '../../common/constants'; interface Deps { getSavedObjects: () => Promise>; logger: Logger; + solution?: OnBoardingDefaultSolution; } -export async function createDefaultSpace({ getSavedObjects, logger }: Deps) { +export async function createDefaultSpace({ getSavedObjects, logger, solution }: Deps) { const { createInternalRepository } = await getSavedObjects(); const savedObjectsRepository = createInternalRepository(['space']); @@ -48,6 +50,7 @@ export async function createDefaultSpace({ getSavedObjects, logger }: Deps) { color: '#00bfb3', disabledFeatures: [], _reserved: true, + ...(solution ? { solution } : {}), }, options ); diff --git a/x-pack/plugins/spaces/server/default_space/default_space_service.ts b/x-pack/plugins/spaces/server/default_space/default_space_service.ts index d4c601369fde1..1faaf5490de89 100644 --- a/x-pack/plugins/spaces/server/default_space/default_space_service.ts +++ b/x-pack/plugins/spaces/server/default_space/default_space_service.ts @@ -19,6 +19,7 @@ import { timer, } from 'rxjs'; +import type { OnBoardingDefaultSolution } from '@kbn/cloud-plugin/common'; import type { CoreSetup, Logger, SavedObjectsServiceStart, ServiceStatus } from '@kbn/core/server'; import { ServiceStatusLevels } from '@kbn/core/server'; import type { ILicense } from '@kbn/licensing-plugin/server'; @@ -32,6 +33,7 @@ interface Deps { license$: Observable; spacesLicense: SpacesLicense; logger: Logger; + solution?: OnBoardingDefaultSolution; } export const RETRY_SCALE_DURATION = 100; @@ -64,7 +66,7 @@ export class DefaultSpaceService { private serviceStatus$?: BehaviorSubject; - public setup({ coreStatus, getSavedObjects, license$, spacesLicense, logger }: Deps) { + public setup({ coreStatus, getSavedObjects, license$, spacesLicense, logger, solution }: Deps) { const statusLogger = logger.get('status'); this.serviceStatus$ = new BehaviorSubject({ @@ -96,6 +98,7 @@ export class DefaultSpaceService { createDefaultSpace({ getSavedObjects, logger, + solution, }).then(() => { return { level: ServiceStatusLevels.available, diff --git a/x-pack/plugins/spaces/server/lib/space_schema.test.ts b/x-pack/plugins/spaces/server/lib/space_schema.test.ts index c31d7f5ca3c22..59795a6519dec 100644 --- a/x-pack/plugins/spaces/server/lib/space_schema.test.ts +++ b/x-pack/plugins/spaces/server/lib/space_schema.test.ts @@ -238,7 +238,7 @@ describe('#imageUrl', () => { describe('#solution', () => { it('should throw error if solution is defined in serverless offering', () => { expect(() => - spaceServerlessSchema.validate({ ...defaultProperties, solution: 'search' }) + spaceServerlessSchema.validate({ ...defaultProperties, solution: 'es' }) ).toThrow(); }); @@ -253,13 +253,13 @@ describe('#solution', () => { .toThrowErrorMatchingInlineSnapshot(` "[solution]: types that failed validation: - [solution.0]: expected value to equal [security] - - [solution.1]: expected value to equal [observability] - - [solution.2]: expected value to equal [search] + - [solution.1]: expected value to equal [oblt] + - [solution.2]: expected value to equal [es] - [solution.3]: expected value to equal [classic]" `); expect(() => - spaceBaseSchema.validate({ ...defaultProperties, solution: ' search ' }, {}) + spaceBaseSchema.validate({ ...defaultProperties, solution: ' es ' }, {}) ).toThrow(); }); }); diff --git a/x-pack/plugins/spaces/server/lib/space_schema.ts b/x-pack/plugins/spaces/server/lib/space_schema.ts index cb184e322b5a9..f43ca3bc58f53 100644 --- a/x-pack/plugins/spaces/server/lib/space_schema.ts +++ b/x-pack/plugins/spaces/server/lib/space_schema.ts @@ -46,8 +46,8 @@ const spaceSchema = schema.object({ const solutionSchema = schema.oneOf([ schema.literal('security'), - schema.literal('observability'), - schema.literal('search'), + schema.literal('oblt'), + schema.literal('es'), schema.literal('classic'), ]); diff --git a/x-pack/plugins/spaces/server/plugin.test.ts b/x-pack/plugins/spaces/server/plugin.test.ts index 3ac505c6e2e9a..b181b87e9cc94 100644 --- a/x-pack/plugins/spaces/server/plugin.test.ts +++ b/x-pack/plugins/spaces/server/plugin.test.ts @@ -7,15 +7,20 @@ import { lastValueFrom } from 'rxjs'; +import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; +import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { CoreSetup } from '@kbn/core/server'; import { coreMock } from '@kbn/core/server/mocks'; import { featuresPluginMock } from '@kbn/features-plugin/server/mocks'; import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/server/mocks'; +import { createDefaultSpace } from './default_space/create_default_space'; import type { PluginsStart } from './plugin'; import { SpacesPlugin } from './plugin'; +jest.mock('./default_space/create_default_space'); + describe('Spaces plugin', () => { describe('#setup', () => { it('can setup with all optional plugins disabled, exposing the expected contract', () => { @@ -76,6 +81,25 @@ describe('Spaces plugin', () => { expect(usageCollection.getCollectorByType('spaces')).toBeDefined(); }); + + it('can setup space with default solution', async () => { + const initializerContext = coreMock.createPluginInitializerContext({ maxSpaces: 1000 }); + const core = coreMock.createSetup() as CoreSetup; + const features = featuresPluginMock.createSetup(); + const licensing = licensingMock.createSetup(); + const cloud = { + ...cloudMock.createSetup(), + apm: {}, + onboarding: { defaultSolution: 'security' }, + } as CloudSetup; + + const plugin = new SpacesPlugin(initializerContext); + plugin.setup(core, { features, licensing, cloud }); + + expect(createDefaultSpace).toHaveBeenCalledWith( + expect.objectContaining({ solution: 'security' }) + ); + }); }); describe('#start', () => { diff --git a/x-pack/plugins/spaces/server/plugin.ts b/x-pack/plugins/spaces/server/plugin.ts index a20396c3e4695..6ca6e27291f7d 100644 --- a/x-pack/plugins/spaces/server/plugin.ts +++ b/x-pack/plugins/spaces/server/plugin.ts @@ -8,6 +8,7 @@ import type { Observable } from 'rxjs'; import { map } from 'rxjs'; +import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { CoreSetup, CoreStart, @@ -46,6 +47,7 @@ export interface PluginsSetup { licensing: LicensingPluginSetup; usageCollection?: UsageCollectionSetup; home?: HomeServerPluginSetup; + cloud?: CloudSetup; } export interface PluginsStart { @@ -161,6 +163,7 @@ export class SpacesPlugin license$: plugins.licensing.license$, spacesLicense: license, logger: this.log, + solution: plugins.cloud?.onboarding?.defaultSolution, }); initSpacesViewsRoutes({ diff --git a/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.ts b/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.ts index eb038d03e21aa..ffa9ee7a8f574 100644 --- a/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.ts +++ b/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.ts @@ -46,8 +46,8 @@ export class SpacesSavedObjectsService { solution: schema.maybe( schema.oneOf([ schema.literal('security'), - schema.literal('observability'), - schema.literal('search'), + schema.literal('oblt'), + schema.literal('es'), schema.literal('classic'), ]) ), diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts index 600f6d1aa316c..d9a23a3bc0c45 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts @@ -42,7 +42,7 @@ describe('#getAll', () => { imageUrl: 'go-bots/predates/transformers', disabledFeatures: [], _reserved: true, - solution: 'search', + solution: 'es', bar: 'foo-bar', // an extra attribute that will be ignored during conversion }, }, @@ -81,7 +81,7 @@ describe('#getAll', () => { initials: 'FB', imageUrl: 'go-bots/predates/transformers', disabledFeatures: [], - solution: 'search', + solution: 'es', _reserved: true, }, { @@ -224,7 +224,7 @@ describe('#get', () => { const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); mockCallWithRequestRepository.get.mockResolvedValue({ ...savedObject, - attributes: { ...(savedObject.attributes as Record), solution: 'search' }, + attributes: { ...(savedObject.attributes as Record), solution: 'es' }, }); const mockConfig = createMockConfig(); @@ -247,7 +247,7 @@ describe('#get', () => { const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); mockCallWithRequestRepository.get.mockResolvedValue({ ...savedObject, - attributes: { ...(savedObject.attributes as Record), solution: 'search' }, + attributes: { ...(savedObject.attributes as Record), solution: 'es' }, }); const mockConfig = createMockConfig(); @@ -261,7 +261,7 @@ describe('#get', () => { const id = savedObject.id; const actualSpace = await client.get(id); - expect(actualSpace).toEqual({ ...expectedSpace, solution: 'search' }); + expect(actualSpace).toEqual({ ...expectedSpace, solution: 'es' }); }); }); @@ -399,7 +399,7 @@ describe('#create', () => { ); await expect( - client.create({ ...spaceToCreate, solution: 'search' }) + client.create({ ...spaceToCreate, solution: 'es' }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Unable to create Space, solution property is forbidden in serverless"` ); @@ -418,7 +418,7 @@ describe('#create', () => { const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); mockCallWithRequestRepository.create.mockResolvedValue({ ...savedObject, - attributes: { ...(savedObject.attributes as Record), solution: 'search' }, + attributes: { ...(savedObject.attributes as Record), solution: 'es' }, }); mockCallWithRequestRepository.find.mockResolvedValue({ total: maxSpaces - 1, @@ -438,9 +438,9 @@ describe('#create', () => { 'traditional' ); - const actualSpace = await client.create({ ...spaceToCreate, solution: 'search' }); + const actualSpace = await client.create({ ...spaceToCreate, solution: 'es' }); - expect(actualSpace).toEqual({ ...expectedReturnedSpace, solution: 'search' }); + expect(actualSpace).toEqual({ ...expectedReturnedSpace, solution: 'es' }); expect(mockCallWithRequestRepository.find).toHaveBeenCalledWith({ type: 'space', @@ -449,7 +449,7 @@ describe('#create', () => { }); expect(mockCallWithRequestRepository.create).toHaveBeenCalledWith( 'space', - { ...attributes, solution: 'search' }, + { ...attributes, solution: 'es' }, { id, } @@ -609,7 +609,7 @@ describe('#update', () => { ); await expect( - client.update(id, { ...spaceToUpdate, solution: 'search' }) + client.update(id, { ...spaceToUpdate, solution: 'es' }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Unable to update Space, solution property is forbidden in serverless"` ); @@ -659,11 +659,11 @@ describe('#update', () => { 'traditional' ); const id = savedObject.id; - await client.update(id, { ...spaceToUpdate, solution: 'search' }); + await client.update(id, { ...spaceToUpdate, solution: 'es' }); expect(mockCallWithRequestRepository.update).toHaveBeenCalledWith('space', id, { ...attributes, - solution: 'search', + solution: 'es', }); expect(mockCallWithRequestRepository.get).toHaveBeenCalledWith('space', id); }); diff --git a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts index 16b5ca1a1e9f0..d222c21fc72be 100644 --- a/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts +++ b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts @@ -46,7 +46,7 @@ async function getSpacesUsage( } const knownFeatureIds = features.getKibanaFeatures().map((feature) => feature.id); - const knownSolutions = ['classic', 'search', 'observability', 'security', 'unset']; + const knownSolutions = ['classic', 'es', 'oblt', 'security', 'unset']; const resp = (await esClient.search({ index: kibanaIndex, @@ -205,13 +205,13 @@ export function getSpacesUsageCollector( description: 'The number of spaces which have solution set to classic.', }, }, - search: { + es: { type: 'long', _meta: { description: 'The number of spaces which have solution set to search.', }, }, - observability: { + oblt: { type: 'long', _meta: { description: 'The number of spaces which have solution set to observability.', diff --git a/x-pack/plugins/spaces/tsconfig.json b/x-pack/plugins/spaces/tsconfig.json index 048004ff11334..9f79252873307 100644 --- a/x-pack/plugins/spaces/tsconfig.json +++ b/x-pack/plugins/spaces/tsconfig.json @@ -36,6 +36,8 @@ "@kbn/react-kibana-context-render", "@kbn/utility-types-jest", "@kbn/security-plugin-types-public", + "@kbn/cloud-plugin", + "@kbn/cloud-experiments-plugin", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/constants.ts b/x-pack/plugins/stack_alerts/public/rule_types/es_query/constants.ts index 7d01043bfbc21..e40f373cb95fc 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/constants.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/constants.ts @@ -22,7 +22,6 @@ export const DEFAULT_VALUES = { GROUP_BY: 'all', EXCLUDE_PREVIOUS_HITS: false, CAN_SELECT_MULTI_TERMS: true, - SOURCE_FIELDS: [], }; export const SERVERLESS_DEFAULT_VALUES = { SIZE: 10, diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.tsx index 0c749b175b2e6..fd68e61ecb987 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/es_query_expression.tsx @@ -345,7 +345,9 @@ export const EsQueryExpression: React.FC< errors={errors} hasValidationErrors={hasExpressionValidationErrors(currentRuleParams, isServerless)} onTestFetch={onTestQuery} - excludeHitsFromPreviousRun={excludeHitsFromPreviousRun} + excludeHitsFromPreviousRun={ + excludeHitsFromPreviousRun ?? DEFAULT_VALUES.EXCLUDE_PREVIOUS_HITS + } onChangeExcludeHitsFromPreviousRun={useCallback( (exclude) => setParam('excludeHitsFromPreviousRun', exclude), [setParam] diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/esql_query_expression.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/esql_query_expression.tsx index febae77ad0f22..3ff2b70522e9a 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/esql_query_expression.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/esql_query_expression.tsx @@ -6,7 +6,7 @@ */ import React, { useState, Fragment, useEffect, useCallback } from 'react'; -import { debounce, get } from 'lodash'; +import { get } from 'lodash'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiFieldNumber, @@ -16,7 +16,6 @@ import { EuiSelect, EuiSpacer, } from '@elastic/eui'; -import { getESQLQueryColumns } from '@kbn/esql-utils'; import { getFields, RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; import { TextBasedLangEditor } from '@kbn/text-based-languages/public'; import { fetchFieldsFromESQL } from '@kbn/text-based-editor'; @@ -24,14 +23,12 @@ import { getIndexPatternFromESQLQuery } from '@kbn/esql-utils'; import type { AggregateQuery } from '@kbn/es-query'; import { parseDuration } from '@kbn/alerting-plugin/common'; import { - FieldOption, firstFieldOption, getTimeFieldOptions, getTimeOptions, parseAggregationResults, } from '@kbn/triggers-actions-ui-plugin/public/common'; import { DataView } from '@kbn/data-views-plugin/common'; -import { SourceFields } from '../../components/source_fields_select'; import { EsQueryRuleParams, EsQueryRuleMetaData, SearchType } from '../types'; import { DEFAULT_VALUES, SERVERLESS_DEFAULT_VALUES } from '../constants'; import { useTriggerUiActionServices } from '../util'; @@ -42,8 +39,8 @@ import { rowToDocument, toEsQueryHits, transformDatatableToEsqlTable } from '../ export const EsqlQueryExpression: React.FC< RuleTypeParamsExpressionProps, EsQueryRuleMetaData> > = ({ ruleParams, setRuleParams, setRuleProperty, errors }) => { - const { expressions, http, fieldFormats, isServerless, data } = useTriggerUiActionServices(); - const { esqlQuery, timeWindowSize, timeWindowUnit, timeField, sourceFields } = ruleParams; + const { expressions, http, fieldFormats, isServerless } = useTriggerUiActionServices(); + const { esqlQuery, timeWindowSize, timeWindowUnit, timeField } = ruleParams; const [currentRuleParams, setCurrentRuleParams] = useState< EsQueryRuleParams @@ -61,12 +58,12 @@ export const EsqlQueryExpression: React.FC< groupBy: DEFAULT_VALUES.GROUP_BY, termSize: DEFAULT_VALUES.TERM_SIZE, searchType: SearchType.esqlQuery, - sourceFields: sourceFields ?? DEFAULT_VALUES.SOURCE_FIELDS, + // The sourceFields param is ignored for the ES|QL type + sourceFields: [], }); const [query, setQuery] = useState({ esql: '' }); const [timeFieldOptions, setTimeFieldOptions] = useState([firstFieldOption]); const [detectTimestamp, setDetectTimestamp] = useState(false); - const [esFields, setEsFields] = useState([]); const [isLoading, setIsLoading] = useState(false); const setParam = useCallback( @@ -86,7 +83,6 @@ export const EsqlQueryExpression: React.FC< if (esqlQuery) { if (esqlQuery.esql) { refreshTimeFields(esqlQuery); - refreshEsFields(esqlQuery, false); } } if (timeField) { @@ -180,32 +176,6 @@ export const EsqlQueryExpression: React.FC< setDetectTimestamp(hasTimestamp); }; - const refreshEsFields = async (q: AggregateQuery, resetSourceFields: boolean = true) => { - let fields: FieldOption[] = []; - try { - const columns = await getESQLQueryColumns({ - esqlQuery: `${get(q, 'esql')}`, - search: data.search.search, - }); - if (columns.length) { - fields = columns.map((c) => ({ - name: c.id, - type: c.meta.type, - normalizedType: c.meta.type, - searchable: true, - aggregatable: true, - })); - } - } catch (error) { - /** ignore error */ - } - - if (resetSourceFields) { - setParam('sourceFields', undefined); - } - setEsFields(fields); - }; - return ( { + onTextLangQueryChange={(q: AggregateQuery) => { setQuery(q); setParam('esqlQuery', q); refreshTimeFields(q); - refreshEsFields(q); - }, 1000)} + }} expandCodeEditor={() => true} isCodeEditorExpanded={true} onTextLangQuerySubmit={async () => {}} @@ -236,14 +205,6 @@ export const EsqlQueryExpression: React.FC< isLoading={isLoading} /> - - setParam('sourceFields', selectedSourceFields) - } - esFields={esFields} - sourceFields={sourceFields} - errors={errors.sourceFields} - /> = { }, }, shouldWrite: true, + useEcs: true, }; diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.test.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.test.ts index 1d7096d20140e..b23cddc0eaab7 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.test.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.test.ts @@ -7,7 +7,7 @@ import { OnlyEsqlQueryRuleParams } from '../types'; import { Comparator } from '../../../../common/comparator_types'; -import { getEsqlQuery } from './fetch_esql_query'; +import { getEsqlQuery, getSourceFields } from './fetch_esql_query'; const getTimeRange = () => { const date = Date.now(); @@ -98,4 +98,30 @@ describe('fetchEsqlQuery', () => { `); }); }); + + describe('getSourceFields', () => { + it('should generate the correct source fields', async () => { + const sourceFields = getSourceFields({ + columns: [ + { name: '@timestamp', type: 'date' }, + { name: 'ecs.version', type: 'keyword' }, + { name: 'error.code', type: 'keyword' }, + ], + values: [['2023-07-12T13:32:04.174Z', '1.8.0', null]], + }); + + expect(sourceFields).toMatchInlineSnapshot(` + Array [ + Object { + "label": "ecs.version", + "searchPath": "ecs.version", + }, + Object { + "label": "error.code", + "searchPath": "error.code", + }, + ] + `); + }); + }); }); diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.ts index 87ae2c1123547..5ecb258b0579f 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.ts @@ -5,9 +5,11 @@ * 2.0. */ +import { intersectionBy } from 'lodash'; import { parseAggregationResults } from '@kbn/triggers-actions-ui-plugin/common'; import { SharePluginStart } from '@kbn/share-plugin/server'; import { IScopedClusterClient, Logger } from '@kbn/core/server'; +import { ecsFieldMap, alertFieldMap } from '@kbn/alerts-as-data-utils'; import { OnlyEsqlQueryRuleParams } from '../types'; import { EsqlTable, toEsQueryHits } from '../../../../common'; @@ -47,6 +49,8 @@ export async function fetchEsqlQuery({ path: '/_query', body: query, }); + const hits = toEsQueryHits(response); + const sourceFields = getSourceFields(response); const link = `${publicBaseUrl}${spacePrefix}/app/management/insightsAndAlerting/triggersActions/rule/${ruleId}`; @@ -60,10 +64,10 @@ export async function fetchEsqlQuery({ took: 0, timed_out: false, _shards: { failed: 0, successful: 0, total: 0 }, - hits: toEsQueryHits(response), + hits, }, resultLimit: alertLimit, - sourceFieldsParams: params.sourceFields, + sourceFieldsParams: sourceFields, generateSourceFieldsFromHits: true, }), index: null, @@ -98,3 +102,17 @@ export const getEsqlQuery = ( }; return query; }; + +export const getSourceFields = (results: EsqlTable) => { + const resultFields = results.columns.map((c) => ({ + label: c.name, + searchPath: c.name, + })); + const alertFields = Object.keys(alertFieldMap); + const ecsFields = Object.keys(ecsFieldMap) + // exclude the alert fields that we don't want to override + .filter((key) => !alertFields.includes(key)) + .map((key) => ({ label: key, searchPath: key })); + + return intersectionBy(resultFields, ecsFields, 'label'); +}; diff --git a/x-pack/plugins/stack_connectors/common/webhook/constants.ts b/x-pack/plugins/stack_connectors/common/auth/constants.ts similarity index 79% rename from x-pack/plugins/stack_connectors/common/webhook/constants.ts rename to x-pack/plugins/stack_connectors/common/auth/constants.ts index 48134762b34db..bdd5b7352f921 100644 --- a/x-pack/plugins/stack_connectors/common/webhook/constants.ts +++ b/x-pack/plugins/stack_connectors/common/auth/constants.ts @@ -5,7 +5,7 @@ * 2.0. */ -export enum WebhookAuthType { +export enum AuthType { Basic = 'webhook-authentication-basic', SSL = 'webhook-authentication-ssl', } @@ -14,3 +14,9 @@ export enum SSLCertType { CRT = 'ssl-crt-key', PFX = 'ssl-pfx', } + +export enum WebhookMethods { + PATCH = 'patch', + POST = 'post', + PUT = 'put', +} diff --git a/x-pack/plugins/stack_connectors/server/connector_types/webhook/mocks.ts b/x-pack/plugins/stack_connectors/common/auth/mocks.ts similarity index 100% rename from x-pack/plugins/stack_connectors/server/connector_types/webhook/mocks.ts rename to x-pack/plugins/stack_connectors/common/auth/mocks.ts diff --git a/x-pack/plugins/stack_connectors/common/auth/schema.ts b/x-pack/plugins/stack_connectors/common/auth/schema.ts new file mode 100644 index 0000000000000..91754b38f84db --- /dev/null +++ b/x-pack/plugins/stack_connectors/common/auth/schema.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +import { schema } from '@kbn/config-schema'; +import { AuthType, SSLCertType } from './constants'; + +export const authTypeSchema = schema.maybe( + schema.oneOf( + [schema.literal(AuthType.Basic), schema.literal(AuthType.SSL), schema.literal(null)], + { + defaultValue: AuthType.Basic, + } + ) +); + +export const hasAuthSchema = schema.boolean({ defaultValue: true }); + +export const AuthConfiguration = { + hasAuth: hasAuthSchema, + authType: authTypeSchema, + certType: schema.maybe( + schema.oneOf([schema.literal(SSLCertType.CRT), schema.literal(SSLCertType.PFX)]) + ), + ca: schema.maybe(schema.string()), + verificationMode: schema.maybe( + schema.oneOf([schema.literal('none'), schema.literal('certificate'), schema.literal('full')]) + ), +}; + +export const SecretConfiguration = { + user: schema.nullable(schema.string()), + password: schema.nullable(schema.string()), + crt: schema.nullable(schema.string()), + key: schema.nullable(schema.string()), + pfx: schema.nullable(schema.string()), +}; + +export const SecretConfigurationSchemaValidation = { + validate: (secrets: any) => { + // user and password must be set together (or not at all) + if (!secrets.password && !secrets.user && !secrets.crt && !secrets.key && !secrets.pfx) return; + if (secrets.password && secrets.user && !secrets.crt && !secrets.key && !secrets.pfx) return; + if (secrets.crt && secrets.key && !secrets.user && !secrets.pfx) return; + if (!secrets.crt && !secrets.key && !secrets.user && secrets.pfx) return; + return i18n.translate('xpack.stackConnectors.webhook.invalidSecrets', { + defaultMessage: + 'must specify one of the following schemas: user and password; crt and key (with optional password); or pfx (with optional password)', + }); + }, +}; + +export const SecretConfigurationSchema = schema.object( + SecretConfiguration, + SecretConfigurationSchemaValidation +); diff --git a/x-pack/plugins/stack_connectors/common/auth/types.ts b/x-pack/plugins/stack_connectors/common/auth/types.ts new file mode 100644 index 0000000000000..ba09bc1a23d68 --- /dev/null +++ b/x-pack/plugins/stack_connectors/common/auth/types.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { TypeOf } from '@kbn/config-schema'; +import { + AuthConfiguration, + authTypeSchema, + hasAuthSchema, + SecretConfigurationSchema, +} from './schema'; + +export type HasAuth = TypeOf; +export type AuthTypeName = TypeOf; +export type SecretsConfigurationType = TypeOf; +export type CAType = TypeOf; +export type VerificationModeType = TypeOf; diff --git a/x-pack/plugins/stack_connectors/common/auth/utils.test.ts b/x-pack/plugins/stack_connectors/common/auth/utils.test.ts new file mode 100644 index 0000000000000..78209d0d1db7b --- /dev/null +++ b/x-pack/plugins/stack_connectors/common/auth/utils.test.ts @@ -0,0 +1,209 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AuthType } from './constants'; +import { buildConnectorAuth, isBasicAuth, validateConnectorAuthConfiguration } from './utils'; + +describe('utils', () => { + describe('isBasicAuth', () => { + it('returns false when hasAuth is false and authType is undefined', () => { + expect( + isBasicAuth({ + hasAuth: false, + authType: undefined, + }) + ).toBe(false); + }); + + it('returns false when hasAuth is false and authType is basic', () => { + expect( + isBasicAuth({ + hasAuth: false, + authType: AuthType.Basic, + }) + ).toBe(false); + }); + + it('returns false when hasAuth is true and authType is ssl', () => { + expect( + isBasicAuth({ + hasAuth: true, + authType: AuthType.SSL, + }) + ).toBe(false); + }); + + it('returns true when hasAuth is true and authType is undefined', () => { + expect( + isBasicAuth({ + hasAuth: true, + authType: undefined, + }) + ).toBe(true); + }); + + it('returns true when hasAuth is true and authType is basic', () => { + expect( + isBasicAuth({ + hasAuth: true, + authType: AuthType.Basic, + }) + ).toBe(true); + }); + }); + + describe('validateConnectorAuthConfiguration', () => { + it('does not throw with correct authType=basic params', () => { + expect(() => + validateConnectorAuthConfiguration({ + hasAuth: true, + authType: AuthType.Basic, + basicAuth: { auth: { username: 'foo', password: 'bar' } }, + sslOverrides: {}, + connectorName: 'foobar', + }) + ).not.toThrow(); + }); + + it('does not throw with correct authType=undefined params', () => { + expect(() => + validateConnectorAuthConfiguration({ + hasAuth: true, + authType: undefined, + basicAuth: { auth: { username: 'foo', password: 'bar' } }, + sslOverrides: {}, + connectorName: 'foobar', + }) + ).not.toThrow(); + }); + + it('throws when type is basic and the username is missing', () => { + expect(() => + validateConnectorAuthConfiguration({ + hasAuth: true, + authType: undefined, + // @ts-ignore: that's what we are testing + basicAuth: { auth: { password: 'bar' } }, + sslOverrides: {}, + connectorName: 'Foobar', + }) + ).toThrow('[Action]Foobar: Wrong configuration.'); + }); + + it('throws when type is basic and the password is missing', () => { + expect(() => + validateConnectorAuthConfiguration({ + hasAuth: true, + authType: undefined, + // @ts-ignore: that's what we are testing + basicAuth: { auth: { username: 'foo' } }, + sslOverrides: {}, + connectorName: 'Foobar', + }) + ).toThrow('[Action]Foobar: Wrong configuration.'); + }); + + it('does not throw with correct authType=ssl params', () => { + expect(() => + validateConnectorAuthConfiguration({ + hasAuth: true, + authType: AuthType.SSL, + basicAuth: {}, + sslOverrides: { verificationMode: 'none', passphrase: 'passphrase' }, + connectorName: 'foobar', + }) + ).not.toThrow(); + }); + + it('throws when type is SSL and the sslOverrides are missing', () => { + expect(() => + validateConnectorAuthConfiguration({ + hasAuth: true, + authType: AuthType.SSL, + basicAuth: {}, + sslOverrides: {}, + connectorName: 'Foobar', + }) + ).toThrow('[Action]Foobar: Wrong configuration.'); + }); + }); + + describe('buildConnectorAuth', () => { + it('returns empty objects when hasAuth=false', () => { + expect( + buildConnectorAuth({ + hasAuth: false, + authType: AuthType.SSL, + secrets: { user: 'foo', password: 'bar', crt: null, key: null, pfx: null }, + verificationMode: undefined, + ca: undefined, + }) + ).toEqual({ basicAuth: {}, sslOverrides: {} }); + }); + + it('builds basicAuth correctly with authType=basic', () => { + expect( + buildConnectorAuth({ + hasAuth: true, + authType: AuthType.Basic, + secrets: { user: 'foo', password: 'bar', crt: null, key: null, pfx: null }, + verificationMode: undefined, + ca: undefined, + }) + ).toEqual({ basicAuth: { auth: { username: 'foo', password: 'bar' } }, sslOverrides: {} }); + }); + + it('builds basicAuth correctly with hasAuth=true and authType=undefined', () => { + expect( + buildConnectorAuth({ + hasAuth: true, + authType: undefined, + secrets: { user: 'foo', password: 'bar', crt: null, key: null, pfx: null }, + verificationMode: undefined, + ca: undefined, + }) + ).toEqual({ basicAuth: { auth: { username: 'foo', password: 'bar' } }, sslOverrides: {} }); + }); + + it('builds sslOverrides correctly with authType=ssl', () => { + expect( + buildConnectorAuth({ + hasAuth: true, + authType: AuthType.SSL, + secrets: { user: 'foo', password: 'bar', crt: 'null', key: 'null', pfx: 'null' }, + verificationMode: 'certificate', + ca: 'foobar?', + }) + ).toMatchInlineSnapshot(` + Object { + "basicAuth": Object {}, + "sslOverrides": Object { + "ca": Object { + "data": Array [ + 126, + 138, + 27, + 106, + ], + "type": "Buffer", + }, + "passphrase": "bar", + "pfx": Object { + "data": Array [ + 158, + 233, + 101, + ], + "type": "Buffer", + }, + "verificationMode": "certificate", + }, + } + `); + }); + }); +}); diff --git a/x-pack/plugins/stack_connectors/common/auth/utils.ts b/x-pack/plugins/stack_connectors/common/auth/utils.ts new file mode 100644 index 0000000000000..8c16ee3316274 --- /dev/null +++ b/x-pack/plugins/stack_connectors/common/auth/utils.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { isString, isEmpty } from 'lodash'; + +import type { SSLSettings } from '@kbn/actions-plugin/server/types'; +import type { + AuthTypeName, + CAType, + HasAuth, + SecretsConfigurationType, + VerificationModeType, +} from './types'; + +import { AuthType } from './constants'; + +// For backwards compatibility with connectors created before authType was added, interpret a +// hasAuth: true and undefined authType as basic auth +export const isBasicAuth = ({ + hasAuth, + authType, +}: { + hasAuth: HasAuth; + authType: AuthTypeName; +}): boolean => hasAuth && (authType === AuthType.Basic || !authType); + +interface BasicAuthResponse { + auth?: { username: string; password: string }; +} + +export const buildConnectorAuth = ({ + hasAuth, + authType, + secrets, + verificationMode, + ca, +}: { + hasAuth: HasAuth; + authType: AuthTypeName; + secrets: SecretsConfigurationType; + verificationMode: VerificationModeType; + ca: CAType; +}): { basicAuth: BasicAuthResponse; sslOverrides: SSLSettings } => { + let basicAuth: BasicAuthResponse = {}; + let sslOverrides: SSLSettings = {}; + let sslCertificate = {}; + + if (isBasicAuth({ hasAuth, authType })) { + basicAuth = + isString(secrets.user) && isString(secrets.password) + ? { auth: { username: secrets.user, password: secrets.password } } + : {}; + } else if (hasAuth && authType === AuthType.SSL) { + sslCertificate = + (isString(secrets.crt) && isString(secrets.key)) || isString(secrets.pfx) + ? isString(secrets.pfx) + ? { + pfx: Buffer.from(secrets.pfx, 'base64'), + ...(isString(secrets.password) ? { passphrase: secrets.password } : {}), + } + : { + cert: Buffer.from(secrets.crt!, 'base64'), + key: Buffer.from(secrets.key!, 'base64'), + ...(isString(secrets.password) ? { passphrase: secrets.password } : {}), + } + : {}; + } + + sslOverrides = { + ...sslCertificate, + ...(verificationMode ? { verificationMode } : {}), + ...(ca ? { ca: Buffer.from(ca, 'base64') } : {}), + }; + + return { basicAuth, sslOverrides }; +}; + +export const validateConnectorAuthConfiguration = ({ + hasAuth, + authType, + basicAuth, + sslOverrides, + connectorName, +}: { + hasAuth: HasAuth; + authType: AuthTypeName; + basicAuth: BasicAuthResponse; + sslOverrides: SSLSettings; + connectorName: string; +}) => { + if ( + (isBasicAuth({ hasAuth, authType }) && + (!basicAuth.auth?.password || !basicAuth.auth?.username)) || + (authType === AuthType.SSL && isEmpty(sslOverrides)) + ) { + throw Error(`[Action]${connectorName}: Wrong configuration.`); + } +}; diff --git a/x-pack/plugins/stack_connectors/common/bedrock/schema.ts b/x-pack/plugins/stack_connectors/common/bedrock/schema.ts index 2fade1be5fc40..bf35aa6bb8e0d 100644 --- a/x-pack/plugins/stack_connectors/common/bedrock/schema.ts +++ b/x-pack/plugins/stack_connectors/common/bedrock/schema.ts @@ -38,6 +38,7 @@ export const InvokeAIActionParamsSchema = schema.object({ temperature: schema.maybe(schema.number()), stopSequences: schema.maybe(schema.arrayOf(schema.string())), system: schema.maybe(schema.string()), + maxTokens: schema.maybe(schema.number()), // abort signal from client signal: schema.maybe(schema.any()), timeout: schema.maybe(schema.number()), diff --git a/x-pack/plugins/stack_connectors/common/gemini/constants.ts b/x-pack/plugins/stack_connectors/common/gemini/constants.ts index 2df2e4f635e33..e0c1c6f56c65a 100644 --- a/x-pack/plugins/stack_connectors/common/gemini/constants.ts +++ b/x-pack/plugins/stack_connectors/common/gemini/constants.ts @@ -18,6 +18,8 @@ export enum SUB_ACTION { RUN = 'run', DASHBOARD = 'getDashboard', TEST = 'test', + INVOKE_AI = 'invokeAI', + INVOKE_STREAM = 'invokeStream', } export const DEFAULT_TOKEN_LIMIT = 8192; diff --git a/x-pack/plugins/stack_connectors/common/gemini/schema.ts b/x-pack/plugins/stack_connectors/common/gemini/schema.ts index aa29f92916db9..91b523ef4853b 100644 --- a/x-pack/plugins/stack_connectors/common/gemini/schema.ts +++ b/x-pack/plugins/stack_connectors/common/gemini/schema.ts @@ -24,6 +24,8 @@ export const RunActionParamsSchema = schema.object({ model: schema.maybe(schema.string()), signal: schema.maybe(schema.any()), timeout: schema.maybe(schema.number()), + temperature: schema.maybe(schema.number()), + stopSequences: schema.maybe(schema.arrayOf(schema.string())), }); export const RunApiResponseSchema = schema.object({ @@ -50,6 +52,28 @@ export const RunActionResponseSchema = schema.object( { unknowns: 'ignore' } ); +export const InvokeAIActionParamsSchema = schema.object({ + messages: schema.any(), + model: schema.maybe(schema.string()), + temperature: schema.maybe(schema.number()), + stopSequences: schema.maybe(schema.arrayOf(schema.string())), + signal: schema.maybe(schema.any()), + timeout: schema.maybe(schema.number()), +}); + +export const InvokeAIActionResponseSchema = schema.object({ + message: schema.string(), + usageMetadata: schema.maybe( + schema.object({ + promptTokenCount: schema.number(), + candidatesTokenCount: schema.number(), + totalTokenCount: schema.number(), + }) + ), +}); + +export const StreamingResponseSchema = schema.any(); + export const DashboardActionParamsSchema = schema.object({ dashboardId: schema.string(), }); diff --git a/x-pack/plugins/stack_connectors/common/gemini/types.ts b/x-pack/plugins/stack_connectors/common/gemini/types.ts index 12d3ed4a6b1c4..bc9a96ebdfdd7 100644 --- a/x-pack/plugins/stack_connectors/common/gemini/types.ts +++ b/x-pack/plugins/stack_connectors/common/gemini/types.ts @@ -14,6 +14,9 @@ import { RunActionParamsSchema, RunActionResponseSchema, RunApiResponseSchema, + InvokeAIActionParamsSchema, + InvokeAIActionResponseSchema, + StreamingResponseSchema, } from './schema'; export type Config = TypeOf; @@ -23,3 +26,6 @@ export type RunApiResponse = TypeOf; export type RunActionResponse = TypeOf; export type DashboardActionParams = TypeOf; export type DashboardActionResponse = TypeOf; +export type InvokeAIActionParams = TypeOf; +export type InvokeAIActionResponse = TypeOf; +export type StreamingResponse = TypeOf; diff --git a/x-pack/plugins/stack_connectors/common/servicenow/constants.ts b/x-pack/plugins/stack_connectors/common/servicenow/constants.ts new file mode 100644 index 0000000000000..adc6981bf381a --- /dev/null +++ b/x-pack/plugins/stack_connectors/common/servicenow/constants.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const MAX_ADDITIONAL_FIELDS_LENGTH = 20; diff --git a/x-pack/plugins/stack_connectors/public/common/auth/auth_config.test.tsx b/x-pack/plugins/stack_connectors/public/common/auth/auth_config.test.tsx new file mode 100644 index 0000000000000..6a6e15ac378de --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/common/auth/auth_config.test.tsx @@ -0,0 +1,483 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { AuthConfig } from './auth_config'; +import { render, screen, waitFor, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { AuthType, SSLCertType } from '../../../common/auth/constants'; +import { AuthFormTestProvider } from '../../connector_types/lib/test_utils'; + +describe('AuthConfig renders', () => { + const onSubmit = jest.fn(); + + it('renders all fields for authType=None', async () => { + const testFormData = { + config: { + hasAuth: false, + }, + __internal__: { + hasCA: true, + hasHeaders: true, + }, + }; + render( + + + + ); + + expect(await screen.findByTestId('webhookViewHeadersSwitch')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookHeaderText')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookHeadersKeyInput')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookHeadersValueInput')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookAddHeaderButton')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookViewCASwitch')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookCAInput')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookVerificationModeSelect')).toBeInTheDocument(); + expect(await screen.findByTestId('authNone')).toBeInTheDocument(); + expect(await screen.findByTestId('authBasic')).toBeInTheDocument(); + expect(screen.queryByTestId('basicAuthFields')).not.toBeInTheDocument(); + expect(await screen.findByTestId('authSSL')).toBeInTheDocument(); + expect(screen.queryByTestId('sslCertFields')).not.toBeInTheDocument(); + }); + + it('toggles headers as expected', async () => { + const testFormData = { + config: { + hasAuth: false, + }, + __internal__: { + hasCA: false, + hasHeaders: false, + }, + }; + render( + + + + ); + + const headersToggle = await screen.findByTestId('webhookViewHeadersSwitch'); + + expect(headersToggle).toBeInTheDocument(); + + userEvent.click(headersToggle); + + expect(await screen.findByTestId('webhookHeaderText')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookHeadersKeyInput')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookHeadersValueInput')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookAddHeaderButton')).toBeInTheDocument(); + }); + + it('toggles CA as expected', async () => { + const testFormData = { + config: { + hasAuth: false, + }, + __internal__: { + hasCA: false, + hasHeaders: false, + }, + }; + + render( + + + + ); + + const caToggle = await screen.findByTestId('webhookViewCASwitch'); + + expect(caToggle).toBeInTheDocument(); + + userEvent.click(caToggle); + + expect(await screen.findByTestId('webhookViewCASwitch')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookCAInput')).toBeInTheDocument(); + + const verificationModeSelect = await screen.findByTestId('webhookVerificationModeSelect'); + + expect(verificationModeSelect).toBeInTheDocument(); + + ['None', 'Certificate', 'Full'].forEach((optionName) => { + const select = within(verificationModeSelect); + + expect(select.getByRole('option', { name: optionName })); + }); + }); + + it('renders all fields for authType=Basic', async () => { + const testFormData = { + config: { + hasAuth: true, + authType: AuthType.Basic, + }, + secrets: { + user: 'user', + password: 'pass', + }, + }; + + render( + + + + ); + + expect(await screen.findByTestId('webhookViewHeadersSwitch')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookViewCASwitch')).toBeInTheDocument(); + expect(await screen.findByTestId('authNone')).toBeInTheDocument(); + expect(await screen.findByTestId('authBasic')).toBeInTheDocument(); + expect(await screen.findByTestId('basicAuthFields')).toBeInTheDocument(); + expect(await screen.findByTestId('authSSL')).toBeInTheDocument(); + expect(screen.queryByTestId('sslCertFields')).not.toBeInTheDocument(); + }); + + it('renders all fields for authType=SSL', async () => { + const testFormData = { + config: { + hasAuth: true, + authType: AuthType.SSL, + certType: SSLCertType.CRT, + }, + secrets: { + crt: Buffer.from('some binary string').toString('base64'), + key: Buffer.from('some binary string').toString('base64'), + }, + }; + render( + + + + ); + + expect(await screen.findByTestId('webhookViewHeadersSwitch')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookViewCASwitch')).toBeInTheDocument(); + expect(await screen.findByTestId('authNone')).toBeInTheDocument(); + expect(await screen.findByTestId('authBasic')).toBeInTheDocument(); + expect(screen.queryByTestId('basicAuthFields')).not.toBeInTheDocument(); + expect(await screen.findByTestId('authSSL')).toBeInTheDocument(); + expect(await screen.findByTestId('sslCertFields')).toBeInTheDocument(); + }); + + it('renders all fields for authType=SSL and certType=PFX', async () => { + const testFormData = { + config: { + hasAuth: true, + authType: AuthType.SSL, + certType: SSLCertType.PFX, + }, + secrets: { + crt: Buffer.from('some binary string').toString('base64'), + key: Buffer.from('some binary string').toString('base64'), + }, + }; + render( + + + + ); + + expect(await screen.findByTestId('webhookViewHeadersSwitch')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookViewCASwitch')).toBeInTheDocument(); + expect(await screen.findByTestId('authNone')).toBeInTheDocument(); + expect(await screen.findByTestId('authBasic')).toBeInTheDocument(); + expect(screen.queryByTestId('basicAuthFields')).not.toBeInTheDocument(); + expect(await screen.findByTestId('authSSL')).toBeInTheDocument(); + expect(await screen.findByTestId('sslCertFields')).toBeInTheDocument(); + }); + + describe('Validation', () => { + const defaultTestFormData = { + config: { + headers: [{ key: 'content-type', value: 'text' }], + hasAuth: true, + }, + secrets: { + user: 'user', + password: 'pass', + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('succeeds with hasAuth=True', async () => { + const testFormData = { + config: { + headers: [{ key: 'content-type', value: 'text' }], + hasAuth: true, + }, + secrets: { + user: 'user', + password: 'pass', + }, + }; + render( + + + + ); + + userEvent.click(await screen.findByTestId('form-test-provide-submit')); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith({ + data: { + config: { + headers: [{ key: 'content-type', value: 'text' }], + hasAuth: true, + authType: AuthType.Basic, + }, + secrets: { + user: 'user', + password: 'pass', + }, + __internal__: { + hasHeaders: true, + hasCA: false, + }, + }, + isValid: true, + }); + }); + }); + + it('succeeds with hasAuth=false', async () => { + const testFormData = { + config: { + ...defaultTestFormData.config, + hasAuth: false, + }, + }; + render( + + + + ); + + userEvent.click(await screen.findByTestId('form-test-provide-submit')); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith({ + data: { + config: { + headers: [{ key: 'content-type', value: 'text' }], + hasAuth: false, + authType: null, + }, + __internal__: { + hasHeaders: true, + hasCA: false, + }, + }, + isValid: true, + }); + }); + }); + + it('succeeds without headers', async () => { + const testConfig = { + config: { + hasAuth: true, + authType: AuthType.Basic, + }, + secrets: { + user: 'user', + password: 'pass', + }, + }; + + render( + + + + ); + + userEvent.click(await screen.findByTestId('form-test-provide-submit')); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith({ + data: { + config: { + hasAuth: true, + authType: AuthType.Basic, + }, + secrets: { + user: 'user', + password: 'pass', + }, + __internal__: { + hasHeaders: false, + hasCA: false, + }, + }, + isValid: true, + }); + }); + }); + + it('succeeds with CA and verificationMode', async () => { + const testConfig = { + ...defaultTestFormData, + config: { + ...defaultTestFormData.config, + ca: Buffer.from('some binary string').toString('base64'), + verificationMode: 'full', + }, + }; + + render( + + + + ); + + userEvent.click(await screen.findByTestId('form-test-provide-submit')); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith({ + data: { + config: { + hasAuth: true, + authType: AuthType.Basic, + ca: Buffer.from('some binary string').toString('base64'), + verificationMode: 'full', + headers: [{ key: 'content-type', value: 'text' }], + }, + secrets: { + user: 'user', + password: 'pass', + }, + __internal__: { + hasHeaders: true, + hasCA: true, + }, + }, + isValid: true, + }); + }); + }); + + it('fails with hasCa=true and a missing CA', async () => { + const testConfig = { + ...defaultTestFormData, + config: { + ...defaultTestFormData.config, + verificationMode: 'full', + }, + __internal__: { + hasHeaders: true, + hasCA: true, + }, + }; + + render( + + + + ); + + userEvent.click(await screen.findByTestId('form-test-provide-submit')); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith({ + data: {}, + isValid: false, + }); + }); + }); + + it('succeeds with authType=SSL and a CRT and KEY', async () => { + const testConfig = { + config: { + ...defaultTestFormData.config, + authType: AuthType.SSL, + certType: SSLCertType.CRT, + }, + secrets: { + crt: Buffer.from('some binary string').toString('base64'), + key: Buffer.from('some binary string').toString('base64'), + }, + }; + + render( + + + + ); + + userEvent.click(await screen.findByTestId('form-test-provide-submit')); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith({ + data: { + config: { + hasAuth: true, + authType: AuthType.SSL, + certType: SSLCertType.CRT, + headers: [{ key: 'content-type', value: 'text' }], + }, + secrets: { + crt: Buffer.from('some binary string').toString('base64'), + key: Buffer.from('some binary string').toString('base64'), + }, + __internal__: { + hasHeaders: true, + hasCA: false, + }, + }, + isValid: true, + }); + }); + }); + + it('succeeds with authType=SSL and a PFX', async () => { + const testConfig = { + config: { + ...defaultTestFormData.config, + authType: AuthType.SSL, + certType: SSLCertType.PFX, + }, + secrets: { + pfx: Buffer.from('some binary string').toString('base64'), + }, + }; + + render( + + + + ); + + userEvent.click(await screen.findByTestId('form-test-provide-submit')); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith({ + data: { + config: { + hasAuth: true, + authType: AuthType.SSL, + certType: SSLCertType.PFX, + headers: [{ key: 'content-type', value: 'text' }], + }, + secrets: { + pfx: Buffer.from('some binary string').toString('base64'), + }, + __internal__: { + hasHeaders: true, + hasCA: false, + }, + }, + isValid: true, + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/stack_connectors/public/common/auth/auth_config.tsx b/x-pack/plugins/stack_connectors/public/common/auth/auth_config.tsx new file mode 100644 index 0000000000000..e4477be874ecb --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/common/auth/auth_config.tsx @@ -0,0 +1,292 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent, useEffect } from 'react'; + +import { + EuiButtonEmpty, + EuiButtonIcon, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { + UseArray, + UseField, + useFormContext, + useFormData, +} from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { + ToggleField, + TextField, + CardRadioGroupField, + HiddenField, + FilePickerField, + SelectField, +} from '@kbn/es-ui-shared-plugin/static/forms/components'; + +import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; +import { AuthType, SSLCertType } from '../../../common/auth/constants'; +import { SSLCertFields } from './ssl_cert_fields'; +import { BasicAuthFields } from './basic_auth_fields'; +import * as i18n from './translations'; + +interface Props { + readOnly: boolean; + hideSSL?: boolean; +} + +const { emptyField } = fieldValidators; + +const VERIFICATION_MODE_DEFAULT = 'full'; + +export const AuthConfig: FunctionComponent = ({ readOnly, hideSSL }) => { + const { setFieldValue, getFieldDefaultValue } = useFormContext(); + const [{ config, __internal__ }] = useFormData({ + watch: [ + 'config.hasAuth', + 'config.authType', + 'config.certType', + 'config.verificationMode', + '__internal__.hasHeaders', + '__internal__.hasCA', + ], + }); + + const authType = config == null ? AuthType.Basic : config.authType; + const certType = config == null ? SSLCertType.CRT : config.certType; + const hasHeaders = __internal__ != null ? __internal__.hasHeaders : false; + const hasCA = __internal__ != null ? __internal__.hasCA : false; + const hasInitialCA = !!getFieldDefaultValue('config.ca'); + const hasHeadersDefaultValue = !!getFieldDefaultValue('config.headers'); + const authTypeDefaultValue = + getFieldDefaultValue('config.hasAuth') === false + ? null + : getFieldDefaultValue('config.authType') ?? AuthType.Basic; + const certTypeDefaultValue: SSLCertType = + getFieldDefaultValue('config.certType') ?? SSLCertType.CRT; + const hasCADefaultValue = + !!getFieldDefaultValue('config.ca') || + getFieldDefaultValue('config.verificationMode') === 'none'; + + useEffect(() => setFieldValue('config.hasAuth', Boolean(authType)), [authType, setFieldValue]); + + const hideSSLFields = hideSSL && authType !== AuthType.SSL; + + const authOptions = [ + { + value: null, + label: i18n.AUTHENTICATION_NONE, + 'data-test-subj': 'authNone', + }, + { + value: AuthType.Basic, + label: i18n.AUTHENTICATION_BASIC, + children: authType === AuthType.Basic && , + 'data-test-subj': 'authBasic', + }, + ]; + + if (!hideSSLFields) { + authOptions.push({ + value: AuthType.SSL, + label: i18n.AUTHENTICATION_SSL, + children: authType === AuthType.SSL && ( + + ), + 'data-test-subj': 'authSSL', + }); + } + + return ( + <> + + + +

{i18n.AUTHENTICATION_TITLE}

+
+
+
+ + + + + + {hasHeaders && ( + + {({ items, addItem, removeItem }) => { + return ( + <> + +
{i18n.HEADERS_TITLE}
+
+ + {items.map((item) => ( + + + + + + + + + removeItem(item.id)} + iconType="minusInCircle" + aria-label={i18n.DELETE_BUTTON} + style={{ marginTop: '28px' }} + /> + + + ))} + + + {i18n.ADD_BUTTON} + + + + ); + }} +
+ )} + + {!hideSSLFields && ( + <> + + {hasCA && ( + <> + + + + {}, + }, + ], + }} + component={FilePickerField} + componentProps={{ + euiFieldProps: { + display: 'default', + 'data-test-subj': 'webhookCAInput', + accept: '.ca,.pem', + }, + }} + /> + + + + + + {hasInitialCA && ( + <> + + + + )} + + )} + + )} + + ); +}; diff --git a/x-pack/plugins/stack_connectors/public/common/auth/basic_auth_fields.test.tsx b/x-pack/plugins/stack_connectors/public/common/auth/basic_auth_fields.test.tsx new file mode 100644 index 0000000000000..6d740be7b270e --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/common/auth/basic_auth_fields.test.tsx @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { BasicAuthFields } from './basic_auth_fields'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { AuthFormTestProvider } from '../../connector_types/lib/test_utils'; + +describe('BasicAuthFields', () => { + const onSubmit = jest.fn(); + + it('renders all fields', async () => { + const testFormData = { + secrets: { + user: 'user', + password: 'pass', + }, + }; + + render( + + + + ); + + expect(await screen.findByTestId('basicAuthFields')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookUserInput')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookPasswordInput')).toBeInTheDocument(); + }); + + describe('Validation', () => { + const defaultTestFormData = { + secrets: { + user: 'user', + password: 'pass', + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('validation succeeds with correct fields', async () => { + render( + + + + ); + + userEvent.click(await screen.findByTestId('form-test-provide-submit')); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith({ + data: { + secrets: { + user: 'user', + password: 'pass', + }, + }, + isValid: true, + }); + }); + }); + + it('validates correctly missing user', async () => { + const testConfig = { + secrets: { + user: '', + password: 'password', + }, + }; + + render( + + + + ); + + userEvent.click(await screen.findByTestId('form-test-provide-submit')); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false }); + }); + }); + + it('validates correctly missing password', async () => { + const testConfig = { + secrets: { + user: 'user', + password: '', + }, + }; + + render( + + + + ); + + userEvent.click(await screen.findByTestId('form-test-provide-submit')); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false }); + }); + }); + }); +}); diff --git a/x-pack/plugins/stack_connectors/public/common/auth/basic_auth_fields.tsx b/x-pack/plugins/stack_connectors/public/common/auth/basic_auth_fields.tsx new file mode 100644 index 0000000000000..5b26f0e888e2b --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/common/auth/basic_auth_fields.tsx @@ -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 React from 'react'; + +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { Field, PasswordField } from '@kbn/es-ui-shared-plugin/static/forms/components'; +import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; + +import * as i18n from './translations'; + +const { emptyField } = fieldValidators; + +interface BasicAuthProps { + readOnly: boolean; +} + +export const BasicAuthFields: React.FC = ({ readOnly }) => ( + + + + + + + + +); diff --git a/x-pack/plugins/stack_connectors/public/common/auth/ssl_cert_fields.test.tsx b/x-pack/plugins/stack_connectors/public/common/auth/ssl_cert_fields.test.tsx new file mode 100644 index 0000000000000..9a39926b21324 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/common/auth/ssl_cert_fields.test.tsx @@ -0,0 +1,223 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { SSLCertFields } from './ssl_cert_fields'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { SSLCertType } from '../../../common/auth/constants'; +import { AuthFormTestProvider } from '../../connector_types/lib/test_utils'; + +const certTypeDefaultValue: SSLCertType = SSLCertType.CRT; + +describe('SSLCertFields', () => { + const onSubmit = jest.fn(); + + it('renders all fields for certType=CRT', async () => { + render( + + + + ); + + expect(await screen.findByTestId('sslCertFields')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookSSLPassphraseInput')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookCertTypeTabs')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookSSLCRTInput')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookSSLKEYInput')).toBeInTheDocument(); + }); + + it('renders all fields for certType=PFX', async () => { + render( + + + + ); + + expect(await screen.findByTestId('sslCertFields')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookSSLPassphraseInput')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookCertTypeTabs')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookSSLPFXInput')).toBeInTheDocument(); + }); + + describe('Validation', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('validates correctly with a PFX', async () => { + const testConfig = { + config: { + certType: SSLCertType.PFX, + }, + secrets: { + pfx: Buffer.from('some binary string').toString('base64'), + }, + }; + + render( + + + + ); + + userEvent.click(await screen.findByTestId('form-test-provide-submit')); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith({ + data: { + config: { + certType: SSLCertType.PFX, + }, + secrets: { + pfx: Buffer.from('some binary string').toString('base64'), + }, + }, + isValid: true, + }); + }); + }); + + it('validates correctly a missing PFX', async () => { + const testConfig = { + config: { + certType: SSLCertType.PFX, + }, + }; + + render( + + + + ); + + userEvent.click(await screen.findByTestId('form-test-provide-submit')); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith({ + data: {}, + isValid: false, + }); + }); + }); + + it('validates correctly with a CRT and KEY', async () => { + const testConfig = { + config: { + certType: SSLCertType.CRT, + }, + secrets: { + crt: Buffer.from('some binary string').toString('base64'), + key: Buffer.from('some binary string').toString('base64'), + }, + }; + + render( + + + + ); + + userEvent.click(await screen.findByTestId('form-test-provide-submit')); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith({ + data: { + config: { + certType: SSLCertType.CRT, + }, + secrets: { + crt: Buffer.from('some binary string').toString('base64'), + key: Buffer.from('some binary string').toString('base64'), + }, + }, + isValid: true, + }); + }); + }); + + it('validates correctly with a CRT but a missing KEY', async () => { + const testConfig = { + config: { + certType: SSLCertType.CRT, + }, + secrets: { + crt: Buffer.from('some binary string').toString('base64'), + }, + }; + + render( + + + + ); + + userEvent.click(await screen.findByTestId('form-test-provide-submit')); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith({ + data: {}, + isValid: false, + }); + }); + }); + + it('validates correctly with a KEY but a missing CRT', async () => { + const testConfig = { + config: { + certType: SSLCertType.CRT, + }, + secrets: { + key: Buffer.from('some binary string').toString('base64'), + }, + }; + + render( + + + + ); + + userEvent.click(await screen.findByTestId('form-test-provide-submit')); + + await waitFor(() => { + expect(onSubmit).toHaveBeenCalledWith({ + data: {}, + isValid: false, + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/stack_connectors/public/common/auth/ssl_cert_fields.tsx b/x-pack/plugins/stack_connectors/public/common/auth/ssl_cert_fields.tsx new file mode 100644 index 0000000000000..cf9dc672dde33 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/common/auth/ssl_cert_fields.tsx @@ -0,0 +1,138 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTab, EuiTabs } from '@elastic/eui'; +import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { PasswordField } from '@kbn/es-ui-shared-plugin/static/forms/components'; +import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; +import { FilePickerField } from '@kbn/es-ui-shared-plugin/static/forms/components'; + +import { SSLCertType } from '../../../common/auth/constants'; +import * as i18n from './translations'; + +const { emptyField } = fieldValidators; + +interface BasicAuthProps { + readOnly: boolean; + certTypeDefaultValue: SSLCertType; + certType: SSLCertType; +} + +export const SSLCertFields: React.FC = ({ + readOnly, + certTypeDefaultValue, + certType, +}) => ( + + + + + ( + + field.setValue(SSLCertType.CRT)} + isSelected={field.value === SSLCertType.CRT} + > + {i18n.CERT_TYPE_CRT_KEY} + + field.setValue(SSLCertType.PFX)} + isSelected={field.value === SSLCertType.PFX} + > + {i18n.CERT_TYPE_PFX} + + + )} + /> + + {certType === SSLCertType.CRT && ( + + + + + + + + + )} + {certType === SSLCertType.PFX && ( + + )} + + +); diff --git a/x-pack/plugins/stack_connectors/public/common/auth/translations.ts b/x-pack/plugins/stack_connectors/public/common/auth/translations.ts new file mode 100644 index 0000000000000..88d97bef6364d --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/common/auth/translations.ts @@ -0,0 +1,159 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const AUTHENTICATION_TITLE = i18n.translate( + 'xpack.stackConnectors.components.auth.authenticationTitle', + { + defaultMessage: 'Authentication', + } +); + +export const AUTHENTICATION_NONE = i18n.translate( + 'xpack.stackConnectors.components.auth.authenticationMethodNoneLabel', + { + defaultMessage: 'None', + } +); + +export const AUTHENTICATION_BASIC = i18n.translate( + 'xpack.stackConnectors.components.auth.authenticationMethodBasicLabel', + { + defaultMessage: 'Basic authentication', + } +); + +export const AUTHENTICATION_SSL = i18n.translate( + 'xpack.stackConnectors.components.auth.authenticationMethodSSLLabel', + { + defaultMessage: 'SSL authentication', + } +); + +export const USERNAME = i18n.translate('xpack.stackConnectors.components.auth.userTextFieldLabel', { + defaultMessage: 'Username', +}); + +export const PASSWORD = i18n.translate( + 'xpack.stackConnectors.components.auth.passwordTextFieldLabel', + { + defaultMessage: 'Password', + } +); + +export const USERNAME_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.auth.error.requiredAuthUserNameText', + { + defaultMessage: 'Username is required.', + } +); + +export const PASSWORD_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.auth.error.requiredAuthPasswordText', + { + defaultMessage: 'Password is required.', + } +); + +export const CERT_TYPE_CRT_KEY = i18n.translate( + 'xpack.stackConnectors.components.auth.certTypeCrtKeyLabel', + { + defaultMessage: 'CRT and KEY file', + } +); +export const CERT_TYPE_PFX = i18n.translate( + 'xpack.stackConnectors.components.auth.certTypePfxLabel', + { + defaultMessage: 'PFX file', + } +); + +export const CRT_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.auth.error.requiredCRTText', + { + defaultMessage: 'CRT file is required.', + } +); + +export const KEY_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.auth.error.requiredKEYText', + { + defaultMessage: 'KEY file is required.', + } +); + +export const PFX_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.auth.error.requiredPFXText', + { + defaultMessage: 'PFX file is required.', + } +); + +export const HEADERS_SWITCH = i18n.translate( + 'xpack.stackConnectors.components.auth.viewHeadersSwitch', + { + defaultMessage: 'Add HTTP header', + } +); + +export const HEADERS_TITLE = i18n.translate( + 'xpack.stackConnectors.components.auth.httpHeadersTitle', + { + defaultMessage: 'Headers in use', + } +); + +export const KEY_LABEL = i18n.translate('xpack.stackConnectors.components.auth.keyTextFieldLabel', { + defaultMessage: 'Key', +}); + +export const VALUE_LABEL = i18n.translate( + 'xpack.stackConnectors.components.auth.valueTextFieldLabel', + { + defaultMessage: 'Value', + } +); + +export const ADD_BUTTON = i18n.translate('xpack.stackConnectors.components.auth.addHeaderButton', { + defaultMessage: 'Add', +}); + +export const DELETE_BUTTON = i18n.translate( + 'xpack.stackConnectors.components.auth.deleteHeaderButton', + { + defaultMessage: 'Delete', + description: 'Delete HTTP header', + } +); + +export const CA_REQUIRED = i18n.translate( + 'xpack.stackConnectors.components.auth.error.requiredCAText', + { + defaultMessage: 'CA file is required.', + } +); + +export const ADD_CA_LABEL = i18n.translate( + 'xpack.stackConnectors.components.auth.viewCertificateAuthoritySwitch', + { + defaultMessage: 'Add certificate authority', + } +); + +export const VERIFICATION_MODE_LABEL = i18n.translate( + 'xpack.stackConnectors.components.auth.verificationModeFieldLabel', + { defaultMessage: 'Verification mode' } +); + +export const EDIT_CA_CALLOUT = i18n.translate( + 'xpack.stackConnectors.components.auth.editCACallout', + { + defaultMessage: + 'This connector has an existing certificate authority file. Upload a new one to replace it.', + } +); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/steps/auth.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/steps/auth.tsx index f27ad80ae942c..3d7b5bf27e173 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/steps/auth.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/steps/auth.tsx @@ -6,25 +6,10 @@ */ import React, { FunctionComponent } from 'react'; -import { - EuiButtonEmpty, - EuiButtonIcon, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; -import { - FIELD_TYPES, - UseArray, - UseField, - useFormContext, - useFormData, -} from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { Field, TextField, PasswordField } from '@kbn/es-ui-shared-plugin/static/forms/components'; -import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; -import * as i18n from '../translations'; -const { emptyField } = fieldValidators; + +import { EuiSpacer } from '@elastic/eui'; + +import { AuthConfig } from '../../../common/auth/auth_config'; interface Props { display: boolean; @@ -32,158 +17,10 @@ interface Props { } export const AuthStep: FunctionComponent = ({ display, readOnly }) => { - const { getFieldDefaultValue } = useFormContext(); - const [{ config, __internal__ }] = useFormData({ - watch: ['config.hasAuth', '__internal__.hasHeaders'], - }); - - const hasHeadersDefaultValue = !!getFieldDefaultValue('config.headers'); - - const hasAuth = config == null ? true : config.hasAuth; - const hasHeaders = __internal__ != null ? __internal__.hasHeaders : false; - return ( - - - -

{i18n.AUTH_TITLE}

-
- - -
-
- {hasAuth ? ( - - - - - - - - - ) : null} - - - - {hasHeaders ? ( - - {({ items, addItem, removeItem }) => { - return ( - <> - -
{i18n.HEADERS_TITLE}
-
- - {items.map((item) => ( - - - - - - - - - removeItem(item.id)} - iconType="minusInCircle" - aria-label={i18n.DELETE_BUTTON} - style={{ marginTop: '28px' }} - /> - - - ))} - - - {i18n.ADD_BUTTON} - - - - ); - }} -
- ) : null} + +
); }; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/translations.ts index 02b45786c362f..5986126d17adc 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/translations.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/translations.ts @@ -112,20 +112,6 @@ export const MISSING_VARIABLES = (variables: string[]) => values: { variableCount: variables.length, variables: variables.join(', ') }, }); -export const USERNAME_REQUIRED = i18n.translate( - 'xpack.stackConnectors.components.casesWebhook.error.requiredAuthUserNameText', - { - defaultMessage: 'Username is required.', - } -); - -export const PASSWORD_REQUIRED = i18n.translate( - 'xpack.stackConnectors.components.casesWebhook.error.requiredAuthPasswordText', - { - defaultMessage: 'Password is required.', - } -); - export const SUMMARY_REQUIRED = i18n.translate( 'xpack.stackConnectors.components.casesWebhook.error.requiredWebhookSummaryText', { @@ -133,35 +119,6 @@ export const SUMMARY_REQUIRED = i18n.translate( } ); -export const KEY_LABEL = i18n.translate( - 'xpack.stackConnectors.components.casesWebhook.keyTextFieldLabel', - { - defaultMessage: 'Key', - } -); - -export const VALUE_LABEL = i18n.translate( - 'xpack.stackConnectors.components.casesWebhook.valueTextFieldLabel', - { - defaultMessage: 'Value', - } -); - -export const ADD_BUTTON = i18n.translate( - 'xpack.stackConnectors.components.casesWebhook.addHeaderButton', - { - defaultMessage: 'Add', - } -); - -export const DELETE_BUTTON = i18n.translate( - 'xpack.stackConnectors.components.casesWebhook.deleteHeaderButton', - { - defaultMessage: 'Delete', - description: 'Delete HTTP header', - } -); - export const CREATE_INCIDENT_METHOD = i18n.translate( 'xpack.stackConnectors.components.casesWebhook.createIncidentMethodTextFieldLabel', { @@ -338,41 +295,6 @@ export const HAS_AUTH = i18n.translate( } ); -export const USERNAME = i18n.translate( - 'xpack.stackConnectors.components.casesWebhook.userTextFieldLabel', - { - defaultMessage: 'Username', - } -); - -export const PASSWORD = i18n.translate( - 'xpack.stackConnectors.components.casesWebhook.passwordTextFieldLabel', - { - defaultMessage: 'Password', - } -); - -export const HEADERS_SWITCH = i18n.translate( - 'xpack.stackConnectors.components.casesWebhook.viewHeadersSwitch', - { - defaultMessage: 'Add HTTP header', - } -); - -export const HEADERS_TITLE = i18n.translate( - 'xpack.stackConnectors.components.casesWebhook.httpHeadersTitle', - { - defaultMessage: 'Headers in use', - } -); - -export const AUTH_TITLE = i18n.translate( - 'xpack.stackConnectors.components.casesWebhook.authenticationLabel', - { - defaultMessage: 'Authentication', - } -); - export const STEP_1 = i18n.translate('xpack.stackConnectors.components.casesWebhook.step1', { defaultMessage: 'Set up connector', }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.test.tsx index b06f7accae307..66d15550b7b75 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/cases_webhook/webhook_connectors.test.tsx @@ -7,8 +7,9 @@ import React from 'react'; import CasesWebhookActionConnectorFields from './webhook_connectors'; -import { ConnectorFormTestProvider, waitForComponentToUpdate } from '../lib/test_utils'; -import { act, render } from '@testing-library/react'; +import { ConnectorFormTestProvider } from '../lib/test_utils'; +import { render, screen, waitFor } from '@testing-library/react'; +import { AuthType } from '../../../common/auth/constants'; import userEvent from '@testing-library/user-event'; import * as i18n from './translations'; @@ -27,6 +28,7 @@ jest.mock('@kbn/triggers-actions-ui-plugin/public', () => { const invalidJsonTitle = `{"fields":{"summary":"wrong","description":{{{case.description}}},"project":{"key":"ROC"},"issuetype":{"id":"10024"}}}`; const invalidJsonBoth = `{"fields":{"summary":"wrong","description":"wrong","project":{"key":"ROC"},"issuetype":{"id":"10024"}}}`; const config = { + authType: AuthType.Basic, createCommentJson: '{"body":{{{case.comment}}}}', createCommentMethod: 'post', createCommentUrl: 'https://coolsite.net/rest/api/2/issue/{{{external.system.id}}}/comment', @@ -59,8 +61,8 @@ const actionConnector = { }; describe('CasesWebhookActionConnectorFields renders', () => { - test('All inputs are properly rendered', async () => { - const { getByTestId } = render( + it('All inputs are properly rendered', async () => { + render( { /> ); - await waitForComponentToUpdate(); - expect(getByTestId('webhookUserInput')).toBeInTheDocument(); - expect(getByTestId('webhookPasswordInput')).toBeInTheDocument(); - expect(getByTestId('webhookHeadersKeyInput')).toBeInTheDocument(); - expect(getByTestId('webhookHeadersValueInput')).toBeInTheDocument(); - expect(getByTestId('webhookCreateMethodSelect')).toBeInTheDocument(); - expect(getByTestId('webhookCreateUrlText')).toBeInTheDocument(); - expect(getByTestId('webhookCreateIncidentJson')).toBeInTheDocument(); - expect(getByTestId('createIncidentResponseKeyText')).toBeInTheDocument(); - expect(getByTestId('getIncidentUrlInput')).toBeInTheDocument(); - expect(getByTestId('getIncidentResponseExternalTitleKeyText')).toBeInTheDocument(); - expect(getByTestId('viewIncidentUrlInput')).toBeInTheDocument(); - expect(getByTestId('webhookUpdateMethodSelect')).toBeInTheDocument(); - expect(getByTestId('updateIncidentUrlInput')).toBeInTheDocument(); - expect(getByTestId('webhookUpdateIncidentJson')).toBeInTheDocument(); - expect(getByTestId('webhookCreateCommentMethodSelect')).toBeInTheDocument(); - expect(getByTestId('createCommentUrlInput')).toBeInTheDocument(); - expect(getByTestId('webhookCreateCommentJson')).toBeInTheDocument(); + + expect(await screen.findByTestId('authNone')).toBeInTheDocument(); + expect(await screen.findByTestId('authBasic')).toBeInTheDocument(); + // expect(await screen.findByTestId('authSSL')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookUserInput')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookPasswordInput')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookHeadersKeyInput')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookHeadersValueInput')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookCreateMethodSelect')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookCreateUrlText')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookCreateIncidentJson')).toBeInTheDocument(); + expect(await screen.findByTestId('createIncidentResponseKeyText')).toBeInTheDocument(); + expect(await screen.findByTestId('getIncidentUrlInput')).toBeInTheDocument(); + expect( + await screen.findByTestId('getIncidentResponseExternalTitleKeyText') + ).toBeInTheDocument(); + expect(await screen.findByTestId('viewIncidentUrlInput')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookUpdateMethodSelect')).toBeInTheDocument(); + expect(await screen.findByTestId('updateIncidentUrlInput')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookUpdateIncidentJson')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookCreateCommentMethodSelect')).toBeInTheDocument(); + expect(await screen.findByTestId('createCommentUrlInput')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookCreateCommentJson')).toBeInTheDocument(); }); - test('Toggles work properly', async () => { - const { getByTestId, queryByTestId } = render( + + it('connector auth toggles work as expected', async () => { + render( { /> ); - await waitForComponentToUpdate(); - expect(getByTestId('hasAuthToggle')).toHaveAttribute('aria-checked', 'true'); - await act(async () => { - userEvent.click(getByTestId('hasAuthToggle')); - }); - expect(getByTestId('hasAuthToggle')).toHaveAttribute('aria-checked', 'false'); - expect(queryByTestId('webhookUserInput')).not.toBeInTheDocument(); - expect(queryByTestId('webhookPasswordInput')).not.toBeInTheDocument(); - expect(getByTestId('webhookViewHeadersSwitch')).toHaveAttribute('aria-checked', 'true'); - await act(async () => { - userEvent.click(getByTestId('webhookViewHeadersSwitch')); - }); - expect(getByTestId('webhookViewHeadersSwitch')).toHaveAttribute('aria-checked', 'false'); - expect(queryByTestId('webhookHeadersKeyInput')).not.toBeInTheDocument(); - expect(queryByTestId('webhookHeadersValueInput')).not.toBeInTheDocument(); + const authNoneToggle = await screen.findByTestId('authNone'); + + expect(authNoneToggle).toBeInTheDocument(); + expect(await screen.findByTestId('authBasic')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookUserInput')).toBeInTheDocument(); + expect(await screen.findByTestId('webhookPasswordInput')).toBeInTheDocument(); + + userEvent.click(authNoneToggle); + + expect(screen.queryByTestId('webhookUserInput')).not.toBeInTheDocument(); + expect(screen.queryByTestId('webhookPasswordInput')).not.toBeInTheDocument(); + + expect(await screen.findByTestId('webhookViewHeadersSwitch')).toHaveAttribute( + 'aria-checked', + 'true' + ); + userEvent.click(await screen.findByTestId('webhookViewHeadersSwitch')); + expect(await screen.findByTestId('webhookViewHeadersSwitch')).toHaveAttribute( + 'aria-checked', + 'false' + ); + expect(screen.queryByTestId('webhookHeadersKeyInput')).not.toBeInTheDocument(); + expect(screen.queryByTestId('webhookHeadersValueInput')).not.toBeInTheDocument(); }); + describe('Step Validation', () => { - test('Steps work correctly when all fields valid', async () => { - const { queryByTestId, getByTestId } = render( + it('Steps work correctly when all fields valid', async () => { + render( { /> ); - await waitForComponentToUpdate(); - expect(getByTestId('horizontalStep1-current')).toBeInTheDocument(); - expect(getByTestId('horizontalStep2-incomplete')).toBeInTheDocument(); - expect(getByTestId('horizontalStep3-incomplete')).toBeInTheDocument(); - expect(getByTestId('horizontalStep4-incomplete')).toBeInTheDocument(); - expect(getByTestId('authStep')).toHaveAttribute('style', 'display: block;'); - expect(getByTestId('createStep')).toHaveAttribute('style', 'display: none;'); - expect(getByTestId('getStep')).toHaveAttribute('style', 'display: none;'); - expect(getByTestId('updateStep')).toHaveAttribute('style', 'display: none;'); - expect(queryByTestId('casesWebhookBack')).not.toBeInTheDocument(); - await act(async () => { - userEvent.click(getByTestId('casesWebhookNext')); - }); - expect(getByTestId('horizontalStep1-complete')).toBeInTheDocument(); - expect(getByTestId('horizontalStep2-current')).toBeInTheDocument(); - expect(getByTestId('horizontalStep3-incomplete')).toBeInTheDocument(); - expect(getByTestId('horizontalStep4-incomplete')).toBeInTheDocument(); - expect(getByTestId('authStep')).toHaveAttribute('style', 'display: none;'); - expect(getByTestId('createStep')).toHaveAttribute('style', 'display: block;'); - expect(getByTestId('getStep')).toHaveAttribute('style', 'display: none;'); - expect(getByTestId('updateStep')).toHaveAttribute('style', 'display: none;'); - await act(async () => { - userEvent.click(getByTestId('casesWebhookNext')); - }); - expect(getByTestId('horizontalStep1-complete')).toBeInTheDocument(); - expect(getByTestId('horizontalStep2-complete')).toBeInTheDocument(); - expect(getByTestId('horizontalStep3-current')).toBeInTheDocument(); - expect(getByTestId('horizontalStep4-incomplete')).toBeInTheDocument(); - expect(getByTestId('authStep')).toHaveAttribute('style', 'display: none;'); - expect(getByTestId('createStep')).toHaveAttribute('style', 'display: none;'); - expect(getByTestId('getStep')).toHaveAttribute('style', 'display: block;'); - expect(getByTestId('updateStep')).toHaveAttribute('style', 'display: none;'); - await act(async () => { - userEvent.click(getByTestId('casesWebhookNext')); - }); - expect(getByTestId('horizontalStep1-complete')).toBeInTheDocument(); - expect(getByTestId('horizontalStep2-complete')).toBeInTheDocument(); - expect(getByTestId('horizontalStep3-complete')).toBeInTheDocument(); - expect(getByTestId('horizontalStep4-current')).toBeInTheDocument(); - expect(getByTestId('authStep')).toHaveAttribute('style', 'display: none;'); - expect(getByTestId('createStep')).toHaveAttribute('style', 'display: none;'); - expect(getByTestId('getStep')).toHaveAttribute('style', 'display: none;'); - expect(getByTestId('updateStep')).toHaveAttribute('style', 'display: block;'); - expect(queryByTestId('casesWebhookNext')).not.toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep1-current')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep2-incomplete')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep3-incomplete')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep4-incomplete')).toBeInTheDocument(); + expect(await screen.findByTestId('authStep')).toHaveAttribute('style', 'display: block;'); + expect(await screen.findByTestId('createStep')).toHaveAttribute('style', 'display: none;'); + expect(await screen.findByTestId('getStep')).toHaveAttribute('style', 'display: none;'); + expect(await screen.findByTestId('updateStep')).toHaveAttribute('style', 'display: none;'); + expect(screen.queryByTestId('casesWebhookBack')).not.toBeInTheDocument(); + + userEvent.click(await screen.findByTestId('casesWebhookNext')); + + expect(await screen.findByTestId('horizontalStep1-complete')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep2-current')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep3-incomplete')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep4-incomplete')).toBeInTheDocument(); + expect(await screen.findByTestId('authStep')).toHaveAttribute('style', 'display: none;'); + expect(await screen.findByTestId('createStep')).toHaveAttribute('style', 'display: block;'); + expect(await screen.findByTestId('getStep')).toHaveAttribute('style', 'display: none;'); + expect(await screen.findByTestId('updateStep')).toHaveAttribute('style', 'display: none;'); + + userEvent.click(await screen.findByTestId('casesWebhookNext')); + + expect(await screen.findByTestId('horizontalStep1-complete')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep2-complete')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep3-current')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep4-incomplete')).toBeInTheDocument(); + expect(await screen.findByTestId('authStep')).toHaveAttribute('style', 'display: none;'); + expect(await screen.findByTestId('createStep')).toHaveAttribute('style', 'display: none;'); + expect(await screen.findByTestId('getStep')).toHaveAttribute('style', 'display: block;'); + expect(await screen.findByTestId('updateStep')).toHaveAttribute('style', 'display: none;'); + + userEvent.click(await screen.findByTestId('casesWebhookNext')); + + expect(await screen.findByTestId('horizontalStep1-complete')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep2-complete')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep3-complete')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep4-current')).toBeInTheDocument(); + expect(await screen.findByTestId('authStep')).toHaveAttribute('style', 'display: none;'); + expect(await screen.findByTestId('createStep')).toHaveAttribute('style', 'display: none;'); + expect(await screen.findByTestId('getStep')).toHaveAttribute('style', 'display: none;'); + expect(await screen.findByTestId('updateStep')).toHaveAttribute('style', 'display: block;'); + expect(screen.queryByTestId('casesWebhookNext')).not.toBeInTheDocument(); }); - test('Step 1 is properly validated', async () => { + + it('Step 1 is properly validated', async () => { const incompleteActionConnector = { ...actionConnector, secrets: { @@ -179,7 +196,7 @@ describe('CasesWebhookActionConnectorFields renders', () => { password: '', }, }; - const { getByTestId } = render( + render( { /> ); - await waitForComponentToUpdate(); - expect(getByTestId('horizontalStep1-current')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep1-current')).toBeInTheDocument(); - await act(async () => { - userEvent.click(getByTestId('casesWebhookNext')); - }); - await waitForComponentToUpdate(); + userEvent.click(await screen.findByTestId('casesWebhookNext')); - expect(getByTestId('horizontalStep1-danger')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep1-danger')).toBeInTheDocument(); - await act(async () => { - userEvent.click(getByTestId('hasAuthToggle')); - userEvent.click(getByTestId('webhookViewHeadersSwitch')); - }); - await act(async () => { - userEvent.click(getByTestId('casesWebhookNext')); - }); + userEvent.click(await screen.findByTestId('authNone')); + userEvent.click(await screen.findByTestId('webhookViewHeadersSwitch')); + userEvent.click(await screen.findByTestId('casesWebhookNext')); - expect(getByTestId('horizontalStep1-complete')).toBeInTheDocument(); - expect(getByTestId('horizontalStep2-current')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep1-complete')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep2-current')).toBeInTheDocument(); }); - test('Step 2 is properly validated', async () => { + + it('Step 2 is properly validated', async () => { const incompleteActionConnector = { ...actionConnector, config: { @@ -218,7 +228,7 @@ describe('CasesWebhookActionConnectorFields renders', () => { createIncidentUrl: undefined, }, }; - const { getByText, getByTestId } = render( + render( { /> ); - await waitForComponentToUpdate(); - expect(getByTestId('horizontalStep2-incomplete')).toBeInTheDocument(); - await act(async () => { - userEvent.click(getByTestId('casesWebhookNext')); - }); - await act(async () => { - userEvent.click(getByTestId('casesWebhookNext')); - }); - getByText(i18n.CREATE_URL_REQUIRED); - expect(getByTestId('horizontalStep2-danger')).toBeInTheDocument(); - await act(async () => { - await userEvent.type( - getByTestId('webhookCreateUrlText'), - `{selectall}{backspace}${config.createIncidentUrl}`, - { - delay: 10, - } - ); - }); - await act(async () => { - userEvent.click(getByTestId('casesWebhookNext')); - }); - expect(getByTestId('horizontalStep2-complete')).toBeInTheDocument(); - expect(getByTestId('horizontalStep3-current')).toBeInTheDocument(); - await act(async () => { - userEvent.click(getByTestId('horizontalStep2-complete')); - }); - expect(getByTestId('horizontalStep2-current')).toBeInTheDocument(); - expect(getByTestId('horizontalStep3-incomplete')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep2-incomplete')).toBeInTheDocument(); + userEvent.click(await screen.findByTestId('casesWebhookNext')); + userEvent.click(await screen.findByTestId('casesWebhookNext')); + expect(await screen.findByText(i18n.CREATE_URL_REQUIRED)).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep2-danger')).toBeInTheDocument(); + await userEvent.type( + await screen.findByTestId('webhookCreateUrlText'), + `{selectall}{backspace}${config.createIncidentUrl}`, + { + delay: 10, + } + ); + + userEvent.click(await screen.findByTestId('casesWebhookNext')); + + expect(await screen.findByTestId('horizontalStep2-complete')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep3-current')).toBeInTheDocument(); + + userEvent.click(await screen.findByTestId('horizontalStep2-complete')); + + expect(await screen.findByTestId('horizontalStep2-current')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep3-incomplete')).toBeInTheDocument(); }); - test('Step 3 is properly validated', async () => { + + it('Step 3 is properly validated', async () => { const incompleteActionConnector = { ...actionConnector, config: { @@ -265,7 +269,7 @@ describe('CasesWebhookActionConnectorFields renders', () => { getIncidentResponseExternalTitleKey: undefined, }, }; - const { getByText, getByTestId } = render( + render( { /> ); - await waitForComponentToUpdate(); - expect(getByTestId('horizontalStep2-incomplete')).toBeInTheDocument(); - await act(async () => { - userEvent.click(getByTestId('casesWebhookNext')); - }); - await act(async () => { - userEvent.click(getByTestId('casesWebhookNext')); - }); - await act(async () => { - userEvent.click(getByTestId('casesWebhookNext')); - }); - getByText(i18n.GET_RESPONSE_EXTERNAL_TITLE_KEY_REQUIRED); - expect(getByTestId('horizontalStep3-danger')).toBeInTheDocument(); - await act(async () => { - await userEvent.type( - getByTestId('getIncidentResponseExternalTitleKeyText'), - `{selectall}{backspace}${config.getIncidentResponseExternalTitleKey}`, - { - delay: 10, - } - ); - }); - await act(async () => { - userEvent.click(getByTestId('casesWebhookNext')); - }); - expect(getByTestId('horizontalStep3-complete')).toBeInTheDocument(); - expect(getByTestId('horizontalStep4-current')).toBeInTheDocument(); - await act(async () => { - userEvent.click(getByTestId('horizontalStep3-complete')); - }); - expect(getByTestId('horizontalStep3-current')).toBeInTheDocument(); - expect(getByTestId('horizontalStep4-incomplete')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep2-incomplete')).toBeInTheDocument(); + + userEvent.click(await screen.findByTestId('casesWebhookNext')); + userEvent.click(await screen.findByTestId('casesWebhookNext')); + userEvent.click(await screen.findByTestId('casesWebhookNext')); + + expect( + await screen.findByText(i18n.GET_RESPONSE_EXTERNAL_TITLE_KEY_REQUIRED) + ).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep3-danger')).toBeInTheDocument(); + + await userEvent.type( + await screen.findByTestId('getIncidentResponseExternalTitleKeyText'), + `{selectall}{backspace}${config.getIncidentResponseExternalTitleKey}`, + { + delay: 10, + } + ); + + userEvent.click(await screen.findByTestId('casesWebhookNext')); + + expect(await screen.findByTestId('horizontalStep3-complete')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep4-current')).toBeInTheDocument(); + + userEvent.click(await screen.findByTestId('horizontalStep3-complete')); + + expect(await screen.findByTestId('horizontalStep3-current')).toBeInTheDocument(); + expect(await screen.findByTestId('horizontalStep4-incomplete')).toBeInTheDocument(); }); // step 4 is not validated like the others since it is the last step @@ -346,7 +346,7 @@ describe('CasesWebhookActionConnectorFields renders', () => { ]; it('connector validation succeeds when connector config is valid', async () => { - const { getByTestId } = render( + render( { ); - await act(async () => { - userEvent.click(getByTestId('form-test-provide-submit')); - }); + userEvent.click(await screen.findByTestId('form-test-provide-submit')); const { isPreconfigured, ...rest } = actionConnector; - - expect(onSubmit).toBeCalledWith({ - data: { - ...rest, - __internal__: { - hasHeaders: true, + await waitFor(() => + expect(onSubmit).toBeCalledWith({ + data: { + ...rest, + __internal__: { + // hasCA: false, + hasHeaders: true, + }, }, - }, - isValid: true, - }); + isValid: true, + }) + ); }); it('connector validation succeeds when auth=false', async () => { @@ -381,7 +381,7 @@ describe('CasesWebhookActionConnectorFields renders', () => { }, }; - const { getByTestId } = render( + render( { ); - await act(async () => { - userEvent.click(getByTestId('form-test-provide-submit')); - }); + userEvent.click(await screen.findByTestId('form-test-provide-submit')); const { isPreconfigured, secrets, ...rest } = actionConnector; - expect(onSubmit).toBeCalledWith({ - data: { - ...rest, - config: { - ...actionConnector.config, - hasAuth: false, - }, - __internal__: { - hasHeaders: true, + await waitFor(() => + expect(onSubmit).toBeCalledWith({ + data: { + ...rest, + config: { + ...actionConnector.config, + hasAuth: false, + authType: null, + }, + __internal__: { + // hasCA: false, + hasHeaders: true, + }, }, - }, - isValid: true, - }); + isValid: true, + }) + ); }); it('connector validation succeeds without headers', async () => { @@ -420,7 +422,7 @@ describe('CasesWebhookActionConnectorFields renders', () => { }, }; - const { getByTestId } = render( + render( { ); - await act(async () => { - userEvent.click(getByTestId('form-test-provide-submit')); - }); + userEvent.click(await screen.findByTestId('form-test-provide-submit')); const { isPreconfigured, ...rest } = actionConnector; const { headers, ...rest2 } = actionConnector.config; - expect(onSubmit).toBeCalledWith({ - data: { - ...rest, - config: rest2, - __internal__: { - hasHeaders: false, + await waitFor(() => + expect(onSubmit).toBeCalledWith({ + data: { + ...rest, + config: rest2, + __internal__: { + // hasCA: false, + hasHeaders: false, + }, }, - }, - isValid: true, - }); + isValid: true, + }) + ); }); it('validates correctly if the method is empty', async () => { @@ -457,7 +460,7 @@ describe('CasesWebhookActionConnectorFields renders', () => { }, }; - const res = render( + render( { ); - await act(async () => { - userEvent.click(res.getByTestId('form-test-provide-submit')); - }); - - expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false }); + userEvent.click(await screen.findByTestId('form-test-provide-submit')); + await waitFor(() => expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false })); }); it.each(tests)('validates correctly %p', async (field, value) => { @@ -483,7 +483,7 @@ describe('CasesWebhookActionConnectorFields renders', () => { }, }; - const res = render( + render( { ); - await act(async () => { - await userEvent.type(res.getByTestId(field), `{selectall}{backspace}${value}`, { - delay: 10, - }); + await userEvent.type(await screen.findByTestId(field), `{selectall}{backspace}${value}`, { + delay: 10, }); - await act(async () => { - userEvent.click(res.getByTestId('form-test-provide-submit')); - }); + userEvent.click(await screen.findByTestId('form-test-provide-submit')); - expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false }); + await waitFor(() => expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false })); }); it.each(mustacheTests)( @@ -518,7 +514,7 @@ describe('CasesWebhookActionConnectorFields renders', () => { }, }; - const res = render( + render( { ); - await act(async () => { - userEvent.click(res.getByTestId('form-test-provide-submit')); - }); - - expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false }); - expect(res.getByText(i18n.MISSING_VARIABLES(missingVariables))).toBeInTheDocument(); + userEvent.click(await screen.findByTestId('form-test-provide-submit')); + await waitFor(() => expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false })); + expect( + await screen.findByText(i18n.MISSING_VARIABLES(missingVariables)) + ).toBeInTheDocument(); } ); }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/gemini/logo.tsx b/x-pack/plugins/stack_connectors/public/connector_types/gemini/logo.tsx index dd09c31fb7079..17da9f085e974 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/gemini/logo.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/gemini/logo.tsx @@ -9,25 +9,15 @@ import React from 'react'; import { LogoProps } from '../types'; const Logo = (props: LogoProps) => ( - + + - - - - - - - ); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/jira/jira.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/jira/jira.test.tsx index 15f84f781e41e..0b2227011ab89 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/jira/jira.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/jira/jira.test.tsx @@ -89,9 +89,7 @@ describe('jira action params validation', () => { errors: { 'subActionParams.incident.summary': [], 'subActionParams.incident.labels': [], - 'subActionParams.incident.otherFields': [ - 'Additional fields field must be a valid JSON object.', - ], + 'subActionParams.incident.otherFields': ['Invalid JSON.'], }, }); }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/jira/jira.tsx b/x-pack/plugins/stack_connectors/public/connector_types/jira/jira.tsx index a106514b856a2..ecbf0f0f848b4 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/jira/jira.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/jira/jira.tsx @@ -13,6 +13,7 @@ import type { } from '@kbn/triggers-actions-ui-plugin/public'; import { MAX_OTHER_FIELDS_LENGTH } from '../../../common/jira/constants'; import { JiraConfig, JiraSecrets, JiraActionParams } from './types'; +import { validateJSON } from '../lib/validate_json'; export const JIRA_DESC = i18n.translate('xpack.stackConnectors.components.jira.selectMessageText', { defaultMessage: 'Create an incident in Jira.', @@ -58,19 +59,15 @@ export function getConnectorType(): ConnectorTypeModel MAX_OTHER_FIELDS_LENGTH) { - errors['subActionParams.incident.otherFields'] = [ - translations.OTHER_FIELDS_LENGTH_ERROR(MAX_OTHER_FIELDS_LENGTH), - ]; - } - } - } catch (error) { - errors['subActionParams.incident.otherFields'] = [translations.INVALID_JSON_FORMAT]; + const jsonErrors = validateJSON({ + value: actionParams.subActionParams?.incident?.otherFields, + maxProperties: MAX_OTHER_FIELDS_LENGTH, + }); + + if (jsonErrors) { + errors['subActionParams.incident.otherFields'] = [jsonErrors]; } + return validationResult; }, actionParamsFields: lazy(() => import('./jira_params')), diff --git a/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/additional_fields.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/additional_fields.test.tsx new file mode 100644 index 0000000000000..33f4fe3923b88 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/additional_fields.test.tsx @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import { AdditionalFields } from './additional_fields'; +import userEvent from '@testing-library/user-event'; + +describe('Credentials', () => { + const onChange = jest.fn(); + const value = JSON.stringify({ foo: 'test' }); + const props = { value, errors: [], onChange }; + + it('renders the additional fields correctly', async () => { + render( + + + + ); + + expect(await screen.findByTestId('additionalFields')).toBeInTheDocument(); + }); + + it('sets the value correctly', async () => { + render( + + + + ); + + expect(await screen.findByText(value)).toBeInTheDocument(); + }); + + /** + * Test for the intermediate release process + */ + it('does not show the component if the value is undefined', async () => { + render( + + + + ); + + expect(screen.queryByTestId('additional_fieldsJsonEditor')).not.toBeInTheDocument(); + }); + + it('changes the value correctly', async () => { + const newValue = JSON.stringify({ bar: 'test' }); + + render( + + + + ); + + const editor = await screen.findByTestId('additional_fieldsJsonEditor'); + + userEvent.clear(editor); + userEvent.paste(editor, newValue); + + await waitFor(() => { + expect(onChange).toHaveBeenCalledWith(newValue); + }); + + expect(await screen.findByText(newValue)).toBeInTheDocument(); + }); + + it('updating wth an empty string sets its value to null', async () => { + const newValue = JSON.stringify({ bar: 'test' }); + + render( + + + + ); + + const editor = await screen.findByTestId('additional_fieldsJsonEditor'); + + userEvent.paste(editor, newValue); + userEvent.clear(editor); + + await waitFor(() => { + expect(onChange).toHaveBeenCalledWith(null); + }); + }); +}); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/additional_fields.tsx b/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/additional_fields.tsx new file mode 100644 index 0000000000000..b9d3602112c53 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/additional_fields.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiIconTip } from '@elastic/eui'; +import { JsonEditorWithMessageVariables } from '@kbn/triggers-actions-ui-plugin/public'; +import React from 'react'; +import { ActionVariable } from '@kbn/alerting-types'; +import { isEmpty } from 'lodash'; +import * as i18n from './translations'; + +interface AdditionalFieldsProps { + value?: string | null; + errors?: string[]; + messageVariables?: ActionVariable[]; + onChange: (value: string | null) => void; +} + +export const AdditionalFieldsComponent: React.FC = ({ + value, + errors, + messageVariables, + onChange, +}) => { + /** + * Hide the component if the value is not defined. + * This is needed for the intermediate release process. + * Users will not be able to use the field if they have never set it. + * On the next Serverless release the check will be removed. + */ + if (value === undefined) { + return null; + } + + return ( + + {i18n.ADDITIONAL_FIELDS} + + + } + onDocumentsChange={(json: string) => onChange(isEmpty(json) ? null : json)} + /> + ); +}; + +export const AdditionalFields = React.memo(AdditionalFieldsComponent); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/translations.ts index 8f519e9661881..2f81caa117f26 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/translations.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/servicenow/translations.ts @@ -441,3 +441,32 @@ export const ADDITIONAL_INFO_JSON_ERROR = i18n.translate( defaultMessage: 'The additional info field does not have a valid JSON format.', } ); + +export const ADDITIONAL_FIELDS = i18n.translate( + 'xpack.stackConnectors.components.servicenow.additionalFieldsTooltip', + { + defaultMessage: 'Additional fields', + } +); + +export const ADDITIONAL_FIELDS_HELP = i18n.translate( + 'xpack.stackConnectors.components.servicenow.additionalFieldsHelpTooltip', + { + defaultMessage: 'Additional fields help', + } +); + +export const ADDITIONAL_FIELDS_HELP_TEXT = i18n.translate( + 'xpack.stackConnectors.components.servicenow.additionalFieldsHelpTooltipText', + { + defaultMessage: + 'Additional fields in JSON format as defined in the Elastic ServiceNow application', + } +); + +export const ADDITIONAL_FIELDS_JSON_ERROR = i18n.translate( + 'xpack.stackConnectors.components.servicenow.additionalFieldsError', + { + defaultMessage: 'No valid JSON.', + } +); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/lib/test_utils.tsx b/x-pack/plugins/stack_connectors/public/connector_types/lib/test_utils.tsx index 6503cc7763469..29e0a8dd55b4b 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/lib/test_utils.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/test_utils.tsx @@ -52,6 +52,21 @@ const ConnectorFormTestProviderComponent: React.FC = ({ + children, + defaultValue, + onSubmit, +}) => { + return ( + + {children} + + ); +}; + +AuthFormTestProviderComponent.displayName = 'AuthFormTestProvider'; +export const AuthFormTestProvider = React.memo(AuthFormTestProviderComponent); + const FormTestProviderComponent: React.FC = ({ children, defaultValue, diff --git a/x-pack/plugins/stack_connectors/public/connector_types/lib/validate_json.test.ts b/x-pack/plugins/stack_connectors/public/connector_types/lib/validate_json.test.ts new file mode 100644 index 0000000000000..4e3de337cab1a --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/validate_json.test.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { validateJSON } from './validate_json'; + +describe('validateJSON', () => { + it('does not return an error for valid JSON and no maxProperties', () => { + expect(validateJSON({ value: JSON.stringify({ foo: 'test' }) })).toBeUndefined(); + }); + + it('does not return an error for valid JSON and attributes less than maxProperties', () => { + expect( + validateJSON({ value: JSON.stringify({ foo: 'test' }), maxProperties: 1 }) + ).toBeUndefined(); + }); + + it('does not return an error with empty value and maxProperties=0', () => { + expect(validateJSON({ maxProperties: 0 })).toBeUndefined(); + }); + + it('does not return an error with no values', () => { + expect(validateJSON({})).toBeUndefined(); + }); + + it('does not return an error with empty object and maxProperties=0', () => { + expect(validateJSON({ value: JSON.stringify({}), maxProperties: 0 })).toBeUndefined(); + }); + + it('validates syntax errors correctly', () => { + expect(validateJSON({ value: 'foo' })).toBe('Invalid JSON.'); + }); + + it('validates max properties correctly', () => { + const value = { foo: 'test', bar: 'test 2' }; + + expect(validateJSON({ value: JSON.stringify(value), maxProperties: 1 })).toBe( + 'A maximum of 1 additional fields can be defined at a time.' + ); + }); + + it('does not return an error for an object', () => { + expect(validateJSON({ value: { foo: 'test' } })).toBeUndefined(); + }); +}); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/lib/validate_json.ts b/x-pack/plugins/stack_connectors/public/connector_types/lib/validate_json.ts new file mode 100644 index 0000000000000..c4ff59b48bb82 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/validate_json.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isPlainObject } from 'lodash'; +import { i18n } from '@kbn/i18n'; + +interface ValidateJSONArgs { + value?: string | null | Record; + maxProperties?: number; +} + +const isObject = (value?: ValidateJSONArgs['value']): value is Record => { + return isPlainObject(value); +}; + +export const MAX_ATTRIBUTES_ERROR = (length: number) => + i18n.translate('xpack.stackConnectors.schema.additionalFieldsLengthError', { + values: { length }, + defaultMessage: 'A maximum of {length} additional fields can be defined at a time.', + }); + +export const INVALID_JSON_FORMAT = i18n.translate( + 'xpack.stackConnectors.components.otherFieldsFormatErrorMessage', + { + defaultMessage: 'Invalid JSON.', + } +); + +export const validateJSON = ({ value, maxProperties }: ValidateJSONArgs) => { + try { + if (isObject(value)) { + return; + } + + if (value) { + const parsedOtherFields = JSON.parse(value); + + if (maxProperties && Object.keys(parsedOtherFields).length > maxProperties) { + return MAX_ATTRIBUTES_ERROR(maxProperties); + } + } + } catch (error) { + return INVALID_JSON_FORMAT; + } +}; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/opsgenie/create_alert/tags.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/opsgenie/create_alert/tags.test.tsx index dcfd95aab0caf..d006ff23aed5f 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/opsgenie/create_alert/tags.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/opsgenie/create_alert/tags.test.tsx @@ -122,7 +122,9 @@ describe('Tags', () => { }); act(() => { - userEvent.click(screen.getByText('The tags of the rule.')); + userEvent.click(screen.getByText('The tags of the rule.'), undefined, { + skipPointerEventsCheck: true, + }); }); await waitFor(() => diff --git a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm.test.tsx index 107ccab01e60c..659ad5961f2ff 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm.test.tsx @@ -10,6 +10,7 @@ import { registerConnectorTypes } from '..'; import type { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public/types'; import { experimentalFeaturesMock, registrationServicesMock } from '../../mocks'; import { ExperimentalFeaturesService } from '../../common/experimental_features_service'; +import { MAX_ADDITIONAL_FIELDS_LENGTH } from '../../../common/servicenow/constants'; const SERVICENOW_ITSM_CONNECTOR_TYPE_ID = '.servicenow'; let connectorTypeRegistry: TypeRegistry; @@ -31,6 +32,7 @@ describe('servicenow action params validation', () => { test(`${SERVICENOW_ITSM_CONNECTOR_TYPE_ID}: action params validation succeeds when action params is valid`, async () => { const connectorTypeModel = connectorTypeRegistry.get(SERVICENOW_ITSM_CONNECTOR_TYPE_ID); const actionParams = { + subAction: 'pushToService', subActionParams: { incident: { short_description: 'some title {{test}}' }, comments: [] }, }; @@ -38,6 +40,7 @@ describe('servicenow action params validation', () => { errors: { ['subActionParams.incident.correlation_id']: [], ['subActionParams.incident.short_description']: [], + ['subActionParams.incident.additional_fields']: [], }, }); }); @@ -53,6 +56,7 @@ describe('servicenow action params validation', () => { errors: { ['subActionParams.incident.correlation_id']: [], ['subActionParams.incident.short_description']: [], + ['subActionParams.incident.additional_fields']: [], }, }); }); @@ -67,6 +71,7 @@ describe('servicenow action params validation', () => { errors: { ['subActionParams.incident.correlation_id']: [], ['subActionParams.incident.short_description']: ['Short description is required.'], + ['subActionParams.incident.additional_fields']: [], }, }); }); @@ -82,6 +87,52 @@ describe('servicenow action params validation', () => { errors: { ['subActionParams.incident.correlation_id']: ['Correlation id is required.'], ['subActionParams.incident.short_description']: [], + ['subActionParams.incident.additional_fields']: [], + }, + }); + }); + + test('params validation fails when additional_fields is not valid JSON', async () => { + const connectorTypeModel = connectorTypeRegistry.get(SERVICENOW_ITSM_CONNECTOR_TYPE_ID); + const actionParams = { + subAction: 'pushToService', + subActionParams: { + incident: { short_description: 'some title', additional_fields: 'invalid json' }, + }, + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + 'subActionParams.incident.correlation_id': [], + 'subActionParams.incident.short_description': [], + 'subActionParams.incident.additional_fields': ['Invalid JSON.'], + }, + }); + }); + + test(`params validation succeeds when its valid json and additional_fields has ${ + MAX_ADDITIONAL_FIELDS_LENGTH + 1 + } fields`, async () => { + const longJSON: { [key in string]: string } = {}; + for (let i = 0; i < MAX_ADDITIONAL_FIELDS_LENGTH + 1; i++) { + longJSON[`key${i}`] = 'value'; + } + + const connectorTypeModel = connectorTypeRegistry.get(SERVICENOW_ITSM_CONNECTOR_TYPE_ID); + const actionParams = { + subAction: 'pushToService', + subActionParams: { + incident: { short_description: 'some title', additional_fields: JSON.stringify(longJSON) }, + }, + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + 'subActionParams.incident.correlation_id': [], + 'subActionParams.incident.short_description': [], + ['subActionParams.incident.additional_fields']: [ + `A maximum of ${MAX_ADDITIONAL_FIELDS_LENGTH} additional fields can be defined at a time.`, + ], }, }); }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm.tsx b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm.tsx index dfa6eb5c43987..c6bcf060c55b4 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm.tsx @@ -11,6 +11,7 @@ import type { ActionTypeModel as ConnectorTypeModel, GenericValidationResult, } from '@kbn/triggers-actions-ui-plugin/public'; +import { MAX_ADDITIONAL_FIELDS_LENGTH } from '../../../common/servicenow/constants'; import { ServiceNowConfig, ServiceNowSecrets } from '../lib/servicenow/types'; import { ServiceNowITSMActionParams } from './types'; import { @@ -18,6 +19,7 @@ import { getConnectorDescriptiveTitle, getSelectedConnectorIcon, } from '../lib/servicenow/helpers'; +import { validateJSON } from '../lib/validate_json'; export const SERVICENOW_ITSM_DESC = i18n.translate( 'xpack.stackConnectors.components.serviceNowITSM.selectMessageText', @@ -51,10 +53,13 @@ export function getServiceNowITSMConnectorType(): ConnectorTypeModel< const errors = { 'subActionParams.incident.short_description': new Array(), 'subActionParams.incident.correlation_id': new Array(), + 'subActionParams.incident.additional_fields': new Array(), }; + const validationResult = { errors, }; + if ( actionParams.subActionParams && actionParams.subActionParams.incident && @@ -72,6 +77,16 @@ export function getServiceNowITSMConnectorType(): ConnectorTypeModel< translations.CORRELATION_ID_REQUIRED ); } + + const jsonErrors = validateJSON({ + value: actionParams.subActionParams?.incident?.additional_fields, + maxProperties: MAX_ADDITIONAL_FIELDS_LENGTH, + }); + + if (jsonErrors) { + errors['subActionParams.incident.additional_fields'] = [jsonErrors]; + } + return validationResult; }, actionParamsFields: lazy(() => import('./servicenow_itsm_params')), diff --git a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.test.tsx index e0eb4524341dc..26ec00f07e684 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { act, waitFor } from '@testing-library/react'; +import { act, render, waitFor, screen } from '@testing-library/react'; import { merge } from 'lodash'; import { ActionConnector, ActionConnectorMode } from '@kbn/triggers-actions-ui-plugin/public/types'; @@ -15,6 +15,8 @@ import { useGetChoices } from '../lib/servicenow/use_get_choices'; import ServiceNowITSMParamsFields from './servicenow_itsm_params'; import { Choice } from '../lib/servicenow/types'; import { ACTION_GROUP_RECOVERED } from '../lib/servicenow/helpers'; +import userEvent from '@testing-library/user-event'; +import { I18nProvider } from '@kbn/i18n-react'; jest.mock('../lib/servicenow/use_get_choices'); jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); @@ -35,6 +37,7 @@ const actionParams = { externalId: null, correlation_id: 'alertID', correlation_display: 'Alerting', + additional_fields: null, }, comments: [], }, @@ -366,6 +369,19 @@ describe('ServiceNowITSMParamsFields renders', () => { expect(wrapper.find('.euiFormErrorText').text()).toBe('correlation_id_error'); }); + + it('updates additional fields', async () => { + const newValue = JSON.stringify({ bar: 'test' }); + render(, { + wrapper: ({ children }) => {children}, + }); + + userEvent.paste(await screen.findByTestId('additional_fieldsJsonEditor'), newValue); + + await waitFor(() => { + expect(editAction.mock.calls[0][1].incident.additional_fields).toEqual(newValue); + }); + }); }); describe('Test form', () => { diff --git a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.tsx index 2c3ebc0805930..b08809e2cb5ef 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/servicenow_itsm_params.tsx @@ -34,6 +34,7 @@ import { } from '../lib/servicenow/helpers'; import * as i18n from '../lib/servicenow/translations'; +import { AdditionalFields } from '../lib/servicenow/additional_fields'; const useGetChoicesFields = ['urgency', 'severity', 'impact', 'category', 'subcategory']; const defaultFields: Fields = { @@ -263,6 +264,11 @@ const ServiceNowParamsFields: React.FunctionComponent< // eslint-disable-next-line react-hooks/exhaustive-deps }, [actionConnector, isTestResolveAction, isTestTriggerAction]); + const additionalFieldsOnChange = useCallback( + (value) => editSubActionProperty('additional_fields', value), + [editSubActionProperty] + ); + return ( <> {executionMode === ActionConnectorMode.Test ? ( @@ -451,6 +457,14 @@ const ServiceNowParamsFields: React.FunctionComponent< inputTargetValue={comments && comments.length > 0 ? comments[0].comment : undefined} label={i18n.COMMENTS_LABEL} /> + {!isDeprecatedActionConnector && ( + + )} )} {showOnlyCorrelationId && ( diff --git a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/types.ts b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/types.ts index dd0a47004f359..11d93f9d74388 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/types.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_itsm/types.ts @@ -14,5 +14,10 @@ export enum EventAction { export interface ServiceNowITSMActionParams { subAction: string; - subActionParams: ExecutorSubActionPushParamsITSM; + /* We override "additional_fields" to string because when users fill in the form, the structure won't match until done and + we need to store the current state. To match with the data structure define in the backend, we make sure users can't + send the form while not matching the original object structure. */ + subActionParams: ExecutorSubActionPushParamsITSM & { + incident: { additional_fields: string | null }; + }; } diff --git a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/servicenow_sir.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/servicenow_sir.test.tsx index b2cfd79447ff8..76158882575b9 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/servicenow_sir.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/servicenow_sir.test.tsx @@ -10,6 +10,7 @@ import { registerConnectorTypes } from '..'; import type { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public/types'; import { experimentalFeaturesMock, registrationServicesMock } from '../../mocks'; import { ExperimentalFeaturesService } from '../../common/experimental_features_service'; +import { MAX_ADDITIONAL_FIELDS_LENGTH } from '../../../common/servicenow/constants'; const SERVICENOW_SIR_CONNECTOR_TYPE_ID = '.servicenow-sir'; let connectorTypeRegistry: TypeRegistry; @@ -35,7 +36,10 @@ describe('servicenow action params validation', () => { }; expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ - errors: { ['subActionParams.incident.short_description']: [] }, + errors: { + ['subActionParams.incident.short_description']: [], + ['subActionParams.incident.additional_fields']: [], + }, }); }); @@ -48,6 +52,48 @@ describe('servicenow action params validation', () => { expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ errors: { ['subActionParams.incident.short_description']: ['Short description is required.'], + ['subActionParams.incident.additional_fields']: [], + }, + }); + }); + + test('params validation fails when additional_fields is not valid JSON', async () => { + const connectorTypeModel = connectorTypeRegistry.get(SERVICENOW_SIR_CONNECTOR_TYPE_ID); + const actionParams = { + subActionParams: { + incident: { short_description: 'some title', additional_fields: 'invalid json' }, + }, + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + 'subActionParams.incident.short_description': [], + 'subActionParams.incident.additional_fields': ['Invalid JSON.'], + }, + }); + }); + + test(`params validation succeeds when its valid json and additional_fields has ${ + MAX_ADDITIONAL_FIELDS_LENGTH + 1 + } fields`, async () => { + const longJSON: { [key in string]: string } = {}; + for (let i = 0; i < MAX_ADDITIONAL_FIELDS_LENGTH + 1; i++) { + longJSON[`key${i}`] = 'value'; + } + + const connectorTypeModel = connectorTypeRegistry.get(SERVICENOW_SIR_CONNECTOR_TYPE_ID); + const actionParams = { + subActionParams: { + incident: { short_description: 'some title', additional_fields: JSON.stringify(longJSON) }, + }, + }; + + expect(await connectorTypeModel.validateParams(actionParams)).toEqual({ + errors: { + 'subActionParams.incident.short_description': [], + ['subActionParams.incident.additional_fields']: [ + `A maximum of ${MAX_ADDITIONAL_FIELDS_LENGTH} additional fields can be defined at a time.`, + ], }, }); }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/servicenow_sir.tsx b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/servicenow_sir.tsx index 9efbd1bf2a8bd..72144204e924f 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/servicenow_sir.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/servicenow_sir.tsx @@ -11,9 +11,11 @@ import type { ActionTypeModel as ConnectorTypeModel, GenericValidationResult, } from '@kbn/triggers-actions-ui-plugin/public'; +import { MAX_ADDITIONAL_FIELDS_LENGTH } from '../../../common/servicenow/constants'; import { ServiceNowConfig, ServiceNowSecrets } from '../lib/servicenow/types'; import { ServiceNowSIRActionParams } from './types'; import { getConnectorDescriptiveTitle, getSelectedConnectorIcon } from '../lib/servicenow/helpers'; +import { validateJSON } from '../lib/validate_json'; export const SERVICENOW_SIR_DESC = i18n.translate( 'xpack.stackConnectors.components.serviceNowSIR.selectMessageText', @@ -46,7 +48,9 @@ export function getServiceNowSIRConnectorType(): ConnectorTypeModel< const translations = await import('../lib/servicenow/translations'); const errors = { 'subActionParams.incident.short_description': new Array(), + 'subActionParams.incident.additional_fields': new Array(), }; + const validationResult = { errors, }; @@ -57,6 +61,16 @@ export function getServiceNowSIRConnectorType(): ConnectorTypeModel< ) { errors['subActionParams.incident.short_description'].push(translations.TITLE_REQUIRED); } + + const jsonErrors = validateJSON({ + value: actionParams.subActionParams?.incident?.additional_fields, + maxProperties: MAX_ADDITIONAL_FIELDS_LENGTH, + }); + + if (jsonErrors) { + errors['subActionParams.incident.additional_fields'] = [jsonErrors]; + } + return validationResult; }, actionParamsFields: lazy(() => import('./servicenow_sir_params')), diff --git a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/servicenow_sir_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/servicenow_sir_params.test.tsx index 8a62b6f09a758..e6406c49988c6 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/servicenow_sir_params.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/servicenow_sir_params.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { act } from '@testing-library/react'; +import { act, render, screen, waitFor } from '@testing-library/react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; @@ -14,6 +14,8 @@ import { useGetChoices } from '../lib/servicenow/use_get_choices'; import ServiceNowSIRParamsFields from './servicenow_sir_params'; import { Choice } from '../lib/servicenow/types'; import { merge } from 'lodash'; +import userEvent from '@testing-library/user-event'; +import { I18nProvider } from '@kbn/i18n-react'; jest.mock('../lib/servicenow/use_get_choices'); jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'); @@ -36,6 +38,7 @@ const actionParams = { externalId: null, correlation_id: 'alertID', correlation_display: 'Alerting', + additional_fields: null, }, comments: [], }, @@ -341,5 +344,18 @@ describe('ServiceNowSIRParamsFields renders', () => { expect(comments.simulate('change', changeEvent)); expect(editAction.mock.calls[0][1].comments.length).toEqual(1); }); + + it('updates additional fields', async () => { + const newValue = JSON.stringify({ bar: 'test' }); + render(, { + wrapper: ({ children }) => {children}, + }); + + userEvent.paste(await screen.findByTestId('additional_fieldsJsonEditor'), newValue); + + await waitFor(() => { + expect(editAction.mock.calls[0][1].incident.additional_fields).toEqual(newValue); + }); + }); }); }); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/servicenow_sir_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/servicenow_sir_params.tsx index dcf296e7bf4a5..d4fbb732347a1 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/servicenow_sir_params.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/servicenow_sir_params.tsx @@ -29,6 +29,7 @@ import { ServiceNowSIRActionParams } from './types'; import { Fields, Choice } from '../lib/servicenow/types'; import { choicesToEuiOptions, DEFAULT_CORRELATION_ID } from '../lib/servicenow/helpers'; import { DeprecatedCallout } from '../lib/servicenow/deprecated_callout'; +import { AdditionalFields } from '../lib/servicenow/additional_fields'; const useGetChoicesFields = ['category', 'subcategory', 'priority']; const defaultFields: Fields = { @@ -148,6 +149,11 @@ const ServiceNowSIRParamsFields: React.FunctionComponent< // eslint-disable-next-line react-hooks/exhaustive-deps }, [actionParams]); + const additionalFieldsOnChange = useCallback( + (value) => editSubActionProperty('additional_fields', value), + [editSubActionProperty] + ); + return ( <> {isDeprecatedActionConnector && } @@ -288,6 +294,14 @@ const ServiceNowSIRParamsFields: React.FunctionComponent< label={i18n.COMMENTS_LABEL} /> + {!isDeprecatedActionConnector && ( + + )} ); }; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/types.ts b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/types.ts index acc318117f7fe..78c2e94f3d990 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/types.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/servicenow_sir/types.ts @@ -9,5 +9,10 @@ import type { ExecutorSubActionPushParamsSIR } from '../../../server/connector_t export interface ServiceNowSIRActionParams { subAction: string; - subActionParams: ExecutorSubActionPushParamsSIR; + /* We override "additional_fields" to string because when users fill in the form, the structure won't match until done and + we need to store the current state. To match with the data structure define in the backend, we make sure users can't + send the form while not matching the original object structure. */ + subActionParams: ExecutorSubActionPushParamsSIR & { + incident: { additional_fields: string | null }; + }; } diff --git a/x-pack/plugins/stack_connectors/public/connector_types/webhook/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/webhook/translations.ts index d0802fe5494b5..c6db3f0f1ef1b 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/webhook/translations.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/webhook/translations.ts @@ -14,13 +14,6 @@ export const METHOD_LABEL = i18n.translate( } ); -export const HAS_AUTH_LABEL = i18n.translate( - 'xpack.stackConnectors.components.webhook.hasAuthSwitchLabel', - { - defaultMessage: 'Require authentication for this webhook', - } -); - export const URL_LABEL = i18n.translate( 'xpack.stackConnectors.components.webhook.urlTextFieldLabel', { @@ -28,62 +21,6 @@ export const URL_LABEL = i18n.translate( } ); -export const USERNAME_LABEL = i18n.translate( - 'xpack.stackConnectors.components.webhook.userTextFieldLabel', - { - defaultMessage: 'Username', - } -); - -export const PASSWORD_LABEL = i18n.translate( - 'xpack.stackConnectors.components.webhook.passwordTextFieldLabel', - { - defaultMessage: 'Password', - } -); - -export const PASSPHRASE_LABEL = i18n.translate( - 'xpack.stackConnectors.components.webhook.passphraseTextFieldLabel', - { - defaultMessage: 'Passphrase', - } -); - -export const ADD_HEADERS_LABEL = i18n.translate( - 'xpack.stackConnectors.components.webhook.viewHeadersSwitch', - { - defaultMessage: 'Add HTTP header', - } -); - -export const HEADER_KEY_LABEL = i18n.translate( - 'xpack.stackConnectors.components.webhook.headerKeyTextFieldLabel', - { - defaultMessage: 'Key', - } -); - -export const REMOVE_ITEM_LABEL = i18n.translate( - 'xpack.stackConnectors.components.webhook.removeHeaderIconLabel', - { - defaultMessage: 'Key', - } -); - -export const ADD_HEADER_BTN = i18n.translate( - 'xpack.stackConnectors.components.webhook.addHeaderButtonLabel', - { - defaultMessage: 'Add header', - } -); - -export const HEADER_VALUE_LABEL = i18n.translate( - 'xpack.stackConnectors.components.webhook.headerValueTextFieldLabel', - { - defaultMessage: 'Value', - } -); - export const URL_INVALID = i18n.translate( 'xpack.stackConnectors.components.webhook.error.invalidUrlTextField', { @@ -98,105 +35,9 @@ export const METHOD_REQUIRED = i18n.translate( } ); -export const USERNAME_REQUIRED = i18n.translate( - 'xpack.stackConnectors.components.webhook.error.requiredAuthUserNameText', - { - defaultMessage: 'Username is required.', - } -); - export const BODY_REQUIRED = i18n.translate( 'xpack.stackConnectors.components.webhook.error.requiredWebhookBodyText', { defaultMessage: 'Body is required.', } ); - -export const PASSWORD_REQUIRED = i18n.translate( - 'xpack.stackConnectors.components.webhook.error.requiredWebhookPasswordText', - { - defaultMessage: 'Password is required.', - } -); - -export const AUTHENTICATION_NONE = i18n.translate( - 'xpack.stackConnectors.components.webhook.authenticationMethodNoneLabel', - { - defaultMessage: 'None', - } -); - -export const AUTHENTICATION_BASIC = i18n.translate( - 'xpack.stackConnectors.components.webhook.authenticationMethodBasicLabel', - { - defaultMessage: 'Basic authentication', - } -); - -export const AUTHENTICATION_SSL = i18n.translate( - 'xpack.stackConnectors.components.webhook.authenticationMethodSSLLabel', - { - defaultMessage: 'SSL authentication', - } -); - -export const CERT_TYPE_CRT_KEY = i18n.translate( - 'xpack.stackConnectors.components.webhook.certTypeCrtKeyLabel', - { - defaultMessage: 'CRT and KEY file', - } -); -export const CERT_TYPE_PFX = i18n.translate( - 'xpack.stackConnectors.components.webhook.certTypePfxLabel', - { - defaultMessage: 'PFX file', - } -); - -export const CRT_REQUIRED = i18n.translate( - 'xpack.stackConnectors.components.webhook.error.requiredWebhookCRTText', - { - defaultMessage: 'CRT file is required.', - } -); - -export const KEY_REQUIRED = i18n.translate( - 'xpack.stackConnectors.components.webhook.error.requiredWebhookKEYText', - { - defaultMessage: 'KEY file is required.', - } -); - -export const PFX_REQUIRED = i18n.translate( - 'xpack.stackConnectors.components.webhook.error.requiredWebhookPFXText', - { - defaultMessage: 'PFX file is required.', - } -); - -export const CA_REQUIRED = i18n.translate( - 'xpack.stackConnectors.components.webhook.error.requiredWebhookCAText', - { - defaultMessage: 'CA file is required.', - } -); - -export const ADD_CA_LABEL = i18n.translate( - 'xpack.stackConnectors.components.webhook.viewCertificateAuthoritySwitch', - { - defaultMessage: 'Add certificate authority', - } -); - -export const VERIFICATION_MODE_LABEL = i18n.translate( - 'xpack.stackConnectors.components.webhook.verificationModeFieldLabel', - { defaultMessage: 'Verification mode' } -); - -export const EDIT_CA_CALLOUT = i18n.translate( - 'xpack.stackConnectors.components.webhook.editCACallout', - { - defaultMessage: - 'This webhook has an existing certificate authority file. Upload a new one to replace it.', - } -); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/webhook/webhook_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/webhook/webhook_connectors.test.tsx index 6b74624caaf9a..b9e2983964457 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/webhook/webhook_connectors.test.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/webhook/webhook_connectors.test.tsx @@ -11,10 +11,10 @@ import WebhookActionConnectorFields from './webhook_connectors'; import { ConnectorFormTestProvider, waitForComponentToUpdate } from '../lib/test_utils'; import { act, render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { WebhookAuthType, SSLCertType } from '../../../common/webhook/constants'; +import { AuthType, SSLCertType } from '../../../common/auth/constants'; describe('WebhookActionConnectorFields renders', () => { - test('all connector fields is rendered', async () => { + it('renders all connector fields', async () => { const actionConnector = { actionTypeId: '.webhook', name: 'webhook', @@ -23,7 +23,7 @@ describe('WebhookActionConnectorFields renders', () => { url: 'https://test.com', headers: [{ key: 'content-type', value: 'text' }], hasAuth: true, - authType: WebhookAuthType.Basic, + authType: AuthType.Basic, }, secrets: { user: 'user', @@ -104,7 +104,7 @@ describe('WebhookActionConnectorFields renders', () => { url: 'https://test.com', headers: [{ key: 'content-type', value: 'text' }], hasAuth: true, - authType: WebhookAuthType.Basic, + authType: AuthType.Basic, }, secrets: { user: 'user', @@ -171,7 +171,7 @@ describe('WebhookActionConnectorFields renders', () => { method: 'PUT', url: 'https://test.com', hasAuth: true, - authType: WebhookAuthType.Basic, + authType: AuthType.Basic, }, }; @@ -197,7 +197,7 @@ describe('WebhookActionConnectorFields renders', () => { method: 'PUT', url: 'https://test.com', hasAuth: true, - authType: WebhookAuthType.Basic, + authType: AuthType.Basic, }, secrets: { user: 'user', @@ -303,7 +303,7 @@ describe('WebhookActionConnectorFields renders', () => { method: 'PUT', url: 'https://test.com', hasAuth: true, - authType: WebhookAuthType.Basic, + authType: AuthType.Basic, ca: Buffer.from('some binary string').toString('base64'), verificationMode: 'full', headers: [{ key: 'content-type', value: 'text' }], @@ -327,7 +327,7 @@ describe('WebhookActionConnectorFields renders', () => { ...actionConnector, config: { ...actionConnector.config, - authType: WebhookAuthType.SSL, + authType: AuthType.SSL, certType: SSLCertType.CRT, }, secrets: { @@ -358,7 +358,7 @@ describe('WebhookActionConnectorFields renders', () => { method: 'PUT', url: 'https://test.com', hasAuth: true, - authType: WebhookAuthType.SSL, + authType: AuthType.SSL, certType: SSLCertType.CRT, headers: [{ key: 'content-type', value: 'text' }], }, @@ -381,7 +381,7 @@ describe('WebhookActionConnectorFields renders', () => { ...actionConnector, config: { ...actionConnector.config, - authType: WebhookAuthType.SSL, + authType: AuthType.SSL, certType: SSLCertType.PFX, }, secrets: { @@ -411,7 +411,7 @@ describe('WebhookActionConnectorFields renders', () => { method: 'PUT', url: 'https://test.com', hasAuth: true, - authType: WebhookAuthType.SSL, + authType: AuthType.SSL, certType: SSLCertType.PFX, headers: [{ key: 'content-type', value: 'text' }], }, @@ -433,7 +433,7 @@ describe('WebhookActionConnectorFields renders', () => { ...actionConnector, config: { ...actionConnector.config, - authType: WebhookAuthType.SSL, + authType: AuthType.SSL, certType: SSLCertType.CRT, }, secrets: { diff --git a/x-pack/plugins/stack_connectors/public/connector_types/webhook/webhook_connectors.tsx b/x-pack/plugins/stack_connectors/public/connector_types/webhook/webhook_connectors.tsx index f99f0c964a578..0c567127e4811 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/webhook/webhook_connectors.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/webhook/webhook_connectors.tsx @@ -5,237 +5,25 @@ * 2.0. */ -import React, { useEffect } from 'react'; -import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiButtonIcon, - EuiTitle, - EuiButtonEmpty, - EuiCallOut, - EuiTabs, - EuiTab, -} from '@elastic/eui'; -import { - UseArray, - UseField, - useFormContext, - useFormData, -} from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import { - Field, - SelectField, - TextField, - ToggleField, - PasswordField, - FilePickerField, - CardRadioGroupField, - HiddenField, -} from '@kbn/es-ui-shared-plugin/static/forms/components'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { Field, SelectField } from '@kbn/es-ui-shared-plugin/static/forms/components'; import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; import type { ActionConnectorFieldsProps } from '@kbn/triggers-actions-ui-plugin/public'; -import { WebhookAuthType, SSLCertType } from '../../../common/webhook/constants'; + import * as i18n from './translations'; +import { AuthConfig } from '../../common/auth/auth_config'; const HTTP_VERBS = ['post', 'put']; const { emptyField, urlField } = fieldValidators; -const VERIFICATION_MODE_DEFAULT = 'full'; const WebhookActionConnectorFields: React.FunctionComponent = ({ readOnly, }) => { - const { setFieldValue, getFieldDefaultValue } = useFormContext(); - const [{ config, __internal__ }] = useFormData({ - watch: [ - 'config.hasAuth', - 'config.authType', - 'config.certType', - 'config.verificationMode', - '__internal__.hasHeaders', - '__internal__.hasCA', - ], - }); - - const hasHeadersDefaultValue = !!getFieldDefaultValue('config.headers'); - const authTypeDefaultValue = - getFieldDefaultValue('config.hasAuth') === false - ? null - : getFieldDefaultValue('config.authType') ?? WebhookAuthType.Basic; - const certTypeDefaultValue = getFieldDefaultValue('config.certType') ?? SSLCertType.CRT; - const hasCADefaultValue = - !!getFieldDefaultValue('config.ca') || - getFieldDefaultValue('config.verificationMode') === 'none'; - - const hasHeaders = __internal__ != null ? __internal__.hasHeaders : false; - const hasCA = __internal__ != null ? __internal__.hasCA : false; - const authType = config == null ? WebhookAuthType.Basic : config.authType; - const certType = config == null ? SSLCertType.CRT : config.certType; - - const hasInitialCA = !!getFieldDefaultValue('config.ca'); - - useEffect(() => setFieldValue('config.hasAuth', Boolean(authType)), [authType, setFieldValue]); - - const basicAuthFields = ( - - - - - - - - - ); - - const sslCertAuthFields = ( - - - - - ( - - field.setValue(SSLCertType.CRT)} - isSelected={field.value === SSLCertType.CRT} - > - {i18n.CERT_TYPE_CRT_KEY} - - field.setValue(SSLCertType.PFX)} - isSelected={field.value === SSLCertType.PFX} - > - {i18n.CERT_TYPE_PFX} - - - )} - /> - - {certType === SSLCertType.CRT && ( - - - - - - - - - )} - {certType === SSLCertType.PFX && ( - - )} - - - ); - return ( <> - - - - - -

- -

- - - -
-
- - - - {hasHeaders ? ( - <> - - - {({ items, addItem, removeItem }) => { - return ( - <> - {items.map((item) => ( - - - - - - - - - removeItem(item.id)} - iconType="minusInCircle" - aria-label={i18n.REMOVE_ITEM_LABEL} - style={{ marginTop: '28px' }} - /> - - - ))} - - - {i18n.ADD_HEADER_BTN} - - - - ); - }} - - - ) : null} - - - {hasCA && ( - <> - - - {}, - }, - ], - }} - component={FilePickerField} - componentProps={{ - euiFieldProps: { - display: 'default', - 'data-test-subj': 'webhookCAInput', - accept: '.ca,.pem', - }, - }} - /> - - - - - - {hasInitialCA && ( - <> - - - - )} - - )} + ); }; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.ts b/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.ts index 1a4b6aad6653e..8b05c30a5b0cb 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.ts @@ -78,6 +78,7 @@ export class BedrockConnector extends SubActionConnector { method: 'runApi', schema: RunActionParamsSchema, }); + this.registerSubAction({ name: SUB_ACTION.INVOKE_AI, method: 'invokeAI', @@ -305,11 +306,14 @@ The Kibana Connector in use may need to be reconfigured with an updated Amazon B stopSequences, system, temperature, + maxTokens, signal, timeout, }: InvokeAIActionParams): Promise { const res = await this.runApi({ - body: JSON.stringify(formatBedrockBody({ messages, stopSequences, system, temperature })), + body: JSON.stringify( + formatBedrockBody({ messages, stopSequences, system, temperature, maxTokens }) + ), model, signal, timeout, @@ -323,16 +327,18 @@ const formatBedrockBody = ({ stopSequences, temperature = 0, system, + maxTokens = DEFAULT_TOKEN_LIMIT, }: { messages: Array<{ role: string; content: string }>; stopSequences?: string[]; temperature?: number; + maxTokens?: number; // optional system message to be sent to the API system?: string; }) => ({ anthropic_version: 'bedrock-2023-05-31', ...ensureMessageFormat(messages, system), - max_tokens: DEFAULT_TOKEN_LIMIT, + max_tokens: maxTokens, stop_sequences: stopSequences, temperature, }); @@ -351,6 +357,11 @@ const ensureMessageFormat = ( const newMessages = messages.reduce((acc: Array<{ role: string; content: string }>, m) => { const lastMessage = acc[acc.length - 1]; + if (m.role === 'system') { + system = `${system.length ? `${system}\n` : ''}${m.content}`; + return acc; + } + if (lastMessage && lastMessage.role === m.role) { // Bedrock only accepts assistant and user roles. // If 2 user or 2 assistant messages are sent in a row, combine the messages into a single message @@ -359,13 +370,12 @@ const ensureMessageFormat = ( { content: `${lastMessage.content}\n${m.content}`, role: m.role }, ]; } - if (m.role === 'system') { - system = `${system.length ? `${system}\n` : ''}${m.content}`; - return acc; - } // force role outside of system to ensure it is either assistant or user - return [...acc, { content: m.content, role: m.role === 'assistant' ? 'assistant' : 'user' }]; + return [ + ...acc, + { content: m.content, role: ['assistant', 'ai'].includes(m.role) ? 'assistant' : 'user' }, + ]; }, []); return system.length ? { system, messages: newMessages } : { messages: newMessages }; }; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/index.ts index 9bfb66f1a69aa..62dd881608605 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/index.ts @@ -26,12 +26,13 @@ import { ExternalIncidentServiceSecretConfigurationSchema, } from './schema'; import { api } from './api'; -import { validate } from './validators'; +import { validateCasesWebhookConfig, validateConnector } from './validators'; import * as i18n from './translations'; const supportedSubActions: string[] = ['pushToService']; export type ActionParamsType = CasesWebhookActionParamsType; export const ConnectorTypeId = '.cases-webhook'; + // connector type definition export function getConnectorType(): ConnectorType< CasesWebhookPublicConfigurationType, @@ -46,16 +47,15 @@ export function getConnectorType(): ConnectorType< validate: { config: { schema: ExternalIncidentServiceConfigurationSchema, - customValidator: validate.config, + customValidator: validateCasesWebhookConfig, }, secrets: { schema: ExternalIncidentServiceSecretConfigurationSchema, - customValidator: validate.secrets, }, params: { schema: ExecutorParamsSchema, }, - connector: validate.connector, + connector: validateConnector, }, executor, supportedFeatureIds: [CasesConnectorFeatureId], diff --git a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/schema.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/schema.ts index 9863b1457628a..00b4fdc60a3ab 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/schema.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/schema.ts @@ -6,17 +6,17 @@ */ import { schema } from '@kbn/config-schema'; -import { CasesWebhookMethods } from './types'; -import { nullableType } from '../lib/nullable'; +import { WebhookMethods } from '../../../common/auth/constants'; +import { AuthConfiguration, SecretConfigurationSchema } from '../../../common/auth/schema'; const HeadersSchema = schema.recordOf(schema.string(), schema.string()); export const ExternalIncidentServiceConfiguration = { createIncidentUrl: schema.string(), createIncidentMethod: schema.oneOf( - [schema.literal(CasesWebhookMethods.POST), schema.literal(CasesWebhookMethods.PUT)], + [schema.literal(WebhookMethods.POST), schema.literal(WebhookMethods.PUT)], { - defaultValue: CasesWebhookMethods.POST, + defaultValue: WebhookMethods.POST, } ), createIncidentJson: schema.string(), // stringified object @@ -27,12 +27,12 @@ export const ExternalIncidentServiceConfiguration = { updateIncidentUrl: schema.string(), updateIncidentMethod: schema.oneOf( [ - schema.literal(CasesWebhookMethods.POST), - schema.literal(CasesWebhookMethods.PATCH), - schema.literal(CasesWebhookMethods.PUT), + schema.literal(WebhookMethods.POST), + schema.literal(WebhookMethods.PATCH), + schema.literal(WebhookMethods.PUT), ], { - defaultValue: CasesWebhookMethods.PUT, + defaultValue: WebhookMethods.PUT, } ), updateIncidentJson: schema.string(), @@ -40,33 +40,28 @@ export const ExternalIncidentServiceConfiguration = { createCommentMethod: schema.nullable( schema.oneOf( [ - schema.literal(CasesWebhookMethods.POST), - schema.literal(CasesWebhookMethods.PUT), - schema.literal(CasesWebhookMethods.PATCH), + schema.literal(WebhookMethods.POST), + schema.literal(WebhookMethods.PUT), + schema.literal(WebhookMethods.PATCH), ], { - defaultValue: CasesWebhookMethods.PUT, + defaultValue: WebhookMethods.PUT, } ) ), createCommentJson: schema.nullable(schema.string()), - headers: nullableType(HeadersSchema), - hasAuth: schema.boolean({ defaultValue: true }), + headers: schema.nullable(HeadersSchema), + hasAuth: AuthConfiguration.hasAuth, + authType: AuthConfiguration.authType, + certType: AuthConfiguration.certType, + ca: AuthConfiguration.ca, + verificationMode: AuthConfiguration.verificationMode, }; export const ExternalIncidentServiceConfigurationSchema = schema.object( ExternalIncidentServiceConfiguration ); -export const ExternalIncidentServiceSecretConfiguration = { - user: schema.nullable(schema.string()), - password: schema.nullable(schema.string()), -}; - -export const ExternalIncidentServiceSecretConfigurationSchema = schema.object( - ExternalIncidentServiceSecretConfiguration -); - export const ExecutorSubActionPushParamsSchema = schema.object({ incident: schema.object({ title: schema.string(), @@ -93,3 +88,5 @@ export const ExecutorParamsSchema = schema.oneOf([ subActionParams: ExecutorSubActionPushParamsSchema, }), ]); + +export const ExternalIncidentServiceSecretConfigurationSchema = SecretConfigurationSchema; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/service.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/service.test.ts index 1e7b492ada959..3a8cf2895e60e 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/service.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/service.test.ts @@ -9,11 +9,14 @@ import axios, { AxiosError, AxiosResponse } from 'axios'; import { createExternalService } from './service'; import { request, createAxiosResponse } from '@kbn/actions-plugin/server/lib/axios_utils'; -import { CasesWebhookMethods, CasesWebhookPublicConfigurationType, ExternalService } from './types'; +import { CasesWebhookPublicConfigurationType, ExternalService } from './types'; import { Logger } from '@kbn/core/server'; import { loggingSystemMock } from '@kbn/core/server/mocks'; import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; import { getBasicAuthHeader } from '@kbn/actions-plugin/server/lib'; +import { AuthType, WebhookMethods, SSLCertType } from '../../../common/auth/constants'; +import { CRT_FILE, KEY_FILE } from '../../../common/auth/mocks'; + const logger = loggingSystemMock.create().get() as jest.Mocked; jest.mock('@kbn/actions-plugin/server/lib/axios_utils', () => { @@ -25,36 +28,51 @@ jest.mock('@kbn/actions-plugin/server/lib/axios_utils', () => { }); axios.create = jest.fn(() => axios); + const requestMock = request as jest.Mock; const configurationUtilities = actionsConfigMock.create(); const config: CasesWebhookPublicConfigurationType = { createCommentJson: '{"body":{{{case.comment}}}}', - createCommentMethod: CasesWebhookMethods.POST, + createCommentMethod: WebhookMethods.POST, createCommentUrl: 'https://coolsite.net/issue/{{{external.system.id}}}/comment', createIncidentJson: '{"fields":{"title":{{{case.title}}},"description":{{{case.description}}},"tags":{{{case.tags}}},"project":{"key":"ROC"},"issuetype":{"id":"10024"}}}', - createIncidentMethod: CasesWebhookMethods.POST, + createIncidentMethod: WebhookMethods.POST, createIncidentResponseKey: 'id', createIncidentUrl: 'https://coolsite.net/issue', getIncidentResponseExternalTitleKey: 'key', hasAuth: true, - headers: { ['content-type']: 'application/json' }, + headers: { ['content-type']: 'application/json', foo: 'bar' }, viewIncidentUrl: 'https://coolsite.net/browse/{{{external.system.title}}}', getIncidentUrl: 'https://coolsite.net/issue/{{{external.system.id}}}', updateIncidentJson: '{"fields":{"title":{{{case.title}}},"description":{{{case.description}}},"tags":{{{case.tags}}},"project":{"key":"ROC"},"issuetype":{"id":"10024"}}}', - updateIncidentMethod: CasesWebhookMethods.PUT, + updateIncidentMethod: WebhookMethods.PUT, updateIncidentUrl: 'https://coolsite.net/issue/{{{external.system.id}}}', }; const secrets = { user: 'user', password: 'pass', + crt: null, + key: null, + pfx: null, }; +const defaultSSLOverrides = {}; const actionId = '1234'; const mockTime = new Date('2021-10-20T19:41:02.754+0300'); + +const sslConfig: CasesWebhookPublicConfigurationType = { + ...config, + authType: AuthType.SSL, + certType: SSLCertType.CRT, + hasAuth: true, +}; +const sslSecrets = { crt: CRT_FILE, key: KEY_FILE, password: 'foobar', user: null, pfx: null }; + describe('Cases webhook service', () => { let service: ExternalService; + let sslService: ExternalService; beforeAll(() => { service = createExternalService( @@ -66,6 +84,16 @@ describe('Cases webhook service', () => { logger, configurationUtilities ); + + sslService = createExternalService( + actionId, + { + config: sslConfig, + secrets: sslSecrets, + }, + logger, + configurationUtilities + ); jest.useFakeTimers(); jest.setSystemTime(mockTime); }); @@ -98,13 +126,13 @@ describe('Cases webhook service', () => { ).toThrow(); }); - test('throws if hasAuth and no user/pass', () => { + it('throws if hasAuth and no user/pass', () => { expect(() => createExternalService( actionId, { config, - secrets: { user: '', password: '' }, + secrets: { ...secrets, user: '', password: '' }, }, logger, configurationUtilities @@ -112,13 +140,13 @@ describe('Cases webhook service', () => { ).toThrow(); }); - test('does not throw if hasAuth=false and no user/pass', () => { + it('does not throw if hasAuth=false and no user/pass', () => { expect(() => createExternalService( actionId, { config: { ...config, hasAuth: false }, - secrets: { user: '', password: '' }, + secrets: { ...secrets, user: '', password: '' }, }, logger, configurationUtilities @@ -126,12 +154,12 @@ describe('Cases webhook service', () => { ).not.toThrow(); }); - test('uses the basic auth header for authentication', () => { + it('uses the basic auth header for authentication', () => { createExternalService( actionId, { config, - secrets: { user: 'username', password: 'password' }, + secrets: { ...secrets, user: 'username', password: 'password' }, }, logger, configurationUtilities @@ -141,16 +169,17 @@ describe('Cases webhook service', () => { headers: { ...getBasicAuthHeader({ username: 'username', password: 'password' }), 'content-type': 'application/json', + foo: 'bar', }, }); }); - test('does not add the basic auth header for authentication if hasAuth=false', () => { + it('does not add the basic auth header for authentication if hasAuth=false', () => { createExternalService( actionId, { config: { ...config, hasAuth: false }, - secrets: { user: 'username', password: 'password' }, + secrets: { ...secrets, user: 'username', password: 'password' }, }, logger, configurationUtilities @@ -159,6 +188,7 @@ describe('Cases webhook service', () => { expect(axios.create).toHaveBeenCalledWith({ headers: { 'content-type': 'application/json', + foo: 'bar', }, }); }); @@ -176,7 +206,7 @@ describe('Cases webhook service', () => { }, }; - test('it returns the incident correctly', async () => { + it('it returns the incident correctly', async () => { requestMock.mockImplementation(() => createAxiosResponse(axiosRes)); const res = await service.getIncident('1'); expect(res).toEqual({ @@ -185,7 +215,7 @@ describe('Cases webhook service', () => { }); }); - test('it should call request with correct arguments', async () => { + it('it should call request with correct arguments', async () => { requestMock.mockImplementation(() => createAxiosResponse(axiosRes)); await service.getIncident('1'); @@ -194,10 +224,161 @@ describe('Cases webhook service', () => { url: 'https://coolsite.net/issue/1', logger, configurationUtilities, + sslOverrides: defaultSSLOverrides, }); }); - test('it should throw an error', async () => { + it('it should call request with correct arguments when authType=SSL', async () => { + requestMock.mockImplementation(() => createAxiosResponse(axiosRes)); + + await sslService.getIncident('1'); + + // irrelevant snapshot content + delete requestMock.mock.calls[0][0].configurationUtilities; + expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "axios": [Function], + "logger": Object { + "context": Array [], + "debug": [MockFunction], + "error": [MockFunction], + "fatal": [MockFunction], + "get": [MockFunction], + "info": [MockFunction], + "isLevelEnabled": [MockFunction], + "log": [MockFunction], + "trace": [MockFunction], + "warn": [MockFunction], + }, + "sslOverrides": Object { + "cert": Object { + "data": Array [ + 10, + 45, + 45, + 45, + 45, + 45, + 66, + 69, + 71, + 73, + 78, + 32, + 67, + 69, + 82, + 84, + 73, + 70, + 73, + 67, + 65, + 84, + 69, + 45, + 45, + 45, + 45, + 45, + 10, + 45, + 45, + 45, + 45, + 45, + 69, + 78, + 68, + 32, + 67, + 69, + 82, + 84, + 73, + 70, + 73, + 67, + 65, + 84, + 69, + 45, + 45, + 45, + 45, + 45, + 10, + ], + "type": "Buffer", + }, + "key": Object { + "data": Array [ + 10, + 45, + 45, + 45, + 45, + 45, + 66, + 69, + 71, + 73, + 78, + 32, + 80, + 82, + 73, + 86, + 65, + 84, + 69, + 32, + 75, + 69, + 89, + 45, + 45, + 45, + 45, + 45, + 10, + 45, + 45, + 45, + 45, + 45, + 69, + 78, + 68, + 32, + 80, + 82, + 73, + 86, + 65, + 84, + 69, + 32, + 75, + 69, + 89, + 45, + 45, + 45, + 45, + 45, + 10, + ], + "type": "Buffer", + }, + "passphrase": "foobar", + }, + "url": "https://coolsite.net/issue/1", + } + `); + }); + + it('it should throw an error', async () => { requestMock.mockImplementation(() => { const error: AxiosError = new Error('An error has occurred') as AxiosError; error.response = { statusText: 'Required field' } as AxiosResponse; @@ -208,7 +389,7 @@ describe('Cases webhook service', () => { ); }); - test('it should throw if the request is not a JSON', async () => { + it('it should throw if the request is not a JSON', async () => { requestMock.mockImplementation(() => createAxiosResponse({ ...axiosRes, headers: { ['content-type']: 'text/html' } }) ); @@ -218,7 +399,7 @@ describe('Cases webhook service', () => { ); }); - test('it should throw if the required attributes are not there', async () => { + it('it should throw if the required attributes are not there', async () => { requestMock.mockImplementation(() => createAxiosResponse({ data: { fields: { notRequired: 'test' } } }) ); @@ -241,7 +422,7 @@ describe('Cases webhook service', () => { }, }; - test('it creates the incident correctly', async () => { + it('it creates the incident correctly', async () => { requestMock.mockImplementationOnce(() => createAxiosResponse({ data: { id: '1', key: 'CK-1', fields: { title: 'title', description: 'description' } }, @@ -271,7 +452,7 @@ describe('Cases webhook service', () => { }); }); - test('it should call request with correct arguments', async () => { + it('it should call request with correct arguments', async () => { requestMock.mockImplementationOnce(() => createAxiosResponse({ data: { @@ -296,13 +477,194 @@ describe('Cases webhook service', () => { axios, url: 'https://coolsite.net/issue', logger, - method: CasesWebhookMethods.POST, + method: WebhookMethods.POST, configurationUtilities, + sslOverrides: defaultSSLOverrides, data: `{"fields":{"title":"title","description":"desc","tags":["hello","world"],"project":{"key":"ROC"},"issuetype":{"id":"10024"}}}`, }); }); - test('it should throw an error', async () => { + it('it should call request with correct arguments when authType=SSL', async () => { + requestMock.mockImplementationOnce(() => + createAxiosResponse({ + data: { + id: '1', + key: 'CK-1', + }, + }) + ); + + requestMock.mockImplementationOnce(() => + createAxiosResponse({ + data: { + id: '1', + key: 'CK-1', + }, + }) + ); + + await sslService.createIncident(incident); + + // irrelevant snapshot content + delete requestMock.mock.calls[0][0].configurationUtilities; + expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "axios": [Function], + "data": "{\\"fields\\":{\\"title\\":\\"title\\",\\"description\\":\\"desc\\",\\"tags\\":[\\"hello\\",\\"world\\"],\\"project\\":{\\"key\\":\\"ROC\\"},\\"issuetype\\":{\\"id\\":\\"10024\\"}}}", + "logger": Object { + "context": Array [], + "debug": [MockFunction] { + "calls": Array [ + Array [ + "response from webhook action \\"1234\\": [HTTP 200] OK", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + ], + }, + "error": [MockFunction], + "fatal": [MockFunction], + "get": [MockFunction], + "info": [MockFunction], + "isLevelEnabled": [MockFunction], + "log": [MockFunction], + "trace": [MockFunction], + "warn": [MockFunction], + }, + "method": "post", + "sslOverrides": Object { + "cert": Object { + "data": Array [ + 10, + 45, + 45, + 45, + 45, + 45, + 66, + 69, + 71, + 73, + 78, + 32, + 67, + 69, + 82, + 84, + 73, + 70, + 73, + 67, + 65, + 84, + 69, + 45, + 45, + 45, + 45, + 45, + 10, + 45, + 45, + 45, + 45, + 45, + 69, + 78, + 68, + 32, + 67, + 69, + 82, + 84, + 73, + 70, + 73, + 67, + 65, + 84, + 69, + 45, + 45, + 45, + 45, + 45, + 10, + ], + "type": "Buffer", + }, + "key": Object { + "data": Array [ + 10, + 45, + 45, + 45, + 45, + 45, + 66, + 69, + 71, + 73, + 78, + 32, + 80, + 82, + 73, + 86, + 65, + 84, + 69, + 32, + 75, + 69, + 89, + 45, + 45, + 45, + 45, + 45, + 10, + 45, + 45, + 45, + 45, + 45, + 69, + 78, + 68, + 32, + 80, + 82, + 73, + 86, + 65, + 84, + 69, + 32, + 75, + 69, + 89, + 45, + 45, + 45, + 45, + 45, + 10, + ], + "type": "Buffer", + }, + "passphrase": "foobar", + }, + "url": "https://coolsite.net/issue", + } + `); + }); + + it('it should throw an error', async () => { requestMock.mockImplementation(() => { const error: AxiosError = new Error('An error has occurred') as AxiosError; error.response = { statusText: 'Required field' } as AxiosResponse; @@ -314,7 +676,7 @@ describe('Cases webhook service', () => { ); }); - test('it should throw if the request is not a JSON', async () => { + it('it should throw if the request is not a JSON', async () => { requestMock.mockImplementation(() => createAxiosResponse({ data: { id: '1' }, headers: { ['content-type']: 'text/html' } }) ); @@ -324,7 +686,7 @@ describe('Cases webhook service', () => { ); }); - test('it should throw if the required attributes are not there', async () => { + it('it should throw if the required attributes are not there', async () => { requestMock.mockImplementation(() => createAxiosResponse({ data: { notRequired: 'test' } })); await expect(service.createIncident(incident)).rejects.toThrow( @@ -346,7 +708,7 @@ describe('Cases webhook service', () => { }, }; - test('it updates the incident correctly', async () => { + it('it updates the incident correctly', async () => { requestMock.mockImplementation(() => createAxiosResponse({ data: { @@ -366,7 +728,7 @@ describe('Cases webhook service', () => { }); }); - test('it should call request with correct arguments', async () => { + it('it should call request with correct arguments', async () => { requestMock.mockImplementation(() => createAxiosResponse({ data: { @@ -381,8 +743,9 @@ describe('Cases webhook service', () => { expect(requestMock.mock.calls[0][0]).toEqual({ axios, logger, - method: CasesWebhookMethods.PUT, + method: WebhookMethods.PUT, configurationUtilities, + sslOverrides: defaultSSLOverrides, url: 'https://coolsite.net/issue/1', data: JSON.stringify({ fields: { @@ -396,7 +759,166 @@ describe('Cases webhook service', () => { }); }); - test('it should throw an error', async () => { + it('it should call request with correct arguments when authType=SSL', async () => { + requestMock.mockImplementation(() => + createAxiosResponse({ + data: { + id: '1', + key: 'CK-1', + }, + }) + ); + + await sslService.updateIncident(incident); + + // irrelevant snapshot content + delete requestMock.mock.calls[0][0].configurationUtilities; + expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "axios": [Function], + "data": "{\\"fields\\":{\\"title\\":\\"title\\",\\"description\\":\\"desc\\",\\"tags\\":[\\"hello\\",\\"world\\"],\\"project\\":{\\"key\\":\\"ROC\\"},\\"issuetype\\":{\\"id\\":\\"10024\\"}}}", + "logger": Object { + "context": Array [], + "debug": [MockFunction], + "error": [MockFunction], + "fatal": [MockFunction], + "get": [MockFunction], + "info": [MockFunction], + "isLevelEnabled": [MockFunction], + "log": [MockFunction], + "trace": [MockFunction], + "warn": [MockFunction], + }, + "method": "put", + "sslOverrides": Object { + "cert": Object { + "data": Array [ + 10, + 45, + 45, + 45, + 45, + 45, + 66, + 69, + 71, + 73, + 78, + 32, + 67, + 69, + 82, + 84, + 73, + 70, + 73, + 67, + 65, + 84, + 69, + 45, + 45, + 45, + 45, + 45, + 10, + 45, + 45, + 45, + 45, + 45, + 69, + 78, + 68, + 32, + 67, + 69, + 82, + 84, + 73, + 70, + 73, + 67, + 65, + 84, + 69, + 45, + 45, + 45, + 45, + 45, + 10, + ], + "type": "Buffer", + }, + "key": Object { + "data": Array [ + 10, + 45, + 45, + 45, + 45, + 45, + 66, + 69, + 71, + 73, + 78, + 32, + 80, + 82, + 73, + 86, + 65, + 84, + 69, + 32, + 75, + 69, + 89, + 45, + 45, + 45, + 45, + 45, + 10, + 45, + 45, + 45, + 45, + 45, + 69, + 78, + 68, + 32, + 80, + 82, + 73, + 86, + 65, + 84, + 69, + 32, + 75, + 69, + 89, + 45, + 45, + 45, + 45, + 45, + 10, + ], + "type": "Buffer", + }, + "passphrase": "foobar", + }, + "url": "https://coolsite.net/issue/1", + } + `); + }); + + it('it should throw an error', async () => { requestMock.mockImplementation(() => { const error: AxiosError = new Error('An error has occurred') as AxiosError; error.response = { statusText: 'Required field' } as AxiosResponse; @@ -408,7 +930,7 @@ describe('Cases webhook service', () => { ); }); - test('it should throw if the request is not a JSON', async () => { + it('it should throw if the request is not a JSON', async () => { requestMock.mockImplementation(() => createAxiosResponse({ data: { id: '1' }, headers: { ['content-type']: 'text/html' } }) ); @@ -427,7 +949,7 @@ describe('Cases webhook service', () => { commentId: 'comment-1', }, }; - test('it creates the comment correctly', async () => { + it('it creates the comment correctly', async () => { requestMock.mockImplementation(() => createAxiosResponse({ data: { @@ -442,7 +964,7 @@ describe('Cases webhook service', () => { expect(requestMock.mock.calls[0][0].data).toEqual('{"body":"comment"}'); }); - test('it should call request with correct arguments', async () => { + it('it should call request with correct arguments', async () => { requestMock.mockImplementation(() => createAxiosResponse({ data: { @@ -457,14 +979,174 @@ describe('Cases webhook service', () => { expect(requestMock).toHaveBeenCalledWith({ axios, logger, - method: CasesWebhookMethods.POST, + method: WebhookMethods.POST, configurationUtilities, + sslOverrides: defaultSSLOverrides, url: 'https://coolsite.net/issue/1/comment', data: `{"body":"comment"}`, }); }); - test('it should throw an error', async () => { + it('it should call request with correct arguments when authType=SSL', async () => { + requestMock.mockImplementation(() => + createAxiosResponse({ + data: { + id: '1', + key: 'CK-1', + }, + }) + ); + + await sslService.createComment(commentReq); + + // irrelevant snapshot content + delete requestMock.mock.calls[0][0].configurationUtilities; + expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "axios": [Function], + "data": "{\\"body\\":\\"comment\\"}", + "logger": Object { + "context": Array [], + "debug": [MockFunction], + "error": [MockFunction], + "fatal": [MockFunction], + "get": [MockFunction], + "info": [MockFunction], + "isLevelEnabled": [MockFunction], + "log": [MockFunction], + "trace": [MockFunction], + "warn": [MockFunction], + }, + "method": "post", + "sslOverrides": Object { + "cert": Object { + "data": Array [ + 10, + 45, + 45, + 45, + 45, + 45, + 66, + 69, + 71, + 73, + 78, + 32, + 67, + 69, + 82, + 84, + 73, + 70, + 73, + 67, + 65, + 84, + 69, + 45, + 45, + 45, + 45, + 45, + 10, + 45, + 45, + 45, + 45, + 45, + 69, + 78, + 68, + 32, + 67, + 69, + 82, + 84, + 73, + 70, + 73, + 67, + 65, + 84, + 69, + 45, + 45, + 45, + 45, + 45, + 10, + ], + "type": "Buffer", + }, + "key": Object { + "data": Array [ + 10, + 45, + 45, + 45, + 45, + 45, + 66, + 69, + 71, + 73, + 78, + 32, + 80, + 82, + 73, + 86, + 65, + 84, + 69, + 32, + 75, + 69, + 89, + 45, + 45, + 45, + 45, + 45, + 10, + 45, + 45, + 45, + 45, + 45, + 69, + 78, + 68, + 32, + 80, + 82, + 73, + 86, + 65, + 84, + 69, + 32, + 75, + 69, + 89, + 45, + 45, + 45, + 45, + 45, + 10, + ], + "type": "Buffer", + }, + "passphrase": "foobar", + }, + "url": "https://coolsite.net/issue/1/comment", + } + `); + }); + + it('it should throw an error', async () => { requestMock.mockImplementation(() => { const error: AxiosError = new Error('An error has occurred') as AxiosError; error.response = { statusText: 'Required field' } as AxiosResponse; @@ -476,7 +1158,7 @@ describe('Cases webhook service', () => { ); }); - test('it should throw if the request is not a JSON', async () => { + it('it should throw if the request is not a JSON', async () => { requestMock.mockImplementation(() => createAxiosResponse({ data: { id: '1' }, headers: { ['content-type']: 'text/html' } }) ); @@ -486,7 +1168,7 @@ describe('Cases webhook service', () => { ); }); - test('it fails silently if createCommentUrl is missing', async () => { + it('it fails silently if createCommentUrl is missing', async () => { service = createExternalService( actionId, { @@ -501,7 +1183,7 @@ describe('Cases webhook service', () => { expect(res).toBeUndefined(); }); - test('it fails silently if createCommentJson is missing', async () => { + it('it fails silently if createCommentJson is missing', async () => { service = createExternalService( actionId, { @@ -516,7 +1198,7 @@ describe('Cases webhook service', () => { expect(res).toBeUndefined(); }); - test('properly encodes external system id as string in request body', async () => { + it('properly encodes external system id as string in request body', async () => { requestMock.mockImplementation(() => createAxiosResponse({ data: { @@ -541,14 +1223,15 @@ describe('Cases webhook service', () => { expect(requestMock).toHaveBeenCalledWith({ axios, logger, - method: CasesWebhookMethods.POST, + method: WebhookMethods.POST, configurationUtilities, url: 'https://coolsite.net/issue/1/comment', data: `{"body":"comment","id":"1"}`, + sslOverrides: defaultSSLOverrides, }); }); - test('properly encodes external system id as number in request body', async () => { + it('properly encodes external system id as number in request body', async () => { const commentReq2 = { incidentId: 1 as unknown as string, comment: { @@ -580,10 +1263,11 @@ describe('Cases webhook service', () => { expect(requestMock).toHaveBeenCalledWith({ axios, logger, - method: CasesWebhookMethods.POST, + method: WebhookMethods.POST, configurationUtilities, url: 'https://coolsite.net/issue/1/comment', data: `{"body":"comment","id":1}`, + sslOverrides: defaultSSLOverrides, }); }); }); @@ -609,12 +1293,12 @@ describe('Cases webhook service', () => { beforeEach(() => { jest.clearAllMocks(); }); - test('getIncident- throws for bad url', async () => { + it('getIncident- throws for bad url', async () => { await expect(service.getIncident('whack')).rejects.toThrow( '[Action][Webhook - Case Management]: Unable to get case with id whack. Error: Invalid Get case URL: Error: error configuring connector action: Uri not allowed.' ); }); - test('createIncident- throws for bad url', async () => { + it('createIncident- throws for bad url', async () => { const incident = { incident: { title: 'title', @@ -630,7 +1314,7 @@ describe('Cases webhook service', () => { '[Action][Webhook - Case Management]: Unable to create case. Error: Invalid Create case URL: Error: error configuring connector action: Uri not allowed.' ); }); - test('updateIncident- throws for bad url', async () => { + it('updateIncident- throws for bad url', async () => { const incident = { incidentId: '123', incident: { @@ -647,7 +1331,7 @@ describe('Cases webhook service', () => { '[Action][Webhook - Case Management]: Unable to update case with id 123. Error: Invalid Update case URL: Error: error configuring connector action: Uri not allowed.' ); }); - test('createComment- throws for bad url', async () => { + it('createComment- throws for bad url', async () => { const commentReq = { incidentId: '1', comment: { @@ -683,12 +1367,12 @@ describe('Cases webhook service', () => { beforeEach(() => { jest.clearAllMocks(); }); - test('getIncident- throws for bad protocol', async () => { + it('getIncident- throws for bad protocol', async () => { await expect(service.getIncident('whack')).rejects.toThrow( '[Action][Webhook - Case Management]: Unable to get case with id whack. Error: Invalid Get case URL: Error: Invalid protocol.' ); }); - test('createIncident- throws for bad protocol', async () => { + it('createIncident- throws for bad protocol', async () => { const incident = { incident: { title: 'title', @@ -704,7 +1388,7 @@ describe('Cases webhook service', () => { '[Action][Webhook - Case Management]: Unable to create case. Error: Invalid Create case URL: Error: Invalid protocol.' ); }); - test('updateIncident- throws for bad protocol', async () => { + it('updateIncident- throws for bad protocol', async () => { const incident = { incidentId: '123', incident: { @@ -721,7 +1405,7 @@ describe('Cases webhook service', () => { '[Action][Webhook - Case Management]: Unable to update case with id 123. Error: Invalid Update case URL: Error: Invalid protocol.' ); }); - test('createComment- throws for bad protocol', async () => { + it('createComment- throws for bad protocol', async () => { const commentReq = { incidentId: '1', comment: { @@ -761,14 +1445,14 @@ describe('Cases webhook service', () => { beforeEach(() => { jest.clearAllMocks(); }); - test('getIncident- escapes url', async () => { + it('getIncident- escapes url', async () => { await service.getIncident('../../malicious-app/malicious-endpoint/'); expect(requestMock.mock.calls[0][0].url).toEqual( 'https://coolsite.net/issue/..%2F..%2Fmalicious-app%2Fmalicious-endpoint%2F' ); }); - test('createIncident- escapes url', async () => { + it('createIncident- escapes url', async () => { const incident = { incident: { title: 'title', @@ -785,7 +1469,7 @@ describe('Cases webhook service', () => { ); }); - test('updateIncident- escapes url', async () => { + it('updateIncident- escapes url', async () => { const incident = { incidentId: '../../malicious-app/malicious-endpoint/', incident: { @@ -803,7 +1487,7 @@ describe('Cases webhook service', () => { 'https://coolsite.net/issue/..%2F..%2Fmalicious-app%2Fmalicious-endpoint%2F' ); }); - test('createComment- escapes url', async () => { + it('createComment- escapes url', async () => { const commentReq = { incidentId: '../../malicious-app/malicious-endpoint/', comment: { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/service.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/service.ts index 46407c18bd081..424fe9b394517 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/service.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/service.ts @@ -12,6 +12,7 @@ import { renderMustacheStringNoEscape } from '@kbn/actions-plugin/server/lib/mus import { request } from '@kbn/actions-plugin/server/lib/axios_utils'; import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; import { combineHeadersWithBasicAuthHeader } from '@kbn/actions-plugin/server/lib'; +import { buildConnectorAuth, validateConnectorAuthConfiguration } from '../../../common/auth/utils'; import { validateAndNormalizeUrl, validateJson } from './validators'; import { createServiceError, @@ -20,12 +21,11 @@ import { removeSlash, throwDescriptiveErrorIfResponseIsNotValid, } from './utils'; -import { +import type { CreateIncidentParams, ExternalServiceCredentials, ExternalService, CasesWebhookPublicConfigurationType, - CasesWebhookSecretConfigurationType, ExternalServiceIncidentResponse, GetIncidentResponse, UpdateIncidentParams, @@ -51,31 +51,41 @@ export const createExternalService = ( getIncidentResponseExternalTitleKey, getIncidentUrl, hasAuth, + authType, headers, viewIncidentUrl, updateIncidentJson, updateIncidentMethod, updateIncidentUrl, + verificationMode, + ca, } = config as CasesWebhookPublicConfigurationType; - const { password, user } = secrets as CasesWebhookSecretConfigurationType; - if ( - !getIncidentUrl || - !createIncidentUrlConfig || - !viewIncidentUrl || - !updateIncidentUrl || - (hasAuth && (!password || !user)) - ) { + + const { basicAuth, sslOverrides } = buildConnectorAuth({ + hasAuth, + authType, + secrets, + verificationMode, + ca, + }); + + validateConnectorAuthConfiguration({ + hasAuth, + authType, + basicAuth, + sslOverrides, + connectorName: i18n.NAME, + }); + + if (!getIncidentUrl || !createIncidentUrlConfig || !viewIncidentUrl || !updateIncidentUrl) { throw Error(`[Action]${i18n.NAME}: Wrong configuration.`); } - const createIncidentUrl = removeSlash(createIncidentUrlConfig); - const headersWithBasicAuth = hasAuth - ? combineHeadersWithBasicAuthHeader({ - username: user ?? undefined, - password: password ?? undefined, - headers, - }) - : {}; + const headersWithBasicAuth = combineHeadersWithBasicAuthHeader({ + username: basicAuth.auth?.username, + password: basicAuth.auth?.password, + headers, + }); const axiosInstance = axios.create({ headers: { @@ -84,6 +94,8 @@ export const createExternalService = ( }, }); + const createIncidentUrl = removeSlash(createIncidentUrlConfig); + const getIncident = async (id: string): Promise => { try { const getUrl = renderMustacheStringNoEscape(getIncidentUrl, { @@ -104,6 +116,7 @@ export const createExternalService = ( url: normalizedUrl, logger, configurationUtilities, + sslOverrides, }); throwDescriptiveErrorIfResponseIsNotValid({ @@ -148,6 +161,7 @@ export const createExternalService = ( method: createIncidentMethod, data: json, configurationUtilities, + sslOverrides, }); const { status, statusText, data } = res; @@ -231,6 +245,7 @@ export const createExternalService = ( logger, data: json, configurationUtilities, + sslOverrides, }); throwDescriptiveErrorIfResponseIsNotValid({ @@ -303,6 +318,7 @@ export const createExternalService = ( logger, data: json, configurationUtilities, + sslOverrides, }); throwDescriptiveErrorIfResponseIsNotValid({ diff --git a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/translations.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/translations.ts index a3180e6cca663..188c4516d22a3 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/translations.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/translations.ts @@ -28,12 +28,9 @@ export const CONFIG_ERR = (err: string) => }, }); -export const INVALID_USER_PW = i18n.translate( - 'xpack.stackConnectors.casesWebhook.invalidUsernamePassword', - { - defaultMessage: 'both user and password must be specified', - } -); +export const INVALID_AUTH = i18n.translate('xpack.stackConnectors.casesWebhook.invalidSecrets', { + defaultMessage: 'must specify a secrets configuration', +}); export const ALLOWED_HOSTS_ERROR = (message: string) => i18n.translate('xpack.stackConnectors.casesWebhook.configuration.apiAllowedHostsError', { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/types.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/types.ts index c4404748fc33b..95af6d5e306f2 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/types.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/types.ts @@ -7,7 +7,6 @@ import { TypeOf } from '@kbn/config-schema'; import { Logger } from '@kbn/core/server'; -import { ValidatorServices } from '@kbn/actions-plugin/server/types'; import { ExecutorParamsSchema, ExecutorSubActionPushParamsSchema, @@ -15,13 +14,6 @@ import { ExternalIncidentServiceSecretConfigurationSchema, } from './schema'; -// config definition -export enum CasesWebhookMethods { - PATCH = 'patch', - POST = 'post', - PUT = 'put', -} - // config export type CasesWebhookPublicConfigurationType = TypeOf< typeof ExternalIncidentServiceConfigurationSchema @@ -38,21 +30,6 @@ export interface ExternalServiceCredentials { secrets: CasesWebhookSecretConfigurationType; } -export interface ExternalServiceValidation { - config: ( - configObject: CasesWebhookPublicConfigurationType, - validatorServices: ValidatorServices - ) => void; - secrets: ( - secrets: CasesWebhookSecretConfigurationType, - validatorServices: ValidatorServices - ) => void; - connector: ( - configObject: CasesWebhookPublicConfigurationType, - secrets: CasesWebhookSecretConfigurationType - ) => string | null; -} - export interface ExternalServiceIncidentResponse { id: string; title: string; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/validators.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/validators.ts index 95265cfcaf6ef..21922d608156e 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/validators.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases_webhook/validators.ts @@ -7,14 +7,11 @@ import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; import { ValidatorServices } from '@kbn/actions-plugin/server/types'; +import { isEmpty } from 'lodash'; import * as i18n from './translations'; -import { - CasesWebhookPublicConfigurationType, - CasesWebhookSecretConfigurationType, - ExternalServiceValidation, -} from './types'; +import { CasesWebhookPublicConfigurationType, CasesWebhookSecretConfigurationType } from './types'; -const validateConfig = ( +export const validateCasesWebhookConfig = ( configObject: CasesWebhookPublicConfigurationType, validatorServices: ValidatorServices ) => { @@ -55,26 +52,8 @@ export const validateConnector = ( configObject: CasesWebhookPublicConfigurationType, secrets: CasesWebhookSecretConfigurationType ): string | null => { - // user and password must be set together (or not at all) - if (!configObject.hasAuth) return null; - if (secrets.password && secrets.user) return null; - return i18n.INVALID_USER_PW; -}; - -export const validateSecrets = ( - secrets: CasesWebhookSecretConfigurationType, - validatorServices: ValidatorServices -) => { - // user and password must be set together (or not at all) - if (!secrets.password && !secrets.user) return; - if (secrets.password && secrets.user) return; - throw new Error(i18n.INVALID_USER_PW); -}; - -export const validate: ExternalServiceValidation = { - config: validateConfig, - secrets: validateSecrets, - connector: validateConnector, + if (configObject.hasAuth && isEmpty(secrets)) return i18n.INVALID_AUTH; + return null; }; const validProtocols: string[] = ['http:', 'https:']; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/gemini/gemini.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/gemini/gemini.test.ts index af5fdc8cb781f..ed825d9aecbf3 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/gemini/gemini.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/gemini/gemini.test.ts @@ -11,7 +11,10 @@ import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.moc import { actionsMock } from '@kbn/actions-plugin/server/mocks'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { initDashboard } from '../lib/gen_ai/create_gen_ai_dashboard'; -import { RunApiResponseSchema } from '../../../common/gemini/schema'; +import { RunApiResponseSchema, StreamingResponseSchema } from '../../../common/gemini/schema'; +import { DEFAULT_GEMINI_MODEL } from '../../../common/gemini/constants'; +import { AxiosError } from 'axios'; +import { Transform } from 'stream'; jest.mock('../lib/gen_ai/create_gen_ai_dashboard'); jest.mock('@kbn/actions-plugin/server/sub_action_framework/helpers/validators', () => ({ @@ -28,14 +31,27 @@ let mockRequest: jest.Mock; describe('GeminiConnector', () => { const defaultResponse = { data: { - candidates: [{ content: { parts: [{ text: 'Paris' }] } }], - usageMetadata: { totalTokens: 0, promptTokens: 0, completionTokens: 0 }, + candidates: [{ content: { role: 'model', parts: [{ text: 'Paris' }] } }], + usageMetadata: { totalTokenCount: 0, promptTokenCount: 0, candidatesTokenCount: 0 }, }, }; + const sampleGeminiBody = { + messages: [ + { + contents: [ + { + role: 'user', + parts: [{ text: 'What is the capital of France?' }], + }, + ], + }, + ], + }; + const connectorResponse = { completion: 'Paris', - usageMetadata: { totalTokens: 0, promptTokens: 0, completionTokens: 0 }, + usageMetadata: { totalTokenCount: 0, promptTokenCount: 0, candidatesTokenCount: 0 }, }; beforeEach(() => { @@ -50,7 +66,7 @@ describe('GeminiConnector', () => { configurationUtilities: actionsConfigMock.create(), config: { apiUrl: 'https://api.gemini.com', - defaultModel: 'gemini-1.5-pro-preview-0409', + defaultModel: DEFAULT_GEMINI_MODEL, gcpRegion: 'us-central1', gcpProjectID: 'my-project-12345', }, @@ -72,53 +88,228 @@ describe('GeminiConnector', () => { services: actionsMock.createServices(), }); - describe('runApi', () => { - it('should send a formatted request to the API and return the response', async () => { - const runActionParams: RunActionParams = { - body: JSON.stringify({ - messages: [ - { - contents: [ - { - role: 'user', - parts: [{ text: 'What is the capital of France?' }], - }, - ], + describe('Gemini', () => { + beforeEach(() => { + // @ts-ignore + connector.request = mockRequest; + }); + + describe('runApi', () => { + it('should send a formatted request to the API and return the response', async () => { + const runActionParams: RunActionParams = { + body: JSON.stringify(sampleGeminiBody), + model: DEFAULT_GEMINI_MODEL, + }; + + const response = await connector.runApi(runActionParams); + + // Assertions + expect(mockRequest).toBeCalledTimes(1); + expect(mockRequest).toHaveBeenCalledWith({ + url: `https://api.gemini.com/v1/projects/my-project-12345/locations/us-central1/publishers/google/models/${DEFAULT_GEMINI_MODEL}:generateContent`, + method: 'post', + data: JSON.stringify({ + messages: [ + { + contents: [ + { + role: 'user', + parts: [{ text: 'What is the capital of France?' }], + }, + ], + }, + ], + }), + headers: { + Authorization: 'Bearer mock_access_token', + 'Content-Type': 'application/json', + }, + timeout: 60000, + responseSchema: RunApiResponseSchema, + signal: undefined, + }); + + expect(response).toEqual(connectorResponse); + }); + }); + + describe('invokeAI', () => { + const aiAssistantBody = { + messages: [ + { + role: 'user', + content: 'What is the capital of France?', + }, + ], + }; + + it('the API call is successful with correct parameters', async () => { + await connector.invokeAI(aiAssistantBody); + expect(mockRequest).toBeCalledTimes(1); + expect(mockRequest).toHaveBeenCalledWith({ + url: `https://api.gemini.com/v1/projects/my-project-12345/locations/us-central1/publishers/google/models/${DEFAULT_GEMINI_MODEL}:generateContent`, + method: 'post', + responseSchema: RunApiResponseSchema, + data: JSON.stringify({ + contents: [ + { + role: 'user', + parts: [{ text: 'What is the capital of France?' }], + }, + ], + generation_config: { + temperature: 0, + maxOutputTokens: 8192, }, - ], - }), - model: 'test-model', + }), + headers: { + Authorization: 'Bearer mock_access_token', + 'Content-Type': 'application/json', + }, + signal: undefined, + timeout: 60000, + }); + }); + + it('signal and timeout is properly passed to runApi', async () => { + const signal = jest.fn(); + const timeout = 60000; + await connector.invokeAI({ ...aiAssistantBody, timeout, signal }); + expect(mockRequest).toHaveBeenCalledWith({ + url: `https://api.gemini.com/v1/projects/my-project-12345/locations/us-central1/publishers/google/models/${DEFAULT_GEMINI_MODEL}:generateContent`, + method: 'post', + responseSchema: RunApiResponseSchema, + data: JSON.stringify({ + contents: [ + { + role: 'user', + parts: [{ text: 'What is the capital of France?' }], + }, + ], + generation_config: { + temperature: 0, + maxOutputTokens: 8192, + }, + }), + headers: { + Authorization: 'Bearer mock_access_token', + 'Content-Type': 'application/json', + }, + signal, + timeout: 60000, + }); + }); + }); + + describe('invokeStream', () => { + let stream; + beforeEach(() => { + stream = createStreamMock(); + stream.write(new Uint8Array([1, 2, 3])); + mockRequest = jest.fn().mockResolvedValue({ ...defaultResponse, data: stream.transform }); + // @ts-ignore + connector.request = mockRequest; + }); + const aiAssistantBody = { + messages: [ + { + role: 'user', + content: 'What is the capital of France?', + }, + ], }; - const response = await connector.runApi(runActionParams); + it('the API call is successful with correct request parameters', async () => { + await connector.invokeStream(aiAssistantBody); + expect(mockRequest).toBeCalledTimes(1); + expect(mockRequest).toHaveBeenCalledWith({ + url: `https://api.gemini.com/v1/projects/my-project-12345/locations/us-central1/publishers/google/models/${DEFAULT_GEMINI_MODEL}:streamGenerateContent?alt=sse`, + method: 'post', + responseSchema: StreamingResponseSchema, + data: JSON.stringify({ + contents: [ + { + role: 'user', + parts: [{ text: 'What is the capital of France?' }], + }, + ], + generation_config: { + temperature: 0, + maxOutputTokens: 8192, + }, + }), + responseType: 'stream', + headers: { + Authorization: 'Bearer mock_access_token', + 'Content-Type': 'application/json', + }, + signal: undefined, + timeout: 60000, + }); + }); - // Assertions - expect(mockRequest).toBeCalledTimes(1); - expect(mockRequest).toHaveBeenCalledWith({ - url: 'https://api.gemini.com/v1/projects/my-project-12345/locations/us-central1/publishers/google/models/test-model:generateContent', - method: 'post', - data: JSON.stringify({ - messages: [ - { - contents: [ - { - role: 'user', - parts: [{ text: 'What is the capital of France?' }], - }, - ], + it('signal and timeout is properly passed to streamApi', async () => { + const signal = jest.fn(); + const timeout = 60000; + await connector.invokeStream({ ...aiAssistantBody, timeout, signal }); + expect(mockRequest).toHaveBeenCalledWith({ + url: `https://api.gemini.com/v1/projects/my-project-12345/locations/us-central1/publishers/google/models/${DEFAULT_GEMINI_MODEL}:streamGenerateContent?alt=sse`, + method: 'post', + responseSchema: StreamingResponseSchema, + data: JSON.stringify({ + contents: [ + { + role: 'user', + parts: [{ text: 'What is the capital of France?' }], + }, + ], + generation_config: { + temperature: 0, + maxOutputTokens: 8192, }, - ], - }), - headers: { - Authorization: 'Bearer mock_access_token', - 'Content-Type': 'application/json', - }, - timeout: 60000, - responseSchema: RunApiResponseSchema, - signal: undefined, + }), + responseType: 'stream', + headers: { + Authorization: 'Bearer mock_access_token', + 'Content-Type': 'application/json', + }, + signal, + timeout: 60000, + }); + }); + }); + + describe('getResponseErrorMessage', () => { + it('returns an unknown error message', () => { + // @ts-expect-error expects an axios error as the parameter + expect(connector.getResponseErrorMessage({})).toEqual( + `Unexpected API Error: - Unknown error` + ); }); - expect(response).toEqual(connectorResponse); + it('returns the error.message', () => { + // @ts-expect-error expects an axios error as the parameter + expect(connector.getResponseErrorMessage({ message: 'a message' })).toEqual( + `Unexpected API Error: - a message` + ); + }); + + it('returns the error.response.data.error.message', () => { + const err = { + response: { + headers: {}, + status: 404, + statusText: 'Resource Not Found', + data: { + message: 'Resource not found', + }, + }, + } as AxiosError<{ message?: string }>; + expect( + // @ts-expect-error expects an axios error as the parameter + connector.getResponseErrorMessage(err) + ).toEqual(`API Error: Resource Not Found - Resource not found`); + }); }); }); @@ -190,3 +381,21 @@ describe('GeminiConnector', () => { }); }); }); + +function createStreamMock() { + const transform: Transform = new Transform({}); + + return { + write: (data: Uint8Array) => { + transform.push(data); + }, + fail: () => { + transform.emit('error', new Error('Stream failed')); + transform.end(); + }, + transform, + complete: () => { + transform.end(); + }, + }; +} diff --git a/x-pack/plugins/stack_connectors/server/connector_types/gemini/gemini.ts b/x-pack/plugins/stack_connectors/server/connector_types/gemini/gemini.ts index 61b8834927a48..323b13d3b768e 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/gemini/gemini.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/gemini/gemini.ts @@ -7,23 +7,37 @@ import { ServiceParams, SubActionConnector } from '@kbn/actions-plugin/server'; import { AxiosError, Method } from 'axios'; +import { PassThrough } from 'stream'; +import { IncomingMessage } from 'http'; import { SubActionRequestParams } from '@kbn/actions-plugin/server/sub_action_framework/types'; import { getGoogleOAuthJwtAccessToken } from '@kbn/actions-plugin/server/lib/get_gcp_oauth_access_token'; import { Logger } from '@kbn/core/server'; import { ConnectorTokenClientContract } from '@kbn/actions-plugin/server/types'; import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; -import { RunActionParamsSchema, RunApiResponseSchema } from '../../../common/gemini/schema'; +import { + RunActionParamsSchema, + RunApiResponseSchema, + InvokeAIActionParamsSchema, + StreamingResponseSchema, +} from '../../../common/gemini/schema'; import { initDashboard } from '../lib/gen_ai/create_gen_ai_dashboard'; - import { Config, Secrets, RunActionParams, RunActionResponse, RunApiResponse, + DashboardActionParams, + DashboardActionResponse, + StreamingResponse, + InvokeAIActionParams, + InvokeAIActionResponse, } from '../../../common/gemini/types'; -import { SUB_ACTION, DEFAULT_TIMEOUT_MS } from '../../../common/gemini/constants'; -import { DashboardActionParams, DashboardActionResponse } from '../../../common/gemini/types'; +import { + SUB_ACTION, + DEFAULT_TIMEOUT_MS, + DEFAULT_TOKEN_LIMIT, +} from '../../../common/gemini/constants'; import { DashboardActionParamsSchema } from '../../../common/gemini/schema'; export interface GetAxiosInstanceOpts { @@ -35,6 +49,25 @@ export interface GetAxiosInstanceOpts { configurationUtilities: ActionsConfigurationUtilities; } +/** Interfaces to define Gemini model response type */ + +interface MessagePart { + text: string; +} + +interface MessageContent { + role: string; + parts: MessagePart[]; +} + +interface Payload { + contents: MessageContent[]; + generation_config: { + temperature: number; + maxOutputTokens: number; + }; +} + export class GeminiConnector extends SubActionConnector { private url; private model; @@ -74,6 +107,18 @@ export class GeminiConnector extends SubActionConnector { method: 'runApi', schema: RunActionParamsSchema, }); + + this.registerSubAction({ + name: SUB_ACTION.INVOKE_AI, + method: 'invokeAI', + schema: InvokeAIActionParamsSchema, + }); + + this.registerSubAction({ + name: SUB_ACTION.INVOKE_STREAM, + method: 'invokeStream', + schema: InvokeAIActionParamsSchema, + }); } protected getResponseErrorMessage(error: AxiosError<{ message?: string }>): string { @@ -185,4 +230,111 @@ export class GeminiConnector extends SubActionConnector { return { completion: completionText, usageMetadata }; } + + private async streamAPI({ + body, + model: reqModel, + signal, + timeout, + }: RunActionParams): Promise { + const currentModel = reqModel ?? this.model; + const path = `/v1/projects/${this.gcpProjectID}/locations/${this.gcpRegion}/publishers/google/models/${currentModel}:streamGenerateContent?alt=sse`; + const token = await this.getAccessToken(); + + const response = await this.request({ + url: `${this.url}${path}`, + method: 'post', + responseSchema: StreamingResponseSchema, + data: body, + responseType: 'stream', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + signal, + timeout: timeout ?? DEFAULT_TIMEOUT_MS, + }); + + return response.data.pipe(new PassThrough()); + } + + public async invokeAI({ + messages, + model, + temperature = 0, + signal, + timeout, + }: InvokeAIActionParams): Promise { + const res = await this.runApi({ + body: JSON.stringify(formatGeminiPayload(messages, temperature)), + model, + signal, + timeout, + }); + + return { message: res.completion, usageMetadata: res.usageMetadata }; + } + + /** + * takes in an array of messages and a model as inputs. It calls the streamApi method to make a + * request to the Gemini API with the formatted messages and model. It then returns a Transform stream + * that pipes the response from the API through the transformToString function, + * which parses the proprietary response into a string of the response text alone + * @param messages An array of messages to be sent to the API + * @param model Optional model to be used for the API request. If not provided, the default model from the connector will be used. + */ + public async invokeStream({ + messages, + model, + stopSequences, + temperature = 0, + signal, + timeout, + }: InvokeAIActionParams): Promise { + const res = (await this.streamAPI({ + body: JSON.stringify(formatGeminiPayload(messages, temperature)), + model, + stopSequences, + signal, + timeout, + })) as unknown as IncomingMessage; + return res; + } } + +/** Format the json body to meet Gemini payload requirements */ +const formatGeminiPayload = ( + data: Array<{ role: string; content: string }>, + temperature: number +): Payload => { + const payload: Payload = { + contents: [], + generation_config: { + temperature, + maxOutputTokens: DEFAULT_TOKEN_LIMIT, + }, + }; + let previousRole: string | null = null; + + for (const row of data) { + const correctRole = row.role === 'assistant' ? 'model' : 'user'; + if (correctRole === 'user' && previousRole === 'user') { + /** Append to the previous 'user' content + * This is to ensure that multiturn requests alternate between user and model + */ + payload.contents[payload.contents.length - 1].parts[0].text += ` ${row.content}`; + } else { + // Add a new entry + payload.contents.push({ + role: correctRole, + parts: [ + { + text: row.content, + }, + ], + }); + } + previousRole = correctRole; + } + return payload; +}; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/index.ts index 04c7004f7325b..6364fed0e193f 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/index.ts @@ -58,7 +58,7 @@ export type { SlackApiActionParams as SlackApiActionParams } from '../../common/ export { ConnectorTypeId as TeamsConnectorTypeId } from './teams'; export type { ActionParamsType as TeamsActionParams } from './teams'; export { ConnectorTypeId as WebhookConnectorTypeId } from './webhook'; -export type { ActionParamsType as WebhookActionParams } from './webhook'; +export type { ActionParamsType as WebhookActionParams } from './webhook/types'; export { ConnectorTypeId as XmattersConnectorTypeId } from './xmatters'; export type { ActionParamsType as XmattersActionParams } from './xmatters'; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/jira/schema.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/jira/schema.test.ts index 6ae20ef3cd5d8..2cba126465302 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/jira/schema.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/jira/schema.test.ts @@ -55,7 +55,7 @@ describe('Jira schema', () => { otherFields, }, }) - ).toThrow('A maximum of 20 otherFields can be defined at a time.'); + ).toThrow('A maximum of 20 fields in otherFields can be defined at a time.'); }); it.each(incidentSchemaObjectProperties)( diff --git a/x-pack/plugins/stack_connectors/server/connector_types/jira/schema.ts b/x-pack/plugins/stack_connectors/server/connector_types/jira/schema.ts index 519a10ef8576e..aebd5f18c9615 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/jira/schema.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/jira/schema.ts @@ -6,7 +6,9 @@ */ import { schema } from '@kbn/config-schema'; -import { validateOtherFieldsKeys, validateOtherFieldsLength } from './validators'; +import { MAX_OTHER_FIELDS_LENGTH } from '../../../common/jira/constants'; +import { validateRecordMaxKeys } from '../lib/validators'; +import { validateOtherFieldsKeys } from './validators'; export const ExternalIncidentServiceConfiguration = { apiUrl: schema.string(), @@ -49,7 +51,12 @@ const incidentSchemaObject = { }), schema.any(), { - validate: (value) => validateOtherFieldsLength(value), + validate: (value) => + validateRecordMaxKeys({ + record: value, + maxNumberOfFields: MAX_OTHER_FIELDS_LENGTH, + fieldName: 'otherFields', + }), } ) ), diff --git a/x-pack/plugins/stack_connectors/server/connector_types/jira/validators.ts b/x-pack/plugins/stack_connectors/server/connector_types/jira/validators.ts index d59af47da48f5..5daa0a13f34c6 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/jira/validators.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/jira/validators.ts @@ -13,8 +13,8 @@ import { } from './types'; import * as i18n from './translations'; -import { MAX_OTHER_FIELDS_LENGTH } from '../../../common/jira/constants'; import { incidentSchemaObjectProperties } from './schema'; +import { validateKeysAllowed } from '../lib/validators'; export const validateCommonConfig = ( configObject: JiraPublicConfigurationType, @@ -38,18 +38,10 @@ export const validate: ExternalServiceValidation = { secrets: validateCommonSecrets, }; -export const validateOtherFieldsLength = ( - otherFields: Record -): string | undefined => { - if (Object.keys(otherFields).length > MAX_OTHER_FIELDS_LENGTH) { - return i18n.OTHER_FIELDS_LENGTH_ERROR(MAX_OTHER_FIELDS_LENGTH); - } -}; - export const validateOtherFieldsKeys = (key: string): string | undefined => { - const propertiesSet = new Set(incidentSchemaObjectProperties); - - if (propertiesSet.has(key)) { - return i18n.OTHER_FIELDS_PROPERTY_ERROR(key); - } + return validateKeysAllowed({ + key, + disallowList: incidentSchemaObjectProperties, + fieldName: 'otherFields', + }); }; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/nullable.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/nullable.ts deleted file mode 100644 index af95e728465da..0000000000000 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/nullable.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { schema, Type } from '@kbn/config-schema'; - -// TODO: remove once this is merged: https://github.com/elastic/kibana/pull/41728 - -export function nullableType(type: Type) { - return schema.oneOf([type, schema.literal(null)], { defaultValue: () => null }); -} diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/api.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/api.test.ts index fefe78df73574..ef4953189fee6 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/api.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/api.test.ts @@ -99,6 +99,7 @@ describe('api', () => { correlation_display: 'Alerting', correlation_id: 'ruleId', opened_by: 'elastic', + additional_fields: {}, }, }); expect(externalService.updateIncident).not.toHaveBeenCalled(); @@ -114,6 +115,7 @@ describe('api', () => { logger: mockedLogger, commentFieldKey: 'comments', }); + expect(externalService.updateIncident).toHaveBeenCalledTimes(2); expect(externalService.updateIncident).toHaveBeenNthCalledWith(1, { incident: { @@ -127,6 +129,7 @@ describe('api', () => { short_description: 'Incident title', correlation_display: 'Alerting', correlation_id: 'ruleId', + additional_fields: {}, }, incidentId: 'incident-1', }); @@ -143,6 +146,7 @@ describe('api', () => { short_description: 'Incident title', correlation_display: 'Alerting', correlation_id: 'ruleId', + additional_fields: {}, }, incidentId: 'incident-1', }); @@ -171,6 +175,7 @@ describe('api', () => { short_description: 'Incident title', correlation_display: 'Alerting', correlation_id: 'ruleId', + additional_fields: {}, }, incidentId: 'incident-1', }); @@ -187,6 +192,7 @@ describe('api', () => { short_description: 'Incident title', correlation_display: 'Alerting', correlation_id: 'ruleId', + additional_fields: {}, }, incidentId: 'incident-1', }); @@ -264,6 +270,7 @@ describe('api', () => { short_description: 'Incident title', correlation_display: 'Alerting', correlation_id: 'ruleId', + additional_fields: {}, }, }); expect(externalService.createIncident).not.toHaveBeenCalled(); @@ -291,6 +298,7 @@ describe('api', () => { short_description: 'Incident title', correlation_display: 'Alerting', correlation_id: 'ruleId', + additional_fields: {}, }, incidentId: 'incident-3', }); @@ -307,6 +315,7 @@ describe('api', () => { short_description: 'Incident title', correlation_display: 'Alerting', correlation_id: 'ruleId', + additional_fields: {}, }, incidentId: 'incident-2', }); @@ -334,6 +343,7 @@ describe('api', () => { short_description: 'Incident title', correlation_display: 'Alerting', correlation_id: 'ruleId', + additional_fields: {}, }, incidentId: 'incident-3', }); @@ -350,6 +360,7 @@ describe('api', () => { short_description: 'Incident title', correlation_display: 'Alerting', correlation_id: 'ruleId', + additional_fields: {}, }, incidentId: 'incident-2', }); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/mocks.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/mocks.ts index 410a5f58ab00b..8e81892ff5098 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/mocks.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/mocks.ts @@ -205,6 +205,7 @@ export const executorParams: ExecutorSubActionPushParams = { subcategory: 'os', correlation_id: 'ruleId', correlation_display: 'Alerting', + additional_fields: {}, }, comments: [ { @@ -232,6 +233,7 @@ export const sirParams: PushToServiceApiParamsSIR = { correlation_id: 'ruleId', correlation_display: 'Alerting', priority: '1', + additional_fields: {}, }, comments: [ { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/schema.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/schema.ts index 568d9b01e67e6..e5a13956a0eb4 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/schema.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/schema.ts @@ -6,7 +6,10 @@ */ import { schema } from '@kbn/config-schema'; +import { MAX_ADDITIONAL_FIELDS_LENGTH } from '../../../../common/servicenow/constants'; +import { validateRecordMaxKeys } from '../validators'; import { DEFAULT_ALERTS_GROUPING_KEY } from './config'; +import { validateOtherFieldsKeys } from './validators'; export const ExternalIncidentServiceConfigurationBase = { apiUrl: schema.string(), @@ -58,8 +61,26 @@ const CommonAttributes = { subcategory: schema.nullable(schema.string()), correlation_id: schema.nullable(schema.string({ defaultValue: DEFAULT_ALERTS_GROUPING_KEY })), correlation_display: schema.nullable(schema.string()), + additional_fields: schema.nullable( + schema.recordOf( + schema.string({ + validate: (value) => validateOtherFieldsKeys(value), + }), + schema.any(), + { + validate: (value) => + validateRecordMaxKeys({ + record: value, + maxNumberOfFields: MAX_ADDITIONAL_FIELDS_LENGTH, + fieldName: 'additional_fields', + }), + } + ) + ), }; +export const commonIncidentSchemaObjectProperties = Object.keys(CommonAttributes); + // Schema for ServiceNow Incident Management (ITSM) export const ExecutorSubActionPushParamsSchemaITSM = schema.object({ incident: schema.object({ diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.test.ts index fcdd0f31b6ec6..ce6f33e1cc0d1 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.test.ts @@ -101,7 +101,10 @@ const mockCorrelationIdIncidentResponse = () => }, })); -const createIncident = async (service: ExternalService) => { +const createIncident = async ( + service: ExternalService, + incident?: Partial +) => { // Get application version mockApplicationVersion(); // Import set api response @@ -110,11 +113,18 @@ const createIncident = async (service: ExternalService) => { mockIncidentResponse(false); return await service.createIncident({ - incident: { short_description: 'title', description: 'desc' } as ServiceNowITSMIncident, + incident: { + short_description: 'title', + description: 'desc', + ...incident, + } as ServiceNowITSMIncident, }); }; -const updateIncident = async (service: ExternalService) => { +const updateIncident = async ( + service: ExternalService, + incident?: Partial +) => { // Get application version mockApplicationVersion(); // Import set api response @@ -124,7 +134,11 @@ const updateIncident = async (service: ExternalService) => { return await service.updateIncident({ incidentId: '1', - incident: { short_description: 'title', description: 'desc' } as ServiceNowITSMIncident, + incident: { + short_description: 'title', + description: 'desc', + ...incident, + } as ServiceNowITSMIncident, }); }; @@ -682,6 +696,19 @@ describe('ServiceNow service', () => { '[Action][ServiceNow]: Unable to create incident. Error: An error has occurred while importing the incident Reason: unknown' ); }); + + test('it should create an incident with additional fields correctly without prefixing them with u_', async () => { + await createIncident(service, { additional_fields: { foo: 'test' } }); + + expect(requestMock).toHaveBeenNthCalledWith(2, { + axios, + logger, + configurationUtilities, + url: 'https://example.com/api/now/import/x_elas2_inc_int_elastic_incident', + method: 'post', + data: { u_short_description: 'title', u_description: 'desc', foo: 'test' }, + }); + }); }); // old connectors @@ -755,6 +782,18 @@ describe('ServiceNow service', () => { expect(res.url).toEqual('https://example.com/nav_to.do?uri=sn_si_incident.do?sys_id=1'); }); + + test('it should throw if tries to update an incident with additional_fields', async () => { + await expect( + service.createIncident({ + incident: { + additional_fields: {}, + } as ServiceNowITSMIncident, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"[Action][ServiceNow]: Unable to create incident. Error: ServiceNow additional fields are not supported for deprecated connectors. Reason: unknown: errorResponse was null"` + ); + }); }); }); @@ -860,6 +899,24 @@ describe('ServiceNow service', () => { '[Action][ServiceNow]: Unable to update incident with id 1. Error: An error has occurred while importing the incident Reason: unknown' ); }); + + test('it should update an incident with additional fields correctly without prefixing them with u_', async () => { + await updateIncident(service, { additional_fields: { foo: 'test' } }); + + expect(requestMock).toHaveBeenNthCalledWith(2, { + axios, + logger, + configurationUtilities, + url: 'https://example.com/api/now/import/x_elas2_inc_int_elastic_incident', + method: 'post', + data: { + u_short_description: 'title', + u_description: 'desc', + elastic_incident_id: '1', + foo: 'test', + }, + }); + }); }); // old connectors @@ -935,6 +992,19 @@ describe('ServiceNow service', () => { expect(res.url).toEqual('https://example.com/nav_to.do?uri=sn_si_incident.do?sys_id=1'); }); + + test('it should throw if tries to update an incident with additional_fields', async () => { + await expect( + service.updateIncident({ + incidentId: '1', + incident: { + additional_fields: {}, + } as ServiceNowITSMIncident, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"[Action][ServiceNow]: Unable to update incident with id 1. Error: ServiceNow additional fields are not supported for deprecated connectors. Reason: unknown: errorResponse was null"` + ); + }); }); }); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.ts index 906c47c962d82..42aed9dcf8466 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/service.ts @@ -22,7 +22,12 @@ import { import * as i18n from './translations'; import { ServiceNowPublicConfigurationType, ServiceNowSecretConfigurationType } from './types'; -import { createServiceError, getPushedDate, prepareIncident } from './utils'; +import { + createServiceError, + getPushedDate, + prepareIncident, + throwIfAdditionalFieldsNotSupported, +} from './utils'; export const SYS_DICTIONARY_ENDPOINT = `api/now/table/sys_dictionary`; @@ -186,6 +191,7 @@ export const createExternalService: ServiceFactory = ({ const createIncident = async ({ incident }: ExternalServiceParamsCreate) => { try { + throwIfAdditionalFieldsNotSupported(useTableApi, incident); await checkIfApplicationIsInstalled(); const res = await request({ @@ -219,6 +225,7 @@ export const createExternalService: ServiceFactory = ({ const updateIncident = async ({ incidentId, incident }: ExternalServiceParamsUpdate) => { try { + throwIfAdditionalFieldsNotSupported(useTableApi, incident); await checkIfApplicationIsInstalled(); const res = await request({ diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/utils.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/utils.test.ts index 8bc9fa0565d8f..5b6bc9864a20f 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/utils.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/utils.test.ts @@ -15,6 +15,7 @@ import { getPushedDate, throwIfSubActionIsNotSupported, getAxiosInstance, + throwIfAdditionalFieldsNotSupported, } from './utils'; import type { ResponseError } from './types'; import { connectorTokenClientMock } from '@kbn/actions-plugin/server/lib/connector_token_client.mock'; @@ -62,6 +63,46 @@ describe('utils', () => { const newIncident = prepareIncident(true, incident); expect(newIncident).toEqual(incident); }); + + test('does not prefix additional fields with u_', async () => { + const incident = { + short_description: 'title', + description: 'desc', + additional_fields: { foo: 'test' }, + }; + + const newIncident = prepareIncident(false, incident); + expect(newIncident).toEqual({ + u_short_description: 'title', + u_description: 'desc', + foo: 'test', + }); + }); + + test('strips out additional fields if it is a deprecated connector', async () => { + const incident = { + short_description: 'title', + description: 'desc', + additional_fields: { foo: 'test' }, + }; + + const newIncident = prepareIncident(true, incident); + expect(newIncident).toEqual({ short_description: 'title', description: 'desc' }); + }); + + test('does not overrides base fields', async () => { + const incident = { + short_description: 'title', + description: 'desc', + additional_fields: { u_short_description: 'foo' }, + }; + + const newIncident = prepareIncident(false, incident); + expect(newIncident).toEqual({ + u_short_description: 'title', + u_description: 'desc', + }); + }); }); describe('createServiceError', () => { @@ -417,4 +458,28 @@ describe('utils', () => { expect(connectorTokenClient.deleteConnectorTokens).not.toHaveBeenCalled(); }); }); + + describe('throwIfAdditionalFieldsNotSupported', () => { + it('throws if the connector is deprecated and it sets additional_fields', async () => { + expect.assertions(1); + + expect(() => throwIfAdditionalFieldsNotSupported(true, { additional_fields: {} })).toThrow( + 'ServiceNow additional fields are not supported for deprecated connectors.' + ); + }); + + it('does not throw if the connector is deprecated and it does not set additional_fields', async () => { + expect(() => throwIfAdditionalFieldsNotSupported(true, {})).not.toThrow(); + }); + + it('does not throw if the connector is not and it set additional_fields', async () => { + expect(() => + throwIfAdditionalFieldsNotSupported(false, { additional_fields: {} }) + ).not.toThrow(); + }); + + it('does not throw if the connector is not and it does not set additional_fields', async () => { + expect(() => throwIfAdditionalFieldsNotSupported(false, {})).not.toThrow(); + }); + }); }); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/utils.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/utils.ts index 8998fa9f94c63..ff0755f8d7499 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/utils.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/utils.ts @@ -24,13 +24,23 @@ import { import { FIELD_PREFIX } from './config'; import * as i18n from './translations'; -export const prepareIncident = (useOldApi: boolean, incident: PartialIncident): PartialIncident => - useOldApi - ? incident - : Object.entries(incident).reduce( - (acc, [key, value]) => ({ ...acc, [`${FIELD_PREFIX}${key}`]: value }), - {} as Incident - ); +export const prepareIncident = ( + useOldApi: boolean, + incident: PartialIncident +): Record => { + const { additional_fields: additionalFields, ...restIncidentFields } = incident; + + if (useOldApi) { + return restIncidentFields; + } + + const baseFields = Object.entries(restIncidentFields).reduce>( + (acc, [key, value]) => ({ ...acc, [`${FIELD_PREFIX}${key}`]: value }), + {} + ); + + return { ...additionalFields, ...baseFields }; +}; const createErrorMessage = (errorResponse?: ServiceNowError): string => { if (errorResponse == null) { @@ -91,6 +101,18 @@ export const throwIfSubActionIsNotSupported = ({ } }; +export const throwIfAdditionalFieldsNotSupported = ( + useOldApi: boolean, + incident: PartialIncident +) => { + if (useOldApi && incident.additional_fields) { + throw new AxiosError( + 'ServiceNow additional fields are not supported for deprecated connectors.', + '400' + ); + } +}; + export interface GetAxiosInstanceOpts { connectorId: string; logger: Logger; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/validators.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/validators.test.ts index 598f5ba576d0f..1174081f1adeb 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/validators.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/validators.test.ts @@ -5,7 +5,12 @@ * 2.0. */ -import { validateCommonConfig, validateCommonSecrets, validateCommonConnector } from './validators'; +import { + validateCommonConfig, + validateCommonSecrets, + validateCommonConnector, + validateOtherFieldsKeys, +} from './validators'; import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; const configurationUtilities = actionsConfigMock.create(); @@ -430,4 +435,12 @@ describe('validateCommonConnector', () => { ); }); }); + + describe('validateOtherFieldsKeys', () => { + it('returns an error if the keys are not allowed', () => { + expect(validateOtherFieldsKeys('short_description')).toEqual( + 'The following properties cannot be defined inside additional_fields: short_description.' + ); + }); + }); }); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/validators.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/validators.ts index 1ff9d09ad54ee..e30715c8bce93 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/validators.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/servicenow/validators.ts @@ -13,6 +13,8 @@ import { } from './types'; import * as i18n from './translations'; +import { validateKeysAllowed } from '../validators'; +import { commonIncidentSchemaObjectProperties } from './schema'; export const validateCommonConfig = ( config: ServiceNowPublicConfigurationType, @@ -120,3 +122,11 @@ export const validate: ExternalServiceValidation = { secrets: validateCommonSecrets, connector: validateCommonConnector, }; + +export const validateOtherFieldsKeys = (key: string): string | undefined => { + return validateKeysAllowed({ + key, + disallowList: commonIncidentSchemaObjectProperties, + fieldName: 'additional_fields', + }); +}; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/validators.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/validators.test.ts new file mode 100644 index 0000000000000..3397c24bee8b5 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/validators.test.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { validateKeysAllowed, validateRecordMaxKeys } from './validators'; + +describe('validators', () => { + describe('validateRecordMaxKeys', () => { + it('returns undefined if the keys of the record are less than the maximum', () => { + expect( + validateRecordMaxKeys({ + record: { foo: 'bar' }, + maxNumberOfFields: 2, + fieldName: 'myFieldName', + }) + ).toBeUndefined(); + }); + + it('returns an error if the keys of the record are greater than the maximum', () => { + expect( + validateRecordMaxKeys({ + record: { foo: 'bar', bar: 'test', test: 'foo' }, + maxNumberOfFields: 2, + fieldName: 'myFieldName', + }) + ).toEqual('A maximum of 2 fields in myFieldName can be defined at a time.'); + }); + }); + + describe('validateKeysAllowed', () => { + it('returns undefined if the keys are allowed', () => { + expect( + validateKeysAllowed({ + key: 'foo', + disallowList: ['bar'], + fieldName: 'myFieldName', + }) + ).toBeUndefined(); + }); + + it('returns an error if the keys are not allowed', () => { + expect( + validateKeysAllowed({ + key: 'foo', + disallowList: ['foo'], + fieldName: 'myFieldName', + }) + ).toEqual('The following properties cannot be defined inside myFieldName: foo.'); + }); + }); +}); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/lib/validators.ts b/x-pack/plugins/stack_connectors/server/connector_types/lib/validators.ts new file mode 100644 index 0000000000000..262487d857c26 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/lib/validators.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const FIELDS_MAX_LENGTH_ERROR = (length: number, fieldName: string) => + i18n.translate('xpack.stackConnectors.schema.otherFieldsLengthError', { + values: { length, fieldName }, + defaultMessage: + 'A maximum of {length, plural, =1 {{length} field} other {{length} fields}} in {fieldName} can be defined at a time.', + }); + +export const FIELDS_KEY_NOT_ALLOWED_ERROR = (properties: string, fieldName: string) => + i18n.translate('xpack.stackConnectors.schema.otherFieldsPropertyError', { + values: { properties, fieldName }, + defaultMessage: 'The following properties cannot be defined inside {fieldName}: {properties}.', + }); + +export const validateRecordMaxKeys = ({ + record, + maxNumberOfFields, + fieldName, +}: { + record: Record; + maxNumberOfFields: number; + fieldName: string; +}): string | undefined => { + if (Object.keys(record).length > maxNumberOfFields) { + return FIELDS_MAX_LENGTH_ERROR(maxNumberOfFields, fieldName); + } +}; + +export const validateKeysAllowed = ({ + key, + disallowList, + fieldName, +}: { + key: string; + disallowList: string[]; + fieldName: string; +}): string | undefined => { + const propertiesSet = new Set(disallowList); + + if (propertiesSet.has(key)) { + return FIELDS_KEY_NOT_ALLOWED_ERROR(key, fieldName); + } +}; diff --git a/x-pack/plugins/stack_connectors/server/connector_types/webhook/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/webhook/index.test.ts index 9e82b2d2c31e7..6c51fe11e97de 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/webhook/index.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/webhook/index.test.ts @@ -12,18 +12,14 @@ import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/action import { Logger } from '@kbn/core/server'; import { actionsMock } from '@kbn/actions-plugin/server/mocks'; import axios from 'axios'; -import { - ConnectorTypeConfigType, - ConnectorTypeSecretsType, - getConnectorType, - WebhookConnectorType, - WebhookMethods, -} from '.'; +import { ConnectorTypeConfigType, ConnectorTypeSecretsType, WebhookConnectorType } from './types'; + +import { getConnectorType } from '.'; import * as utils from '@kbn/actions-plugin/server/lib/axios_utils'; import { loggerMock } from '@kbn/logging-mocks'; -import { SSLCertType, WebhookAuthType } from '../../../common/webhook/constants'; -import { PFX_FILE, CRT_FILE, KEY_FILE } from './mocks'; +import { AuthType, SSLCertType, WebhookMethods } from '../../../common/auth/constants'; +import { PFX_FILE, CRT_FILE, KEY_FILE } from '../../../common/auth/mocks'; jest.mock('axios'); jest.mock('@kbn/actions-plugin/server/lib/axios_utils', () => { @@ -157,7 +153,7 @@ describe('config validation', () => { test('config validation passes when only required fields are provided', () => { const config: Record = { url: 'http://mylisteningserver:9200/endpoint', - authType: WebhookAuthType.Basic, + authType: AuthType.Basic, hasAuth: true, }; expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({ @@ -171,7 +167,7 @@ describe('config validation', () => { const config: Record = { url: 'http://mylisteningserver:9200/endpoint', method, - authType: WebhookAuthType.Basic, + authType: AuthType.Basic, hasAuth: true, }; expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({ @@ -198,7 +194,7 @@ describe('config validation', () => { test('config validation passes when a url is specified', () => { const config: Record = { url: 'http://mylisteningserver:9200/endpoint', - authType: WebhookAuthType.Basic, + authType: AuthType.Basic, hasAuth: true, }; expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({ @@ -226,7 +222,7 @@ describe('config validation', () => { headers: { 'Content-Type': 'application/json', }, - authType: WebhookAuthType.Basic, + authType: AuthType.Basic, hasAuth: true, }; expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({ @@ -257,7 +253,7 @@ describe('config validation', () => { headers: { 'Content-Type': 'application/json', }, - authType: WebhookAuthType.Basic, + authType: AuthType.Basic, hasAuth: true, }; @@ -332,7 +328,7 @@ describe('execute()', () => { headers: { aheader: 'a value', }, - authType: WebhookAuthType.Basic, + authType: AuthType.Basic, hasAuth: true, }; await connectorType.executor({ @@ -392,7 +388,7 @@ describe('execute()', () => { headers: { aheader: 'a value', }, - authType: WebhookAuthType.SSL, + authType: AuthType.SSL, certType: SSLCertType.CRT, hasAuth: true, }; @@ -575,7 +571,7 @@ describe('execute()', () => { headers: { aheader: 'a value', }, - authType: WebhookAuthType.Basic, + authType: AuthType.Basic, hasAuth: true, }; requestMock.mockReset(); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/webhook/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/webhook/index.ts index c5314f8e5f7ad..78f02d24b9b87 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/webhook/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/webhook/index.ts @@ -6,18 +6,16 @@ */ import { i18n } from '@kbn/i18n'; -import { isString } from 'lodash'; import axios, { AxiosError, AxiosResponse } from 'axios'; -import { schema, TypeOf } from '@kbn/config-schema'; import { Logger } from '@kbn/core/server'; import { pipe } from 'fp-ts/lib/pipeable'; import { map, getOrElse } from 'fp-ts/lib/Option'; + import type { - ActionType as ConnectorType, - ActionTypeExecutorOptions as ConnectorTypeExecutorOptions, ActionTypeExecutorResult as ConnectorTypeExecutorResult, ValidatorServices, } from '@kbn/actions-plugin/server/types'; + import { request } from '@kbn/actions-plugin/server/lib/axios_utils'; import { AlertingConnectorFeatureId, @@ -26,90 +24,23 @@ import { } from '@kbn/actions-plugin/common/types'; import { renderMustacheString } from '@kbn/actions-plugin/server/lib/mustache_renderer'; import { combineHeadersWithBasicAuthHeader } from '@kbn/actions-plugin/server/lib'; -import { SSLCertType, WebhookAuthType } from '../../../common/webhook/constants'; -import { getRetryAfterIntervalFromHeaders } from '../lib/http_response_retry_header'; -import { nullableType } from '../lib/nullable'; -import { isOk, promiseResult, Result } from '../lib/result_type'; - -// config definition -export enum WebhookMethods { - POST = 'post', - PUT = 'put', -} -export type WebhookConnectorType = ConnectorType< - ConnectorTypeConfigType, - ConnectorTypeSecretsType, +import type { + WebhookConnectorType, ActionParamsType, - unknown ->; -export type WebhookConnectorTypeExecutorOptions = ConnectorTypeExecutorOptions< ConnectorTypeConfigType, + WebhookConnectorTypeExecutorOptions, ConnectorTypeSecretsType, - ActionParamsType ->; +} from './types'; -const HeadersSchema = schema.recordOf(schema.string(), schema.string()); -const configSchemaProps = { - url: schema.string(), - method: schema.oneOf([schema.literal(WebhookMethods.POST), schema.literal(WebhookMethods.PUT)], { - defaultValue: WebhookMethods.POST, - }), - headers: nullableType(HeadersSchema), - hasAuth: schema.boolean({ defaultValue: true }), - authType: schema.maybe( - schema.oneOf( - [ - schema.literal(WebhookAuthType.Basic), - schema.literal(WebhookAuthType.SSL), - schema.literal(null), - ], - { - defaultValue: WebhookAuthType.Basic, - } - ) - ), - certType: schema.maybe( - schema.oneOf([schema.literal(SSLCertType.CRT), schema.literal(SSLCertType.PFX)]) - ), - ca: schema.maybe(schema.string()), - verificationMode: schema.maybe( - schema.oneOf([schema.literal('none'), schema.literal('certificate'), schema.literal('full')]) - ), -}; -const ConfigSchema = schema.object(configSchemaProps); -export type ConnectorTypeConfigType = TypeOf; - -// secrets definition -export type ConnectorTypeSecretsType = TypeOf; -const secretSchemaProps = { - user: schema.nullable(schema.string()), - password: schema.nullable(schema.string()), - crt: schema.nullable(schema.string()), - key: schema.nullable(schema.string()), - pfx: schema.nullable(schema.string()), -}; -const SecretsSchema = schema.object(secretSchemaProps, { - validate: (secrets) => { - // user and password must be set together (or not at all) - if (!secrets.password && !secrets.user && !secrets.crt && !secrets.key && !secrets.pfx) return; - if (secrets.password && secrets.user && !secrets.crt && !secrets.key && !secrets.pfx) return; - if (secrets.crt && secrets.key && !secrets.user && !secrets.pfx) return; - if (!secrets.crt && !secrets.key && !secrets.user && secrets.pfx) return; - return i18n.translate('xpack.stackConnectors.webhook.invalidUsernamePassword', { - defaultMessage: - 'must specify one of the following schemas: user and password; crt and key (with optional password); or pfx (with optional password)', - }); - }, -}); - -// params definition -export type ActionParamsType = TypeOf; -export const ParamsSchema = schema.object({ - body: schema.maybe(schema.string()), -}); +import { getRetryAfterIntervalFromHeaders } from '../lib/http_response_retry_header'; +import { isOk, promiseResult, Result } from '../lib/result_type'; +import { ConfigSchema, ParamsSchema } from './schema'; +import { buildConnectorAuth } from '../../../common/auth/utils'; +import { SecretConfigurationSchema } from '../../../common/auth/schema'; export const ConnectorTypeId = '.webhook'; + // connector type definition export function getConnectorType(): WebhookConnectorType { return { @@ -129,7 +60,7 @@ export function getConnectorType(): WebhookConnectorType { customValidator: validateConnectorTypeConfig, }, secrets: { - schema: SecretsSchema, + schema: SecretConfigurationSchema, }, params: { schema: ParamsSchema, @@ -202,39 +133,16 @@ export async function executor( const { body: data } = params; const secrets: ConnectorTypeSecretsType = execOptions.secrets; - // For backwards compatibility with connectors created before authType was added, interpret a - // hasAuth: true and undefined authType as basic auth - const basicAuth = - hasAuth && - (authType === WebhookAuthType.Basic || !authType) && - isString(secrets.user) && - isString(secrets.password) - ? { auth: { username: secrets.user, password: secrets.password } } - : {}; - - const sslCertificate = - authType === WebhookAuthType.SSL && - ((isString(secrets.crt) && isString(secrets.key)) || isString(secrets.pfx)) - ? isString(secrets.pfx) - ? { - pfx: Buffer.from(secrets.pfx, 'base64'), - ...(isString(secrets.password) ? { passphrase: secrets.password } : {}), - } - : { - cert: Buffer.from(secrets.crt!, 'base64'), - key: Buffer.from(secrets.key!, 'base64'), - ...(isString(secrets.password) ? { passphrase: secrets.password } : {}), - } - : {}; + const { basicAuth, sslOverrides } = buildConnectorAuth({ + hasAuth, + authType, + secrets, + verificationMode, + ca, + }); const axiosInstance = axios.create(); - const sslOverrides = { - ...sslCertificate, - ...(verificationMode ? { verificationMode } : {}), - ...(ca ? { ca: Buffer.from(ca, 'base64') } : {}), - }; - const headersWithBasicAuth = combineHeadersWithBasicAuthHeader({ username: basicAuth.auth?.username, password: basicAuth.auth?.password, diff --git a/x-pack/plugins/stack_connectors/server/connector_types/webhook/schema.ts b/x-pack/plugins/stack_connectors/server/connector_types/webhook/schema.ts new file mode 100644 index 0000000000000..b6eca5de787f3 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/webhook/schema.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 { schema } from '@kbn/config-schema'; +import { AuthConfiguration } from '../../../common/auth/schema'; +import { WebhookMethods } from '../../../common/auth/constants'; + +export const HeadersSchema = schema.recordOf(schema.string(), schema.string()); + +const configSchemaProps = { + url: schema.string(), + method: schema.oneOf([schema.literal(WebhookMethods.POST), schema.literal(WebhookMethods.PUT)], { + defaultValue: WebhookMethods.POST, + }), + headers: schema.nullable(HeadersSchema), + hasAuth: AuthConfiguration.hasAuth, + authType: AuthConfiguration.authType, + certType: AuthConfiguration.certType, + ca: AuthConfiguration.ca, + verificationMode: AuthConfiguration.verificationMode, +}; + +export const ConfigSchema = schema.object(configSchemaProps); + +// params definition +export const ParamsSchema = schema.object({ + body: schema.maybe(schema.string()), +}); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/webhook/types.ts b/x-pack/plugins/stack_connectors/server/connector_types/webhook/types.ts new file mode 100644 index 0000000000000..4857a88f2a042 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/webhook/types.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { TypeOf } from '@kbn/config-schema'; +import type { + ActionType as ConnectorType, + ActionTypeExecutorOptions as ConnectorTypeExecutorOptions, +} from '@kbn/actions-plugin/server/types'; +import { ParamsSchema, ConfigSchema } from './schema'; +import { SecretConfigurationSchema } from '../../../common/auth/schema'; + +export type WebhookConnectorType = ConnectorType< + ConnectorTypeConfigType, + ConnectorTypeSecretsType, + ActionParamsType, + unknown +>; +export type WebhookConnectorTypeExecutorOptions = ConnectorTypeExecutorOptions< + ConnectorTypeConfigType, + ConnectorTypeSecretsType, + ActionParamsType +>; + +export type ConnectorTypeConfigType = TypeOf; + +// secrets definition +export type ConnectorTypeSecretsType = TypeOf; + +// params definition +export type ActionParamsType = TypeOf; diff --git a/x-pack/plugins/stack_connectors/server/index.ts b/x-pack/plugins/stack_connectors/server/index.ts index 0069036c4ea83..38fc3b3297c58 100644 --- a/x-pack/plugins/stack_connectors/server/index.ts +++ b/x-pack/plugins/stack_connectors/server/index.ts @@ -10,7 +10,7 @@ import { SlackApiParamsSchema } from '../common/slack_api/schema'; export { ParamsSchema as SlackParamsSchema } from './connector_types/slack'; export { ParamsSchema as EmailParamsSchema } from './connector_types/email'; -export { ParamsSchema as WebhookParamsSchema } from './connector_types/webhook'; +export { ParamsSchema as WebhookParamsSchema } from './connector_types/webhook/schema'; export { ExecutorParamsSchema as JiraParamsSchema } from './connector_types/jira/schema'; export { ParamsSchema as PagerdutyParamsSchema } from './connector_types/pagerduty'; diff --git a/x-pack/plugins/stack_connectors/tsconfig.json b/x-pack/plugins/stack_connectors/tsconfig.json index 045d5e54b461b..7a6898a5ce829 100644 --- a/x-pack/plugins/stack_connectors/tsconfig.json +++ b/x-pack/plugins/stack_connectors/tsconfig.json @@ -40,6 +40,7 @@ "@kbn/cases-components", "@kbn/code-editor-mock", "@kbn/utility-types", + "@kbn/alerting-types", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/task_manager/server/config.test.ts b/x-pack/plugins/task_manager/server/config.test.ts index e30fb170910f9..bb59a73a305d6 100644 --- a/x-pack/plugins/task_manager/server/config.test.ts +++ b/x-pack/plugins/task_manager/server/config.test.ts @@ -242,12 +242,7 @@ describe('config validation', () => { }).not.toThrowError(); }); - test('the claim strategy is validated', () => { - const config = { claim_strategy: 'invalid-strategy' }; - expect(() => { - configSchema.validate(config); - }).toThrowErrorMatchingInlineSnapshot( - `"The claim strategy is invalid: Unknown task claiming strategy (invalid-strategy)"` - ); + test('any claim strategy is valid', () => { + configSchema.validate({ claim_strategy: 'anything!' }); }); }); diff --git a/x-pack/plugins/task_manager/server/config.ts b/x-pack/plugins/task_manager/server/config.ts index 54783018eed1f..eec63c5be489c 100644 --- a/x-pack/plugins/task_manager/server/config.ts +++ b/x-pack/plugins/task_manager/server/config.ts @@ -6,7 +6,6 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; -import { getTaskClaimer } from './task_claimers'; export const MAX_WORKERS_LIMIT = 100; export const DEFAULT_MAX_WORKERS = 10; @@ -27,6 +26,7 @@ export const DEFAULT_METRICS_RESET_INTERVAL = 30 * 1000; // 30 seconds export const DEFAULT_WORKER_UTILIZATION_RUNNING_AVERAGE_WINDOW = 5; export const CLAIM_STRATEGY_DEFAULT = 'default'; +export const CLAIM_STRATEGY_MGET = 'unsafe_mget'; export const taskExecutionFailureThresholdSchema = schema.object( { @@ -165,11 +165,6 @@ export const configSchema = schema.object( ) { return `The specified monitored_stats_required_freshness (${config.monitored_stats_required_freshness}) is invalid, as it is below the poll_interval (${config.poll_interval})`; } - try { - getTaskClaimer(config.claim_strategy); - } catch (err) { - return `The claim strategy is invalid: ${err.message}`; - } }, } ); diff --git a/x-pack/plugins/task_manager/server/manual_tests/get_rule_run_event_logs.js b/x-pack/plugins/task_manager/server/manual_tests/get_rule_run_event_logs.js new file mode 100644 index 0000000000000..5b1875b208cc4 --- /dev/null +++ b/x-pack/plugins/task_manager/server/manual_tests/get_rule_run_event_logs.js @@ -0,0 +1,232 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +const DOCS_TO_FETCH = 10000; + +// Get the event logs from multiple clusters, focusing on rule runs +// as they test recurring activity easily, and augmenting with other +// bits, producing a single .ndjson file for all clusters. +main(); + +async function main() { + // get urls and their host names + const urls = process.argv.slice(2); + const urlNoCreds = urls.map((url) => new URL(url)).map((url) => url?.origin || 'unknown'); + const urlHosts = urls + .map((url) => new URL(url)) + .map((url) => url?.host || 'unknown') + .map((url) => url.split('.')[0]); + + if (urls.length === 0) return help(); + + // get the event logs + const docPromises = urls.map(getRuleRunEventDocs); + const docResults = await Promise.allSettled(docPromises); + + /** @type { any[][] } */ + const serverDocs = []; + + // log errors, and add urls to event logs + for (let i = 0; i < urls.length; i++) { + const url = urls[i]; + const docResult = docResults[i]; + if (docResult.status === 'rejected') { + console.error(`Failed to get docs from ${url}: ${docResult.reason}`); + } else { + for (const doc of docResult.value) { + if (!doc.kibana) doc.kibana = {}; + // add/remove some bits - remove to save space + doc.kibana.url = urlNoCreds[i]; + doc.kibana.host = urlHosts[i]; + delete doc.kibana.saved_objects; + delete doc.kibana.space_ids; + + if (!doc.event) doc.event = {}; + if (doc.event.start) doc.event.startMs = new Date(doc.event.start).valueOf(); + if (doc.event.end) doc.event.endMs = new Date(doc.event.end).valueOf(); + if (doc.event.endMs && doc.event.startMs) + doc.event.durationMs = doc.event.endMs - doc.event.startMs; + } + serverDocs.push(docResult.value); + } + } + + // for each server's docs, apply a worker id + for (const docs of serverDocs) { + // sort ascending by timestamp + docs.sort((a, b) => a.event.startMs - b.event.startMs); + + assignWorkerIds(docs); + + for (const doc of docs) { + console.log(JSON.stringify(doc)); + } + } +} + +class Worker { + /** @param { string } id */ + constructor(id) { + this.id = id; + /** @type { number | undefined } */ + this.nextEnd = undefined; + /** @type { number | undefined } */ + this.lastEnd = undefined; + } + + /** @type { (currentDate: number) => void } */ + update(currentDate) { + if (currentDate >= this.nextEnd) { + this.lastEnd = this.nextEnd; + this.nextEnd = undefined; + } + } + + /** @type { () => boolean } */ + isAvailable() { + return this.nextEnd === undefined; + } + + /** @type { (end: number) => void } */ + claimTill(end) { + this.nextEnd = end; + } +} + +class Workers { + constructor() { + /** @type { Map } */ + this.workersByServer = new Map(); + + /** @type { Map } */ + this.serverMap = new Map(); + } + + /** @type { (doc: any) => string } */ + getServerId(doc) { + const { server_uuid: serverUuid } = doc?.kibana || {}; + return this.serverMap.get(serverUuid) || 'unknown'; + } + + /** @type { (doc: any) => Worker } */ + getAvailableWorker(doc) { + const { startMs, endMs } = doc?.event || {}; + const { server_uuid: serverUuid } = doc?.kibana || {}; + if (!this.serverMap.has(serverUuid)) { + this.serverMap.set(serverUuid, `${this.serverMap.size + 1}`); + } + + const workers = this.getWorkersForServer(serverUuid); + + for (const worker of workers) { + worker.update(startMs); + if (worker.isAvailable()) { + worker.claimTill(endMs); + return worker; + } + } + const worker = new Worker(workers.length + 1); + worker.claimTill(endMs); + workers.push(worker); + + return worker; + } + + /** @type { (serverUuid) => Worker[] } */ + getWorkersForServer(serverUuid) { + let workers = this.workersByServer.get(serverUuid); + if (workers !== undefined) return workers; + + workers = []; + this.workersByServer.set(serverUuid, workers); + return workers; + } +} + +/** @type { (docs: any[]) => void } */ +function assignWorkerIds(docs) { + const workers = new Workers(); + for (const doc of docs) { + const worker = workers.getAvailableWorker(doc); + const serverId = workers.getServerId(doc).padStart(3, '0'); + const workerId = `${worker.id}`.padStart(3, '0'); + doc.kibana.worker = `${serverId}-${workerId}`; + doc.event.preIdleMs = worker.lastEnd ? doc.event.startMs - worker.lastEnd : 0; + } +} + +/** @type { (url: string) => Promise} */ +async function getRuleRunEventDocs(url) { + const parsedUrl = new URL(url); + const indices = `.kibana-event-log,.kibana-event-log-ds`; + const options = `expand_wildcards=all&ignore_unavailable=true`; + const searchUrl = `${parsedUrl.origin}/${indices}/_search?${options}`; + const query = getQuery(); + const authHeader = getAuthHeader(parsedUrl.username, parsedUrl.password); + const headers = { + 'Content-Type': 'application/json', + ...(authHeader ? { Authorization: authHeader } : {}), + }; + const fetchResult = await fetch(searchUrl, { + method: 'POST', + headers, + body: JSON.stringify(query), + }); + + if (!fetchResult.ok) { + const text = await fetchResult.text(); + throw new Error(`Failed to fetch from ${searchUrl}: ${fetchResult.statusText}\n${text}`); + } + + const result = await fetchResult.json(); + const sources = result.hits.hits.map((hit) => hit._source); + + return sources; +} + +/** @type { (username: string, password: string) => string | undefined } */ +function getAuthHeader(username, password) { + if (!username || !password) return undefined; + if (username.toUpperCase() === 'APIKEY') return `ApiKey ${password}`; + const encoded = Buffer.from(`${username}:${password}`).toString('base64'); + return `Basic ${encoded}`; +} + +/** @type { (size: number) => any} */ +function getQuery() { + return { + size: DOCS_TO_FETCH, + query: { + bool: { + filter: [ + { term: { 'event.provider': 'alerting' } }, + { term: { 'event.action': 'execute' } }, + ], + }, + }, + sort: [{ '@timestamp': { order: 'desc' } }], + }; +} + +function help() { + console.error(` +usage: [this-command] ... + +Will fetch rule execution event logs from each url, and augment them: +- adds event.startMs - event.start as an epoch number +- adds event.endMs - event.end as an epoch number +- adds event.durationMs - event.end as an epoch number +- adds event.preIdleMs - time worker was idle before this +- adds kibana.url - the URL passed in (which is actually ES) +- adds kibana.host - just the host name from that URL +- adds kibana.worker - worker in form of nodeId-workerId (unique only by url) +- deletes kibana.saved_objects - not needed and confusing +- deletes kibana.space_ids - not needed + +The output is a single .ndjson file with all the docs. +`); +} diff --git a/x-pack/plugins/task_manager/server/metrics/task_metrics_collector.test.ts b/x-pack/plugins/task_manager/server/metrics/task_metrics_collector.test.ts index 964bf10f0e9b2..72e1519573728 100644 --- a/x-pack/plugins/task_manager/server/metrics/task_metrics_collector.test.ts +++ b/x-pack/plugins/task_manager/server/metrics/task_metrics_collector.test.ts @@ -110,6 +110,8 @@ describe('TaskManagerMetricsCollector', () => { logger, pollInterval, store: mockTaskStore, + taskTypes: new Set(['taskType1', 'taskType2', 'taskType3', 'taskType4']), + excludedTypes: new Set(['taskType4', 'taskType5']), }); const handler = jest.fn(); taskManagerMetricsCollector.events.subscribe(handler); @@ -186,6 +188,20 @@ describe('TaskManagerMetricsCollector', () => { }, }, ], + minimum_should_match: 1, + must: [ + { + bool: { + must: [ + { + terms: { + 'task.taskType': ['taskType1', 'taskType2', 'taskType3'], + }, + }, + ], + }, + }, + ], }, }, ], @@ -299,10 +315,13 @@ describe('TaskManagerMetricsCollector', () => { const pollInterval = 100; const halfInterval = Math.floor(pollInterval / 2); + const taskTypes = new Set([]); const taskManagerMetricsCollector = new TaskManagerMetricsCollector({ logger, pollInterval, store: mockTaskStore, + taskTypes, + excludedTypes: taskTypes, }); const handler = jest.fn(); taskManagerMetricsCollector.events.subscribe(handler); @@ -365,10 +384,13 @@ describe('TaskManagerMetricsCollector', () => { const pollInterval = 100; const halfInterval = Math.floor(pollInterval / 2); + const taskTypes = new Set([]); const taskManagerMetricsCollector = new TaskManagerMetricsCollector({ logger, pollInterval, store: mockTaskStore, + taskTypes, + excludedTypes: taskTypes, }); const handler = jest.fn(); taskManagerMetricsCollector.events.subscribe(handler); diff --git a/x-pack/plugins/task_manager/server/metrics/task_metrics_collector.ts b/x-pack/plugins/task_manager/server/metrics/task_metrics_collector.ts index 2d87f789128cd..70f47527807ba 100644 --- a/x-pack/plugins/task_manager/server/metrics/task_metrics_collector.ts +++ b/x-pack/plugins/task_manager/server/metrics/task_metrics_collector.ts @@ -16,6 +16,7 @@ import { TaskStore } from '../task_store'; import { IdleTaskWithExpiredRunAt, RunningOrClaimingTaskWithExpiredRetryAt, + OneOfTaskTypes, } from '../queries/mark_available_tasks_as_claimed'; import { ITaskEventEmitter, TaskLifecycleEvent } from '../polling_lifecycle'; import { asTaskManagerMetricEvent } from '../task_events'; @@ -26,6 +27,8 @@ interface ConstructorOpts { logger: Logger; store: TaskStore; pollInterval?: number; + taskTypes: Set; + excludedTypes: Set; } export interface TaskManagerMetrics { @@ -44,15 +47,20 @@ export class TaskManagerMetricsCollector implements ITaskEventEmitter; + private readonly excludedTypes: Set; + private running: boolean = false; // emit collected metrics private metrics$ = new Subject(); - constructor({ logger, store, pollInterval }: ConstructorOpts) { + constructor({ logger, store, pollInterval, taskTypes, excludedTypes }: ConstructorOpts) { this.store = store; this.logger = logger; this.pollInterval = pollInterval ?? DEFAULT_POLL_INTERVAL; + this.taskTypes = taskTypes; + this.excludedTypes = excludedTypes; this.start(); } @@ -70,6 +78,9 @@ export class TaskManagerMetricsCollector implements ITaskEventEmitter !this.excludedTypes.has(type) + ); try { const results = await this.store.aggregate({ size: 0, @@ -78,7 +89,9 @@ export class TaskManagerMetricsCollector implements ITaskEventEmitter isTaskManagerStatEvent(taskEvent) && taskEvent.id === 'pollingDelay' ), - map(() => new Date().toISOString()) + map(() => new Date().toISOString()), + startWith(new Date().toISOString()) ), // get the average ratio of polled tasks by their persistency taskPollingLifecycle.events.pipe( diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index 56135769c9f5d..c34fd1643c7eb 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -257,6 +257,8 @@ export class TaskManagerPlugin this.taskManagerMetricsCollector = new TaskManagerMetricsCollector({ logger: this.logger, store: taskStore, + taskTypes: new Set(this.definitions.getAllTypes()), + excludedTypes: new Set(this.config.unsafe.exclude_task_types), }); this.taskPollingLifecycle = new TaskPollingLifecycle({ config: this.config!, diff --git a/x-pack/plugins/task_manager/server/polling/task_poller.ts b/x-pack/plugins/task_manager/server/polling/task_poller.ts index 570ecc8686693..64d17fad2f81a 100644 --- a/x-pack/plugins/task_manager/server/polling/task_poller.ts +++ b/x-pack/plugins/task_manager/server/polling/task_poller.ts @@ -22,7 +22,7 @@ interface Opts { logger: Logger; initialPollInterval: number; pollInterval$: Observable; - pollIntervalDelay$: Observable; + pollIntervalDelay$?: Observable; getCapacity: () => number; work: WorkFn; } @@ -99,10 +99,12 @@ export function createTaskPoller({ pollInterval = interval; logger.debug(`Task poller now using interval of ${interval}ms`); }); - pollIntervalDelay$.subscribe((delay) => { - pollIntervalDelay = delay; - logger.debug(`Task poller now delaying emission by ${delay}ms`); - }); + if (pollIntervalDelay$) { + pollIntervalDelay$.subscribe((delay) => { + pollIntervalDelay = delay; + logger.debug(`Task poller now delaying emission by ${delay}ms`); + }); + } hasSubscribed = true; } diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.ts index e21cfaa4f7cec..35fc48423f710 100644 --- a/x-pack/plugins/task_manager/server/polling_lifecycle.ts +++ b/x-pack/plugins/task_manager/server/polling_lifecycle.ts @@ -14,7 +14,7 @@ import type { Logger, ExecutionContextStart } from '@kbn/core/server'; import { Result, asErr, mapErr, asOk, map, mapOk } from './lib/result_type'; import { ManagedConfiguration } from './lib/create_managed_configuration'; -import { TaskManagerConfig } from './config'; +import { TaskManagerConfig, CLAIM_STRATEGY_DEFAULT } from './config'; import { TaskMarkRunning, @@ -154,15 +154,18 @@ export class TaskPollingLifecycle implements ITaskEventEmitter emitEvent(asTaskManagerStatEvent('pollingDelay', asOk(delay))))); + let pollIntervalDelay$: Observable | undefined; + if (claimStrategy === CLAIM_STRATEGY_DEFAULT) { + pollIntervalDelay$ = delayOnClaimConflicts( + maxWorkersConfiguration$, + pollIntervalConfiguration$, + this.events$, + config.version_conflict_threshold, + config.monitored_stats_running_average_window + ).pipe(tap((delay) => emitEvent(asTaskManagerStatEvent('pollingDelay', asOk(delay))))); + } const poller = createTaskPoller({ logger, diff --git a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.test.ts b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.test.ts index 56686ea7d46c1..e884683926b2b 100644 --- a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.test.ts +++ b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.test.ts @@ -14,6 +14,9 @@ import { RunningOrClaimingTaskWithExpiredRetryAt, SortByRunAtAndRetryAt, EnabledTask, + InactiveTasks, + RecognizedTask, + OneOfTaskTypes, } from './mark_available_tasks_as_claimed'; import { TaskTypeDictionary } from '../task_type_dictionary'; @@ -170,6 +173,57 @@ if (doc['task.runAt'].size()!=0) { }); }); + test('generates InactiveTasks clause as expected', () => { + expect(InactiveTasks).toMatchInlineSnapshot(` + Object { + "bool": Object { + "must_not": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "must": Object { + "range": Object { + "task.retryAt": Object { + "gt": "now", + }, + }, + }, + "should": Array [ + Object { + "term": Object { + "task.status": "running", + }, + }, + Object { + "term": Object { + "task.status": "claiming", + }, + }, + ], + }, + }, + ], + }, + } + `); + }); + + test('generates RecognizedTask clause as expected', () => { + expect(RecognizedTask).toMatchInlineSnapshot(` + Object { + "bool": Object { + "must_not": Array [ + Object { + "term": Object { + "task.status": "unrecognized", + }, + }, + ], + }, + } + `); + }); + describe(`script`, () => { test('it marks the update as a noop if the type is skipped', async () => { const taskManagerId = '3478fg6-82374f6-83467gf5-384g6f'; @@ -193,4 +247,23 @@ if (doc['task.runAt'].size()!=0) { ).toMatch(/ctx.op = "noop"/); }); }); + + test('generates OneOfTaskTypes clause as expected', () => { + expect(OneOfTaskTypes('field-name', ['type-a', 'type-b'])).toMatchInlineSnapshot(` + Object { + "bool": Object { + "must": Array [ + Object { + "terms": Object { + "field-name": Array [ + "type-a", + "type-b", + ], + }, + }, + ], + }, + } + `); + }); }); diff --git a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts index d2137f6c82b08..0c241aeef14b8 100644 --- a/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts +++ b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts @@ -5,6 +5,8 @@ * 2.0. */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { TaskTypeDictionary } from '../task_type_dictionary'; +import { TaskStatus, TaskPriority } from '../task'; import { ScriptBasedSortClause, ScriptClause, @@ -65,6 +67,8 @@ export const InactiveTasks: MustNotCondition = { { bool: { should: [{ term: { 'task.status': 'running' } }, { term: { 'task.status': 'claiming' } }], + // needed since default value is 0 when there is a `must` in the `bool` + minimum_should_match: 1, must: { range: { 'task.retryAt': { gt: 'now' } } }, }, }, @@ -84,6 +88,18 @@ export const EnabledTask: MustCondition = { }, }; +export const RecognizedTask: MustNotCondition = { + bool: { + must_not: [ + { + term: { + 'task.status': TaskStatus.Unrecognized, + }, + }, + ], + }, +}; + export const RunningOrClaimingTaskWithExpiredRetryAt: MustCondition = { bool: { must: [ @@ -116,6 +132,46 @@ if (doc['task.runAt'].size()!=0) { }; export const SortByRunAtAndRetryAt = SortByRunAtAndRetryAtScript as estypes.SortCombinations; +function getSortByPriority(definitions: TaskTypeDictionary): estypes.SortCombinations | undefined { + if (definitions.size() === 0) return; + + return { + _script: { + type: 'number', + order: 'desc', + script: { + lang: 'painless', + // Use priority if explicitly specified in task definition, otherwise default to 50 (Normal) + // TODO: we could do this locally as well, but they may starve + source: ` + String taskType = doc['task.taskType'].value; + if (params.priority_map.containsKey(taskType)) { + return params.priority_map[taskType]; + } else { + return ${TaskPriority.Normal}; + } + `, + params: { + priority_map: definitions + .getAllDefinitions() + .reduce>((acc, taskDefinition) => { + if (taskDefinition.priority) { + acc[taskDefinition.type] = taskDefinition.priority; + } + return acc; + }, {}), + }, + }, + }, + }; +} + +export function getClaimSort(definitions: TaskTypeDictionary): estypes.SortCombinations[] { + const sortByPriority = getSortByPriority(definitions); + if (!sortByPriority) return [SortByRunAtAndRetryAt]; + return [sortByPriority, SortByRunAtAndRetryAt]; +} + export interface UpdateFieldsAndMarkAsFailedOpts { fieldUpdates: { [field: string]: string | number | Date; @@ -163,3 +219,17 @@ export const updateFieldsAndMarkAsFailed = ({ }, }; }; + +export const OneOfTaskTypes = (field: string, types: string[]): MustCondition => { + return { + bool: { + must: [ + { + terms: { + [field]: types, + }, + }, + ], + }, + }; +}; diff --git a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts index 6c3b2dbc3d694..33e5a0074319d 100644 --- a/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts +++ b/x-pack/plugins/task_manager/server/queries/task_claiming.test.ts @@ -66,21 +66,23 @@ describe('TaskClaiming', () => { .mockImplementation(() => mockApmTrans as any); }); - test(`should throw an error when invalid strategy specified`, () => { + test(`should log a warning when invalid strategy specified`, () => { const definitions = new TaskTypeDictionary(mockLogger()); - expect(() => { - new TaskClaiming({ - logger: taskManagerLogger, - strategy: 'non-default', - definitions, - excludedTaskTypes: [], - unusedTypes: [], - taskStore: taskStoreMock.create({ taskManagerId: '' }), - maxAttempts: 2, - getCapacity: () => 10, - }); - }).toThrowErrorMatchingInlineSnapshot(`"Unknown task claiming strategy (non-default)"`); + new TaskClaiming({ + logger: taskManagerLogger, + strategy: 'non-default', + definitions, + excludedTaskTypes: [], + unusedTypes: [], + taskStore: taskStoreMock.create({ taskManagerId: '' }), + maxAttempts: 2, + getCapacity: () => 10, + }); + + expect(taskManagerLogger.warn).toHaveBeenCalledWith( + 'Unknown task claiming strategy "non-default", falling back to default' + ); }); test(`should log when a certain task type is skipped due to having a zero concurency configuration`, () => { @@ -127,7 +129,7 @@ describe('TaskClaiming', () => { getCapacity: () => 10, }); - expect(taskManagerLogger.info).toHaveBeenCalledTimes(1); + expect(taskManagerLogger.info).toHaveBeenCalledTimes(2); expect(taskManagerLogger.info.mock.calls[0][0]).toMatchInlineSnapshot( `"Task Manager will never claim tasks of the following types as their \\"maxConcurrency\\" is set to 0: limitedToZero, anotherLimitedToZero"` ); diff --git a/x-pack/plugins/task_manager/server/queries/task_claiming.ts b/x-pack/plugins/task_manager/server/queries/task_claiming.ts index 8216e7c8d8dfb..ffd053656d72d 100644 --- a/x-pack/plugins/task_manager/server/queries/task_claiming.ts +++ b/x-pack/plugins/task_manager/server/queries/task_claiming.ts @@ -109,8 +109,10 @@ export class TaskClaiming { this.taskMaxAttempts = Object.fromEntries(this.normalizeMaxAttempts(this.definitions)); this.excludedTaskTypes = opts.excludedTaskTypes; this.unusedTypes = opts.unusedTypes; - this.taskClaimer = getTaskClaimer(opts.strategy); + this.taskClaimer = getTaskClaimer(this.logger, opts.strategy); this.events$ = new Subject(); + + this.logger.info(`using task claiming strategy: ${opts.strategy}`); } private partitionIntoClaimingBatches(definitions: TaskTypeDictionary): TaskClaimingBatches { @@ -175,6 +177,7 @@ export class TaskClaiming { definitions: this.definitions, taskMaxAttempts: this.taskMaxAttempts, excludedTaskTypes: this.excludedTaskTypes, + logger: this.logger, }; return this.taskClaimer(opts).pipe(map((claimResult) => asOk(claimResult))); } diff --git a/x-pack/plugins/task_manager/server/saved_objects/index.ts b/x-pack/plugins/task_manager/server/saved_objects/index.ts index 390990bd10dbf..68bc796d7aba3 100644 --- a/x-pack/plugins/task_manager/server/saved_objects/index.ts +++ b/x-pack/plugins/task_manager/server/saved_objects/index.ts @@ -7,12 +7,12 @@ import type { SavedObjectsServiceSetup } from '@kbn/core/server'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { taskModelVersions } from './task_model_versions'; import { taskMappings } from './mappings'; import { getMigrations } from './migrations'; import { TaskManagerConfig } from '../config'; import { getOldestIdleActionTask } from '../queries/oldest_idle_action_task'; import { TASK_MANAGER_INDEX } from '../constants'; +import { taskModelVersions } from './model_versions'; export function setupSavedObjects( savedObjects: SavedObjectsServiceSetup, diff --git a/x-pack/plugins/task_manager/server/saved_objects/model_versions/index.ts b/x-pack/plugins/task_manager/server/saved_objects/model_versions/index.ts new file mode 100644 index 0000000000000..9d84e528a3d39 --- /dev/null +++ b/x-pack/plugins/task_manager/server/saved_objects/model_versions/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 { taskModelVersions } from './task_model_versions'; diff --git a/x-pack/plugins/task_manager/server/saved_objects/task_model_versions.ts b/x-pack/plugins/task_manager/server/saved_objects/model_versions/task_model_versions.ts similarity index 92% rename from x-pack/plugins/task_manager/server/saved_objects/task_model_versions.ts rename to x-pack/plugins/task_manager/server/saved_objects/model_versions/task_model_versions.ts index adfebecf84dce..d76d26e56c23e 100644 --- a/x-pack/plugins/task_manager/server/saved_objects/task_model_versions.ts +++ b/x-pack/plugins/task_manager/server/saved_objects/model_versions/task_model_versions.ts @@ -6,7 +6,7 @@ */ import { SavedObjectsModelVersionMap } from '@kbn/core-saved-objects-server'; -import { taskSchemaV1 } from './schemas/task'; +import { taskSchemaV1 } from '../schemas/task'; export const taskModelVersions: SavedObjectsModelVersionMap = { '1': { diff --git a/x-pack/plugins/task_manager/server/task.ts b/x-pack/plugins/task_manager/server/task.ts index c239263a872b5..054b8f4686388 100644 --- a/x-pack/plugins/task_manager/server/task.ts +++ b/x-pack/plugins/task_manager/server/task.ts @@ -56,6 +56,7 @@ export type SuccessfulRunResult = { state: Record; taskRunError?: DecoratedError; shouldValidate?: boolean; + shouldDeleteTask?: boolean; } & ( | // ensure a SuccessfulRunResult can either specify a new `runAt` or a new `schedule`, but not both { @@ -88,6 +89,11 @@ export type FailedRunResult = SuccessfulRunResult & { export type RunResult = FailedRunResult | SuccessfulRunResult; +export const getDeleteTaskRunResult = () => ({ + state: {}, + shouldDeleteTask: true, +}); + export const isFailedRunResult = (result: unknown): result is FailedRunResult => !!((result as FailedRunResult)?.error ?? false); @@ -205,6 +211,7 @@ export enum TaskStatus { Claiming = 'claiming', Running = 'running', Failed = 'failed', + ShouldDelete = 'should_delete', Unrecognized = 'unrecognized', DeadLetter = 'dead_letter', } @@ -421,6 +428,17 @@ export interface ConcreteTaskInstance extends TaskInstance { ownerId: string | null; } +export interface ConcreteTaskInstanceVersion { + /** The _id of the the document (not the SO id) */ + esId: string; + /** The _seq_no of the document when using seq_no_primary_term on fetch */ + seqNo?: number; + /** The _primary_term of the document when using seq_no_primary_term on fetch */ + primaryTerm?: number; + /** The error found if trying to resolve the version info for this esId */ + error?: string; +} + /** * A task instance that has an id and is ready for storage. */ diff --git a/x-pack/plugins/task_manager/server/task_claimers/README.md b/x-pack/plugins/task_manager/server/task_claimers/README.md index 0c92f02031d2e..210e08b021af0 100644 --- a/x-pack/plugins/task_manager/server/task_claimers/README.md +++ b/x-pack/plugins/task_manager/server/task_claimers/README.md @@ -18,3 +18,17 @@ idea: - A search is then run on the documents updated from the update by query. + +`mget` task claiming strategy +------------------------------------------------------------------------ + +see: https://github.com/elastic/kibana/issues/155770 + +The idea is to get more tasks than we have workers for with a search, +and then validate that they are still valid (not been claimed) with an +mget, since they may be stale. + +There are lots of interesting potential things we can do here, like maybe +skipping polling completely for a round (think single Kibana, and the earlier +poll got 2 * workers tasks out). But we'll probably start with the bare +minimum to get it working. diff --git a/x-pack/plugins/task_manager/server/task_claimers/index.test.ts b/x-pack/plugins/task_manager/server/task_claimers/index.test.ts index be26f0d2f9efb..406a719baf5b1 100644 --- a/x-pack/plugins/task_manager/server/task_claimers/index.test.ts +++ b/x-pack/plugins/task_manager/server/task_claimers/index.test.ts @@ -6,18 +6,33 @@ */ import { getTaskClaimer } from '.'; +import { mockLogger } from '../test_utils'; import { claimAvailableTasksDefault } from './strategy_default'; +import { claimAvailableTasksMget } from './strategy_mget'; + +const logger = mockLogger(); describe('task_claimers/index', () => { + beforeEach(() => jest.resetAllMocks()); + describe('getTaskClaimer()', () => { test('returns expected result for default', () => { - const taskClaimer = getTaskClaimer('default'); + const taskClaimer = getTaskClaimer(logger, 'default'); expect(taskClaimer).toBe(claimAvailableTasksDefault); + expect(logger.warn).not.toHaveBeenCalled(); }); - test('throws error for unsupported parameter', () => { - expect(() => getTaskClaimer('not-supported')).toThrowErrorMatchingInlineSnapshot( - `"Unknown task claiming strategy (not-supported)"` + test('returns expected result for mget', () => { + const taskClaimer = getTaskClaimer(logger, 'unsafe_mget'); + expect(taskClaimer).toBe(claimAvailableTasksMget); + expect(logger.warn).not.toHaveBeenCalled(); + }); + + test('logs a warning for unsupported parameter', () => { + const taskClaimer = getTaskClaimer(logger, 'not-supported'); + expect(taskClaimer).toBe(claimAvailableTasksDefault); + expect(logger.warn).toHaveBeenCalledWith( + 'Unknown task claiming strategy "not-supported", falling back to default' ); }); }); diff --git a/x-pack/plugins/task_manager/server/task_claimers/index.ts b/x-pack/plugins/task_manager/server/task_claimers/index.ts index 8074197a147b6..927d4c762f625 100644 --- a/x-pack/plugins/task_manager/server/task_claimers/index.ts +++ b/x-pack/plugins/task_manager/server/task_claimers/index.ts @@ -6,6 +6,7 @@ */ import { Subject, Observable } from 'rxjs'; +import { Logger } from '@kbn/core/server'; import { TaskStore } from '../task_store'; import { TaskClaim, TaskTiming } from '../task_events'; @@ -13,7 +14,8 @@ import { TaskTypeDictionary } from '../task_type_dictionary'; import { TaskClaimingBatches } from '../queries/task_claiming'; import { ConcreteTaskInstance } from '../task'; import { claimAvailableTasksDefault } from './strategy_default'; -import { CLAIM_STRATEGY_DEFAULT } from '../config'; +import { claimAvailableTasksMget } from './strategy_mget'; +import { CLAIM_STRATEGY_DEFAULT, CLAIM_STRATEGY_MGET } from '../config'; export interface TaskClaimerOpts { getCapacity: (taskType?: string | undefined) => number; @@ -25,6 +27,7 @@ export interface TaskClaimerOpts { unusedTypes: string[]; excludedTaskTypes: string[]; taskMaxAttempts: Record; + logger: Logger; } export interface ClaimOwnershipResult { @@ -39,10 +42,31 @@ export interface ClaimOwnershipResult { export type TaskClaimerFn = (opts: TaskClaimerOpts) => Observable; -export function getTaskClaimer(strategy: string): TaskClaimerFn { +let WarnedOnInvalidClaimer = false; + +export function getTaskClaimer(logger: Logger, strategy: string): TaskClaimerFn { switch (strategy) { case CLAIM_STRATEGY_DEFAULT: return claimAvailableTasksDefault; + case CLAIM_STRATEGY_MGET: + return claimAvailableTasksMget; + } + + if (!WarnedOnInvalidClaimer) { + WarnedOnInvalidClaimer = true; + logger.warn(`Unknown task claiming strategy "${strategy}", falling back to default`); } - throw new Error(`Unknown task claiming strategy (${strategy})`); + return claimAvailableTasksDefault; +} + +export function getEmptyClaimOwnershipResult() { + return { + stats: { + tasksUpdated: 0, + tasksConflicted: 0, + tasksClaimed: 0, + tasksRejected: 0, + }, + docs: [], + }; } diff --git a/x-pack/plugins/task_manager/server/task_claimers/strategy_default.test.ts b/x-pack/plugins/task_manager/server/task_claimers/strategy_default.test.ts index bb511cdc0958f..e07038960d371 100644 --- a/x-pack/plugins/task_manager/server/task_claimers/strategy_default.test.ts +++ b/x-pack/plugins/task_manager/server/task_claimers/strategy_default.test.ts @@ -105,7 +105,7 @@ describe('TaskClaiming', () => { store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); if (hits.length === 1) { - store.fetch.mockResolvedValue({ docs: hits[0] }); + store.fetch.mockResolvedValue({ docs: hits[0], versionMap: new Map() }); store.updateByQuery.mockResolvedValue({ updated: hits[0].length, version_conflicts: versionConflicts, @@ -113,7 +113,7 @@ describe('TaskClaiming', () => { }); } else { for (const docs of hits) { - store.fetch.mockResolvedValueOnce({ docs }); + store.fetch.mockResolvedValueOnce({ docs, versionMap: new Map() }); store.updateByQuery.mockResolvedValueOnce({ updated: docs.length, version_conflicts: versionConflicts, @@ -364,13 +364,13 @@ describe('TaskClaiming', () => { }, }, source: ` - String taskType = doc['task.taskType'].value; - if (params.priority_map.containsKey(taskType)) { - return params.priority_map[taskType]; - } else { - return 50; - } - `, + String taskType = doc['task.taskType'].value; + if (params.priority_map.containsKey(taskType)) { + return params.priority_map[taskType]; + } else { + return 50; + } + `, }, }, }, @@ -1227,7 +1227,7 @@ if (doc['task.runAt'].size()!=0) { const taskStore = taskStoreMock.create({ taskManagerId }); taskStore.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); for (const docs of taskCycles) { - taskStore.fetch.mockResolvedValueOnce({ docs }); + taskStore.fetch.mockResolvedValueOnce({ docs, versionMap: new Map() }); taskStore.updateByQuery.mockResolvedValueOnce({ updated: docs.length, version_conflicts: 0, @@ -1235,7 +1235,7 @@ if (doc['task.runAt'].size()!=0) { }); } - taskStore.fetch.mockResolvedValue({ docs: [] }); + taskStore.fetch.mockResolvedValue({ docs: [], versionMap: new Map() }); taskStore.updateByQuery.mockResolvedValue({ updated: 0, version_conflicts: 0, diff --git a/x-pack/plugins/task_manager/server/task_claimers/strategy_default.ts b/x-pack/plugins/task_manager/server/task_claimers/strategy_default.ts index 765be571eeb5d..6482c7e861dea 100644 --- a/x-pack/plugins/task_manager/server/task_claimers/strategy_default.ts +++ b/x-pack/plugins/task_manager/server/task_claimers/strategy_default.ts @@ -8,7 +8,6 @@ /* * This module contains helpers for managing the task manager storage layer. */ -import type { estypes } from '@elastic/elasticsearch'; import apm from 'elastic-apm-node'; import minimatch from 'minimatch'; import { Subject, Observable, from, of } from 'rxjs'; @@ -17,8 +16,8 @@ import { groupBy, pick } from 'lodash'; import { asOk } from '../lib/result_type'; import { TaskTypeDictionary } from '../task_type_dictionary'; -import { TaskClaimerOpts, ClaimOwnershipResult } from '.'; -import { ConcreteTaskInstance, TaskPriority } from '../task'; +import { TaskClaimerOpts, ClaimOwnershipResult, getEmptyClaimOwnershipResult } from '.'; +import { ConcreteTaskInstance } from '../task'; import { TASK_MANAGER_TRANSACTION_TYPE } from '../task_running'; import { isLimited, TASK_MANAGER_MARK_AS_CLAIMED } from '../queries/task_claiming'; import { TaskClaim, asTaskClaimEvent, startTaskTimer } from '../task_events'; @@ -29,7 +28,7 @@ import { IdleTaskWithExpiredRunAt, InactiveTasks, RunningOrClaimingTaskWithExpiredRetryAt, - SortByRunAtAndRetryAt, + getClaimSort, tasksClaimedByOwner, tasksOfType, EnabledTask, @@ -225,20 +224,8 @@ async function sweepForClaimedTasks( return docs; } -function emptyClaimOwnershipResult() { - return { - stats: { - tasksUpdated: 0, - tasksConflicted: 0, - tasksClaimed: 0, - tasksRejected: 0, - }, - docs: [], - }; -} - function accumulateClaimOwnershipResults( - prev: ClaimOwnershipResult = emptyClaimOwnershipResult(), + prev: ClaimOwnershipResult = getEmptyClaimOwnershipResult(), next?: ClaimOwnershipResult ) { if (next) { @@ -256,38 +243,3 @@ function accumulateClaimOwnershipResults( } return prev; } - -function getClaimSort(definitions: TaskTypeDictionary): estypes.SortCombinations[] { - // Sort by descending priority, then by ascending runAt/retryAt time - return [ - { - _script: { - type: 'number', - order: 'desc', - script: { - lang: 'painless', - // Use priority if explicitly specified in task definition, otherwise default to 50 (Normal) - source: ` - String taskType = doc['task.taskType'].value; - if (params.priority_map.containsKey(taskType)) { - return params.priority_map[taskType]; - } else { - return ${TaskPriority.Normal}; - } - `, - params: { - priority_map: definitions - .getAllDefinitions() - .reduce>((acc, taskDefinition) => { - if (taskDefinition.priority) { - acc[taskDefinition.type] = taskDefinition.priority; - } - return acc; - }, {}), - }, - }, - }, - }, - SortByRunAtAndRetryAt, - ]; -} diff --git a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts new file mode 100644 index 0000000000000..0306f9dda3da8 --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.test.ts @@ -0,0 +1,463 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import _ from 'lodash'; +import { v4 as uuidv4 } from 'uuid'; +import { filter, take, toArray } from 'rxjs'; +import { CLAIM_STRATEGY_MGET } from '../config'; + +import { + TaskStatus, + ConcreteTaskInstance, + ConcreteTaskInstanceVersion, + TaskPriority, +} from '../task'; +import { StoreOpts } from '../task_store'; +import { asTaskClaimEvent, TaskEvent } from '../task_events'; +import { asOk, isOk, unwrap } from '../lib/result_type'; +import { TaskTypeDictionary } from '../task_type_dictionary'; +import { mockLogger } from '../test_utils'; +import { + TaskClaiming, + OwnershipClaimingOpts, + TaskClaimingOpts, + TASK_MANAGER_MARK_AS_CLAIMED, +} from '../queries/task_claiming'; +import { Observable } from 'rxjs'; +import { taskStoreMock } from '../task_store.mock'; +import apm from 'elastic-apm-node'; +import { TASK_MANAGER_TRANSACTION_TYPE } from '../task_running'; +import { ClaimOwnershipResult } from '.'; +import { FillPoolResult } from '../lib/fill_pool'; + +jest.mock('../constants', () => ({ + CONCURRENCY_ALLOW_LIST_BY_TASK_TYPE: [ + 'limitedToZero', + 'limitedToOne', + 'anotherLimitedToZero', + 'anotherLimitedToOne', + 'limitedToTwo', + 'limitedToFive', + ], +})); + +const taskManagerLogger = mockLogger(); + +beforeEach(() => jest.clearAllMocks()); + +const mockedDate = new Date('2019-02-12T21:01:22.479Z'); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +(global as any).Date = class Date { + constructor() { + return mockedDate; + } + static now() { + return mockedDate.getTime(); + } +}; + +const taskDefinitions = new TaskTypeDictionary(taskManagerLogger); +taskDefinitions.registerTaskDefinitions({ + report: { + title: 'report', + createTaskRunner: jest.fn(), + }, + dernstraight: { + title: 'dernstraight', + createTaskRunner: jest.fn(), + }, + yawn: { + title: 'yawn', + createTaskRunner: jest.fn(), + }, +}); + +const mockApmTrans = { + end: jest.fn(), +}; + +// needs more tests in the similar to the `strategy_default.test.ts` test suite +describe('TaskClaiming', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest + .spyOn(apm, 'startTransaction') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .mockImplementation(() => mockApmTrans as any); + }); + + describe('claimAvailableTasks', () => { + function initialiseTestClaiming({ + storeOpts = {}, + taskClaimingOpts = {}, + hits, + versionMaps, + excludedTaskTypes = [], + unusedTaskTypes = [], + }: { + storeOpts: Partial; + taskClaimingOpts: Partial; + hits?: ConcreteTaskInstance[][]; + versionMaps?: Array>; + excludedTaskTypes?: string[]; + unusedTaskTypes?: string[]; + }) { + const definitions = storeOpts.definitions ?? taskDefinitions; + const store = taskStoreMock.create({ taskManagerId: storeOpts.taskManagerId }); + store.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + + if (hits == null) hits = [generateFakeTasks(1)]; + if (versionMaps == null) { + versionMaps = [new Map()]; + for (const oneHit of hits) { + const map = new Map(); + versionMaps.push(map); + for (const task of oneHit) { + map.set(task.id, { esId: task.id, seqNo: 32, primaryTerm: 32 }); + } + } + } + + for (let i = 0; i < hits.length; i++) { + store.fetch.mockResolvedValueOnce({ docs: hits[i], versionMap: versionMaps[i] }); + store.getDocVersions.mockResolvedValueOnce(versionMaps[i]); + const oneBulkResult = hits[i].map((hit) => asOk(hit)); + store.bulkUpdate.mockResolvedValueOnce(oneBulkResult); + } + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: CLAIM_STRATEGY_MGET, + definitions, + taskStore: store, + excludedTaskTypes, + unusedTypes: unusedTaskTypes, + maxAttempts: taskClaimingOpts.maxAttempts ?? 2, + getCapacity: taskClaimingOpts.getCapacity ?? (() => 10), + ...taskClaimingOpts, + }); + + return { taskClaiming, store }; + } + + async function testClaimAvailableTasks({ + storeOpts = {}, + taskClaimingOpts = {}, + claimingOpts, + hits = [generateFakeTasks(1)], + excludedTaskTypes = [], + unusedTaskTypes = [], + }: { + storeOpts: Partial; + taskClaimingOpts: Partial; + claimingOpts: Omit; + hits?: ConcreteTaskInstance[][]; + excludedTaskTypes?: string[]; + unusedTaskTypes?: string[]; + }) { + const { taskClaiming, store } = initialiseTestClaiming({ + storeOpts, + taskClaimingOpts, + excludedTaskTypes, + unusedTaskTypes, + hits, + }); + + const resultsOrErr = await getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable(claimingOpts) + ); + for (const resultOrErr of resultsOrErr) { + if (!isOk(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + } + + const results = resultsOrErr.map((resultOrErr) => { + if (!isOk(resultOrErr)) { + expect(resultOrErr).toBe(undefined); + } + return unwrap(resultOrErr) as ClaimOwnershipResult; + }); + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('success'); + + expect(store.fetch.mock.calls).toMatchObject({}); + expect(store.getDocVersions.mock.calls).toMatchObject({}); + return results.map((result, index) => ({ + result, + args: {}, + })); + } + + test('makes calls to APM as expected when markAvailableTasksAsClaimed throws error', async () => { + const maxAttempts = _.random(2, 43); + const customMaxAttempts = _.random(44, 100); + + const definitions = new TaskTypeDictionary(mockLogger()); + definitions.registerTaskDefinitions({ + foo: { + title: 'foo', + createTaskRunner: jest.fn(), + }, + bar: { + title: 'bar', + maxAttempts: customMaxAttempts, + createTaskRunner: jest.fn(), + }, + }); + + const { taskClaiming, store } = initialiseTestClaiming({ + storeOpts: { + definitions, + }, + taskClaimingOpts: { + maxAttempts, + }, + }); + + store.fetch.mockReset(); + store.fetch.mockRejectedValue(new Error('Oh no')); + + await expect( + getAllAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ + claimOwnershipUntil: new Date(), + }) + ) + ).rejects.toMatchInlineSnapshot(`[Error: Oh no]`); + + expect(apm.startTransaction).toHaveBeenCalledWith( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + expect(mockApmTrans.end).toHaveBeenCalledWith('failure'); + }); + + test('it filters claimed tasks down by supported types, maxAttempts, status, and runAt', async () => { + const maxAttempts = _.random(2, 43); + const customMaxAttempts = _.random(44, 100); + + const definitions = new TaskTypeDictionary(mockLogger()); + definitions.registerTaskDefinitions({ + foo: { + title: 'foo', + priority: TaskPriority.Low, + createTaskRunner: jest.fn(), + }, + bar: { + title: 'bar', + maxAttempts: customMaxAttempts, + createTaskRunner: jest.fn(), + }, + foobar: { + title: 'foobar', + maxAttempts: customMaxAttempts, + createTaskRunner: jest.fn(), + }, + }); + + const result = await testClaimAvailableTasks({ + storeOpts: { definitions }, + taskClaimingOpts: { maxAttempts }, + claimingOpts: { claimOwnershipUntil: new Date() }, + excludedTaskTypes: ['foobar'], + }); + expect(result).toMatchObject({}); + }); + }); + + describe('task events', () => { + function generateTasks(taskManagerId: string) { + const runAt = new Date(); + const tasks = [ + { + id: 'claimed-by-id', + runAt, + taskType: 'foo', + schedule: undefined, + attempts: 0, + status: TaskStatus.Claiming, + params: { hello: 'world' }, + state: { baby: 'Henhen' }, + user: 'jimbo', + scope: ['reporting'], + ownerId: taskManagerId, + startedAt: null, + retryAt: null, + scheduledAt: new Date(), + traceparent: 'parent', + }, + { + id: 'claimed-by-schedule', + runAt, + taskType: 'bar', + schedule: { interval: '5m' }, + attempts: 2, + status: TaskStatus.Claiming, + params: { shazm: 1 }, + state: { henry: 'The 8th' }, + user: 'dabo', + scope: ['reporting', 'ceo'], + ownerId: taskManagerId, + startedAt: null, + retryAt: null, + scheduledAt: new Date(), + traceparent: 'newParent', + }, + { + id: 'already-running', + runAt, + taskType: 'bar', + schedule: { interval: '5m' }, + attempts: 2, + status: TaskStatus.Running, + params: { shazm: 1 }, + state: { henry: 'The 8th' }, + user: 'dabo', + scope: ['reporting', 'ceo'], + ownerId: taskManagerId, + startedAt: null, + retryAt: null, + scheduledAt: new Date(), + traceparent: '', + }, + ]; + + return { taskManagerId, runAt, tasks }; + } + + function instantiateStoreWithMockedApiResponses({ + taskManagerId = uuidv4(), + definitions = taskDefinitions, + getCapacity = () => 10, + tasksClaimed, + }: Partial> & { + taskManagerId?: string; + tasksClaimed?: ConcreteTaskInstance[][]; + } = {}) { + const { runAt, tasks: generatedTasks } = generateTasks(taskManagerId); + const taskCycles = tasksClaimed ?? [generatedTasks]; + + const taskStore = taskStoreMock.create({ taskManagerId }); + taskStore.convertToSavedObjectIds.mockImplementation((ids) => ids.map((id) => `task:${id}`)); + for (const docs of taskCycles) { + taskStore.fetch.mockResolvedValueOnce({ docs, versionMap: new Map() }); + taskStore.updateByQuery.mockResolvedValueOnce({ + updated: docs.length, + version_conflicts: 0, + total: docs.length, + }); + } + + taskStore.fetch.mockResolvedValue({ docs: [], versionMap: new Map() }); + taskStore.updateByQuery.mockResolvedValue({ + updated: 0, + version_conflicts: 0, + total: 0, + }); + + const taskClaiming = new TaskClaiming({ + logger: taskManagerLogger, + strategy: 'default', + definitions, + excludedTaskTypes: [], + unusedTypes: [], + taskStore, + maxAttempts: 2, + getCapacity, + }); + + return { taskManagerId, runAt, taskClaiming }; + } + + test('emits an event when a task is succesfully by scheduling', async () => { + const { taskManagerId, runAt, taskClaiming } = instantiateStoreWithMockedApiResponses(); + + const promise = taskClaiming.events + .pipe( + filter( + (event: TaskEvent) => event.id === 'claimed-by-schedule' + ), + take(1) + ) + .toPromise(); + + await getFirstAsPromise( + taskClaiming.claimAvailableTasksIfCapacityIsAvailable({ + claimOwnershipUntil: new Date(), + }) + ); + + const event = await promise; + expect(event).toMatchObject( + asTaskClaimEvent( + 'claimed-by-schedule', + asOk({ + id: 'claimed-by-schedule', + runAt, + taskType: 'bar', + schedule: { interval: '5m' }, + attempts: 2, + status: 'claiming' as TaskStatus, + params: { shazm: 1 }, + state: { henry: 'The 8th' }, + user: 'dabo', + scope: ['reporting', 'ceo'], + ownerId: taskManagerId, + startedAt: null, + retryAt: null, + scheduledAt: new Date(), + traceparent: 'newParent', + }) + ) + ); + }); + }); +}); + +function generateFakeTasks(count: number = 1) { + return _.times(count, (index) => mockInstance({ id: `task:id-${index}` })); +} + +function mockInstance(instance: Partial = {}) { + return Object.assign( + { + id: uuidv4(), + taskType: 'bar', + sequenceNumber: 32, + primaryTerm: 32, + runAt: new Date(), + scheduledAt: new Date(), + startedAt: null, + retryAt: null, + attempts: 0, + params: {}, + scope: ['reporting'], + state: {}, + status: 'idle', + user: 'example', + ownerId: null, + traceparent: '', + }, + instance + ); +} + +function getFirstAsPromise(obs$: Observable): Promise { + return new Promise((resolve, reject) => { + obs$.subscribe(resolve, reject); + }); +} +function getAllAsPromise(obs$: Observable): Promise { + return new Promise((resolve, reject) => { + obs$.pipe(toArray()).subscribe(resolve, reject); + }); +} diff --git a/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.ts b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.ts new file mode 100644 index 0000000000000..07d18a39a1dbc --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_claimers/strategy_mget.ts @@ -0,0 +1,326 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// Basic operation of this task claimer: +// - search for candidate tasks to run, more than we actually can run +// - for each task found, do an mget to get the current seq_no and primary_term +// - if the mget result doesn't match the search result, the task is stale +// - from the non-stale search results, return as many as we can run + +import { SavedObjectsErrorHelpers } from '@kbn/core/server'; + +import apm from 'elastic-apm-node'; +import { Subject, Observable } from 'rxjs'; + +import { TaskTypeDictionary } from '../task_type_dictionary'; +import { TaskClaimerOpts, ClaimOwnershipResult, getEmptyClaimOwnershipResult } from '.'; +import { ConcreteTaskInstance, TaskStatus, ConcreteTaskInstanceVersion } from '../task'; +import { TASK_MANAGER_TRANSACTION_TYPE } from '../task_running'; +import { + isLimited, + TASK_MANAGER_MARK_AS_CLAIMED, + TaskClaimingBatches, +} from '../queries/task_claiming'; +import { TaskClaim, asTaskClaimEvent, startTaskTimer } from '../task_events'; +import { shouldBeOneOf, mustBeAllOf, filterDownBy, matchesClauses } from '../queries/query_clauses'; + +import { + IdleTaskWithExpiredRunAt, + InactiveTasks, + RunningOrClaimingTaskWithExpiredRetryAt, + getClaimSort, + EnabledTask, + OneOfTaskTypes, + RecognizedTask, +} from '../queries/mark_available_tasks_as_claimed'; + +import { TaskStore, SearchOpts } from '../task_store'; +import { isOk, asOk } from '../lib/result_type'; + +interface OwnershipClaimingOpts { + claimOwnershipUntil: Date; + size: number; + taskTypes: Set; + removedTypes: Set; + excludedTypes: Set; + taskStore: TaskStore; + events$: Subject; + definitions: TaskTypeDictionary; + taskMaxAttempts: Record; +} + +const SIZE_MULTIPLIER_FOR_TASK_FETCH = 4; + +export function claimAvailableTasksMget(opts: TaskClaimerOpts): Observable { + const taskClaimOwnership$ = new Subject(); + + claimAvailableTasksApm(opts) + .then((result) => { + taskClaimOwnership$.next(result); + }) + .catch((err) => { + taskClaimOwnership$.error(err); + }) + .finally(() => { + taskClaimOwnership$.complete(); + }); + + return taskClaimOwnership$; +} + +async function claimAvailableTasksApm(opts: TaskClaimerOpts): Promise { + const apmTrans = apm.startTransaction( + TASK_MANAGER_MARK_AS_CLAIMED, + TASK_MANAGER_TRANSACTION_TYPE + ); + + try { + const result = await claimAvailableTasks(opts); + apmTrans.end('success'); + return result; + } catch (err) { + apmTrans.end('failure'); + throw err; + } +} + +async function claimAvailableTasks(opts: TaskClaimerOpts): Promise { + const { getCapacity, claimOwnershipUntil, batches, events$, taskStore } = opts; + const { definitions, unusedTypes, excludedTaskTypes, taskMaxAttempts } = opts; + const { logger } = opts; + const loggerTag = claimAvailableTasksMget.name; + const logMeta = { tags: [loggerTag] }; + const initialCapacity = getCapacity(); + const stopTaskTimer = startTaskTimer(); + + const removedTypes = new Set(unusedTypes); // REMOVED_TYPES + const excludedTypes = new Set(excludedTaskTypes); // excluded via config + + // get a list of candidate tasks to claim, with their version info + const { docs, versionMap } = await searchAvailableTasks({ + definitions, + taskTypes: new Set(definitions.getAllTypes()), + excludedTypes, + removedTypes, + taskStore, + events$, + claimOwnershipUntil, + size: initialCapacity * SIZE_MULTIPLIER_FOR_TASK_FETCH, + taskMaxAttempts, + }); + + if (docs.length === 0) + return { + ...getEmptyClaimOwnershipResult(), + timing: stopTaskTimer(), + }; + + // use mget to get the latest version of each task + const docLatestVersions = await taskStore.getDocVersions(docs.map((doc) => `task:${doc.id}`)); + + // filter out stale, missing and removed tasks + const currentTasks: ConcreteTaskInstance[] = []; + const staleTasks: ConcreteTaskInstance[] = []; + const missingTasks: ConcreteTaskInstance[] = []; + const removedTasks: ConcreteTaskInstance[] = []; + + for (const searchDoc of docs) { + if (removedTypes.has(searchDoc.taskType)) { + removedTasks.push(searchDoc); + continue; + } + + const searchVersion = versionMap.get(searchDoc.id); + const latestVersion = docLatestVersions.get(`task:${searchDoc.id}`); + if (!searchVersion || !latestVersion) { + missingTasks.push(searchDoc); + continue; + } + + if ( + searchVersion.seqNo === latestVersion.seqNo && + searchVersion.primaryTerm === latestVersion.primaryTerm + ) { + currentTasks.push(searchDoc); + continue; + } else { + staleTasks.push(searchDoc); + continue; + } + } + // apply limited concurrency limits (TODO: can currently starve other tasks) + const candidateTasks = applyLimitedConcurrency(currentTasks, batches); + + // build the updated task objects we'll claim + const taskUpdates: ConcreteTaskInstance[] = Array.from(candidateTasks) + .slice(0, initialCapacity) + .map((task) => { + if (task.retryAt != null && new Date(task.retryAt).getTime() < Date.now()) { + task.scheduledAt = task.retryAt; + } else { + task.scheduledAt = task.runAt; + } + task.retryAt = claimOwnershipUntil; + task.ownerId = taskStore.taskManagerId; + task.status = TaskStatus.Claiming; + + return task; + }); + + // perform the task object updates, deal with errors + const finalResults: ConcreteTaskInstance[] = []; + let conflicts = staleTasks.length; + let bulkErrors = 0; + + try { + const updateResults = await taskStore.bulkUpdate(taskUpdates, { validate: false }); + for (const updateResult of updateResults) { + if (isOk(updateResult)) { + finalResults.push(updateResult.value); + } else { + const { id, type, error } = updateResult.error; + + // this check is needed so error will be typed correctly for isConflictError + if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { + if (SavedObjectsErrorHelpers.isConflictError(error)) { + conflicts++; + } else { + logger.warn( + `Saved Object error updating task ${id}:${type} during claim: ${error.error}`, + logMeta + ); + bulkErrors++; + } + } else { + logger.warn(`Error updating task ${id}:${type} during claim: ${error.message}`, logMeta); + bulkErrors++; + } + } + } + } catch (err) { + logger.warn(`Error updating tasks during claim: ${err}`, logMeta); + } + + // separate update for removed tasks; shouldn't happen often, so unlikely + // a performance concern, and keeps the rest of the logic simpler + let removedCount = 0; + if (removedTasks.length > 0) { + const tasksToRemove = Array.from(removedTasks); + for (const task of tasksToRemove) { + task.status = TaskStatus.Unrecognized; + } + + // don't worry too much about errors, we'll get them next time + try { + const removeResults = await taskStore.bulkUpdate(tasksToRemove, { validate: false }); + for (const removeResult of removeResults) { + if (isOk(removeResult)) { + removedCount++; + } else { + const { id, type, error } = removeResult.error; + logger.warn( + `Error updating task ${id}:${type} to mark as unrecognized during claim: ${error.message}`, + logMeta + ); + } + } + } catch (err) { + logger.warn(`Error updating tasks to mark as unrecognized during claim: ${err}`, logMeta); + } + } + + // TODO: need a better way to generate stats + const message = `task claimer claimed: ${finalResults.length}; stale: ${staleTasks.length}; conflicts: ${conflicts}; missing: ${missingTasks.length}; updateErrors: ${bulkErrors}; removed: ${removedCount};`; + logger.debug(message, logMeta); + + // build results + const finalResult = { + stats: { + tasksUpdated: finalResults.length, + tasksConflicted: conflicts, + tasksClaimed: finalResults.length, + }, + docs: finalResults, + timing: stopTaskTimer(), + }; + + for (const doc of finalResults) { + events$.next(asTaskClaimEvent(doc.id, asOk(doc), finalResult.timing)); + } + + return finalResult; +} + +interface SearchAvailableTasksResponse { + docs: ConcreteTaskInstance[]; + versionMap: Map; +} + +async function searchAvailableTasks({ + definitions, + taskTypes, + removedTypes, + excludedTypes, + taskStore, + size, + taskMaxAttempts, +}: OwnershipClaimingOpts): Promise { + const searchedTypes = Array.from(taskTypes) + .concat(Array.from(removedTypes)) + .filter((type) => !excludedTypes.has(type)); + const queryForScheduledTasks = mustBeAllOf( + // Task must be enabled + EnabledTask, + // a task type that's not excluded (may be removed or not) + OneOfTaskTypes('task.taskType', searchedTypes), + // Either a task with idle status and runAt <= now or + // status running or claiming with a retryAt <= now. + shouldBeOneOf(IdleTaskWithExpiredRunAt, RunningOrClaimingTaskWithExpiredRetryAt), + // must have a status that isn't 'unrecognized' + RecognizedTask + ); + + const sort: NonNullable = getClaimSort(definitions); + const query = matchesClauses(queryForScheduledTasks, filterDownBy(InactiveTasks)); + + return await taskStore.fetch({ + query, + sort, + size, + seq_no_primary_term: true, + }); +} + +function applyLimitedConcurrency( + tasks: ConcreteTaskInstance[], + batches: TaskClaimingBatches +): ConcreteTaskInstance[] { + // create a map of task type - concurrency + const limitedBatches = batches.filter(isLimited); + const limitedMap = new Map(); + for (const limitedBatch of limitedBatches) { + const { tasksTypes, concurrency } = limitedBatch; + limitedMap.set(tasksTypes, concurrency); + } + + // apply the limited concurrency + const result: ConcreteTaskInstance[] = []; + for (const task of tasks) { + const concurrency = limitedMap.get(task.taskType); + if (concurrency == null) { + result.push(task); + continue; + } + + if (concurrency > 0) { + result.push(task); + limitedMap.set(task.taskType, concurrency - 1); + } + } + + return result; +} diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts index c68a06af0b1f0..9274e0583547e 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts @@ -24,7 +24,7 @@ import { TaskPersistence, asTaskManagerStatEvent, } from '../task_events'; -import { ConcreteTaskInstance, TaskStatus } from '../task'; +import { ConcreteTaskInstance, getDeleteTaskRunResult, TaskStatus } from '../task'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import moment from 'moment'; import { TaskDefinitionRegistry, TaskTypeDictionary } from '../task_type_dictionary'; @@ -1140,6 +1140,58 @@ describe('TaskManagerRunner', () => { expect(onTaskEvent).toHaveBeenCalledTimes(2); }); + test(`doesn't reschedule recurring tasks that return shouldDeleteTask = true`, async () => { + const id = _.random(1, 20).toString(); + const onTaskEvent = jest.fn(); + const { + runner, + store, + instance: originalInstance, + } = await readyToRunStageSetup({ + onTaskEvent, + instance: { + id, + schedule: { interval: '20m' }, + status: TaskStatus.Running, + startedAt: new Date(), + enabled: true, + }, + definitions: { + bar: { + title: 'Bar!', + createTaskRunner: () => ({ + async run() { + return getDeleteTaskRunResult(); + }, + }), + }, + }, + }); + + await runner.run(); + + expect(store.remove).toHaveBeenCalled(); + expect(store.update).not.toHaveBeenCalled(); + + expect(onTaskEvent).toHaveBeenCalledWith( + withAnyTiming( + asTaskRunEvent( + id, + asOk({ + persistence: TaskPersistence.Recurring, + task: originalInstance, + result: TaskRunResult.Deleted, + isExpired: false, + }) + ) + ) + ); + expect(onTaskEvent).toHaveBeenCalledWith( + asTaskManagerStatEvent('runDelay', asOk(expect.any(Number))) + ); + expect(onTaskEvent).toHaveBeenCalledTimes(2); + }); + test('tasks that return runAt override the schedule', async () => { const runAt = minutesFromNow(_.random(5)); const { runner, store } = await readyToRunStageSetup({ diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.ts index 0e0c1e1dfded0..fb73f295cf372 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.ts @@ -119,6 +119,8 @@ export enum TaskRunResult { RetryScheduled = 'RetryScheduled', // Task has failed Failed = 'Failed', + // Task deleted + Deleted = 'Deleted', } // A ConcreteTaskInstance which we *know* has a `startedAt` Date on it @@ -620,7 +622,13 @@ export class TaskManagerRunner implements TaskRunner { schedule: reschedule, state, attempts = 0, + shouldDeleteTask, }: SuccessfulRunResult & { attempts: number }) => { + if (shouldDeleteTask) { + // set the status to failed so task will get deleted + return asOk({ status: TaskStatus.ShouldDelete }); + } + const { startedAt, schedule } = this.instance.task; return asOk({ @@ -642,7 +650,10 @@ export class TaskManagerRunner implements TaskRunner { counterType: 'taskManagerTaskRunner', incrementBy: 1, }); - } else if (fieldUpdates.status === TaskStatus.Failed) { + } else if ( + fieldUpdates.status === TaskStatus.Failed || + fieldUpdates.status === TaskStatus.ShouldDelete + ) { // Delete the SO instead so it doesn't remain in the index forever this.instance = asRan(this.instance.task); await this.removeTask(); @@ -667,6 +678,8 @@ export class TaskManagerRunner implements TaskRunner { return fieldUpdates.status === TaskStatus.Failed ? TaskRunResult.Failed + : fieldUpdates.status === TaskStatus.ShouldDelete + ? TaskRunResult.Deleted : hasTaskRunFailed ? TaskRunResult.SuccessRescheduled : TaskRunResult.RetryScheduled; diff --git a/x-pack/plugins/task_manager/server/task_store.mock.ts b/x-pack/plugins/task_manager/server/task_store.mock.ts index 861f7d60bd221..c15518eaed510 100644 --- a/x-pack/plugins/task_manager/server/task_store.mock.ts +++ b/x-pack/plugins/task_manager/server/task_store.mock.ts @@ -31,6 +31,8 @@ export const taskStoreMock = { aggregate: jest.fn(), updateByQuery: jest.fn(), bulkGet: jest.fn(), + bulkGetVersions: jest.fn(), + getDocVersions: jest.fn(), index, taskManagerId, } as unknown as jest.Mocked; diff --git a/x-pack/plugins/task_manager/server/task_store.test.ts b/x-pack/plugins/task_manager/server/task_store.test.ts index 69179366d574f..925dc4d1a4c69 100644 --- a/x-pack/plugins/task_manager/server/task_store.test.ts +++ b/x-pack/plugins/task_manager/server/task_store.test.ts @@ -910,7 +910,7 @@ describe('TaskStore', () => { describe('getLifecycle', () => { test('returns the task status if the task exists ', async () => { - expect.assertions(6); + expect.assertions(7); return Promise.all( Object.values(TaskStatus).map(async (status) => { const task = { @@ -1269,4 +1269,201 @@ describe('TaskStore', () => { }); }); }); + + describe('bulkGetVersions', () => { + let store: TaskStore; + let esClient: ReturnType['asInternalUser']; + let childEsClient: ReturnType< + typeof elasticsearchServiceMock.createClusterClient + >['asInternalUser']; + + beforeAll(() => { + esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + childEsClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + esClient.child.mockReturnValue(childEsClient as unknown as Client); + store = new TaskStore({ + logger: mockLogger(), + index: 'tasky', + taskManagerId: '', + serializer, + esClient, + definitions: taskDefinitions, + savedObjectsRepository: savedObjectsClient, + adHocTaskCounter, + allowReadingInvalidState: false, + requestTimeouts: { + update_by_query: 1000, + }, + }); + }); + + test('should return the version of the tasks when found', async () => { + childEsClient.mget.mockResponse({ + docs: [ + { + _index: 'ignored-1', + _id: 'task:some-task-a', + _version: 424242, + _seq_no: 123, + _primary_term: 1, + found: true, + }, + { + _index: 'ignored-2', + _id: 'task:some-task-b', + _version: 31415, + _seq_no: 456, + _primary_term: 2, + found: true, + }, + ], + }); + + const result = await store.bulkGetVersions(['task:some-task-a', 'task:some-task-b']); + expect(result).toMatchInlineSnapshot(` + Array [ + Object { + "esId": "task:some-task-a", + "primaryTerm": 1, + "seqNo": 123, + }, + Object { + "esId": "task:some-task-b", + "primaryTerm": 2, + "seqNo": 456, + }, + ] + `); + }); + + test('should handle errors and missing tasks', async () => { + childEsClient.mget.mockResponse({ + docs: [ + { + _index: 'ignored-1', + _id: 'task:some-task-a', + _version: 424242, + _seq_no: 123, + _primary_term: 1, + found: true, + }, + { + _index: 'ignored-2', + _id: 'task:some-task-b', + found: false, + }, + { + _index: 'ignored-3', + _id: 'task:some-task-c', + error: { + type: 'index_not_found_exception', + reason: 'no such index "ignored-4"', + }, + }, + ], + }); + + const result = await store.bulkGetVersions([ + 'task:some-task-a', + 'task:some-task-b', + 'task:some-task-c', + ]); + expect(result).toMatchInlineSnapshot(` + Array [ + Object { + "esId": "task:some-task-a", + "primaryTerm": 1, + "seqNo": 123, + }, + Object { + "error": "task \\"task:some-task-b\\" not found", + "esId": "task:some-task-b", + }, + Object { + "error": "error getting version for task:some-task-c: index_not_found_exception: no such index \\"ignored-4\\"", + "esId": "task:some-task-c", + }, + ] + `); + }); + }); + + describe('getDocVersions', () => { + let store: TaskStore; + let esClient: ReturnType['asInternalUser']; + let childEsClient: ReturnType< + typeof elasticsearchServiceMock.createClusterClient + >['asInternalUser']; + + beforeAll(() => { + esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + childEsClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + esClient.child.mockReturnValue(childEsClient as unknown as Client); + store = new TaskStore({ + logger: mockLogger(), + index: 'tasky', + taskManagerId: '', + serializer, + esClient, + definitions: taskDefinitions, + savedObjectsRepository: savedObjectsClient, + adHocTaskCounter, + allowReadingInvalidState: false, + requestTimeouts: { + update_by_query: 1000, + }, + }); + }); + + test('should return the version as expected, with errors included', async () => { + childEsClient.mget.mockResponse({ + docs: [ + { + _index: 'ignored-1', + _id: 'task:some-task-a', + _version: 424242, + _seq_no: 123, + _primary_term: 1, + found: true, + }, + { + _index: 'ignored-2', + _id: 'task:some-task-b', + found: false, + }, + { + _index: 'ignored-3', + _id: 'task:some-task-c', + error: { + type: 'index_not_found_exception', + reason: 'no such index "ignored-4"', + }, + }, + ], + }); + + const result = await store.getDocVersions([ + 'task:some-task-a', + 'task:some-task-b', + 'task:some-task-c', + ]); + expect(result).toMatchInlineSnapshot(` + Map { + "task:some-task-a" => Object { + "esId": "task:some-task-a", + "primaryTerm": 1, + "seqNo": 123, + }, + "task:some-task-b" => Object { + "error": "task \\"task:some-task-b\\" not found", + "esId": "task:some-task-b", + }, + "task:some-task-c" => Object { + "error": "error getting version for task:some-task-c: index_not_found_exception: no such index \\"ignored-4\\"", + "esId": "task:some-task-c", + }, + } + `); + }); + }); }); diff --git a/x-pack/plugins/task_manager/server/task_store.ts b/x-pack/plugins/task_manager/server/task_store.ts index 45bcb6589ab26..3cc50a05259a5 100644 --- a/x-pack/plugins/task_manager/server/task_store.ts +++ b/x-pack/plugins/task_manager/server/task_store.ts @@ -29,6 +29,7 @@ import { asOk, asErr, Result } from './lib/result_type'; import { ConcreteTaskInstance, + ConcreteTaskInstanceVersion, TaskInstance, TaskLifecycle, TaskLifecycleResult, @@ -77,6 +78,7 @@ export interface UpdateByQueryOpts extends SearchOpts { export interface FetchResult { docs: ConcreteTaskInstance[]; + versionMap: Map; } export type BulkUpdateResult = Result< @@ -415,6 +417,55 @@ export class TaskStore { }); } + /** + * Gets task version info by ids + * + * @param {Array} esIds + * @returns {Promise} + */ + public async bulkGetVersions(ids: string[]): Promise { + let taskVersions: estypes.MgetResponse; + try { + taskVersions = await this.esClientWithoutRetries.mget({ + index: this.index, + _source: false, + body: { + ids, + }, + }); + } catch (e) { + this.errors$.next(e); + throw e; + } + + const result = taskVersions.docs.map((taskVersion) => { + if (isMGetSuccess(taskVersion)) { + if (!taskVersion.found) { + return { + esId: taskVersion._id, + error: `task "${taskVersion._id}" not found`, + }; + } else { + return { + esId: taskVersion._id, + seqNo: taskVersion._seq_no, + primaryTerm: taskVersion._primary_term, + }; + } + } + + const type = taskVersion.error?.type || 'unknown type of error'; + const reason = taskVersion.error?.reason || 'unknown reason'; + const error = `error getting version for ${taskVersion._id}: ${type}: ${reason}`; + return { + esId: taskVersion._id, + error, + }; + }); + + return result; + } + /** * Gets task lifecycle step by id * @@ -437,9 +488,7 @@ export class TaskStore { const { query } = ensureQueryOnlyReturnsTaskObjects(opts); try { - const { - hits: { hits: tasks }, - } = await this.esClientWithoutRetries.search({ + const result = await this.esClientWithoutRetries.search({ index: this.index, ignore_unavailable: true, body: { @@ -447,6 +496,21 @@ export class TaskStore { query, }, }); + const { + hits: { hits: tasks }, + } = result; + + const versionMap = new Map(); + for (const task of tasks) { + if (task._seq_no == null || task._primary_term == null) continue; + + const esId = task._id.startsWith('task:') ? task._id.slice(5) : task._id; + versionMap.set(esId, { + esId: task._id, + seqNo: task._seq_no, + primaryTerm: task._primary_term, + }); + } return { docs: tasks @@ -457,6 +521,7 @@ export class TaskStore { .map((doc) => omit(doc, 'namespace') as SavedObject) .map((doc) => savedObjectToConcreteTaskInstance(doc)) .filter((doc): doc is ConcreteTaskInstance => !!doc), + versionMap, }; } catch (e) { this.errors$.next(e); @@ -527,6 +592,15 @@ export class TaskStore { throw e; } } + + public async getDocVersions(esIds: string[]): Promise> { + const versions = await this.bulkGetVersions(esIds); + const result = new Map(); + for (const version of versions) { + result.set(version.esId, version); + } + return result; + } } /** @@ -614,3 +688,7 @@ function ensureAggregationOnlyReturnsEnabledTaskObjects(opts: AggregationOpts): query, }; } + +function isMGetSuccess(doc: estypes.MgetResponseItem): doc is estypes.GetGetResult { + return (doc as estypes.GetGetResult).found !== undefined; +} diff --git a/x-pack/plugins/task_manager/server/task_type_dictionary.ts b/x-pack/plugins/task_manager/server/task_type_dictionary.ts index ae46978562223..f45cbad172d5a 100644 --- a/x-pack/plugins/task_manager/server/task_type_dictionary.ts +++ b/x-pack/plugins/task_manager/server/task_type_dictionary.ts @@ -113,6 +113,10 @@ export class TaskTypeDictionary { return this.definitions.has(type); } + public size() { + return this.definitions.size; + } + public get(type: string): TaskDefinition { this.ensureHas(type); return this.definitions.get(type)!; diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index b63d1f52b37a3..a42825ed281f6 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -15121,13 +15121,13 @@ "description": "The number of spaces which have solution set to classic." } }, - "search": { + "es": { "type": "long", "_meta": { "description": "The number of spaces which have solution set to search." } }, - "observability": { + "oblt": { "type": "long", "_meta": { "description": "The number of spaces which have solution set to observability." diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index b0abe61986bc1..cb1314b562822 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -547,7 +547,6 @@ "controls.timeSlider.previousLabel": "Fenêtre temporelle précédente", "controls.timeSlider.settings.pinStart": "Épingler le début", "controls.timeSlider.settings.unpinStart": "Désépingler le début", - "core.chrome.browserDeprecationWarning": "La prise en charge d'Internet Explorer sera abandonnée dans les futures versions de ce logiciel. Veuillez consulter le site {link}.", "core.deprecations.deprecations.fetchFailedMessage": "Impossible d'extraire les informations de déclassement pour le plug-in {domainId}.", "core.deprecations.deprecations.fetchFailedTitle": "Impossible d'extraire les déclassements pour {domainId}", "core.deprecations.elasticsearchSSL.manualSteps1": "Ajoutez le paramètre \"{missingSetting}\" à kibana.yml.", @@ -678,7 +677,6 @@ "core.application.appNotFound.pageDescription": "Aucune application détectée pour cette URL. Revenez en arrière ou sélectionnez une application dans le menu.", "core.application.appNotFound.title": "Application introuvable", "core.application.appRenderError.defaultTitle": "Erreur d'application", - "core.chrome.browserDeprecationLink": "la matrice de prise en charge sur notre site web", "core.chrome.legacyBrowserWarning": "Votre navigateur ne satisfait pas aux exigences de sécurité de Kibana.", "core.deprecations.deprecations.fetchFailed.manualStepOneMessage": "Vérifiez le message d'erreur dans les logs de serveur Kibana.", "core.deprecations.elasticsearchUsername.manualSteps1": "Utilisez l'outil CLI elasticsearch-service-tokens afin de créer un jeton pour le compte de service \"elastic/kibana\".", @@ -2398,12 +2396,12 @@ "discover.fieldChooser.discoverField.removeFieldTooltip": "Supprimer le champ du tableau", "discover.globalSearch.esqlSearchTitle": "Créer des recherches ES|QL", "discover.goToDiscoverButtonText": "Aller à Discover", - "discover.grid.flyout.documentNavigation": "Navigation dans le document", - "discover.grid.flyout.toastColumnAdded": "La colonne \"{columnName}\" a été ajoutée.", - "discover.grid.flyout.toastColumnRemoved": "La colonne \"{columnName}\" a été supprimée.", + "unifiedDocViewer.flyout.documentNavigation": "Navigation dans le document", + "unifiedDocViewer.flyout.toastColumnAdded": "La colonne \"{columnName}\" a été ajoutée.", + "unifiedDocViewer.flyout.toastColumnRemoved": "La colonne \"{columnName}\" a été supprimée.", "discover.grid.tableRow.actionsLabel": "Actions", - "discover.grid.tableRow.docViewerDetailHeading": "Document", - "discover.grid.tableRow.docViewerEsqlDetailHeading": "Ligne", + "unifiedDocViewer.flyout.docViewerDetailHeading": "Document", + "unifiedDocViewer.flyout.docViewerEsqlDetailHeading": "Ligne", "discover.grid.tableRow.mobileFlyoutActionsButton": "Actions", "discover.grid.tableRow.moreFlyoutActionsButton": "Plus d'actions", "discover.grid.tableRow.esqlDetailHeading": "Ligne développée", @@ -9436,8 +9434,6 @@ "xpack.apm.onboarding.shared_clients.configure.commands.serviceNameHint": "Le nom de service est le filtre principal dans l'interface utilisateur APM et est utilisé pour regrouper les erreurs et suivre les données ensemble. Caractères autorisés : a-z, A-Z, 0-9, -, _ et espace.", "xpack.apm.pages.alertDetails.alertSummary.actualValue": "Valeur réelle", "xpack.apm.pages.alertDetails.alertSummary.expectedValue": "Valeur attendue", - "xpack.apm.pages.alertDetails.alertSummary.serviceEnv": "Environnement de service", - "xpack.apm.pages.alertDetails.alertSummary.serviceName": "Nom de service", "xpack.apm.profiling.callout.description": "Universal Profiling fournit une visibilité sans précédent du code au milieu du comportement en cours d'exécution de toutes les applications. La fonctionnalité profile chaque ligne de code chez le ou les hôtes qui exécutent vos services, y compris votre code applicatif, le kernel et même les bibliothèque tierces.", "xpack.apm.profiling.callout.dismiss": "Rejeter", "xpack.apm.profiling.callout.learnMore": "En savoir plus", @@ -21393,7 +21389,6 @@ "xpack.infra.metricDetailPage.sqsMetricsLayout.oldestMessage.chartLabel": "Âge", "xpack.infra.metricDetailPage.sqsMetricsLayout.oldestMessage.sectionLabel": "Message le plus ancien", "xpack.infra.metricDetailPage.sqsMetricsLayout.overviewSection.sectionLabel": "Aperçu SQS AWS", - "xpack.infra.metrics.alertDetailsAppSection.summaryField.rule": "Règle", "xpack.infra.metrics.alertDetailsAppSection.thresholdTitle": "Seuil dépassé", "xpack.infra.metrics.alertFlyout.addCondition": "Ajouter une condition", "xpack.infra.metrics.alertFlyout.addWarningThreshold": "Ajouter un seuil d'avertissement", @@ -23465,7 +23460,6 @@ "xpack.lens.shared.tickLabels": "Étiquettes de graduation", "xpack.lens.shared.ticksPositionOptions": "Coches sur les bandes", "xpack.lens.shared.ticksPositionOptionsTooltip": "Place les coches sur chaque bordure de bande au lieu de les répartir de manière homogène.", - "xpack.lens.shared.valueInLegendLabel": "Afficher la valeur", "xpack.lens.shared.valueLabelsVisibility.auto": "Masquer", "xpack.lens.shared.valueLabelsVisibility.inside": "Afficher", "xpack.lens.staticValue.headingLabel": "Placement", @@ -29576,7 +29570,6 @@ "xpack.observability.apmProgressiveLoadingDescription": "{technicalPreviewLabel} S'il faut charger les données de façon progressive pour les vues APM. Les données peuvent être demandées d'abord avec un taux d'échantillonnage inférieur, avec une précision plus faible mais des temps de réponse plus rapides, pendant que les données non échantillonnées se chargent en arrière-plan", "xpack.observability.apmServiceInventoryOptimizedSortingDescription": "{technicalPreviewLabel} Tri par défaut des pages d'inventaire et de stockage des services APM (pour les services hors Machine Learning), en fonction du nom de service.", "xpack.observability.apmTraceExplorerTabDescription": "{technicalPreviewLabel} Activer la fonctionnalité Explorateur de traces APM, qui vous permet de rechercher et d'inspecter les traces avec KQL ou EQL. {link}", - "xpack.observability.customThreshold.rule..charts.noData.title": "Aucune donnée du graphique n'est disponible, vérifiez la règle {errorSourceField}", "xpack.observability.customThreshold.rule.aggregators.average": "{metric} moyen", "xpack.observability.customThreshold.rule.aggregators.cardinality": "Cardinalité de {metric}", "xpack.observability.customThreshold.rule.aggregators.max": "{metric} max.", @@ -29585,7 +29578,6 @@ "xpack.observability.customThreshold.rule.aggregators.p99": "99e centile de {metric}", "xpack.observability.customThreshold.rule.aggregators.rate": "Taux de {metric}", "xpack.observability.customThreshold.rule.aggregators.sum": "Somme de {metric}", - "xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.moreTags": "+{number} de plus", "xpack.observability.customThreshold.rule.alertFlyout.alertPerRedundantFilterError": "Il est possible que cette règle signale {matchedGroups} moins que prévu, car la requête de filtre comporte une correspondance pour {groupCount, plural, one {ce champ} other {ces champs}}. Pour en savoir plus, veuillez consulter {filteringAndGroupingLink}.", "xpack.observability.customThreshold.rule.alertFlyout.condition": "Condition {conditionNumber}", "xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.aggregationLabel": "{name} de l'agrégation", @@ -29690,8 +29682,6 @@ "xpack.observability.customThreshold.alertChartTitle": "Résultat de l'équation pour ", "xpack.observability.customThreshold.alertDetails.logRateAnalysis.sectionTitle": "Analyse du taux de log", "xpack.observability.customThreshold.alertDetails.logRateAnalysisTitle": "Causes possibles et résolutions", - "xpack.observability.customThreshold.rule..charts.error_equation.description": "Vérifiez l'équation de la règle.", - "xpack.observability.customThreshold.rule..charts.error_equation.title": "Une erreur s'est produite lors de l'affichage du graphique", "xpack.observability.customThreshold.rule..charts.errorMessage": "Oups, un problème est survenu", "xpack.observability.customThreshold.rule..charts.noDataMessage": "Aucune donnée graphique disponible", "xpack.observability.customThreshold.rule..timeLabels.days": "jours", @@ -29700,10 +29690,6 @@ "xpack.observability.customThreshold.rule..timeLabels.seconds": "secondes", "xpack.observability.customThreshold.rule.aggregators.customEquation": "Équation personnalisée", "xpack.observability.customThreshold.rule.aggregators.documentCount": "Nombre de documents", - "xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.moreTags.ariaLabel": "badge plus de balises", - "xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.rule": "Règle", - "xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.source": "Source", - "xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.tags": "Balises", "xpack.observability.customThreshold.rule.alertDetailsAppSection.thresholdTitle": "Seuil dépassé", "xpack.observability.customThreshold.rule.alertDetailUrlActionVariableDescription": "Lien vers l’affichage de résolution des problèmes d’alerte pour voir plus de contextes et de détails. La chaîne sera vide si server.publicBaseUrl n'est pas configuré.", "xpack.observability.customThreshold.rule.alertFlyout.addCondition": "Ajouter une condition", @@ -31549,9 +31535,6 @@ "xpack.rollupJobs.jobDetails.tabSummary.sectionLogisticsLabel": "Logistique", "xpack.rollupJobs.jobDetails.tabSummary.sectionStatsTitle": "Statistiques", "xpack.rollupJobs.jobList.createButtonLabel": "Créer une tâche de cumul", - "xpack.rollupJobs.jobList.emptyPrompt.createButtonLabel": "Créer une tâche de cumul", - "xpack.rollupJobs.jobList.emptyPromptDescription": "Les tâches de cumul résument et stockent les données historiques dans un index plus petit pour une analyse ultérieure.", - "xpack.rollupJobs.jobList.emptyPromptTitle": "Créer votre première tâche de cumul", "xpack.rollupJobs.jobList.loadingErrorTitle": "Erreur lors du chargement des tâches de cumul", "xpack.rollupJobs.jobList.loadingTitle": "Chargement des tâches de cumul…", "xpack.rollupJobs.jobList.noPermissionText": "Vous n'êtes pas autorisé à afficher ou à ajouter des tâches de cumul.", @@ -36154,8 +36137,6 @@ "xpack.securitySolution.flyout.entityDetails.userDetails.tableTabLabel": "Tableau", "xpack.securitySolution.flyout.entityDetails.userDetails.tabsLegend": "Onglets des documents de ressources", "xpack.securitySolution.flyout.entityDetails.valuesColumnTitle": "Valeurs", - "xpack.securitySolution.flyout.isolateHost.isolateTitle": "Isoler l'hôte", - "xpack.securitySolution.flyout.isolateHost.releaseTitle": "Libérer l'hôte", "xpack.securitySolution.flyout.left.insights.buttonGroupLegendLabel": "Options des informations exploitables", "xpack.securitySolution.flyout.left.insights.correlations.ancestryAlertsNoDataDescription": "Aucune alerte associée par ancêtre.", "xpack.securitySolution.flyout.left.insights.correlations.nameColumnLabel": "Nom", @@ -36639,7 +36620,6 @@ "xpack.securitySolution.navigation.case": "Cas", "xpack.securitySolution.navigation.coverageOverviewDashboard": "Couverture MITRE ATT&CK®", "xpack.securitySolution.navigation.dashboards": "Tableaux de bord", - "xpack.securitySolution.navigation.detect": "Détecter", "xpack.securitySolution.navigation.detectionResponse": "Détection et réponse", "xpack.securitySolution.navigation.detectionRules": "Règles de détection (SIEM)", "xpack.securitySolution.navigation.ecsDataQualityDashboard": "Qualité des données", @@ -36647,7 +36627,6 @@ "xpack.securitySolution.navigation.entityRiskScore": "Score de risque des entités", "xpack.securitySolution.navigation.exceptions": "Listes d'exceptions partagées", "xpack.securitySolution.navigation.explore": "Explorer", - "xpack.securitySolution.navigation.findings": "Résultats", "xpack.securitySolution.navigation.gettingStarted": "Démarrer", "xpack.securitySolution.navigation.hosts": "Hôtes", "xpack.securitySolution.navigation.kubernetes": "Kubernetes", @@ -36659,7 +36638,6 @@ "xpack.securitySolution.navigation.protectionUpdates": "Mises à jour de la protection", "xpack.securitySolution.navigation.responseActionsHistory": "Historique des actions de réponse", "xpack.securitySolution.navigation.rules": "Règles", - "xpack.securitySolution.navigation.threatIntelligence": "Intelligence", "xpack.securitySolution.navigation.timelines": "Chronologies", "xpack.securitySolution.navigation.users": "Utilisateurs", "xpack.securitySolution.network.ipDetails.ipOverview.asDestinationDropDownOptionLabel": "Comme destination", @@ -39315,7 +39293,6 @@ "xpack.stackConnectors.xmatters.invalidUrlError": "secretsUrl non valide : {err}", "xpack.stackConnectors.xmatters.postingRetryErrorMessage": "Erreur lors du déclenchement du flux xMatters : statut http {status}, réessayer plus tard", "xpack.stackConnectors.xmatters.unexpectedStatusErrorMessage": "Erreur de déclenchement du flux xMatters : statut inattendu {status}", - "xpack.stackConnectors.casesWebhook.invalidUsernamePassword": "l'utilisateur et le mot de passe doivent être spécifiés", "xpack.stackConnectors.casesWebhook.title": "Webhook - Gestion des cas", "xpack.stackConnectors.components.bedrock.accessKeySecret": "Clé d'accès", "xpack.stackConnectors.components.bedrock.apiUrlTextFieldLabel": "URL", @@ -39330,9 +39307,7 @@ "xpack.stackConnectors.components.bedrock.selectMessageText": "Envoyer une requête à Amazon Bedrock.", "xpack.stackConnectors.components.bedrock.title": "Amazon Bedrock", "xpack.stackConnectors.components.bedrock.urlTextFieldLabel": "URL", - "xpack.stackConnectors.components.casesWebhook.addHeaderButton": "Ajouter", "xpack.stackConnectors.components.casesWebhook.addVariable": "Ajouter une variable", - "xpack.stackConnectors.components.casesWebhook.authenticationLabel": "Authentification", "xpack.stackConnectors.components.casesWebhook.caseCommentDesc": "Commentaire de cas Kibana", "xpack.stackConnectors.components.casesWebhook.caseDescriptionDesc": "Description de cas Kibana", "xpack.stackConnectors.components.casesWebhook.caseIdDesc": "ID de cas Kibana", @@ -39355,11 +39330,8 @@ "xpack.stackConnectors.components.casesWebhook.createIncidentResponseKeyTextFieldLabel": "Clé externe dans la réponse de création d'un cas", "xpack.stackConnectors.components.casesWebhook.createIncidentUrlTextFieldLabel": "URL de création d'un cas", "xpack.stackConnectors.components.casesWebhook.criticalLabel": "Critique", - "xpack.stackConnectors.components.casesWebhook.deleteHeaderButton": "Supprimer", "xpack.stackConnectors.components.casesWebhook.descriptionTextAreaFieldLabel": "Description", "xpack.stackConnectors.components.casesWebhook.docLink": "Configuration de Webhook - Connecteur de gestion des cas.", - "xpack.stackConnectors.components.casesWebhook.error.requiredAuthPasswordText": "Le mot de passe est requis.", - "xpack.stackConnectors.components.casesWebhook.error.requiredAuthUserNameText": "Le nom d'utilisateur est requis.", "xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentIncidentText": "L'objet de création de commentaire doit être un JSON valide.", "xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentMethodText": "La méthode de création de commentaire est requise.", "xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentUrlText": "L'URL de création de commentaire doit être au format URL.", @@ -39384,15 +39356,12 @@ "xpack.stackConnectors.components.casesWebhook.getIncidentUrlTextFieldLabel": "URL d'obtention de cas", "xpack.stackConnectors.components.casesWebhook.hasAuthSwitchLabel": "Demander une authentification pour ce webhook", "xpack.stackConnectors.components.casesWebhook.highLabel": "Élevé", - "xpack.stackConnectors.components.casesWebhook.httpHeadersTitle": "En-têtes utilisés", "xpack.stackConnectors.components.casesWebhook.idFieldLabel": "ID de cas", "xpack.stackConnectors.components.casesWebhook.jsonCodeEditorAriaLabel": "Éditeur de code", "xpack.stackConnectors.components.casesWebhook.jsonFieldLabel": "JSON", - "xpack.stackConnectors.components.casesWebhook.keyTextFieldLabel": "Clé", "xpack.stackConnectors.components.casesWebhook.lowLabel": "Bas", "xpack.stackConnectors.components.casesWebhook.mediumLabel": "Moyenne", "xpack.stackConnectors.components.casesWebhook.next": "Suivant", - "xpack.stackConnectors.components.casesWebhook.passwordTextFieldLabel": "Mot de passe", "xpack.stackConnectors.components.casesWebhook.previous": "Précédent", "xpack.stackConnectors.components.casesWebhook.selectMessageText": "Envoyer une requête à un service web de gestion de cas.", "xpack.stackConnectors.components.casesWebhook.severityFieldLabel": "Sévérité", @@ -39417,9 +39386,6 @@ "xpack.stackConnectors.components.casesWebhook.updateIncidentMethodTextFieldLabel": "Méthode de mise à jour de cas", "xpack.stackConnectors.components.casesWebhook.updateIncidentUrlHelp": "URL d'API pour mettre à jour un cas.", "xpack.stackConnectors.components.casesWebhook.updateIncidentUrlTextFieldLabel": "URL de mise à jour du cas", - "xpack.stackConnectors.components.casesWebhook.userTextFieldLabel": "Nom d'utilisateur", - "xpack.stackConnectors.components.casesWebhook.valueTextFieldLabel": "Valeur", - "xpack.stackConnectors.components.casesWebhook.viewHeadersSwitch": "Ajouter un en-tête HTTP", "xpack.stackConnectors.components.casesWebhook.viewIncidentUrlHelp": "URL pour afficher un cas dans le système externe. Utilisez le sélecteur de variable pour ajouter à l'URL l'ID ou le titre du système externe.", "xpack.stackConnectors.components.casesWebhook.viewIncidentUrlTextFieldLabel": "URL de visualisation de cas externe", "xpack.stackConnectors.components.casesWebhookxpack.stackConnectors.components.casesWebhook.connectorTypeTitle": "Webhook - Données de gestion des cas", @@ -39769,39 +39735,15 @@ "xpack.stackConnectors.components.teams.messageTextAreaFieldLabel": "Message", "xpack.stackConnectors.components.teams.selectMessageText": "Envoyer un message à un canal Microsoft Teams.", "xpack.stackConnectors.components.teams.webhookUrlHelpLabel": "Créer une URL de webhook Microsoft Teams", - "xpack.stackConnectors.components.webhook.addHeaderButtonLabel": "Ajouter un en-tête", - "xpack.stackConnectors.components.webhook.authenticationLabel": "Authentification", - "xpack.stackConnectors.components.webhook.authenticationMethodBasicLabel": "Authentification de base", - "xpack.stackConnectors.components.webhook.authenticationMethodNoneLabel": "Aucun", - "xpack.stackConnectors.components.webhook.authenticationMethodSSLLabel": "Authentification SSL", "xpack.stackConnectors.components.webhook.bodyCodeEditorAriaLabel": "Éditeur de code", "xpack.stackConnectors.components.webhook.bodyFieldLabel": "Corps", - "xpack.stackConnectors.components.webhook.certTypeCrtKeyLabel": "Fichier CRT et KEY", - "xpack.stackConnectors.components.webhook.certTypePfxLabel": "Fichier PFX", "xpack.stackConnectors.components.webhook.connectorTypeTitle": "Données de webhook", - "xpack.stackConnectors.components.webhook.editCACallout": "Ce webhook comporte déjà un fichier d'autorité de certificat. Charger un nouveau pour le remplacer.", "xpack.stackConnectors.components.webhook.error.invalidUrlTextField": "L'URL n'est pas valide.", - "xpack.stackConnectors.components.webhook.error.requiredAuthUserNameText": "Le nom d'utilisateur est requis.", "xpack.stackConnectors.components.webhook.error.requiredMethodText": "La méthode est requise.", "xpack.stackConnectors.components.webhook.error.requiredWebhookBodyText": "Le corps est requis.", - "xpack.stackConnectors.components.webhook.error.requiredWebhookCAText": "Le fichier CA est requis.", - "xpack.stackConnectors.components.webhook.error.requiredWebhookCRTText": "Le fichier CRT est requis.", - "xpack.stackConnectors.components.webhook.error.requiredWebhookKEYText": "Le fichier KEY est requis.", - "xpack.stackConnectors.components.webhook.error.requiredWebhookPasswordText": "Le mot de passe est requis.", - "xpack.stackConnectors.components.webhook.error.requiredWebhookPFXText": "Le fichier PFX est requis.", - "xpack.stackConnectors.components.webhook.hasAuthSwitchLabel": "Demander une authentification pour ce webhook", - "xpack.stackConnectors.components.webhook.headerKeyTextFieldLabel": "Clé", - "xpack.stackConnectors.components.webhook.headerValueTextFieldLabel": "Valeur", "xpack.stackConnectors.components.webhook.methodTextFieldLabel": "Méthode", - "xpack.stackConnectors.components.webhook.passphraseTextFieldLabel": "Phrase secrète", - "xpack.stackConnectors.components.webhook.passwordTextFieldLabel": "Mot de passe", - "xpack.stackConnectors.components.webhook.removeHeaderIconLabel": "Clé", "xpack.stackConnectors.components.webhook.selectMessageText": "Envoyer une requête à un service Web.", "xpack.stackConnectors.components.webhook.urlTextFieldLabel": "URL", - "xpack.stackConnectors.components.webhook.userTextFieldLabel": "Nom d'utilisateur", - "xpack.stackConnectors.components.webhook.verificationModeFieldLabel": "Mode de vérification", - "xpack.stackConnectors.components.webhook.viewCertificateAuthoritySwitch": "Ajouter une autorité de certificat", - "xpack.stackConnectors.components.webhook.viewHeadersSwitch": "Ajouter un en-tête HTTP", "xpack.stackConnectors.components.xmatters.authenticationLabel": "Authentification", "xpack.stackConnectors.components.xmatters.basicAuthButtonGroupLegend": "Authentification de base", "xpack.stackConnectors.components.xmatters.basicAuthLabel": "Authentification de base", @@ -39978,7 +39920,6 @@ "xpack.stackConnectors.webhook.authConfigurationError": "erreur lors de la configuration d'action webhook : authType doit être null ou undefined si hasAuth est faux", "xpack.stackConnectors.webhook.invalidResponseErrorMessage": "erreur lors de l'appel de webhook, réponse non valide", "xpack.stackConnectors.webhook.invalidResponseRetryLaterErrorMessage": "erreur lors de l'appel de webhook, réessayer ultérieurement", - "xpack.stackConnectors.webhook.invalidUsernamePassword": "doit préciser l'un des schémas suivants : utilisateur et mot de passe, crt et key (avec mot de passe facultatif) ou pfx (avec mot de passe facultatif)", "xpack.stackConnectors.webhook.requestFailedErrorMessage": "erreur lors de l'appel de webhook, requête échouée", "xpack.stackConnectors.webhook.title": "Webhook", "xpack.stackConnectors.webhook.unexpectedNullResponseErrorMessage": "réponse nulle inattendue de webhook", @@ -44290,8 +44231,6 @@ "uiActions.errors.incompatibleAction": "Action non compatible", "uiActions.triggers.rowClickkDescription": "Un clic sur une ligne de tableau", "uiActions.triggers.rowClickTitle": "Clic sur ligne de tableau", - "unifiedDocViewer.docView.table.actions.label": "Actions", - "unifiedDocViewer.docView.table.actions.open": "Actions ouvertes", "unifiedDocViewer.docView.table.ignored.multiAboveTooltip": "Une ou plusieurs valeurs dans ce champ sont trop longues et ne peuvent pas être recherchées ni filtrées.", "unifiedDocViewer.docView.table.ignored.multiMalformedTooltip": "Ce champ comporte une ou plusieurs valeurs mal formées qui ne peuvent pas être recherchées ni filtrées.", "unifiedDocViewer.docView.table.ignored.multiUnknownTooltip": "Une ou plusieurs valeurs dans ce champ ont été ignorées par Elasticsearch et ne peuvent pas être recherchées ni filtrées.", @@ -44308,7 +44247,6 @@ "unifiedDocViewer.docViews.table.filterOutValueButtonTooltip": "Exclure la valeur", "unifiedDocViewer.docViews.table.ignored.multiValueLabel": "Contient des valeurs ignorées", "unifiedDocViewer.docViews.table.ignored.singleValueLabel": "Valeur ignorée", - "unifiedDocViewer.docViews.table.pinFieldAriaLabel": "Épingler le champ", "unifiedDocViewer.docViews.table.pinFieldLabel": "Épingler le champ", "unifiedDocViewer.docViews.table.tableTitle": "Tableau", "unifiedDocViewer.docViews.table.toggleColumnInTableButtonAriaLabel": "Afficher/Masquer la colonne dans le tableau", @@ -44316,7 +44254,6 @@ "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "Impossible de filtrer sur les champs méta", "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "Impossible de filtrer sur les champs scriptés", "unifiedDocViewer.docViews.table.unindexedFieldsCanNotBeSearchedTooltip": "Les champs non indexés ou les valeurs ignorées ne peuvent pas être recherchés", - "unifiedDocViewer.docViews.table.unpinFieldAriaLabel": "Désépingler le champ", "unifiedDocViewer.docViews.table.unpinFieldLabel": "Désépingler le champ", "unifiedDocViewer.fieldChooser.discoverField.actions": "Actions", "unifiedDocViewer.fieldChooser.discoverField.multiField": "champ multiple", @@ -44681,4 +44618,4 @@ "xpack.serverlessObservability.nav.projectSettings": "Paramètres de projet", "xpack.serverlessObservability.nav.synthetics": "Synthetics" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 9357810f33c97..48eee058b1aeb 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -549,7 +549,6 @@ "controls.timeSlider.previousLabel": "前の時間ウィンドウ", "controls.timeSlider.settings.pinStart": "開始をピン留め", "controls.timeSlider.settings.unpinStart": "開始をピン留め解除", - "core.chrome.browserDeprecationWarning": "このソフトウェアの将来のバージョンでは、Internet Explorerのサポートが削除されます。{link}をご確認ください。", "core.deprecations.deprecations.fetchFailedMessage": "プラグイン{domainId}の廃止予定情報を取得できません。", "core.deprecations.deprecations.fetchFailedTitle": "{domainId}の廃止予定を取得できませんでした", "core.deprecations.elasticsearchSSL.manualSteps1": "\"{missingSetting}\"設定をkibana.ymlに追加します。", @@ -678,7 +677,6 @@ "core.application.appNotFound.pageDescription": "この URL にアプリケーションが見つかりませんでした。前の画面に戻るか、メニューからアプリを選択してみてください。", "core.application.appNotFound.title": "アプリケーションが見つかりません", "core.application.appRenderError.defaultTitle": "アプリケーションエラー", - "core.chrome.browserDeprecationLink": "Web サイトのサポートマトリックス", "core.chrome.legacyBrowserWarning": "ご使用のブラウザが Kibana のセキュリティ要件を満たしていません。", "core.deprecations.deprecations.fetchFailed.manualStepOneMessage": "エラーメッセージについては、Kibanaサーバーログを確認してください。", "core.deprecations.elasticsearchUsername.manualSteps1": "elasticsearch-service-tokens CLIツールを使用して、「elastic/kibana」サービスアカウントの新しいサービスアカウントトークンを作成します。", @@ -2395,12 +2393,12 @@ "discover.fieldChooser.discoverField.removeFieldTooltip": "フィールドを表から削除", "discover.globalSearch.esqlSearchTitle": "ES|QLクエリを作成", "discover.goToDiscoverButtonText": "Discoverに移動", - "discover.grid.flyout.documentNavigation": "ドキュメントナビゲーション", - "discover.grid.flyout.toastColumnAdded": "列'{columnName}'が追加されました", - "discover.grid.flyout.toastColumnRemoved": "列'{columnName}'が削除されました", + "unifiedDocViewer.flyout.documentNavigation": "ドキュメントナビゲーション", + "unifiedDocViewer.flyout.toastColumnAdded": "列'{columnName}'が追加されました", + "unifiedDocViewer.flyout.toastColumnRemoved": "列'{columnName}'が削除されました", "discover.grid.tableRow.actionsLabel": "アクション", - "discover.grid.tableRow.docViewerDetailHeading": "ドキュメント", - "discover.grid.tableRow.docViewerEsqlDetailHeading": "行", + "unifiedDocViewer.flyout.docViewerDetailHeading": "ドキュメント", + "unifiedDocViewer.flyout.docViewerEsqlDetailHeading": "行", "discover.grid.tableRow.mobileFlyoutActionsButton": "アクション", "discover.grid.tableRow.moreFlyoutActionsButton": "さらにアクションを表示", "discover.grid.tableRow.esqlDetailHeading": "展開された行", @@ -9416,8 +9414,6 @@ "xpack.apm.onboarding.shared_clients.configure.commands.serviceNameHint": "このサービス名はAPM UIの主フィルターであり、エラーとトレースデータをグループ化するために使用されます。使用できる文字はA-Z、0-9、-、_、スペースです。", "xpack.apm.pages.alertDetails.alertSummary.actualValue": "実際の値", "xpack.apm.pages.alertDetails.alertSummary.expectedValue": "想定された値", - "xpack.apm.pages.alertDetails.alertSummary.serviceEnv": "サービス環境", - "xpack.apm.pages.alertDetails.alertSummary.serviceName": "サービス名", "xpack.apm.profiling.callout.description": "ユニバーサルプロファイリングは、すべてのアプリケーションの実行時の動作に関して、かつてないほどコードを可視化します。アプリケーションコードだけでなく、カーネルやサードパーティライブラリも含め、サービスを実行するホスト上のすべてのコード行をプロファイリングします。", "xpack.apm.profiling.callout.dismiss": "閉じる", "xpack.apm.profiling.callout.learnMore": "詳細", @@ -21368,7 +21364,6 @@ "xpack.infra.metricDetailPage.sqsMetricsLayout.oldestMessage.chartLabel": "年齢", "xpack.infra.metricDetailPage.sqsMetricsLayout.oldestMessage.sectionLabel": "最も古いメッセージ", "xpack.infra.metricDetailPage.sqsMetricsLayout.overviewSection.sectionLabel": "Aws SQS概要", - "xpack.infra.metrics.alertDetailsAppSection.summaryField.rule": "ルール", "xpack.infra.metrics.alertDetailsAppSection.thresholdTitle": "しきい値を超えました", "xpack.infra.metrics.alertFlyout.addCondition": "条件を追加", "xpack.infra.metrics.alertFlyout.addWarningThreshold": "警告しきい値を追加", @@ -23446,7 +23441,6 @@ "xpack.lens.shared.tickLabels": "目盛ラベル", "xpack.lens.shared.ticksPositionOptions": "帯の目盛", "xpack.lens.shared.ticksPositionOptionsTooltip": "目盛を均等に分布するのではなく、各帯の境界に目盛を表示します", - "xpack.lens.shared.valueInLegendLabel": "値を表示", "xpack.lens.shared.valueLabelsVisibility.auto": "非表示", "xpack.lens.shared.valueLabelsVisibility.inside": "表示", "xpack.lens.staticValue.headingLabel": "配置", @@ -29553,7 +29547,6 @@ "xpack.observability.apmProgressiveLoadingDescription": "{technicalPreviewLabel} APMビューでデータのプログレッシブ読み込みを行うかどうか。サンプリングされていないデータをバックグラウンドで読み込みながら、最初は低いサンプリングレート、低い精度、高速の応答時間でデータを要求できます", "xpack.observability.apmServiceInventoryOptimizedSortingDescription": "{technicalPreviewLabel} サービス名によるデフォルトAPMサービスインベントリおよびストレージエクスプローラーページの並べ替え(機械学習が適用されていないサービス)。", "xpack.observability.apmTraceExplorerTabDescription": "{technicalPreviewLabel} APMトレースエクスプローラー機能を有効にし、KQLまたはEQLでトレースを検索、検査できます。{link}", - "xpack.observability.customThreshold.rule..charts.noData.title": "グラフデータはありません。ルール{errorSourceField}を確認してください", "xpack.observability.customThreshold.rule.aggregators.average": "平均{metric}", "xpack.observability.customThreshold.rule.aggregators.cardinality": "{metric}のカーディナリティ", "xpack.observability.customThreshold.rule.aggregators.max": "最大{metric}", @@ -29562,7 +29555,6 @@ "xpack.observability.customThreshold.rule.aggregators.p99": "{metric}の99パーセンタイル", "xpack.observability.customThreshold.rule.aggregators.rate": "{metric}の比率", "xpack.observability.customThreshold.rule.aggregators.sum": "{metric}の合計", - "xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.moreTags": "その他{number}", "xpack.observability.customThreshold.rule.alertFlyout.alertPerRedundantFilterError": "このルールは想定未満の{matchedGroups}に対してアラートを通知できます。フィルタークエリには{groupCount, plural, one {このフィールド} other {これらのフィールド}}の完全一致が含まれるためです。詳細については、{filteringAndGroupingLink}を参照してください。", "xpack.observability.customThreshold.rule.alertFlyout.condition": "条件{conditionNumber}", "xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.aggregationLabel": "集約{name}", @@ -29668,8 +29660,6 @@ "xpack.observability.customThreshold.alertChartTitle": "式の結果 ", "xpack.observability.customThreshold.alertDetails.logRateAnalysis.sectionTitle": "ログレート分析", "xpack.observability.customThreshold.alertDetails.logRateAnalysisTitle": "考えられる原因と修正方法", - "xpack.observability.customThreshold.rule..charts.error_equation.description": "ルール式を確認してください。", - "xpack.observability.customThreshold.rule..charts.error_equation.title": "グラフの表示中にエラーが発生しました", "xpack.observability.customThreshold.rule..charts.errorMessage": "問題が発生しました", "xpack.observability.customThreshold.rule..charts.noDataMessage": "グラフデータがありません", "xpack.observability.customThreshold.rule..timeLabels.days": "日", @@ -29678,10 +29668,6 @@ "xpack.observability.customThreshold.rule..timeLabels.seconds": "秒", "xpack.observability.customThreshold.rule.aggregators.customEquation": "カスタム等式", "xpack.observability.customThreshold.rule.aggregators.documentCount": "ドキュメントカウント", - "xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.moreTags.ariaLabel": "その他のタグバッジ", - "xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.rule": "ルール", - "xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.source": "送信元", - "xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.tags": "タグ", "xpack.observability.customThreshold.rule.alertDetailsAppSection.thresholdTitle": "しきい値を超えました", "xpack.observability.customThreshold.rule.alertDetailUrlActionVariableDescription": "アラートトラブルシューティングビューにリンクして、さらに詳しい状況や詳細を確認できます。server.publicBaseUrlが構成されていない場合は、空の文字列になります。", "xpack.observability.customThreshold.rule.alertFlyout.addCondition": "条件を追加", @@ -31525,9 +31511,6 @@ "xpack.rollupJobs.jobDetails.tabSummary.sectionLogisticsLabel": "ロジスティクス", "xpack.rollupJobs.jobDetails.tabSummary.sectionStatsTitle": "統計", "xpack.rollupJobs.jobList.createButtonLabel": "ロールアップジョブを作成", - "xpack.rollupJobs.jobList.emptyPrompt.createButtonLabel": "ロールアップジョブを作成", - "xpack.rollupJobs.jobList.emptyPromptDescription": "ロールアップジョブは、今後の分析用に履歴データを小さなインデックスに要約して格納します。", - "xpack.rollupJobs.jobList.emptyPromptTitle": "初めてのロールアップジョブの作成", "xpack.rollupJobs.jobList.loadingErrorTitle": "ロールアップジョブを読み込み中にエラーが発生", "xpack.rollupJobs.jobList.loadingTitle": "ロールアップジョブを読み込み中...", "xpack.rollupJobs.jobList.noPermissionText": "ロールアップジョブの表示または追加パーミッションがありません。", @@ -36129,8 +36112,6 @@ "xpack.securitySolution.flyout.entityDetails.userDetails.tableTabLabel": "表", "xpack.securitySolution.flyout.entityDetails.userDetails.tabsLegend": "アセットドキュメントタブ", "xpack.securitySolution.flyout.entityDetails.valuesColumnTitle": "値", - "xpack.securitySolution.flyout.isolateHost.isolateTitle": "ホストの分離", - "xpack.securitySolution.flyout.isolateHost.releaseTitle": "ホストのリリース", "xpack.securitySolution.flyout.left.insights.buttonGroupLegendLabel": "インサイトオプション", "xpack.securitySolution.flyout.left.insights.correlations.ancestryAlertsNoDataDescription": "上位項目に関連するアラートはありません。", "xpack.securitySolution.flyout.left.insights.correlations.nameColumnLabel": "名前", @@ -36614,7 +36595,6 @@ "xpack.securitySolution.navigation.case": "ケース", "xpack.securitySolution.navigation.coverageOverviewDashboard": "MITRE ATT&CK®の範囲", "xpack.securitySolution.navigation.dashboards": "ダッシュボード", - "xpack.securitySolution.navigation.detect": "検知", "xpack.securitySolution.navigation.detectionResponse": "検出と対応", "xpack.securitySolution.navigation.detectionRules": "検出ルール(SIEM)", "xpack.securitySolution.navigation.ecsDataQualityDashboard": "データ品質", @@ -36622,7 +36602,6 @@ "xpack.securitySolution.navigation.entityRiskScore": "エンティティリスクスコア", "xpack.securitySolution.navigation.exceptions": "共有例外リスト", "xpack.securitySolution.navigation.explore": "探索", - "xpack.securitySolution.navigation.findings": "調査結果", "xpack.securitySolution.navigation.gettingStarted": "使ってみる", "xpack.securitySolution.navigation.hosts": "ホスト", "xpack.securitySolution.navigation.kubernetes": "Kubernetes", @@ -36634,7 +36613,6 @@ "xpack.securitySolution.navigation.protectionUpdates": "保護更新", "xpack.securitySolution.navigation.responseActionsHistory": "対応アクション履歴", "xpack.securitySolution.navigation.rules": "ルール", - "xpack.securitySolution.navigation.threatIntelligence": "インテリジェンス", "xpack.securitySolution.navigation.timelines": "タイムライン", "xpack.securitySolution.navigation.users": "ユーザー", "xpack.securitySolution.network.ipDetails.ipOverview.asDestinationDropDownOptionLabel": "送信先として", @@ -39287,7 +39265,6 @@ "xpack.stackConnectors.xmatters.invalidUrlError": "無効なsecretsUrl:{err}", "xpack.stackConnectors.xmatters.postingRetryErrorMessage": "xMattersフローのトリガーエラー:HTTPステータス{status}。しばらくたってから再試行してください", "xpack.stackConnectors.xmatters.unexpectedStatusErrorMessage": "xMattersフローのトリガーエラー:予期しないステータス{status}", - "xpack.stackConnectors.casesWebhook.invalidUsernamePassword": "ユーザーとパスワードの両方を指定する必要があります", "xpack.stackConnectors.casesWebhook.title": "Webフック - ケース管理", "xpack.stackConnectors.components.bedrock.accessKeySecret": "アクセスキー", "xpack.stackConnectors.components.bedrock.apiUrlTextFieldLabel": "URL", @@ -39302,9 +39279,7 @@ "xpack.stackConnectors.components.bedrock.selectMessageText": "Amazon Bedrockにリクエストを送信します。", "xpack.stackConnectors.components.bedrock.title": "Amazon Bedrock", "xpack.stackConnectors.components.bedrock.urlTextFieldLabel": "URL", - "xpack.stackConnectors.components.casesWebhook.addHeaderButton": "追加", "xpack.stackConnectors.components.casesWebhook.addVariable": "変数を追加", - "xpack.stackConnectors.components.casesWebhook.authenticationLabel": "認証", "xpack.stackConnectors.components.casesWebhook.caseCommentDesc": "Kibanaケースコメント", "xpack.stackConnectors.components.casesWebhook.caseDescriptionDesc": "Kibanaケース説明", "xpack.stackConnectors.components.casesWebhook.caseIdDesc": "KibanaケースID", @@ -39327,11 +39302,8 @@ "xpack.stackConnectors.components.casesWebhook.createIncidentResponseKeyTextFieldLabel": "ケース対応の作成の外部キー", "xpack.stackConnectors.components.casesWebhook.createIncidentUrlTextFieldLabel": "ケースURLを作成", "xpack.stackConnectors.components.casesWebhook.criticalLabel": "重大", - "xpack.stackConnectors.components.casesWebhook.deleteHeaderButton": "削除", "xpack.stackConnectors.components.casesWebhook.descriptionTextAreaFieldLabel": "説明", "xpack.stackConnectors.components.casesWebhook.docLink": "Webフックの構成 - ケース管理コネクター。", - "xpack.stackConnectors.components.casesWebhook.error.requiredAuthPasswordText": "パスワードが必要です。", - "xpack.stackConnectors.components.casesWebhook.error.requiredAuthUserNameText": "ユーザー名が必要です。", "xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentIncidentText": "コメントオブジェクトの作成は有効なJSONでなければなりません。", "xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentMethodText": "コメントメソッドを作成は必須です。", "xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentUrlText": "コメントURLの作成はURL形式でなければなりません。", @@ -39356,15 +39328,12 @@ "xpack.stackConnectors.components.casesWebhook.getIncidentUrlTextFieldLabel": "ケースURLを取得", "xpack.stackConnectors.components.casesWebhook.hasAuthSwitchLabel": "この Web フックの認証が必要です", "xpack.stackConnectors.components.casesWebhook.highLabel": "高", - "xpack.stackConnectors.components.casesWebhook.httpHeadersTitle": "使用中のヘッダー", "xpack.stackConnectors.components.casesWebhook.idFieldLabel": "ケースID", "xpack.stackConnectors.components.casesWebhook.jsonCodeEditorAriaLabel": "コードエディター", "xpack.stackConnectors.components.casesWebhook.jsonFieldLabel": "JSON", - "xpack.stackConnectors.components.casesWebhook.keyTextFieldLabel": "キー", "xpack.stackConnectors.components.casesWebhook.lowLabel": "低", "xpack.stackConnectors.components.casesWebhook.mediumLabel": "中", "xpack.stackConnectors.components.casesWebhook.next": "次へ", - "xpack.stackConnectors.components.casesWebhook.passwordTextFieldLabel": "パスワード", "xpack.stackConnectors.components.casesWebhook.previous": "前へ", "xpack.stackConnectors.components.casesWebhook.selectMessageText": "ケース管理Webサービスにリクエストを送信します。", "xpack.stackConnectors.components.casesWebhook.severityFieldLabel": "深刻度", @@ -39389,9 +39358,6 @@ "xpack.stackConnectors.components.casesWebhook.updateIncidentMethodTextFieldLabel": "ケースメソッドを更新", "xpack.stackConnectors.components.casesWebhook.updateIncidentUrlHelp": "ケースを更新するAPI URL。", "xpack.stackConnectors.components.casesWebhook.updateIncidentUrlTextFieldLabel": "ケースURLを更新", - "xpack.stackConnectors.components.casesWebhook.userTextFieldLabel": "ユーザー名", - "xpack.stackConnectors.components.casesWebhook.valueTextFieldLabel": "値", - "xpack.stackConnectors.components.casesWebhook.viewHeadersSwitch": "HTTP ヘッダーを追加", "xpack.stackConnectors.components.casesWebhook.viewIncidentUrlHelp": "外部システムでケースを表示するURL。変数セレクターを使用して、外部システムIDまたは外部システムタイトルをURLに追加します。", "xpack.stackConnectors.components.casesWebhook.viewIncidentUrlTextFieldLabel": "外部ケース表示URL", "xpack.stackConnectors.components.casesWebhookxpack.stackConnectors.components.casesWebhook.connectorTypeTitle": "Webフック - ケース管理データ", @@ -39741,39 +39707,15 @@ "xpack.stackConnectors.components.teams.messageTextAreaFieldLabel": "メッセージ", "xpack.stackConnectors.components.teams.selectMessageText": "メッセージを Microsoft Teams チャネルに送信します。", "xpack.stackConnectors.components.teams.webhookUrlHelpLabel": "Microsoft Teams Web フック URL を作成", - "xpack.stackConnectors.components.webhook.addHeaderButtonLabel": "ヘッダーを追加", - "xpack.stackConnectors.components.webhook.authenticationLabel": "認証", - "xpack.stackConnectors.components.webhook.authenticationMethodBasicLabel": "基本認証", - "xpack.stackConnectors.components.webhook.authenticationMethodNoneLabel": "なし", - "xpack.stackConnectors.components.webhook.authenticationMethodSSLLabel": "SSL認証", "xpack.stackConnectors.components.webhook.bodyCodeEditorAriaLabel": "コードエディター", "xpack.stackConnectors.components.webhook.bodyFieldLabel": "本文", - "xpack.stackConnectors.components.webhook.certTypeCrtKeyLabel": "CRTおよびKEYファイル", - "xpack.stackConnectors.components.webhook.certTypePfxLabel": "PFXファイル", "xpack.stackConnectors.components.webhook.connectorTypeTitle": "Web フックデータ", - "xpack.stackConnectors.components.webhook.editCACallout": "このWebフックには既存の認証局ファイルがあります。新しいファイルをアップロードして置き換えてください。", "xpack.stackConnectors.components.webhook.error.invalidUrlTextField": "URL が無効です。", - "xpack.stackConnectors.components.webhook.error.requiredAuthUserNameText": "ユーザー名が必要です。", "xpack.stackConnectors.components.webhook.error.requiredMethodText": "メソッドが必要です。", "xpack.stackConnectors.components.webhook.error.requiredWebhookBodyText": "本文が必要です。", - "xpack.stackConnectors.components.webhook.error.requiredWebhookCAText": "CAファイルが必要です。", - "xpack.stackConnectors.components.webhook.error.requiredWebhookCRTText": "CRTファイルが必要です。", - "xpack.stackConnectors.components.webhook.error.requiredWebhookKEYText": "KEYファイルが必要です。", - "xpack.stackConnectors.components.webhook.error.requiredWebhookPasswordText": "パスワードが必要です。", - "xpack.stackConnectors.components.webhook.error.requiredWebhookPFXText": "PFXファイルが必要です。", - "xpack.stackConnectors.components.webhook.hasAuthSwitchLabel": "この Web フックの認証が必要です", - "xpack.stackConnectors.components.webhook.headerKeyTextFieldLabel": "キー", - "xpack.stackConnectors.components.webhook.headerValueTextFieldLabel": "値", "xpack.stackConnectors.components.webhook.methodTextFieldLabel": "メソド", - "xpack.stackConnectors.components.webhook.passphraseTextFieldLabel": "パスフレーズ", - "xpack.stackConnectors.components.webhook.passwordTextFieldLabel": "パスワード", - "xpack.stackConnectors.components.webhook.removeHeaderIconLabel": "キー", "xpack.stackConnectors.components.webhook.selectMessageText": "Web サービスにリクエストを送信してください。", "xpack.stackConnectors.components.webhook.urlTextFieldLabel": "URL", - "xpack.stackConnectors.components.webhook.userTextFieldLabel": "ユーザー名", - "xpack.stackConnectors.components.webhook.verificationModeFieldLabel": "認証モード", - "xpack.stackConnectors.components.webhook.viewCertificateAuthoritySwitch": "認証局を追加", - "xpack.stackConnectors.components.webhook.viewHeadersSwitch": "HTTP ヘッダーを追加", "xpack.stackConnectors.components.xmatters.authenticationLabel": "認証", "xpack.stackConnectors.components.xmatters.basicAuthButtonGroupLegend": "基本認証", "xpack.stackConnectors.components.xmatters.basicAuthLabel": "基本認証", @@ -39950,7 +39892,6 @@ "xpack.stackConnectors.webhook.authConfigurationError": "webフックアクションの構成エラー:hasAuthがfalseの場合、authTypeはnullまたはundefinedでなければなりません", "xpack.stackConnectors.webhook.invalidResponseErrorMessage": "Webフックの呼び出しエラー、無効な応答", "xpack.stackConnectors.webhook.invalidResponseRetryLaterErrorMessage": "Webフックの呼び出しエラー、後ほど再試行", - "xpack.stackConnectors.webhook.invalidUsernamePassword": "ユーザーとパスワード、crtと鍵(任意のパスワード付き)、またはpfx(任意のパスワード付き)のいずれかのスキーマを指定する必要があります", "xpack.stackConnectors.webhook.requestFailedErrorMessage": "Webフックの呼び出しエラー。要求が失敗しました", "xpack.stackConnectors.webhook.title": "Web フック", "xpack.stackConnectors.webhook.unexpectedNullResponseErrorMessage": "Webフックからの予期しないnull応答", @@ -44266,8 +44207,6 @@ "uiActions.errors.incompatibleAction": "操作に互換性がありません", "uiActions.triggers.rowClickkDescription": "テーブル行をクリック", "uiActions.triggers.rowClickTitle": "テーブル行クリック", - "unifiedDocViewer.docView.table.actions.label": "アクション", - "unifiedDocViewer.docView.table.actions.open": "アクションを開く", "unifiedDocViewer.docView.table.ignored.multiAboveTooltip": "このフィールドの1つ以上の値が長すぎるため、検索またはフィルタリングできません。", "unifiedDocViewer.docView.table.ignored.multiMalformedTooltip": "このフィールドは、検索またはフィルタリングできない正しくない形式の値が1つ以上あります。", "unifiedDocViewer.docView.table.ignored.multiUnknownTooltip": "このフィールドの1つ以上の値がElasticsearchによって無視されたため、検索またはフィルタリングできません。", @@ -44284,7 +44223,6 @@ "unifiedDocViewer.docViews.table.filterOutValueButtonTooltip": "値を除外", "unifiedDocViewer.docViews.table.ignored.multiValueLabel": "無視された値を含む", "unifiedDocViewer.docViews.table.ignored.singleValueLabel": "無視された値", - "unifiedDocViewer.docViews.table.pinFieldAriaLabel": "フィールドを固定", "unifiedDocViewer.docViews.table.pinFieldLabel": "フィールドを固定", "unifiedDocViewer.docViews.table.tableTitle": "表", "unifiedDocViewer.docViews.table.toggleColumnInTableButtonAriaLabel": "表の列を切り替える", @@ -44292,7 +44230,6 @@ "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "メタフィールドの有無でフィルタリングできません", "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "スクリプトフィールドの有無でフィルタリングできません", "unifiedDocViewer.docViews.table.unindexedFieldsCanNotBeSearchedTooltip": "インデックスがないフィールドまたは無視された値は検索できません", - "unifiedDocViewer.docViews.table.unpinFieldAriaLabel": "フィールドを固定解除", "unifiedDocViewer.docViews.table.unpinFieldLabel": "フィールドを固定解除", "unifiedDocViewer.fieldChooser.discoverField.actions": "アクション", "unifiedDocViewer.fieldChooser.discoverField.multiField": "複数フィールド", @@ -44657,4 +44594,4 @@ "xpack.serverlessObservability.nav.projectSettings": "プロジェクト設定", "xpack.serverlessObservability.nav.synthetics": "Synthetics" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 491f4d39d9f12..f372694d38107 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -549,7 +549,6 @@ "controls.timeSlider.previousLabel": "上一时间窗口", "controls.timeSlider.settings.pinStart": "固定开始屏幕", "controls.timeSlider.settings.unpinStart": "取消固定开始屏幕", - "core.chrome.browserDeprecationWarning": "本软件的未来版本将放弃对 Internet Explorer 的支持,请查看{link}。", "core.deprecations.deprecations.fetchFailedMessage": "无法提取插件 {domainId} 的弃用信息。", "core.deprecations.deprecations.fetchFailedTitle": "无法提取 {domainId} 的弃用信息", "core.deprecations.elasticsearchSSL.manualSteps1": "将“{missingSetting}”设置添加到 kibana.yml。", @@ -680,7 +679,6 @@ "core.application.appNotFound.pageDescription": "在此 URL 未找到任何应用程序。尝试返回或从菜单中选择应用。", "core.application.appNotFound.title": "未找到应用程序", "core.application.appRenderError.defaultTitle": "应用程序错误", - "core.chrome.browserDeprecationLink": "我们网站上的支持矩阵", "core.chrome.legacyBrowserWarning": "您的浏览器不满足 Kibana 的安全要求。", "core.deprecations.deprecations.fetchFailed.manualStepOneMessage": "请在 Kibana 服务器日志中查看错误消息。", "core.deprecations.elasticsearchUsername.manualSteps1": "使用 elasticsearch-service-tokens CLI 工具为“elastic/kibana”服务帐户创建新的服务帐户令牌。", @@ -2399,12 +2397,12 @@ "discover.fieldChooser.discoverField.removeFieldTooltip": "从表中移除字段", "discover.globalSearch.esqlSearchTitle": "创建 ES|QL 查询", "discover.goToDiscoverButtonText": "前往 Discover", - "discover.grid.flyout.documentNavigation": "文档导航", - "discover.grid.flyout.toastColumnAdded": "已添加列“{columnName}”", - "discover.grid.flyout.toastColumnRemoved": "已移除列“{columnName}”", + "unifiedDocViewer.flyout.documentNavigation": "文档导航", + "unifiedDocViewer.flyout.toastColumnAdded": "已添加列“{columnName}”", + "unifiedDocViewer.flyout.toastColumnRemoved": "已移除列“{columnName}”", "discover.grid.tableRow.actionsLabel": "操作", - "discover.grid.tableRow.docViewerDetailHeading": "文档", - "discover.grid.tableRow.docViewerEsqlDetailHeading": "行", + "unifiedDocViewer.flyout.docViewerDetailHeading": "文档", + "unifiedDocViewer.flyout.docViewerEsqlDetailHeading": "行", "discover.grid.tableRow.mobileFlyoutActionsButton": "操作", "discover.grid.tableRow.moreFlyoutActionsButton": "更多操作", "discover.grid.tableRow.esqlDetailHeading": "已展开行", @@ -9439,8 +9437,6 @@ "xpack.apm.onboarding.shared_clients.configure.commands.serviceNameHint": "服务名称是 APM UI 中的初级筛选,用于分组错误并跟踪数据。允许使用的字符包括 a-z、A-Z、0-9、-、_ 和空格。", "xpack.apm.pages.alertDetails.alertSummary.actualValue": "实际值", "xpack.apm.pages.alertDetails.alertSummary.expectedValue": "预期值", - "xpack.apm.pages.alertDetails.alertSummary.serviceEnv": "服务环境", - "xpack.apm.pages.alertDetails.alertSummary.serviceName": "服务名称", "xpack.apm.profiling.callout.description": "Universal Profiling 为所有应用程序的运行时行为提供了前所未有的代码可见性。它会剖析运行服务的主机上的每一行代码,不仅包括您的应用程序代码,而且包括内核和第三方库。", "xpack.apm.profiling.callout.dismiss": "关闭", "xpack.apm.profiling.callout.learnMore": "了解详情", @@ -21400,7 +21396,6 @@ "xpack.infra.metricDetailPage.sqsMetricsLayout.oldestMessage.chartLabel": "存在时间", "xpack.infra.metricDetailPage.sqsMetricsLayout.oldestMessage.sectionLabel": "最旧消息", "xpack.infra.metricDetailPage.sqsMetricsLayout.overviewSection.sectionLabel": "Aws SQS 概览", - "xpack.infra.metrics.alertDetailsAppSection.summaryField.rule": "规则", "xpack.infra.metrics.alertDetailsAppSection.thresholdTitle": "超出阈值", "xpack.infra.metrics.alertFlyout.addCondition": "添加条件", "xpack.infra.metrics.alertFlyout.addWarningThreshold": "添加警告阈值", @@ -23479,7 +23474,6 @@ "xpack.lens.shared.tickLabels": "刻度标签", "xpack.lens.shared.ticksPositionOptions": "带上的刻度", "xpack.lens.shared.ticksPositionOptionsTooltip": "将刻度放在每个带边框上,而不是平均分布", - "xpack.lens.shared.valueInLegendLabel": "显示值", "xpack.lens.shared.valueLabelsVisibility.auto": "隐藏", "xpack.lens.shared.valueLabelsVisibility.inside": "显示", "xpack.lens.staticValue.headingLabel": "位置", @@ -29593,7 +29587,6 @@ "xpack.observability.apmProgressiveLoadingDescription": "{technicalPreviewLabel} 是否以渐进方式为 APM 视图加载数据。可以先以较低的采样速率请求数据,这样的准确性较低,但响应时间更快,同时在后台加载未采样数据", "xpack.observability.apmServiceInventoryOptimizedSortingDescription": "{technicalPreviewLabel} 默认 APM 服务库存和 Storage Explorer 页面排序(对于未应用 Machine Learning 的服务)将按服务名称排序。", "xpack.observability.apmTraceExplorerTabDescription": "{technicalPreviewLabel} 启用 APM Trace Explorer 功能,它允许您通过 KQL 或 EQL 搜索和检查跟踪。{link}", - "xpack.observability.customThreshold.rule..charts.noData.title": "没有可用图表数据,请检查规则 {errorSourceField}", "xpack.observability.customThreshold.rule.aggregators.average": "平均值 {metric}", "xpack.observability.customThreshold.rule.aggregators.cardinality": "{metric} 的基数", "xpack.observability.customThreshold.rule.aggregators.max": "{metric} 最大值", @@ -29602,7 +29595,6 @@ "xpack.observability.customThreshold.rule.aggregators.p99": "{metric} 的第 99 个百分位", "xpack.observability.customThreshold.rule.aggregators.rate": "{metric} 的比率", "xpack.observability.customThreshold.rule.aggregators.sum": "{metric} 的总和", - "xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.moreTags": "+ 另外 {number} 个", "xpack.observability.customThreshold.rule.alertFlyout.alertPerRedundantFilterError": "此规则可能针对低于预期的 {matchedGroups} 告警,因为筛选查询包含{groupCount, plural, one {此字段} other {这些字段}}的匹配项。有关更多信息,请参阅 {filteringAndGroupingLink}。", "xpack.observability.customThreshold.rule.alertFlyout.condition": "条件 {conditionNumber}", "xpack.observability.customThreshold.rule.alertFlyout.customEquationEditor.aggregationLabel": "聚合 {name}", @@ -29708,8 +29700,6 @@ "xpack.observability.customThreshold.alertChartTitle": "方程结果用于 ", "xpack.observability.customThreshold.alertDetails.logRateAnalysis.sectionTitle": "日志速率分析", "xpack.observability.customThreshold.alertDetails.logRateAnalysisTitle": "可能的原因和补救措施", - "xpack.observability.customThreshold.rule..charts.error_equation.description": "检查规则方程。", - "xpack.observability.customThreshold.rule..charts.error_equation.title": "渲染图表时出错", "xpack.observability.customThreshold.rule..charts.errorMessage": "哇哦,出问题了", "xpack.observability.customThreshold.rule..charts.noDataMessage": "没有可用图表数据", "xpack.observability.customThreshold.rule..timeLabels.days": "天", @@ -29718,10 +29708,6 @@ "xpack.observability.customThreshold.rule..timeLabels.seconds": "秒", "xpack.observability.customThreshold.rule.aggregators.customEquation": "定制方程", "xpack.observability.customThreshold.rule.aggregators.documentCount": "文档计数", - "xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.moreTags.ariaLabel": "更多标签徽章", - "xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.rule": "规则", - "xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.source": "源", - "xpack.observability.customThreshold.rule.alertDetailsAppSection.summaryField.tags": "标签", "xpack.observability.customThreshold.rule.alertDetailsAppSection.thresholdTitle": "超出阈值", "xpack.observability.customThreshold.rule.alertDetailUrlActionVariableDescription": "链接到告警故障排除视图获取进一步的上下文和详情。如果未配置 server.publicBaseUrl,这将为空字符串。", "xpack.observability.customThreshold.rule.alertFlyout.addCondition": "添加条件", @@ -31566,9 +31552,6 @@ "xpack.rollupJobs.jobDetails.tabSummary.sectionLogisticsLabel": "运筹", "xpack.rollupJobs.jobDetails.tabSummary.sectionStatsTitle": "统计信息", "xpack.rollupJobs.jobList.createButtonLabel": "创建汇总/打包作业", - "xpack.rollupJobs.jobList.emptyPrompt.createButtonLabel": "创建汇总/打包作业", - "xpack.rollupJobs.jobList.emptyPromptDescription": "汇总/打包作业可汇总历史数据并将其存储在较小的索引中以供将来分析。", - "xpack.rollupJobs.jobList.emptyPromptTitle": "创建您的首个汇总/打包作业", "xpack.rollupJobs.jobList.loadingErrorTitle": "加载汇总/打包作业时出错", "xpack.rollupJobs.jobList.loadingTitle": "正在加载汇总/打包作业……", "xpack.rollupJobs.jobList.noPermissionText": "您没有权限查看或添加汇总/打包作业。", @@ -36172,8 +36155,6 @@ "xpack.securitySolution.flyout.entityDetails.userDetails.tableTabLabel": "表", "xpack.securitySolution.flyout.entityDetails.userDetails.tabsLegend": "资产文档选项卡", "xpack.securitySolution.flyout.entityDetails.valuesColumnTitle": "值", - "xpack.securitySolution.flyout.isolateHost.isolateTitle": "隔离主机", - "xpack.securitySolution.flyout.isolateHost.releaseTitle": "释放主机", "xpack.securitySolution.flyout.left.insights.buttonGroupLegendLabel": "洞见选项", "xpack.securitySolution.flyout.left.insights.correlations.ancestryAlertsNoDataDescription": "无告警与体系相关。", "xpack.securitySolution.flyout.left.insights.correlations.nameColumnLabel": "名称", @@ -36657,7 +36638,6 @@ "xpack.securitySolution.navigation.case": "案例", "xpack.securitySolution.navigation.coverageOverviewDashboard": "MITRE ATT&CK® 支持", "xpack.securitySolution.navigation.dashboards": "仪表板", - "xpack.securitySolution.navigation.detect": "检测", "xpack.securitySolution.navigation.detectionResponse": "检测和响应", "xpack.securitySolution.navigation.detectionRules": "检测规则 (SIEM)", "xpack.securitySolution.navigation.ecsDataQualityDashboard": "数据质量", @@ -36665,7 +36645,6 @@ "xpack.securitySolution.navigation.entityRiskScore": "实体风险分数", "xpack.securitySolution.navigation.exceptions": "共享例外列表", "xpack.securitySolution.navigation.explore": "浏览", - "xpack.securitySolution.navigation.findings": "结果", "xpack.securitySolution.navigation.gettingStarted": "开始使用", "xpack.securitySolution.navigation.hosts": "主机", "xpack.securitySolution.navigation.kubernetes": "Kubernetes", @@ -36677,7 +36656,6 @@ "xpack.securitySolution.navigation.protectionUpdates": "防护更新", "xpack.securitySolution.navigation.responseActionsHistory": "响应操作历史记录", "xpack.securitySolution.navigation.rules": "规则", - "xpack.securitySolution.navigation.threatIntelligence": "情报", "xpack.securitySolution.navigation.timelines": "时间线", "xpack.securitySolution.navigation.users": "用户", "xpack.securitySolution.network.ipDetails.ipOverview.asDestinationDropDownOptionLabel": "作为目标", @@ -39333,7 +39311,6 @@ "xpack.stackConnectors.xmatters.invalidUrlError": "secretsUrl 无效:{err}", "xpack.stackConnectors.xmatters.postingRetryErrorMessage": "触发 xMatters 流时出错:http 状态为 {status},请稍后重试", "xpack.stackConnectors.xmatters.unexpectedStatusErrorMessage": "触发 xMatters 流时出错:非预期状态 {status}", - "xpack.stackConnectors.casesWebhook.invalidUsernamePassword": "必须指定用户及密码", "xpack.stackConnectors.casesWebhook.title": "Webhook - 案例管理", "xpack.stackConnectors.components.bedrock.accessKeySecret": "访问密钥", "xpack.stackConnectors.components.bedrock.apiUrlTextFieldLabel": "URL", @@ -39348,9 +39325,7 @@ "xpack.stackConnectors.components.bedrock.selectMessageText": "向 Amazon Bedrock 发送请求。", "xpack.stackConnectors.components.bedrock.title": "Amazon Bedrock", "xpack.stackConnectors.components.bedrock.urlTextFieldLabel": "URL", - "xpack.stackConnectors.components.casesWebhook.addHeaderButton": "添加", "xpack.stackConnectors.components.casesWebhook.addVariable": "添加变量", - "xpack.stackConnectors.components.casesWebhook.authenticationLabel": "身份验证", "xpack.stackConnectors.components.casesWebhook.caseCommentDesc": "Kibana 案例注释", "xpack.stackConnectors.components.casesWebhook.caseDescriptionDesc": "Kibana 案例描述", "xpack.stackConnectors.components.casesWebhook.caseIdDesc": "Kibana 案例 ID", @@ -39373,11 +39348,8 @@ "xpack.stackConnectors.components.casesWebhook.createIncidentResponseKeyTextFieldLabel": "创建案例响应外部键", "xpack.stackConnectors.components.casesWebhook.createIncidentUrlTextFieldLabel": "创建案例 URL", "xpack.stackConnectors.components.casesWebhook.criticalLabel": "紧急", - "xpack.stackConnectors.components.casesWebhook.deleteHeaderButton": "删除", "xpack.stackConnectors.components.casesWebhook.descriptionTextAreaFieldLabel": "描述", "xpack.stackConnectors.components.casesWebhook.docLink": "正在配置 Webhook - 案例管理连接器。", - "xpack.stackConnectors.components.casesWebhook.error.requiredAuthPasswordText": "“密码”必填。", - "xpack.stackConnectors.components.casesWebhook.error.requiredAuthUserNameText": "“用户名”必填。", "xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentIncidentText": "创建注释对象必须为有效 JSON。", "xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentMethodText": "“创建注释方法”必填。", "xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentUrlText": "创建注释 URL 必须为 URL 格式。", @@ -39402,15 +39374,12 @@ "xpack.stackConnectors.components.casesWebhook.getIncidentUrlTextFieldLabel": "获取案例 URL", "xpack.stackConnectors.components.casesWebhook.hasAuthSwitchLabel": "此 Webhook 需要身份验证", "xpack.stackConnectors.components.casesWebhook.highLabel": "高", - "xpack.stackConnectors.components.casesWebhook.httpHeadersTitle": "在用的标头", "xpack.stackConnectors.components.casesWebhook.idFieldLabel": "案例 ID", "xpack.stackConnectors.components.casesWebhook.jsonCodeEditorAriaLabel": "代码编辑器", "xpack.stackConnectors.components.casesWebhook.jsonFieldLabel": "JSON", - "xpack.stackConnectors.components.casesWebhook.keyTextFieldLabel": "钥匙", "xpack.stackConnectors.components.casesWebhook.lowLabel": "低", "xpack.stackConnectors.components.casesWebhook.mediumLabel": "中", "xpack.stackConnectors.components.casesWebhook.next": "下一步", - "xpack.stackConnectors.components.casesWebhook.passwordTextFieldLabel": "密码", "xpack.stackConnectors.components.casesWebhook.previous": "上一步", "xpack.stackConnectors.components.casesWebhook.selectMessageText": "发送请求到案例管理 Web 服务。", "xpack.stackConnectors.components.casesWebhook.severityFieldLabel": "严重性", @@ -39435,9 +39404,6 @@ "xpack.stackConnectors.components.casesWebhook.updateIncidentMethodTextFieldLabel": "更新案例方法", "xpack.stackConnectors.components.casesWebhook.updateIncidentUrlHelp": "用于更新案例的 API URL。", "xpack.stackConnectors.components.casesWebhook.updateIncidentUrlTextFieldLabel": "更新案例 URL", - "xpack.stackConnectors.components.casesWebhook.userTextFieldLabel": "用户名", - "xpack.stackConnectors.components.casesWebhook.valueTextFieldLabel": "值", - "xpack.stackConnectors.components.casesWebhook.viewHeadersSwitch": "添加 HTTP 标头", "xpack.stackConnectors.components.casesWebhook.viewIncidentUrlHelp": "用于查看外部系统中的案例的 URL。使用变量选择器添加外部系统 ID 或外部系统标题到 URL。", "xpack.stackConnectors.components.casesWebhook.viewIncidentUrlTextFieldLabel": "外部案例查看 URL", "xpack.stackConnectors.components.casesWebhookxpack.stackConnectors.components.casesWebhook.connectorTypeTitle": "Webhook - 案例管理数据", @@ -39787,39 +39753,15 @@ "xpack.stackConnectors.components.teams.messageTextAreaFieldLabel": "消息", "xpack.stackConnectors.components.teams.selectMessageText": "向 Microsoft Teams 频道发送消息。", "xpack.stackConnectors.components.teams.webhookUrlHelpLabel": "创建 Microsoft Teams Webhook URL", - "xpack.stackConnectors.components.webhook.addHeaderButtonLabel": "添加标头", - "xpack.stackConnectors.components.webhook.authenticationLabel": "身份验证", - "xpack.stackConnectors.components.webhook.authenticationMethodBasicLabel": "基本身份验证", - "xpack.stackConnectors.components.webhook.authenticationMethodNoneLabel": "无", - "xpack.stackConnectors.components.webhook.authenticationMethodSSLLabel": "SSL 身份验证", "xpack.stackConnectors.components.webhook.bodyCodeEditorAriaLabel": "代码编辑器", "xpack.stackConnectors.components.webhook.bodyFieldLabel": "正文", - "xpack.stackConnectors.components.webhook.certTypeCrtKeyLabel": "CRT 和 KEY 文件", - "xpack.stackConnectors.components.webhook.certTypePfxLabel": "PFX 文件", "xpack.stackConnectors.components.webhook.connectorTypeTitle": "Webhook 数据", - "xpack.stackConnectors.components.webhook.editCACallout": "此 Webhook 具有现有证书颁发机构文件。上传新文件将其替换。", "xpack.stackConnectors.components.webhook.error.invalidUrlTextField": "URL 无效。", - "xpack.stackConnectors.components.webhook.error.requiredAuthUserNameText": "“用户名”必填。", "xpack.stackConnectors.components.webhook.error.requiredMethodText": "“方法”必填", "xpack.stackConnectors.components.webhook.error.requiredWebhookBodyText": "“正文”必填。", - "xpack.stackConnectors.components.webhook.error.requiredWebhookCAText": "CA 文件必填。", - "xpack.stackConnectors.components.webhook.error.requiredWebhookCRTText": "CRT 文件必填。", - "xpack.stackConnectors.components.webhook.error.requiredWebhookKEYText": "KEY 文件必填。", - "xpack.stackConnectors.components.webhook.error.requiredWebhookPasswordText": "“密码”必填。", - "xpack.stackConnectors.components.webhook.error.requiredWebhookPFXText": "PFX 文件必填。", - "xpack.stackConnectors.components.webhook.hasAuthSwitchLabel": "此 Webhook 需要身份验证", - "xpack.stackConnectors.components.webhook.headerKeyTextFieldLabel": "钥匙", - "xpack.stackConnectors.components.webhook.headerValueTextFieldLabel": "值", "xpack.stackConnectors.components.webhook.methodTextFieldLabel": "方法", - "xpack.stackConnectors.components.webhook.passphraseTextFieldLabel": "密码", - "xpack.stackConnectors.components.webhook.passwordTextFieldLabel": "密码", - "xpack.stackConnectors.components.webhook.removeHeaderIconLabel": "钥匙", "xpack.stackConnectors.components.webhook.selectMessageText": "将请求发送到 Web 服务。", "xpack.stackConnectors.components.webhook.urlTextFieldLabel": "URL", - "xpack.stackConnectors.components.webhook.userTextFieldLabel": "用户名", - "xpack.stackConnectors.components.webhook.verificationModeFieldLabel": "验证模式", - "xpack.stackConnectors.components.webhook.viewCertificateAuthoritySwitch": "添加证书颁发机构", - "xpack.stackConnectors.components.webhook.viewHeadersSwitch": "添加 HTTP 标头", "xpack.stackConnectors.components.xmatters.authenticationLabel": "身份验证", "xpack.stackConnectors.components.xmatters.basicAuthButtonGroupLegend": "基本身份验证", "xpack.stackConnectors.components.xmatters.basicAuthLabel": "基本身份验证", @@ -39996,7 +39938,6 @@ "xpack.stackConnectors.webhook.authConfigurationError": "配置 Webhook 操作时出错:如果 hasAuth 为 false,authType 必须为 null 或未定义", "xpack.stackConnectors.webhook.invalidResponseErrorMessage": "调用 webhook 时出错,响应无效", "xpack.stackConnectors.webhook.invalidResponseRetryLaterErrorMessage": "调用 webhook 时出错,请稍后重试", - "xpack.stackConnectors.webhook.invalidUsernamePassword": "必须指定以下方案之一:用户和密码;crt 和密钥(密码可选);或 pfx(密码可选)", "xpack.stackConnectors.webhook.requestFailedErrorMessage": "调用 webhook 时出错,请求失败", "xpack.stackConnectors.webhook.title": "Webhook", "xpack.stackConnectors.webhook.unexpectedNullResponseErrorMessage": "来自 Webhook 的异常空响应", @@ -44314,8 +44255,6 @@ "uiActions.errors.incompatibleAction": "操作不兼容", "uiActions.triggers.rowClickkDescription": "表格行的单击", "uiActions.triggers.rowClickTitle": "表格行单击", - "unifiedDocViewer.docView.table.actions.label": "操作", - "unifiedDocViewer.docView.table.actions.open": "打开操作", "unifiedDocViewer.docView.table.ignored.multiAboveTooltip": "此字段中的一个或多个值过长,无法搜索或筛选。", "unifiedDocViewer.docView.table.ignored.multiMalformedTooltip": "此字段包含一个或多个格式错误的值,无法搜索或筛选。", "unifiedDocViewer.docView.table.ignored.multiUnknownTooltip": "此字段中的一个或多个值被 Elasticsearch 忽略,无法搜索或筛选。", @@ -44332,7 +44271,6 @@ "unifiedDocViewer.docViews.table.filterOutValueButtonTooltip": "筛除值", "unifiedDocViewer.docViews.table.ignored.multiValueLabel": "包含被忽略的值", "unifiedDocViewer.docViews.table.ignored.singleValueLabel": "被忽略的值", - "unifiedDocViewer.docViews.table.pinFieldAriaLabel": "固定字段", "unifiedDocViewer.docViews.table.pinFieldLabel": "固定字段", "unifiedDocViewer.docViews.table.tableTitle": "表", "unifiedDocViewer.docViews.table.toggleColumnInTableButtonAriaLabel": "在表中切换列", @@ -44340,7 +44278,6 @@ "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "无法筛选元数据字段是否存在", "unifiedDocViewer.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "无法筛选脚本字段是否存在", "unifiedDocViewer.docViews.table.unindexedFieldsCanNotBeSearchedTooltip": "无法搜索未编入索引的字段或被忽略的值", - "unifiedDocViewer.docViews.table.unpinFieldAriaLabel": "取消固定字段", "unifiedDocViewer.docViews.table.unpinFieldLabel": "取消固定字段", "unifiedDocViewer.fieldChooser.discoverField.actions": "操作", "unifiedDocViewer.fieldChooser.discoverField.multiField": "多字段", @@ -44705,4 +44642,4 @@ "xpack.serverlessObservability.nav.projectSettings": "项目设置", "xpack.serverlessObservability.nav.synthetics": "Synthetics" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/triggers_actions_ui/common/data/lib/parse_aggregation_results.test.ts b/x-pack/plugins/triggers_actions_ui/common/data/lib/parse_aggregation_results.test.ts index e22d4959c6093..b881aa7609954 100644 --- a/x-pack/plugins/triggers_actions_ui/common/data/lib/parse_aggregation_results.test.ts +++ b/x-pack/plugins/triggers_actions_ui/common/data/lib/parse_aggregation_results.test.ts @@ -843,9 +843,9 @@ describe('parseAggregationResults', () => { sampleEsqlSourceFieldsHit, ], sourceFields: { - 'host.hostname': ['host-1'], - 'host.id': ['1'], - 'host.name': ['host-1'], + 'host.hostname': ['host-1', 'host-1', 'host-1', 'host-1'], + 'host.id': ['1', '1', '1', '1'], + 'host.name': ['host-1', 'host-1', 'host-1', 'host-1'], }, }, ], diff --git a/x-pack/plugins/triggers_actions_ui/common/data/lib/parse_aggregation_results.ts b/x-pack/plugins/triggers_actions_ui/common/data/lib/parse_aggregation_results.ts index 6fe7040abacc3..756f79dceec97 100644 --- a/x-pack/plugins/triggers_actions_ui/common/data/lib/parse_aggregation_results.ts +++ b/x-pack/plugins/triggers_actions_ui/common/data/lib/parse_aggregation_results.ts @@ -87,10 +87,10 @@ export const parseAggregationResults = ({ sourceFieldsParams.forEach((field) => { if (generateSourceFieldsFromHits) { - const fieldsSet = new Set(); + const fieldsSet: string[] = []; groupBucket.topHitsAgg.hits.hits.forEach((hit: SearchHit<{ [key: string]: string }>) => { if (hit._source && hit._source[field.label]) { - fieldsSet.add(hit._source[field.label]); + fieldsSet.push(hit._source[field.label]); } }); sourceFields[field.label] = Array.from(fieldsSet); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/action_type_registry.mock.ts b/x-pack/plugins/triggers_actions_ui/public/application/action_type_registry.mock.ts index ad1bc979ba239..b169d37614ab3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/action_type_registry.mock.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/action_type_registry.mock.ts @@ -5,40 +5,4 @@ * 2.0. */ -import React, { lazy } from 'react'; -import { v4 as uuidv4 } from 'uuid'; -import { ActionTypeModel, ActionTypeRegistryContract } from '../types'; - -const createActionTypeRegistryMock = () => { - const mocked: jest.Mocked = { - has: jest.fn((x) => true), - register: jest.fn(), - get: jest.fn(), - list: jest.fn(), - }; - return mocked; -}; - -const mockedActionParamsFields = lazy(async () => ({ - default() { - return React.createElement(React.Fragment); - }, -})); - -const createMockActionTypeModel = (actionType: Partial = {}): ActionTypeModel => { - const id = uuidv4(); - return { - id, - iconClass: `iconClass-${id}`, - selectMessage: `selectMessage-${id}`, - validateParams: jest.fn(), - actionConnectorFields: null, - actionParamsFields: mockedActionParamsFields, - ...actionType, - }; -}; - -export const actionTypeRegistryMock = { - create: createActionTypeRegistryMock, - createMockActionTypeModel, -}; +export { actionTypeRegistryMock } from '@kbn/alerts-ui-shared/src/common/test_utils/action_type_registry.mock'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx index 909b347b38289..2dd71c7ee1773 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx @@ -15,13 +15,13 @@ import { i18n } from '@kbn/i18n'; import { EuiEmptyPrompt } from '@elastic/eui'; import { DocLinksStart, HttpSetup } from '@kbn/core/public'; -import { AlertingFrameworkHealth } from '@kbn/alerting-plugin/common'; +import { AlertingFrameworkHealth } from '@kbn/alerting-types'; import './health_check.scss'; +import { fetchUiHealthStatus as triggersActionsUiHealth } from '@kbn/alerts-ui-shared/src/common/apis/fetch_ui_health_status'; +import { fetchAlertingFrameworkHealth as alertingFrameworkHealth } from '@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health'; import { useHealthContext } from '../context/health_context'; import { useKibana } from '../../common/lib/kibana'; import { CenterJustifiedSpinner } from './center_justified_spinner'; -import { triggersActionsUiHealth } from '../../common/lib/health_api'; -import { alertingFrameworkHealth } from '../lib/rule_api/health'; interface Props { inFlyout?: boolean; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_config_query.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_config_query.ts index d852bde4c91f7..bc3420ecf346c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_config_query.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_config_query.ts @@ -5,8 +5,8 @@ * 2.0. */ import { useQuery } from '@tanstack/react-query'; +import { fetchUiConfig as triggersActionsUiConfig } from '@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config'; import { useKibana } from '../../common/lib/kibana'; -import { triggersActionsUiConfig } from '../../common/lib/config_api'; export const useLoadConfigQuery = () => { const { http } = useKibana().services; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/health.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/health.ts deleted file mode 100644 index 80962de1b17f8..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/health.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { HttpSetup } from '@kbn/core/public'; -import { AsApiContract, RewriteRequestCase } from '@kbn/actions-plugin/common'; -import { AlertingFrameworkHealth, AlertsHealth } from '@kbn/alerting-plugin/common'; -import { BASE_ALERTING_API_PATH } from '../../constants'; - -const rewriteAlertingFrameworkHealth: RewriteRequestCase = ({ - decryption_health: decryptionHealth, - execution_health: executionHealth, - read_health: readHealth, - ...res -}: AsApiContract) => ({ - decryptionHealth, - executionHealth, - readHealth, - ...res, -}); - -const rewriteBodyRes: RewriteRequestCase = ({ - is_sufficiently_secure: isSufficientlySecure, - has_permanent_encryption_key: hasPermanentEncryptionKey, - // eslint-disable-next-line @typescript-eslint/no-shadow - alerting_framework_health: alertingFrameworkHealth, - ...res -}: AsApiContract) => ({ - isSufficientlySecure, - hasPermanentEncryptionKey, - alertingFrameworkHealth, - ...res, -}); - -export async function alertingFrameworkHealth({ - http, -}: { - http: HttpSetup; -}): Promise { - const res = await http.get>( - `${BASE_ALERTING_API_PATH}/_health` - ); - const alertingFrameworkHealthRewrited = rewriteAlertingFrameworkHealth( - res.alerting_framework_health as unknown as AsApiContract - ); - return { - ...rewriteBodyRes(res), - alertingFrameworkHealth: alertingFrameworkHealthRewrited, - }; -} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx index e04a0d6e42a1f..a565f2ae56982 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.tsx @@ -60,7 +60,7 @@ export const TestConnectorForm = ({ (async () => { const res = (await actionTypeModel?.validateParams(actionParams)).errors as IErrorObject; setActionErrors({ ...res }); - setHasErrors(!!Object.values(res).find((errors) => errors.length > 0)); + setHasErrors(!!Object.values(res).find((errors) => (errors.length as number) > 0)); })(); }, [actionTypeModel, actionParams]); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index 5f583790d3b8b..d7f5a2beb4c8f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -236,7 +236,7 @@ const ActionsConnectorsList: React.FunctionComponent = () => { const name = getConnectorName(value, item); const link = ( - + ({ jest.mock('../../../lib/rule_api/get_rule', () => ({ loadRule: jest.fn(), })); -jest.mock('../../../lib/rule_api/resolve_rule', () => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/resolve_rule', () => ({ resolveRule: jest.fn(), })); jest.mock('../../../lib/rule_api/rule_types', () => ({ @@ -59,7 +59,7 @@ const { bulkDeleteRules } = jest.requireMock('../../../lib/rule_api/bulk_delete' const { bulkEnableRules } = jest.requireMock('../../../lib/rule_api/bulk_enable'); const { bulkDisableRules } = jest.requireMock('../../../lib/rule_api/bulk_disable'); const { loadRule } = jest.requireMock('../../../lib/rule_api/get_rule'); -const { resolveRule } = jest.requireMock('../../../lib/rule_api/resolve_rule'); +const { resolveRule } = jest.requireMock('@kbn/alerts-ui-shared/src/common/apis/resolve_rule'); const { loadRuleTypes } = jest.requireMock('../../../lib/rule_api/rule_types'); const { loadActionErrorLog } = jest.requireMock('../../../lib/rule_api/load_action_error_log'); @@ -283,7 +283,7 @@ describe('with_bulk_rule_api_operations', () => { component.find('button').simulate('click'); expect(resolveRule).toHaveBeenCalledTimes(1); - expect(resolveRule).toHaveBeenCalledWith({ ruleId, http }); + expect(resolveRule).toHaveBeenCalledWith({ id: ruleId, http }); }); it('loadRuleTypes calls the loadRuleTypes api', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx index 81a4f27ef5e1c..60ceeaf24d516 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx @@ -12,12 +12,14 @@ import { IExecutionErrorsResult, IExecutionKPIResult, } from '@kbn/alerting-plugin/common'; +import { AlertingFrameworkHealth } from '@kbn/alerting-types'; +import { fetchAlertingFrameworkHealth as alertingFrameworkHealth } from '@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health'; +import { resolveRule } from '@kbn/alerts-ui-shared/src/common/apis/resolve_rule'; import { Rule, RuleType, RuleTaskState, RuleSummary, - AlertingFrameworkHealth, ResolvedRule, SnoozeSchedule, BulkEditResponse, @@ -34,7 +36,6 @@ import type { LoadGlobalExecutionKPIAggregationsProps, BulkUnsnoozeRulesProps, } from '../../../lib/rule_api'; -import { alertingFrameworkHealth } from '../../../lib/rule_api/health'; import { cloneRule } from '../../../lib/rule_api/clone'; import { loadRule } from '../../../lib/rule_api/get_rule'; import { loadRuleSummary } from '../../../lib/rule_api/rule_summary'; @@ -51,7 +52,6 @@ import { loadExecutionKPIAggregations } from '../../../lib/rule_api/load_executi import { loadGlobalExecutionKPIAggregations } from '../../../lib/rule_api/load_global_execution_kpi_aggregations'; import { loadActionErrorLog } from '../../../lib/rule_api/load_action_error_log'; import { unmuteAlertInstance } from '../../../lib/rule_api/unmute_alert'; -import { resolveRule } from '../../../lib/rule_api/resolve_rule'; import { snoozeRule, bulkSnoozeRules } from '../../../lib/rule_api/snooze'; import { unsnoozeRule, bulkUnsnoozeRules } from '../../../lib/rule_api/unsnooze'; import { bulkDeleteRules } from '../../../lib/rule_api/bulk_delete'; @@ -177,7 +177,7 @@ export function withBulkRuleOperations( http, }) } - resolveRule={async (ruleId: Rule['id']) => resolveRule({ http, ruleId })} + resolveRule={async (ruleId: Rule['id']) => resolveRule({ http, id: ruleId })} getHealth={async () => alertingFrameworkHealth({ http })} snoozeRule={async (rule: Rule, snoozeSchedule: SnoozeSchedule) => { return await snoozeRule({ http, id: rule.id, snoozeSchedule }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx index b9cac4bd33b08..b5d12cd6b9caa 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.test.tsx @@ -23,8 +23,8 @@ import { ruleTypeRegistryMock } from '../../../rule_type_registry.mock'; jest.mock('../../../../common/lib/kibana'); -jest.mock('../../../../common/lib/config_api', () => ({ - triggersActionsUiConfig: jest +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config', () => ({ + fetchUiConfig: jest .fn() .mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false } }), })); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx index 7421bec047e03..a9e0be99d42ba 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx @@ -27,6 +27,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { toMountPoint } from '@kbn/react-kibana-mount'; import { RuleExecutionStatusErrorReasons, parseDuration } from '@kbn/alerting-plugin/common'; import { getRuleDetailsRoute } from '@kbn/rule-data-utils'; +import { fetchUiConfig as triggersActionsUiConfig } from '@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config'; import { UpdateApiKeyModalConfirmation } from '../../../components/update_api_key_modal_confirmation'; import { bulkUpdateAPIKey } from '../../../lib/rule_api/update_api_key'; import { RulesDeleteModalConfirmation } from '../../../components/rules_delete_modal_confirmation'; @@ -60,7 +61,6 @@ import { import { useKibana } from '../../../../common/lib/kibana'; import { getRuleReducer } from '../../rule_form/rule_reducer'; import { loadAllActions as loadConnectors } from '../../../lib/action_connector_api'; -import { triggersActionsUiConfig } from '../../../../common/lib/config_api'; import { runRule } from '../../../lib/run_rule'; import { getConfirmDeletionButtonText, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details_route.test.tsx index a04e0b2e14df7..885b7fc0ec67e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details_route.test.tsx @@ -19,8 +19,8 @@ import { spacesPluginMock } from '@kbn/spaces-plugin/public/mocks'; import { useKibana } from '../../../../common/lib/kibana'; jest.mock('../../../../common/lib/kibana'); -jest.mock('../../../../common/lib/config_api', () => ({ - triggersActionsUiConfig: jest +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config', () => ({ + fetchUiConfig: jest .fn() .mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false } }), })); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.test.tsx index 043d102b4cf90..5c5e8b8ca7d67 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.test.tsx @@ -13,8 +13,9 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiFormLabel } from '@elastic/eui'; import { coreMock } from '@kbn/core/public/mocks'; import RuleAdd from './rule_add'; -import { createRule } from '../../lib/rule_api/create'; -import { alertingFrameworkHealth } from '../../lib/rule_api/health'; +import { createRule } from '@kbn/alerts-ui-shared/src/common/apis/create_rule'; + +import { fetchAlertingFrameworkHealth as fetchAlertingFrameworkHealth } from '@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { AlertConsumers, OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { @@ -31,8 +32,9 @@ import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ReactWrapper } from 'enzyme'; import { ALERTING_FEATURE_ID } from '@kbn/alerting-plugin/common'; import { useKibana } from '../../../common/lib/kibana'; -import { triggersActionsUiConfig } from '../../../common/lib/config_api'; -import { triggersActionsUiHealth } from '../../../common/lib/health_api'; + +import { fetchUiConfig } from '@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config'; +import { fetchUiHealthStatus } from '@kbn/alerts-ui-shared/src/common/apis/fetch_ui_health_status'; import { loadActionTypes, loadAllActions } from '../../lib/action_connector_api'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { waitFor } from '@testing-library/react'; @@ -41,22 +43,22 @@ jest.mock('../../../common/lib/kibana'); jest.mock('../../lib/rule_api/rule_types', () => ({ loadRuleTypes: jest.fn(), })); -jest.mock('../../lib/rule_api/create', () => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/create_rule', () => ({ createRule: jest.fn(), })); -jest.mock('../../lib/rule_api/health', () => ({ - alertingFrameworkHealth: jest.fn(() => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health', () => ({ + fetchAlertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, })), })); -jest.mock('../../../common/lib/config_api', () => ({ - triggersActionsUiConfig: jest.fn(), +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config', () => ({ + fetchUiConfig: jest.fn(), })); -jest.mock('../../../common/lib/health_api', () => ({ - triggersActionsUiHealth: jest.fn(() => ({ isRulesAvailable: true })), +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_health_status', () => ({ + fetchUiHealthStatus: jest.fn(() => ({ isRulesAvailable: true })), })); jest.mock('../../lib/action_connector_api', () => ({ @@ -219,7 +221,7 @@ describe.skip('rule_add', () => { } it('renders rule add flyout', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false }, }); const onClose = jest.fn(); @@ -245,7 +247,7 @@ describe.skip('rule_add', () => { }); it('renders selection of rule types to pick in the modal', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false }, }); const onClose = jest.fn(); @@ -271,7 +273,7 @@ describe.skip('rule_add', () => { }); it('renders a confirm close modal if the flyout is closed after inputs have changed', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false }, }); const onClose = jest.fn(); @@ -303,7 +305,7 @@ describe.skip('rule_add', () => { }); it('renders rule add flyout with initial values', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false }, }); const onClose = jest.fn(); @@ -326,7 +328,7 @@ describe.skip('rule_add', () => { }); it('renders rule add flyout with DEFAULT_RULE_INTERVAL if no initialValues specified and no minimumScheduleInterval', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({}); + (fetchUiConfig as jest.Mock).mockResolvedValue({}); await setup({ ruleTypeId: 'my-rule-type' }); expect(wrapper.find('[data-test-subj="intervalInput"]').first().props().value).toEqual(1); @@ -334,7 +336,7 @@ describe.skip('rule_add', () => { }); it('renders rule add flyout with minimumScheduleInterval if minimumScheduleInterval is greater than DEFAULT_RULE_INTERVAL', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '5m', enforce: false }, }); await setup({ ruleTypeId: 'my-rule-type' }); @@ -344,7 +346,7 @@ describe.skip('rule_add', () => { }); it('emit an onClose event when the rule is saved', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false }, }); const onClose = jest.fn(); @@ -379,7 +381,7 @@ describe.skip('rule_add', () => { }); it('should set consumer automatically if only 1 authorized consumer exists', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false }, }); const onClose = jest.fn(); @@ -460,7 +462,7 @@ describe.skip('rule_add', () => { }); it('should enforce any default interval', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false }, }); await setup({ @@ -490,7 +492,7 @@ describe.skip('rule_add', () => { }); it('should load connectors and connector types when there is a pre-selected rule type', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false }, }); @@ -508,18 +510,18 @@ describe.skip('rule_add', () => { }); await waitFor(() => { - expect(triggersActionsUiHealth).toHaveBeenCalledTimes(1); - expect(alertingFrameworkHealth).toHaveBeenCalledTimes(1); + expect(fetchUiHealthStatus).toHaveBeenCalledTimes(1); + expect(fetchAlertingFrameworkHealth).toHaveBeenCalledTimes(1); expect(loadActionTypes).toHaveBeenCalledTimes(1); expect(loadAllActions).toHaveBeenCalledTimes(1); }); }); it('should not load connectors and connector types when there is not an encryptionKey', async () => { - (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + (fetchUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false }, }); - (alertingFrameworkHealth as jest.Mock).mockResolvedValue({ + (fetchAlertingFrameworkHealth as jest.Mock).mockResolvedValue({ isSufficientlySecure: true, hasPermanentEncryptionKey: false, }); @@ -538,8 +540,8 @@ describe.skip('rule_add', () => { }); await waitFor(() => { - expect(triggersActionsUiHealth).toHaveBeenCalledTimes(1); - expect(alertingFrameworkHealth).toHaveBeenCalledTimes(1); + expect(fetchUiHealthStatus).toHaveBeenCalledTimes(1); + expect(fetchAlertingFrameworkHealth).toHaveBeenCalledTimes(1); expect(loadActionTypes).not.toHaveBeenCalled(); expect(loadAllActions).not.toHaveBeenCalled(); expect(wrapper.find('[data-test-subj="actionNeededEmptyPrompt"]').first().text()).toContain( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx index d932a62659483..a623c04eeea6f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx @@ -12,6 +12,8 @@ import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import { toMountPoint } from '@kbn/react-kibana-mount'; import { parseRuleCircuitBreakerErrorMessage } from '@kbn/alerting-plugin/common'; +import { createRule, CreateRuleBody } from '@kbn/alerts-ui-shared/src/common/apis/create_rule'; +import { fetchUiConfig as triggersActionsUiConfig } from '@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config'; import { Rule, RuleTypeParams, @@ -27,7 +29,6 @@ import { import { RuleForm } from './rule_form'; import { getRuleActionErrors, getRuleErrors, isValidRule } from './rule_errors'; import { InitialRule, getRuleReducer } from './rule_reducer'; -import { createRule } from '../../lib/rule_api/create'; import { loadRuleTypes } from '../../lib/rule_api/rule_types'; import { HealthCheck } from '../../components/health_check'; import { ConfirmRuleSave } from './confirm_rule_save'; @@ -39,7 +40,6 @@ import { useKibana } from '../../../common/lib/kibana'; import { hasRuleChanged, haveRuleParamsChanged } from './has_rule_changed'; import { getRuleWithInvalidatedFields } from '../../lib/value_validators'; import { DEFAULT_RULE_INTERVAL, MULTI_CONSUMER_RULE_TYPE_IDS } from '../../constants'; -import { triggersActionsUiConfig } from '../../../common/lib/config_api'; import { getInitialInterval } from './get_initial_interval'; import { ToastWithCircuitBreakerContent } from '../../components/toast_with_circuit_breaker_content'; import { ShowRequestModal } from './show_request_modal'; @@ -258,7 +258,7 @@ const RuleAdd = < rule: { ...rule, ...(selectableConsumer && selectedConsumer ? { consumer: selectedConsumer } : {}), - } as RuleUpdates, + } as CreateRuleBody, }); toasts.addSuccess( i18n.translate('xpack.triggersActionsUI.sections.ruleAdd.saveSuccessNotificationText', { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx index 5bf56aec01977..0109ba0acec19 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.test.tsx @@ -25,18 +25,18 @@ const useKibanaMock = useKibana as jest.Mocked; jest.mock('../../lib/rule_api/rule_types', () => ({ loadRuleTypes: jest.fn(), })); -jest.mock('../../lib/rule_api/update', () => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/update_rule', () => ({ updateRule: jest.fn().mockRejectedValue({ body: { message: 'Fail message' } }), })); -jest.mock('../../lib/rule_api/health', () => ({ - alertingFrameworkHealth: jest.fn(() => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health', () => ({ + fetchAlertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, })), })); -jest.mock('../../../common/lib/config_api', () => ({ - triggersActionsUiConfig: jest.fn().mockResolvedValue({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config', () => ({ + fetchUiConfig: jest.fn().mockResolvedValue({ isUsingSecurity: true, minimumScheduleInterval: { value: '1m', enforce: false }, }), @@ -59,8 +59,8 @@ jest.mock('./rule_errors', () => ({ isValidRule: jest.fn(), })); -jest.mock('../../../common/lib/health_api', () => ({ - triggersActionsUiHealth: jest.fn(() => ({ isRulesAvailable: true })), +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_health_status', () => ({ + fetchUiHealthStatus: jest.fn(() => ({ isRulesAvailable: true })), })); describe('rule_edit', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx index 17e595d51498e..409f3cf39400a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx @@ -28,6 +28,8 @@ import { cloneDeep, omit } from 'lodash'; import { i18n } from '@kbn/i18n'; import { toMountPoint } from '@kbn/react-kibana-mount'; import { parseRuleCircuitBreakerErrorMessage } from '@kbn/alerting-plugin/common'; +import { updateRule } from '@kbn/alerts-ui-shared/src/common/apis/update_rule'; +import { fetchUiConfig as triggersActionsUiConfig } from '@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config'; import { Rule, RuleFlyoutCloseReason, @@ -44,7 +46,6 @@ import { import { RuleForm } from './rule_form'; import { getRuleActionErrors, getRuleErrors, isValidRule } from './rule_errors'; import { getRuleReducer } from './rule_reducer'; -import { updateRule } from '../../lib/rule_api/update'; import { loadRuleTypes } from '../../lib/rule_api/rule_types'; import { HealthCheck } from '../../components/health_check'; import { HealthContextProvider } from '../../context/health_context'; @@ -52,7 +53,6 @@ import { useKibana } from '../../../common/lib/kibana'; import { ConfirmRuleClose } from './confirm_rule_close'; import { hasRuleChanged } from './has_rule_changed'; import { getRuleWithInvalidatedFields } from '../../lib/value_validators'; -import { triggersActionsUiConfig } from '../../../common/lib/config_api'; import { ToastWithCircuitBreakerContent } from '../../components/toast_with_circuit_breaker_content'; import { ShowRequestModal } from './show_request_modal'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/show_request_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/show_request_modal.tsx index 864899cf93d9d..f987243b436db 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/show_request_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/show_request_modal.tsx @@ -18,14 +18,14 @@ import { EuiFlexGroup, EuiFlexItem, } from '@elastic/eui'; +import { + transformUpdateRuleBody as rewriteUpdateBodyRequest, + UPDATE_FIELDS, +} from '@kbn/alerts-ui-shared/src/common/apis/update_rule'; +import { transformCreateRuleBody as rewriteCreateBodyRequest } from '@kbn/alerts-ui-shared/src/common/apis/create_rule'; import * as i18n from '../translations'; import { RuleUpdates } from '../../../types'; import { BASE_ALERTING_API_PATH } from '../../constants'; -import { rewriteBodyRequest as rewriteCreateBodyRequest } from '../../lib/rule_api/create'; -import { - rewriteBodyRequest as rewriteUpdateBodyRequest, - UPDATE_FIELDS, -} from '../../lib/rule_api/update'; const stringify = (rule: RuleUpdates, edit: boolean): string => { try { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx index 33d7ec7a11ca2..363c348407bfe 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.test.tsx @@ -81,8 +81,8 @@ jest.mock('../../../lib/rule_api/bulk_delete', () => ({ jest.mock('../../../lib/rule_api/update_api_key', () => ({ bulkUpdateAPIKey: jest.fn(), })); -jest.mock('../../../lib/rule_api/health', () => ({ - alertingFrameworkHealth: jest.fn(() => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health', () => ({ + fetchAlertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, })), @@ -91,11 +91,11 @@ jest.mock('../../../lib/rule_api/health', () => ({ jest.mock('../../../lib/rule_api/aggregate_kuery_filter'); jest.mock('../../../lib/rule_api/rules_kuery_filter'); -jest.mock('../../../../common/lib/health_api', () => ({ - triggersActionsUiHealth: jest.fn(() => ({ isRulesAvailable: true })), +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_health_status', () => ({ + fetchUiHealthStatus: jest.fn(() => ({ isRulesAvailable: true })), })); -jest.mock('../../../../common/lib/config_api', () => ({ - triggersActionsUiConfig: jest +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config', () => ({ + fetchUiConfig: jest .fn() .mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false } }), })); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_delete.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_delete.test.tsx index f5cf07b7d0dac..8dc84a6ffc5dd 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_delete.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_delete.test.tsx @@ -56,8 +56,8 @@ jest.mock('../../../lib/rule_api/aggregate', () => ({ jest.mock('../../../lib/rule_api/bulk_delete', () => ({ bulkDeleteRules: jest.fn().mockResolvedValue({ errors: [], total: 10 }), })); -jest.mock('../../../lib/rule_api/health', () => ({ - alertingFrameworkHealth: jest.fn(() => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health', () => ({ + fetchAlertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, })), @@ -66,11 +66,11 @@ jest.mock('../../../lib/rule_api/health', () => ({ jest.mock('../../../lib/rule_api/aggregate_kuery_filter'); jest.mock('../../../lib/rule_api/rules_kuery_filter'); -jest.mock('../../../../common/lib/health_api', () => ({ - triggersActionsUiHealth: jest.fn(() => ({ isRulesAvailable: true })), +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_health_status', () => ({ + fetchUiHealthStatus: jest.fn(() => ({ isRulesAvailable: true })), })); -jest.mock('../../../../common/lib/config_api', () => ({ - triggersActionsUiConfig: jest +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config', () => ({ + fetchUiConfig: jest .fn() .mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false } }), })); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_disable.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_disable.test.tsx index 40aad0464e4f2..de5cfa4f74bd8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_disable.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_disable.test.tsx @@ -55,19 +55,19 @@ jest.mock('../../../lib/rule_api/aggregate', () => ({ jest.mock('../../../lib/rule_api/bulk_disable', () => ({ bulkDisableRules: jest.fn().mockResolvedValue({ errors: [], total: 10 }), })); -jest.mock('../../../lib/rule_api/health', () => ({ - alertingFrameworkHealth: jest.fn(() => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health', () => ({ + fetchAlertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, })), })); jest.mock('../../../lib/rule_api/aggregate_kuery_filter'); jest.mock('../../../lib/rule_api/rules_kuery_filter'); -jest.mock('../../../../common/lib/health_api', () => ({ - triggersActionsUiHealth: jest.fn(() => ({ isRulesAvailable: true })), +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_health_status', () => ({ + fetchUiHealthStatus: jest.fn(() => ({ isRulesAvailable: true })), })); -jest.mock('../../../../common/lib/config_api', () => ({ - triggersActionsUiConfig: jest +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config', () => ({ + fetchUiConfig: jest .fn() .mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false } }), })); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx index 44d6e11d7ab95..48e16c053f51f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_edit.test.tsx @@ -54,19 +54,19 @@ jest.mock('../../../lib/rule_api/unsnooze', () => ({ jest.mock('../../../lib/rule_api/update_api_key', () => ({ bulkUpdateAPIKey: jest.fn(), })); -jest.mock('../../../lib/rule_api/health', () => ({ - alertingFrameworkHealth: jest.fn(() => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health', () => ({ + fetchAlertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, })), })); jest.mock('../../../lib/rule_api/aggregate_kuery_filter'); jest.mock('../../../lib/rule_api/rules_kuery_filter'); -jest.mock('../../../../common/lib/health_api', () => ({ - triggersActionsUiHealth: jest.fn(() => ({ isRulesAvailable: true })), +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_health_status', () => ({ + fetchUiHealthStatus: jest.fn(() => ({ isRulesAvailable: true })), })); -jest.mock('../../../../common/lib/config_api', () => ({ - triggersActionsUiConfig: jest +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config', () => ({ + fetchUiConfig: jest .fn() .mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false } }), })); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_enable.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_enable.test.tsx index e8e1bd8b99899..09ac26cf9d98b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_enable.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list_bulk_enable.test.tsx @@ -55,19 +55,19 @@ jest.mock('../../../lib/rule_api/aggregate', () => ({ jest.mock('../../../lib/rule_api/bulk_enable', () => ({ bulkEnableRules: jest.fn().mockResolvedValue({ errors: [], total: 10 }), })); -jest.mock('../../../lib/rule_api/health', () => ({ - alertingFrameworkHealth: jest.fn(() => ({ +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_alerting_framework_health', () => ({ + fetchAlertingFrameworkHealth: jest.fn(() => ({ isSufficientlySecure: true, hasPermanentEncryptionKey: true, })), })); jest.mock('../../../lib/rule_api/aggregate_kuery_filter'); jest.mock('../../../lib/rule_api/rules_kuery_filter'); -jest.mock('../../../../common/lib/health_api', () => ({ - triggersActionsUiHealth: jest.fn(() => ({ isRulesAvailable: true })), +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_health_status', () => ({ + fetchUiHealthStatus: jest.fn(() => ({ isRulesAvailable: true })), })); -jest.mock('../../../../common/lib/config_api', () => ({ - triggersActionsUiConfig: jest +jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_ui_config', () => ({ + fetchUiConfig: jest .fn() .mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false } }), })); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts b/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts index 379470eca99d6..7c47b5d41e13f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/type_registry.ts @@ -5,60 +5,4 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; - -interface BaseObjectType { - id: string; -} - -export class TypeRegistry { - private readonly objectTypes: Map = new Map(); - - /** - * Returns if the object type registry has the given type registered - */ - public has(id: string) { - return this.objectTypes.has(id); - } - - /** - * Registers an object type to the type registry - */ - public register(objectType: T) { - if (this.has(objectType.id)) { - throw new Error( - i18n.translate( - 'xpack.triggersActionsUI.typeRegistry.register.duplicateObjectTypeErrorMessage', - { - defaultMessage: 'Object type "{id}" is already registered.', - values: { - id: objectType.id, - }, - } - ) - ); - } - this.objectTypes.set(objectType.id, objectType); - } - - /** - * Returns an object type, throw error if not registered - */ - public get(id: string): T { - if (!this.has(id)) { - throw new Error( - i18n.translate('xpack.triggersActionsUI.typeRegistry.get.missingActionTypeErrorMessage', { - defaultMessage: 'Object type "{id}" is not registered.', - values: { - id, - }, - }) - ); - } - return this.objectTypes.get(id)!; - } - - public list() { - return Array.from(this.objectTypes).map(([id, objectType]) => objectType); - } -} +export { TypeRegistry } from '@kbn/alerts-ui-shared/src/common/type_registry'; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/config_api.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/config_api.ts deleted file mode 100644 index ceeae9271b5ba..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/config_api.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { HttpSetup } from '@kbn/core/public'; -import { BASE_TRIGGERS_ACTIONS_UI_API_PATH } from '../../../common'; -import { TriggersActionsUiConfig } from '../../types'; - -export async function triggersActionsUiConfig({ - http, -}: { - http: HttpSetup; -}): Promise { - return await http.get(`${BASE_TRIGGERS_ACTIONS_UI_API_PATH}/_config`); -} diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/health_api.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/health_api.ts deleted file mode 100644 index 24a7324262316..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/health_api.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { HttpSetup } from '@kbn/core/public'; -import { BASE_TRIGGERS_ACTIONS_UI_API_PATH } from '../../../common'; - -interface TriggersActionsUiHealth { - isRulesAvailable: boolean; -} - -interface TriggersActionsServerHealth { - isAlertsAvailable: boolean; -} - -export async function triggersActionsUiHealth({ - http, -}: { - http: HttpSetup; -}): Promise { - const result = await http.get( - `${BASE_TRIGGERS_ACTIONS_UI_API_PATH}/_health` - ); - if (result) { - return { - isRulesAvailable: result.isAlertsAvailable, - }; - } - return result; -} diff --git a/x-pack/plugins/triggers_actions_ui/public/mocks.ts b/x-pack/plugins/triggers_actions_ui/public/mocks.ts index 5224a2e0d505f..e6d98240d2c5f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/mocks.ts +++ b/x-pack/plugins/triggers_actions_ui/public/mocks.ts @@ -7,6 +7,7 @@ import { RuleAction } from '@kbn/alerting-plugin/common'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; +import { TypeRegistry } from '@kbn/alerts-ui-shared/src/common/type_registry'; import { getAlertsTableDefaultAlertActionsLazy } from './common/get_alerts_table_default_row_actions'; import type { TriggersAndActionsUIPublicPluginStart } from './plugin'; @@ -14,7 +15,6 @@ import { getAddConnectorFlyoutLazy } from './common/get_add_connector_flyout'; import { getEditConnectorFlyoutLazy } from './common/get_edit_connector_flyout'; import { getAddRuleFlyoutLazy } from './common/get_add_rule_flyout'; import { getEditRuleFlyoutLazy } from './common/get_edit_rule_flyout'; -import { TypeRegistry } from './application/type_registry'; import { ActionTypeModel, RuleTypeModel, diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 829e0bf364562..f3fd01e66856f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -31,10 +31,10 @@ import { ServerlessPluginStart } from '@kbn/serverless/public'; import { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common'; import { LensPublicStart } from '@kbn/lens-plugin/public'; import { RuleAction } from '@kbn/alerting-plugin/common'; +import { TypeRegistry } from '@kbn/alerts-ui-shared/src/common/type_registry'; import { getAlertsTableDefaultAlertActionsLazy } from './common/get_alerts_table_default_row_actions'; import type { AlertActionsProps, RuleUiAction } from './types'; import type { AlertsSearchBarProps } from './application/sections/alerts_search_bar'; -import { TypeRegistry } from './application/type_registry'; import { getAddConnectorFlyoutLazy } from './common/get_add_connector_flyout'; import { getEditConnectorFlyoutLazy } from './common/get_edit_connector_flyout'; diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index f2a8d86eeb88a..ae57e56f3b6a2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -15,8 +15,6 @@ import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common'; import type { - IconType, - RecursivePartial, EuiDataGridCellValueElementProps, EuiDataGridToolBarAdditionalControlsOptions, EuiDataGridProps, @@ -43,7 +41,6 @@ import { } from '@kbn/actions-plugin/common'; import { ActionGroup, - RuleActionParam, SanitizedRule as AlertingSanitizedRule, ResolvedSanitizedRule, RuleSystemAction, @@ -52,11 +49,9 @@ import { ExecutionDuration, AlertStatus, RawAlertInstance, - AlertingFrameworkHealth, RuleNotifyWhenType, RuleTypeParams, RuleTypeMetaData, - ActionVariable, RuleLastRun, MaintenanceWindow, SanitizedRuleAction as RuleAction, @@ -71,7 +66,13 @@ import { import React from 'react'; import { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public'; import type { RuleType, RuleTypeIndex } from '@kbn/triggers-actions-ui-types'; -import { TypeRegistry } from './application/type_registry'; +import { + ValidationResult, + UserConfiguredActionConnector, + ActionConnector, + ActionTypeRegistryContract, +} from '@kbn/alerts-ui-shared/src/common/types'; +import { TypeRegistry } from '@kbn/alerts-ui-shared/src/common/type_registry'; import type { ComponentOpts as RuleStatusDropdownProps } from './application/sections/rules_list/components/rule_status_dropdown'; import type { RuleTagFilterProps } from './application/sections/rules_list/components/rule_tag_filter'; import type { RuleStatusFilterProps } from './application/sections/rules_list/components/rule_status_filter'; @@ -101,6 +102,26 @@ import type { RulesListNotifyBadgePropsWithApi } from './application/sections/ru import { Case } from './application/sections/alerts_table/hooks/apis/bulk_get_cases'; import { AlertTableConfigRegistry } from './application/alert_table_config_registry'; +export type { + GenericValidationResult, + ValidationResult, + ConnectorValidationError, + ConnectorValidationFunc, + ActionConnectorFieldsProps, + ActionConnectorProps, + SystemAction, + PreConfiguredActionConnector, + UserConfiguredActionConnector, + ActionConnector, + ActionParamsProps, + ActionReadOnlyElementProps, + CustomConnectorSelectionItem, + ActionTypeModel, + ActionTypeRegistryContract, +} from '@kbn/alerts-ui-shared/src/common/types'; + +export { ActionConnectorMode } from '@kbn/alerts-ui-shared/src/common/types'; + export type { ActionVariables, RuleType, RuleTypeIndex } from '@kbn/triggers-actions-ui-types'; export { @@ -139,7 +160,6 @@ export type { ExecutionDuration, AlertStatus, RawAlertInstance, - AlertingFrameworkHealth, RuleNotifyWhenType, RuleTypeParams, RuleTypeMetaData, @@ -175,28 +195,9 @@ export { }; export type ActionTypeIndex = Record; -export type ActionTypeRegistryContract< - ActionConnector = unknown, - ActionParams = unknown -> = PublicMethodsOf>>; export type RuleTypeRegistryContract = PublicMethodsOf>; export type AlertsTableConfigurationRegistryContract = PublicMethodsOf; -export interface ConnectorValidationError { - message: ReactNode; -} - -export type ConnectorValidationFunc = () => Promise; -export interface ActionConnectorFieldsProps { - readOnly: boolean; - isEdit: boolean; - registerPreSubmitValidator: (validator: ConnectorValidationFunc) => void; -} -export interface ActionReadOnlyElementProps { - connectorId: string; - connectorName: string; -} - export enum RuleFlyoutCloseReason { SAVED, CANCELED, @@ -208,11 +209,6 @@ export interface BulkEditResponse { total: number; } -export enum ActionConnectorMode { - Test = 'test', - ActionForm = 'actionForm', -} - export interface BulkOperationResponse { rules: Rule[]; errors: BulkOperationError[]; @@ -243,25 +239,6 @@ export type BulkDisableParams = BulkDisableParamsWithoutHttp & { http: HttpSetup; }; -export interface ActionParamsProps { - actionParams: Partial; - index: number; - editAction: (key: string, value: RuleActionParam, index: number) => void; - errors: IErrorObject; - ruleTypeId?: string; - messageVariables?: ActionVariable[]; - defaultMessage?: string; - useDefaultMessage?: boolean; - actionConnector?: ActionConnector; - isLoading?: boolean; - isDisabled?: boolean; - selectedActionGroupId?: string; - showEmailSubjectAndMessage?: boolean; - executionMode?: ActionConnectorMode; - onBlur?: (field?: string) => void; - producerId?: string; -} - export interface Pagination { index: number; size: number; @@ -272,86 +249,6 @@ export interface Sorting { direction: string; } -interface CustomConnectorSelectionItem { - getText: (actionConnector: ActionConnector) => string; - getComponent: ( - actionConnector: ActionConnector - ) => React.LazyExoticComponent> | undefined; -} - -export interface ActionTypeModel { - id: string; - iconClass: IconType; - selectMessage: string; - actionTypeTitle?: string; - validateParams: ( - actionParams: ActionParams - ) => Promise | unknown>>; - actionConnectorFields: React.LazyExoticComponent< - ComponentType - > | null; - actionParamsFields: React.LazyExoticComponent>>; - actionReadOnlyExtraComponent?: React.LazyExoticComponent< - ComponentType - >; - defaultActionParams?: RecursivePartial; - defaultRecoveredActionParams?: RecursivePartial; - customConnectorSelectItem?: CustomConnectorSelectionItem; - isExperimental?: boolean; - subtype?: Array<{ id: string; name: string }>; - convertParamsBetweenGroups?: (params: ActionParams) => ActionParams | {}; - hideInUi?: boolean; - modalWidth?: number; - isSystemActionType?: boolean; -} - -export interface GenericValidationResult { - errors: Record, string[] | unknown>; -} - -export interface ValidationResult { - errors: Record; -} - -export interface ActionConnectorProps { - secrets: Secrets; - id: string; - actionTypeId: string; - name: string; - referencedByCount?: number; - config: Config; - isPreconfigured: boolean; - isDeprecated: boolean; - isSystemAction: boolean; - isMissingSecrets?: boolean; -} - -export type PreConfiguredActionConnector = Omit< - ActionConnectorProps, - 'config' | 'secrets' -> & { - isPreconfigured: true; - isSystemAction: false; -}; - -export type UserConfiguredActionConnector = ActionConnectorProps< - Config, - Secrets -> & { - isPreconfigured: false; - isSystemAction: false; -}; - -export type SystemAction = Omit, 'config' | 'secrets'> & { - isSystemAction: true; - isPreconfigured: false; -}; - -export type ActionConnector, Secrets = Record> = - | PreConfiguredActionConnector - | SystemAction - | UserConfiguredActionConnector; - export type ActionConnectorWithoutId< Config = Record, Secrets = Record diff --git a/x-pack/plugins/watcher/common/lib/serialization/serialize_json_watch.js b/x-pack/plugins/watcher/common/lib/serialization/serialize_json_watch.js index d614ea325da57..0b7e2506477d8 100644 --- a/x-pack/plugins/watcher/common/lib/serialization/serialize_json_watch.js +++ b/x-pack/plugins/watcher/common/lib/serialization/serialize_json_watch.js @@ -5,17 +5,21 @@ * 2.0. */ -import { set } from '@kbn/safer-lodash-set'; import { WATCH_TYPES } from '../../constants'; export function serializeJsonWatch(name, json) { // We don't want to overwrite any metadata provided by the consumer. const { metadata = {} } = json; - set(metadata, 'xpack.type', WATCH_TYPES.JSON); const serializedWatch = { ...json, - metadata, + metadata: { + ...metadata, + xpack: { + ...metadata.xpack, + type: WATCH_TYPES.JSON, + }, + }, }; if (name) { diff --git a/x-pack/test/alerting_api_integration/observability/synthetics_rule.ts b/x-pack/test/alerting_api_integration/observability/synthetics_rule.ts index 15113c2c1cd08..864d73a991e50 100644 --- a/x-pack/test/alerting_api_integration/observability/synthetics_rule.ts +++ b/x-pack/test/alerting_api_integration/observability/synthetics_rule.ts @@ -262,6 +262,7 @@ const statusRule = { externalId: null, correlation_id: null, correlation_display: null, + additional_fields: null, }, comments: [], }, @@ -464,6 +465,7 @@ const tlsRule = { externalId: null, correlation_id: null, correlation_display: null, + additional_fields: null, }, comments: [], }, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/cases_webhook.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/cases_webhook.ts index 35401f0c426a2..f26ba86f6fa3a 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/cases_webhook.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/cases_webhook.ts @@ -202,7 +202,7 @@ export default function casesWebhookTest({ getService }: FtrProviderContext) { statusCode: 400, error: 'Bad Request', message: - 'error validating action type connector: both user and password must be specified', + 'error validating action type connector: must specify a secrets configuration', }); }); }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/jira.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/jira.ts index e291f11250df8..054678996888f 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/jira.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/jira.ts @@ -426,7 +426,7 @@ export default function jiraTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.incident.otherFields]: types that failed validation:\n - [subActionParams.incident.otherFields.0]: A maximum of 20 otherFields can be defined at a time.\n - [subActionParams.incident.otherFields.1]: expected value to equal [null]\n- [4.subAction]: expected value to equal [issueTypes]\n- [5.subAction]: expected value to equal [fieldsByIssueType]\n- [6.subAction]: expected value to equal [issues]\n- [7.subAction]: expected value to equal [issue]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.incident.otherFields]: types that failed validation:\n - [subActionParams.incident.otherFields.0]: A maximum of 20 fields in otherFields can be defined at a time.\n - [subActionParams.incident.otherFields.1]: expected value to equal [null]\n- [4.subAction]: expected value to equal [issueTypes]\n- [5.subAction]: expected value to equal [fieldsByIssueType]\n- [6.subAction]: expected value to equal [issues]\n- [7.subAction]: expected value to equal [issue]', errorSource: TaskErrorSource.FRAMEWORK, }); }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_itsm.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_itsm.ts index 3241f9b80ab1f..ec62e6d30f6ff 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_itsm.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_itsm.ts @@ -14,6 +14,7 @@ import http from 'http'; import { getHttpProxyServer } from '@kbn/alerting-api-integration-helpers'; import { getServiceNowServer } from '@kbn/actions-simulators-plugin/server/plugin'; import { TaskErrorSource } from '@kbn/task-manager-plugin/common'; +import { MAX_ADDITIONAL_FIELDS_LENGTH } from '@kbn/stack-connectors-plugin/common/servicenow/constants'; import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export @@ -578,6 +579,81 @@ export default function serviceNowITSMTest({ getService }: FtrProviderContext) { }); }); + it('throws when trying to create an incident with too many "additional_fields"', async () => { + const additionalFields = new Array(MAX_ADDITIONAL_FIELDS_LENGTH + 1) + .fill('foobar') + .reduce((acc, curr, idx) => { + acc[idx] = curr; + return acc; + }, {}); + + const res = await supertest + .post(`/api/actions/connector/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + ...mockServiceNowBasic.params, + subActionParams: { + ...mockServiceNowBasic.params.subActionParams, + incident: { + ...mockServiceNowBasic.params.subActionParams.incident, + additional_fields: additionalFields, + }, + comments: [], + }, + }, + }) + .expect(200); + + expect(res.body.status).to.eql('error'); + }); + + it('throws when trying to create an incident with "additional_fields" keys that are not allowed', async () => { + const res = await supertest + .post(`/api/actions/connector/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + ...mockServiceNowBasic.params, + subActionParams: { + ...mockServiceNowBasic.params.subActionParams, + incident: { + ...mockServiceNowBasic.params.subActionParams.incident, + additional_fields: { + short_description: 'foo', + }, + }, + comments: [], + }, + }, + }) + .expect(200); + + expect(res.body.status).to.eql('error'); + }); + + it('does not throw when "additional_fields" is a valid JSON object send as string', async () => { + const res = await supertest + .post(`/api/actions/connector/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + ...mockServiceNowBasic.params, + subActionParams: { + ...mockServiceNowBasic.params.subActionParams, + incident: { + ...mockServiceNowBasic.params.subActionParams.incident, + otherFields: '{ "foo": "bar" }', + }, + comments: [], + }, + }, + }) + .expect(200); + + expect(res.body.status).to.eql('error'); + }); + describe('getChoices', () => { it('should fail when field is not provided', async () => { await supertest diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_sir.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_sir.ts index 6ab9a257c9261..fd4781f6d9e62 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_sir.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/servicenow_sir.ts @@ -14,6 +14,7 @@ import http from 'http'; import { getHttpProxyServer } from '@kbn/alerting-api-integration-helpers'; import { getServiceNowServer } from '@kbn/actions-simulators-plugin/server/plugin'; import { TaskErrorSource } from '@kbn/task-manager-plugin/common'; +import { MAX_ADDITIONAL_FIELDS_LENGTH } from '@kbn/stack-connectors-plugin/common/servicenow/constants'; import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export @@ -591,6 +592,81 @@ export default function serviceNowSIRTest({ getService }: FtrProviderContext) { }); }); + it('throws when trying to create an incident with too many "additional_fields"', async () => { + const additionalFields = new Array(MAX_ADDITIONAL_FIELDS_LENGTH + 1) + .fill('foobar') + .reduce((acc, curr, idx) => { + acc[idx] = curr; + return acc; + }, {}); + + const res = await supertest + .post(`/api/actions/connector/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + ...mockServiceNowBasic.params, + subActionParams: { + ...mockServiceNowBasic.params.subActionParams, + incident: { + ...mockServiceNowBasic.params.subActionParams.incident, + additional_fields: additionalFields, + }, + comments: [], + }, + }, + }) + .expect(200); + + expect(res.body.status).to.eql('error'); + }); + + it('throws when trying to create an incident with "additional_fields" keys that are not allowed', async () => { + const res = await supertest + .post(`/api/actions/connector/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + ...mockServiceNowBasic.params, + subActionParams: { + ...mockServiceNowBasic.params.subActionParams, + incident: { + ...mockServiceNowBasic.params.subActionParams.incident, + additional_fields: { + short_description: 'foo', + }, + }, + comments: [], + }, + }, + }) + .expect(200); + + expect(res.body.status).to.eql('error'); + }); + + it('does not throw when "additional_fields" is a valid JSON object send as string', async () => { + const res = await supertest + .post(`/api/actions/connector/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + ...mockServiceNowBasic.params, + subActionParams: { + ...mockServiceNowBasic.params.subActionParams, + incident: { + ...mockServiceNowBasic.params.subActionParams.incident, + otherFields: '{ "foo": "bar" }', + }, + comments: [], + }, + }, + }) + .expect(200); + + expect(res.body.status).to.eql('error'); + }); + describe('getChoices', () => { it('should fail when field is not provided', async () => { await supertest diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/esql_only.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/esql_only.ts index f193af1b81703..56934c5d5f8a3 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/esql_only.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/esql_only.ts @@ -22,7 +22,6 @@ import { RULE_INTERVAL_MILLIS, RULE_INTERVAL_SECONDS, RULE_TYPE_ID, - SourceField, } from './common'; import { createDataStream, deleteDataStream } from '../../../create_test_data'; @@ -39,12 +38,6 @@ export default function ruleTests({ getService }: FtrProviderContext) { getAllAADDocs, } = getRuleServices(getService); - const sourceFields = [ - { label: 'host.hostname', searchPath: 'host.hostname.keyword' }, - { label: 'host.id', searchPath: 'host.id' }, - { label: 'host.name', searchPath: 'host.name' }, - ]; - describe('rule', async () => { let endDate: string; let connectorId: string; @@ -81,13 +74,11 @@ export default function ruleTests({ getService }: FtrProviderContext) { name: 'never fire', esqlQuery: 'from .kibana-alerting-test-data | stats c = count(date) by host.hostname, host.name, host.id | where c < 0', - sourceFields, }); await createRule({ name: 'always fire', esqlQuery: 'from .kibana-alerting-test-data | stats c = count(date) by host.hostname, host.name, host.id | where c > -1', - sourceFields, }); const docs = await waitForDocs(2); @@ -225,13 +216,11 @@ export default function ruleTests({ getService }: FtrProviderContext) { name: 'never fire', esqlQuery: 'from test-data-stream | stats c = count(@timestamp) by host.hostname, host.name, host.id | where c < 0', - sourceFields, }); await createRule({ name: 'always fire', esqlQuery: 'from test-data-stream | stats c = count(@timestamp) by host.hostname, host.name, host.id | where c > -1', - sourceFields, }); const messagePattern = /Document count is \d+ in the last 20s. Alert when greater than 0./; @@ -397,7 +386,6 @@ export default function ruleTests({ getService }: FtrProviderContext) { groupBy?: string; termField?: string; termSize?: number; - sourceFields?: SourceField[]; } async function createRule(params: CreateRuleParams): Promise { @@ -469,7 +457,7 @@ export default function ruleTests({ getService }: FtrProviderContext) { termSize: params.termSize, timeField: params.timeField || 'date', esqlQuery: { esql: params.esqlQuery }, - sourceFields: params.sourceFields, + sourceFields: [], }, }) .expect(200); diff --git a/x-pack/test/api_integration/apis/aiops/test_helpers.ts b/x-pack/test/api_integration/apis/aiops/test_helpers.ts index cf5f1d41ebbce..1915dc82a355c 100644 --- a/x-pack/test/api_integration/apis/aiops/test_helpers.ts +++ b/x-pack/test/api_integration/apis/aiops/test_helpers.ts @@ -6,15 +6,16 @@ */ export const getAddSignificationItemsActions = (data: any[]) => - data.filter((d) => d.type === 'add_significant_items'); + data.filter((d) => d.type === 'logRateAnalysisResults/addSignificantItems'); export const getHistogramActions = (data: any[]) => - data.filter((d) => d.type === 'add_significant_items_histogram'); + data.filter((d) => d.type === 'logRateAnalysisResults/addSignificantItemsHistogram'); export const getGroupActions = (data: any[]) => - data.filter((d) => d.type === 'add_significant_items_group'); + data.filter((d) => d.type === 'logRateAnalysisResults/addSignificantItemsGroup'); export const getGroupHistogramActions = (data: any[]) => - data.filter((d) => d.type === 'add_significant_items_group_histogram'); + data.filter((d) => d.type === 'logRateAnalysisResults/addSignificantItemsGroupHistogram'); -export const getErrorActions = (data: any[]) => data.filter((d) => d.type === 'add_error'); +export const getErrorActions = (data: any[]) => + data.filter((d) => d.type === 'logRateAnalysisResults/addError'); diff --git a/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js b/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js index 2a101609ebe51..b653e52f97e10 100644 --- a/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js +++ b/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js @@ -13,16 +13,24 @@ import { stringify } from 'query-string'; import { registerHelpers } from './rollup.test_helpers'; import { getRandomString } from './lib'; import { DataViewType } from '@kbn/data-views-plugin/common'; +import { INDEX_TO_ROLLUP_MAPPINGS } from './constants'; export default function ({ getService }) { const supertest = getService('supertest'); - const { createIndexWithMappings, getJobPayload, createJob, cleanUp } = + const { createIndexWithMappings, createMockRollupIndex, getJobPayload, createJob, cleanUp } = registerHelpers(getService); describe('index patterns extension', () => { - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/184081 - describe.skip('Fields for wildcards', () => { + // From 8.15, Es only allows creating a new rollup job when there is existing rollup usage in the cluster + // We will simulate rollup usage by creating a mock-up rollup index + before(async () => { + await createMockRollupIndex(); + }); + + after(() => cleanUp()); + + describe('Fields for wildcards', () => { describe('query params validation', () => { let uri; let body; @@ -57,7 +65,7 @@ export default function ({ getService }) { it('should return the correct fields', async () => { // Create a Rollup job on an index with the INDEX_TO_ROLLUP_MAPPINGS - const indexName = await createIndexWithMappings(); + const indexName = await createIndexWithMappings(undefined, INDEX_TO_ROLLUP_MAPPINGS); const rollupIndex = getRandomString(); const payload = getJobPayload(indexName, undefined, rollupIndex); await createJob(payload); @@ -65,7 +73,6 @@ export default function ({ getService }) { // Query for wildcard const params = { pattern: indexName, - type: DataViewType.ROLLUP, rollup_index: rollupIndex, }; const uri = `${BASE_URI}?${stringify(params, { sort: false })}`; @@ -75,12 +82,12 @@ export default function ({ getService }) { .expect(200); // Verify that the fields for wildcard correspond to our declared mappings - // noting that testTotalField and testTagField are not shown in the field caps results const fieldsForWildcard = body.fields.map((field) => field.name); - expect(fieldsForWildcard.sort()).eql(['testCreatedField']); - - // Cleanup - await cleanUp(); + expect(fieldsForWildcard.sort()).eql([ + 'testCreatedField', + 'testTagField', + 'testTotalField', + ]); }); }); }); diff --git a/x-pack/test/api_integration/apis/management/rollup/rollup.js b/x-pack/test/api_integration/apis/management/rollup/rollup.js index a44da818a94e4..16e7921ff1c2e 100644 --- a/x-pack/test/api_integration/apis/management/rollup/rollup.js +++ b/x-pack/test/api_integration/apis/management/rollup/rollup.js @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import { API_BASE_PATH, ROLLUP_INDEX_NAME } from './constants'; +import { API_BASE_PATH, INDEX_TO_ROLLUP_MAPPINGS, ROLLUP_INDEX_NAME } from './constants'; import { registerHelpers } from './rollup.test_helpers'; @@ -15,6 +15,7 @@ export default function ({ getService }) { const { createIndexWithMappings, + createMockRollupIndex, getJobPayload, loadJobs, createJob, @@ -24,8 +25,7 @@ export default function ({ getService }) { cleanUp, } = registerHelpers(getService); - // Failing: See https://github.com/elastic/kibana/issues/184073 - describe.skip('jobs', () => { + describe('jobs', () => { after(() => cleanUp()); describe('indices', () => { @@ -84,212 +84,231 @@ export default function ({ getService }) { }); }); - describe('crud', () => { - describe('list', () => { - it('should return an empty array when there are no jobs', async () => { - const { body } = await loadJobs().expect(200); + describe('with no rollup usage in the cluster', () => { + // From 8.15, Es only allows creating a new rollup job when there is existing rollup usage in the cluster + // The test cluster doesn't currently have any rollup usage so creation of a rollup job should fail + it('should throw a 400 error when trying to create a rollup job', async () => { + const indexName = await createIndexWithMappings(); - expect(body).to.eql({ jobs: [] }); - }); + const payload = getJobPayload(indexName); + + return createJob(payload).expect(400); + }); + }); + + describe('with existing rollup usage in the cluster', () => { + // From 8.15, Es only allows creating a new rollup job when there is existing rollup usage in the cluster + // We will simulate rollup usage by creating a mock-up rollup index + before(async () => { + await createMockRollupIndex(); }); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/183928 - describe.skip('create', () => { - let indexName; + describe('crud', () => { + describe('list', () => { + it('should return an empty array when there are no jobs', async () => { + const { body } = await loadJobs().expect(200); - beforeEach(async () => { - indexName = await createIndexWithMappings(); + expect(body).to.eql({ jobs: [] }); + }); }); - it('should create a rollup job', async () => { - const payload = getJobPayload(indexName); + describe('create', () => { + let indexName; - return createJob(payload).expect(200); - }); + beforeEach(async () => { + indexName = await createIndexWithMappings(undefined, INDEX_TO_ROLLUP_MAPPINGS); + }); - it('should throw a 409 conflict when trying to create 2 jobs with the same id', async () => { - const payload = getJobPayload(indexName); + it('should create a rollup job', async () => { + const payload = getJobPayload(indexName); - await createJob(payload); + return createJob(payload).expect(200); + }); - return createJob(payload).expect(409); - }); + it('should throw a 409 conflict when trying to create 2 jobs with the same id', async () => { + const payload = getJobPayload(indexName); - it('should handle ES errors', async () => { - const payload = { job: { id: 'abc', invalid: 'property' } }; + await createJob(payload); - const { body } = await createJob(payload); - expect(body.message).to.contain('unknown field [invalid]'); - }); + return createJob(payload).expect(409); + }); + + it('should handle ES errors', async () => { + const payload = { job: { id: 'abc', invalid: 'property' } }; - it('should list the newly created job', async () => { - const payload = getJobPayload(indexName); - await createJob(payload); + const { body } = await createJob(payload); + expect(body.message).to.contain('unknown field [invalid]'); + }); - const { - body: { jobs }, - } = await loadJobs(); - const job = jobs.find((job) => job.config.id === payload.job.id); + it('should list the newly created job', async () => { + const payload = getJobPayload(indexName); + await createJob(payload); - expect(job).not.be(undefined); - expect(job.config.index_pattern).to.eql(payload.job.index_pattern); - expect(job.config.rollup_index).to.eql(payload.job.rollup_index); - }); + const { + body: { jobs }, + } = await loadJobs(); + const job = jobs.find((job) => job.config.id === payload.job.id); - it('should create the underlying rollup index with the correct aggregations', async () => { - await createJob(getJobPayload(indexName)); - - const { body } = await supertest.get(`${API_BASE_PATH}/indices`); - - expect(body[ROLLUP_INDEX_NAME]).to.not.be(undefined); - - expect(body).to.eql({ - rollup_index: { - aggs: { - date_histogram: { - testCreatedField: { - agg: 'date_histogram', - delay: '1d', - // TODO: Note that we created the job with `interval`, but ES has coerced this to - // `fixed_interval` based on the value we provided. Once we update the UI and - // tests to no longer use the deprecated `interval` property, we can remove - // this comment. - fixed_interval: '24h', - time_zone: 'UTC', + expect(job).not.be(undefined); + expect(job.config.index_pattern).to.eql(payload.job.index_pattern); + expect(job.config.rollup_index).to.eql(payload.job.rollup_index); + }); + + it('should create the underlying rollup index with the correct aggregations', async () => { + await createJob(getJobPayload(indexName)); + + const { body } = await supertest.get(`${API_BASE_PATH}/indices`); + + expect(body[ROLLUP_INDEX_NAME]).to.not.be(undefined); + + expect(body).to.eql({ + rollup_index: { + aggs: { + date_histogram: { + testCreatedField: { + agg: 'date_histogram', + delay: '1d', + // TODO: Note that we created the job with `interval`, but ES has coerced this to + // `fixed_interval` based on the value we provided. Once we update the UI and + // tests to no longer use the deprecated `interval` property, we can remove + // this comment. + fixed_interval: '24h', + time_zone: 'UTC', + }, }, - }, - max: { - testCreatedField: { - agg: 'max', + max: { + testCreatedField: { + agg: 'max', + }, }, - }, - min: { - testCreatedField: { - agg: 'min', + min: { + testCreatedField: { + agg: 'min', + }, }, - }, - terms: { - testTagField: { - agg: 'terms', + terms: { + testTagField: { + agg: 'terms', + }, + testTotalField: { + agg: 'terms', + }, }, - testTotalField: { - agg: 'terms', + histogram: { + testTotalField: { + agg: 'histogram', + interval: 7, + }, }, - }, - histogram: { - testTotalField: { - agg: 'histogram', - interval: 7, + avg: { + testTotalField: { + agg: 'avg', + }, }, - }, - avg: { - testTotalField: { - agg: 'avg', - }, - }, - value_count: { - testTotalField: { - agg: 'value_count', + value_count: { + testTotalField: { + agg: 'value_count', + }, }, }, }, - }, + }); }); }); - }); - describe('delete', () => { - let jobId; + describe('delete', () => { + let jobId; - beforeEach(async () => { - const indexName = await createIndexWithMappings(); - const payload = getJobPayload(indexName); - jobId = payload.job.id; - await createJob(payload); - }); + beforeEach(async () => { + const indexName = await createIndexWithMappings(undefined, INDEX_TO_ROLLUP_MAPPINGS); + const payload = getJobPayload(indexName); + jobId = payload.job.id; + await createJob(payload); + }); - it('should delete a job that has been stopped', async () => { - await stopJob(jobId); - const { body } = await deleteJob(jobId).expect(200); - expect(body).to.eql({ success: true }); - }); + it('should delete a job that has been stopped', async () => { + await stopJob(jobId); + const { body } = await deleteJob(jobId).expect(200); + expect(body).to.eql({ success: true }); + }); - it('should throw a 400 error if trying to delete a job that is started', async () => { - await startJob(jobId); - const { body } = await deleteJob(jobId).expect(400); - expect(body.message).to.contain('Job must be [STOPPED] before deletion'); + it('should throw a 400 error if trying to delete a job that is started', async () => { + await startJob(jobId); + const { body } = await deleteJob(jobId).expect(400); + expect(body.message).to.contain('Job must be [STOPPED] before deletion'); + }); }); }); - }); - describe('actions', () => { - describe('start', () => { - let job; + describe('actions', () => { + describe('start', () => { + let job; - beforeEach(async () => { - const indexName = await createIndexWithMappings(); - const payload = getJobPayload(indexName); - await createJob(payload); + beforeEach(async () => { + const indexName = await createIndexWithMappings(undefined, INDEX_TO_ROLLUP_MAPPINGS); + const payload = getJobPayload(indexName); + await createJob(payload); - const { - body: { jobs }, - } = await loadJobs(); - job = jobs.find((job) => job.config.id === payload.job.id); - }); + const { + body: { jobs }, + } = await loadJobs(); + job = jobs.find((job) => job.config.id === payload.job.id); + }); - it('should start the job', async () => { - expect(job.status.job_state).to.eql('stopped'); + it('should start the job', async () => { + expect(job.status.job_state).to.eql('stopped'); - const { body } = await startJob(job.config.id).expect(200); + const { body } = await startJob(job.config.id).expect(200); - expect(body).to.eql({ success: true }); + expect(body).to.eql({ success: true }); - // Fetch the job to make sure it has been started - const jobId = job.config.id; - const { - body: { jobs }, - } = await loadJobs(); - job = jobs.find((job) => job.config.id === jobId); - expect(job.status.job_state).to.eql('started'); - }); + // Fetch the job to make sure it has been started + const jobId = job.config.id; + const { + body: { jobs }, + } = await loadJobs(); + job = jobs.find((job) => job.config.id === jobId); + expect(job.status.job_state).to.eql('started'); + }); - it('should return 200 if the job is already started', async () => { - await startJob(job.config.id); // Start the job - await startJob(job.config.id).expect(200); + it('should return 200 if the job is already started', async () => { + await startJob(job.config.id); // Start the job + await startJob(job.config.id).expect(200); + }); }); - }); - describe('stop', () => { - let job; + describe('stop', () => { + let job; - beforeEach(async () => { - const indexName = await createIndexWithMappings(); - const payload = getJobPayload(indexName); - await createJob(payload); + beforeEach(async () => { + const indexName = await createIndexWithMappings(undefined, INDEX_TO_ROLLUP_MAPPINGS); + const payload = getJobPayload(indexName); + await createJob(payload); - const { - body: { jobs }, - } = await loadJobs(); - job = jobs.find((job) => job.config.id === payload.job.id); - }); + const { + body: { jobs }, + } = await loadJobs(); + job = jobs.find((job) => job.config.id === payload.job.id); + }); - it('should stop the job', async () => { - await startJob(job.config.id); - const { body } = await stopJob(job.config.id).expect(200); + it('should stop the job', async () => { + await startJob(job.config.id); + const { body } = await stopJob(job.config.id).expect(200); - expect(body).to.eql({ success: true }); + expect(body).to.eql({ success: true }); - // Fetch the job to make sure it has been stopped - const jobId = job.config.id; - const { - body: { jobs }, - } = await loadJobs(); - job = jobs.find((job) => job.config.id === jobId); - expect(job.status.job_state).to.eql('stopped'); - }); + // Fetch the job to make sure it has been stopped + const jobId = job.config.id; + const { + body: { jobs }, + } = await loadJobs(); + job = jobs.find((job) => job.config.id === jobId); + expect(job.status.job_state).to.eql('stopped'); + }); - it('should return 200 if the job is already stopped', async () => { - await stopJob(job.config.id).expect(200); + it('should return 200 if the job is already stopped', async () => { + await stopJob(job.config.id).expect(200); + }); }); }); }); diff --git a/x-pack/test/api_integration/apis/management/rollup/rollup.test_helpers.js b/x-pack/test/api_integration/apis/management/rollup/rollup.test_helpers.js index 85e5c0c522699..a99b4137d3d09 100644 --- a/x-pack/test/api_integration/apis/management/rollup/rollup.test_helpers.js +++ b/x-pack/test/api_integration/apis/management/rollup/rollup.test_helpers.js @@ -119,8 +119,50 @@ export const registerHelpers = (getService) => { throw err; }); + const createMockRollupIndex = () => + createIndexWithMappings('mock_rollup_index', { + _meta: { + _rollup: { + logs_job: { + id: 'mockRollupJob', + index_pattern: 'mockRollupIndex', + rollup_index: ROLLUP_INDEX_NAME, + cron: '0 0 0 ? * 7', + page_size: 1000, + groups: { + date_histogram: { + interval: '24h', + delay: '1d', + time_zone: 'UTC', + field: 'testCreatedField', + }, + terms: { + fields: ['testTotalField', 'testTagField'], + }, + histogram: { + interval: '7', + fields: ['testTotalField'], + }, + }, + metrics: [ + { + field: 'testTotalField', + metrics: ['avg', 'value_count'], + }, + { + field: 'testCreatedField', + metrics: ['max', 'min'], + }, + ], + }, + }, + 'rollup-version': '', + }, + }); + return { createIndexWithMappings, + createMockRollupIndex, getJobPayload, loadJobs, createJob, diff --git a/x-pack/test/api_integration/apis/management/rollup/rollup_search.js b/x-pack/test/api_integration/apis/management/rollup/rollup_search.js index 8d2fb780c9ff3..2ab2069cf9634 100644 --- a/x-pack/test/api_integration/apis/management/rollup/rollup_search.js +++ b/x-pack/test/api_integration/apis/management/rollup/rollup_search.js @@ -8,17 +8,18 @@ import expect from '@kbn/expect'; import { registerHelpers } from './rollup.test_helpers'; -import { API_BASE_PATH } from './constants'; +import { API_BASE_PATH, INDEX_TO_ROLLUP_MAPPINGS } from './constants'; import { getRandomString } from './lib'; export default function ({ getService }) { const supertest = getService('supertest'); - const { createIndexWithMappings, getJobPayload, createJob, cleanUp } = + const { createIndexWithMappings, createMockRollupIndex, getJobPayload, createJob, cleanUp } = registerHelpers(getService); - // Failing: See https://github.com/elastic/kibana/issues/184128 - describe.skip('search', () => { + describe('search', () => { + after(() => cleanUp()); + const URI = `${API_BASE_PATH}/search`; it('return a 404 if the rollup index does not exist', async () => { @@ -32,8 +33,12 @@ export default function ({ getService }) { }); it('should return a 200 when searching on existing rollup index', async () => { + // From 8.15, Es only allows creating a new rollup job when there is existing rollup usage in the cluster + // We will simulate rollup usage by creating a mock-up rollup index + await createMockRollupIndex(); + // Create a Rollup job on an index with the INDEX_TO_ROLLUP_MAPPINGS - const indexName = await createIndexWithMappings(); + const indexName = await createIndexWithMappings(undefined, INDEX_TO_ROLLUP_MAPPINGS); const rollupIndex = getRandomString(); await createJob(getJobPayload(indexName, undefined, rollupIndex)); diff --git a/x-pack/test/api_integration/apis/maps/maps_telemetry.ts b/x-pack/test/api_integration/apis/maps/maps_telemetry.ts index 8f5c9ae95f8af..acd846f77c982 100644 --- a/x-pack/test/api_integration/apis/maps/maps_telemetry.ts +++ b/x-pack/test/api_integration/apis/maps/maps_telemetry.ts @@ -36,8 +36,8 @@ export default function ({ getService }: FtrProviderContext) { return fieldStat.name === 'geo_point'; } ); - expect(geoPointFieldStats.count).to.be(39); - expect(geoPointFieldStats.index_count).to.be(10); + expect(geoPointFieldStats.count).to.be(47); + expect(geoPointFieldStats.index_count).to.be(11); const geoShapeFieldStats = apiResponse.cluster_stats.indices.mappings.field_types.find( (fieldStat: estypes.ClusterStatsFieldTypes) => { diff --git a/x-pack/test/api_integration/apis/slos/create_slo.ts b/x-pack/test/api_integration/apis/slos/create_slo.ts index 72735e7f668bb..f6fa12fe3d8ba 100644 --- a/x-pack/test/api_integration/apis/slos/create_slo.ts +++ b/x-pack/test/api_integration/apis/slos/create_slo.ts @@ -151,8 +151,8 @@ export default function ({ getService }: FtrProviderContext) { }, }, dest: { - index: '.slo-observability.sli-v3.2', - pipeline: '.slo-observability.sli.pipeline-v3.2', + index: '.slo-observability.sli-v3.3', + pipeline: '.slo-observability.sli.pipeline-v3.3', }, frequency: '1m', sync: { time: { field: '@timestamp', delay: '1m' } }, @@ -184,7 +184,7 @@ export default function ({ getService }: FtrProviderContext) { }, description: `Rolled-up SLI data for SLO: Test SLO for api integration [id: ${id}, revision: 1]`, settings: { deduce_mappings: false, unattended: true }, - _meta: { version: 3.2, managed: true, managed_by: 'observability' }, + _meta: { version: 3.3, managed: true, managed_by: 'observability' }, }, ], }); @@ -206,7 +206,7 @@ export default function ({ getService }: FtrProviderContext) { version: '10.0.0', create_time: summaryTransform.body.transforms[0].create_time, source: { - index: ['.slo-observability.sli-v3.2*'], + index: ['.slo-observability.sli-v3.3*'], query: { bool: { filter: [ @@ -218,7 +218,7 @@ export default function ({ getService }: FtrProviderContext) { }, }, dest: { - index: '.slo-observability.summary-v3.2', + index: '.slo-observability.summary-v3.3', pipeline: `.slo-observability.summary.pipeline-${id}-1`, }, frequency: '1m', @@ -302,11 +302,77 @@ export default function ({ getService }: FtrProviderContext) { }, }, latestSliTimestamp: { max: { field: '@timestamp' } }, + fiveMinuteBurnRate: { + filter: { + range: { + '@timestamp': { + gte: 'now-480s/m', + lte: 'now-180s/m', + }, + }, + }, + aggs: { + goodEvents: { + sum: { + field: 'slo.numerator', + }, + }, + totalEvents: { + sum: { + field: 'slo.denominator', + }, + }, + }, + }, + oneHourBurnRate: { + filter: { + range: { + '@timestamp': { + gte: 'now-3780s/m', + lte: 'now-180s/m', + }, + }, + }, + aggs: { + goodEvents: { + sum: { + field: 'slo.numerator', + }, + }, + totalEvents: { + sum: { + field: 'slo.denominator', + }, + }, + }, + }, + oneDayBurnRate: { + filter: { + range: { + '@timestamp': { + gte: 'now-86580s/m', + lte: 'now-180s/m', + }, + }, + }, + aggs: { + goodEvents: { + sum: { + field: 'slo.numerator', + }, + }, + totalEvents: { + sum: { + field: 'slo.denominator', + }, + }, + }, + }, }, }, description: `Summarise the rollup data of SLO: Test SLO for api integration [id: ${id}, revision: 1].`, settings: { deduce_mappings: false, unattended: true }, - _meta: { version: 3.2, managed: true, managed_by: 'observability' }, + _meta: { version: 3.3, managed: true, managed_by: 'observability' }, }, ], }); diff --git a/x-pack/test/api_integration/apis/slos/update_slo.ts b/x-pack/test/api_integration/apis/slos/update_slo.ts index ebddd16c6508f..b8f4a14654d4d 100644 --- a/x-pack/test/api_integration/apis/slos/update_slo.ts +++ b/x-pack/test/api_integration/apis/slos/update_slo.ts @@ -155,8 +155,8 @@ export default function ({ getService }: FtrProviderContext) { }, }, dest: { - index: '.slo-observability.sli-v3.2', - pipeline: '.slo-observability.sli.pipeline-v3.2', + index: '.slo-observability.sli-v3.3', + pipeline: '.slo-observability.sli.pipeline-v3.3', }, frequency: '1m', sync: { time: { field: '@timestamp', delay: '1m' } }, @@ -188,7 +188,7 @@ export default function ({ getService }: FtrProviderContext) { }, description: `Rolled-up SLI data for SLO: Test SLO for api integration [id: ${id}, revision: 2]`, settings: { deduce_mappings: false, unattended: true }, - _meta: { version: 3.2, managed: true, managed_by: 'observability' }, + _meta: { version: 3.3, managed: true, managed_by: 'observability' }, }, ], }); @@ -210,7 +210,7 @@ export default function ({ getService }: FtrProviderContext) { version: '10.0.0', create_time: summaryTransform.body.transforms[0].create_time, source: { - index: ['.slo-observability.sli-v3.2*'], + index: ['.slo-observability.sli-v3.3*'], query: { bool: { filter: [ @@ -222,7 +222,7 @@ export default function ({ getService }: FtrProviderContext) { }, }, dest: { - index: '.slo-observability.summary-v3.2', + index: '.slo-observability.summary-v3.3', pipeline: `.slo-observability.summary.pipeline-${id}-2`, }, frequency: '1m', @@ -306,11 +306,77 @@ export default function ({ getService }: FtrProviderContext) { }, }, latestSliTimestamp: { max: { field: '@timestamp' } }, + fiveMinuteBurnRate: { + filter: { + range: { + '@timestamp': { + gte: 'now-480s/m', + lte: 'now-180s/m', + }, + }, + }, + aggs: { + goodEvents: { + sum: { + field: 'slo.numerator', + }, + }, + totalEvents: { + sum: { + field: 'slo.denominator', + }, + }, + }, + }, + oneHourBurnRate: { + filter: { + range: { + '@timestamp': { + gte: 'now-3780s/m', + lte: 'now-180s/m', + }, + }, + }, + aggs: { + goodEvents: { + sum: { + field: 'slo.numerator', + }, + }, + totalEvents: { + sum: { + field: 'slo.denominator', + }, + }, + }, + }, + oneDayBurnRate: { + filter: { + range: { + '@timestamp': { + gte: 'now-86580s/m', + lte: 'now-180s/m', + }, + }, + }, + aggs: { + goodEvents: { + sum: { + field: 'slo.numerator', + }, + }, + totalEvents: { + sum: { + field: 'slo.denominator', + }, + }, + }, + }, }, }, description: `Summarise the rollup data of SLO: Test SLO for api integration [id: ${id}, revision: 2].`, settings: { deduce_mappings: false, unattended: true }, - _meta: { version: 3.2, managed: true, managed_by: 'observability' }, + _meta: { version: 3.3, managed: true, managed_by: 'observability' }, }, ], }); diff --git a/x-pack/test/api_integration/services/security_solution_api.gen.ts b/x-pack/test/api_integration/services/security_solution_api.gen.ts index 7650153c53ca3..4bc611e50bba2 100644 --- a/x-pack/test/api_integration/services/security_solution_api.gen.ts +++ b/x-pack/test/api_integration/services/security_solution_api.gen.ts @@ -19,18 +19,22 @@ import { X_ELASTIC_INTERNAL_ORIGIN_REQUEST, } from '@kbn/core-http-common'; +import { AlertsMigrationCleanupRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals_migration/delete_signals_migration/delete_signals_migration.gen'; import { BulkCreateRulesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/bulk_crud/bulk_create_rules/bulk_create_rules_route.gen'; import { BulkDeleteRulesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/bulk_crud/bulk_delete_rules/bulk_delete_rules_route.gen'; import { BulkPatchRulesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/bulk_crud/bulk_patch_rules/bulk_patch_rules_route.gen'; import { BulkUpdateRulesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/bulk_crud/bulk_update_rules/bulk_update_rules_route.gen'; +import { CreateAlertsMigrationRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals_migration/create_signals_migration/create_signals_migration.gen'; import { CreateRuleRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/create_rule/create_rule_route.gen'; import { DeleteRuleRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/delete_rule/delete_rule_route.gen'; import { ExportRulesRequestQueryInput, ExportRulesRequestBodyInput, } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/export_rules/export_rules_route.gen'; +import { FinalizeAlertsMigrationRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals_migration/finalize_signals_migration/finalize_signals_migration.gen'; import { FindRulesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/find_rules/find_rules_route.gen'; import { GetAgentPolicySummaryRequestQueryInput } from '@kbn/security-solution-plugin/common/api/endpoint/policy/policy.gen'; +import { GetAlertsMigrationStatusRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals_migration/get_signals_migration_status/get_signals_migration_status.gen'; import { GetEndpointSuggestionsRequestParamsInput, GetEndpointSuggestionsRequestBodyInput, @@ -45,13 +49,16 @@ import { GetRuleExecutionResultsRequestParamsInput, } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring/rule_execution_logs/get_rule_execution_results/get_rule_execution_results_route.gen'; import { ImportRulesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/import_rules/import_rules_route.gen'; +import { ManageAlertTagsRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/alert_tags/set_alert_tags/set_alert_tags.gen'; import { PatchRuleRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/patch_rule/patch_rule_route.gen'; import { PerformBulkActionRequestQueryInput, PerformBulkActionRequestBodyInput, } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.gen'; import { ReadRuleRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/read_rule/read_rule_route.gen'; +import { SearchAlertsRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals/query_signals/query_signals_route.gen'; import { SetAlertAssigneesRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/alert_assignees/set_alert_assignees_route.gen'; +import { SetAlertsStatusRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/signals/set_signal_status/set_signals_status_route.gen'; import { SuggestUserProfilesRequestQueryInput } from '@kbn/security-solution-plugin/common/api/detection_engine/users/suggest_user_profiles_route.gen'; import { UpdateRuleRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_management/crud/update_rule/update_rule_route.gen'; import { FtrProviderContext } from '../ftr_provider_context'; @@ -60,6 +67,22 @@ export function SecuritySolutionApiProvider({ getService }: FtrProviderContext) const supertest = getService('supertest'); return { + /** + * Migrations favor data integrity over shard size. Consequently, unused or orphaned indices are artifacts of +the migration process. A successful migration will result in both the old and new indices being present. +As such, the old, orphaned index can (and likely should) be deleted. While you can delete these indices manually, +the endpoint accomplishes this task by applying a deletion policy to the relevant index, causing it to be deleted +after 30 days. It also deletes other artifacts specific to the migration implementation. + + */ + alertsMigrationCleanup(props: AlertsMigrationCleanupProps) { + return supertest + .delete('/api/detection_engine/signals/migration') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, /** * Creates new detection rules in bulk. */ @@ -104,6 +127,14 @@ export function SecuritySolutionApiProvider({ getService }: FtrProviderContext) .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send(props.body as object); }, + createAlertsMigration(props: CreateAlertsMigrationProps) { + return supertest + .post('/api/detection_engine/signals/migration') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, /** * Create a single detection rule */ @@ -138,6 +169,20 @@ export function SecuritySolutionApiProvider({ getService }: FtrProviderContext) .send(props.body as object) .query(props.query); }, + /** + * The finalization endpoint replaces the original index's alias with the successfully migrated index's alias. +The endpoint is idempotent; therefore, it can safely be used to poll a given migration and, upon completion, +finalize it. + + */ + finalizeAlertsMigration(props: FinalizeAlertsMigrationProps) { + return supertest + .post('/api/detection_engine/signals/finalize_migration') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, /** * Finds rules that match the given query. */ @@ -157,6 +202,14 @@ export function SecuritySolutionApiProvider({ getService }: FtrProviderContext) .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .query(props.query); }, + getAlertsMigrationStatus(props: GetAlertsMigrationStatusProps) { + return supertest + .post('/api/detection_engine/signals/migration_status') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .query(props.query); + }, getEndpointSuggestions(props: GetEndpointSuggestionsProps) { return supertest .post(replaceParams('/api/endpoint/suggestions/{suggestion_type}', props.params)) @@ -218,6 +271,14 @@ export function SecuritySolutionApiProvider({ getService }: FtrProviderContext) .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, + manageAlertTags(props: ManageAlertTagsProps) { + return supertest + .post('/api/detection_engine/signals/tags') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, /** * Patch a single rule */ @@ -259,6 +320,14 @@ export function SecuritySolutionApiProvider({ getService }: FtrProviderContext) .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana'); }, + searchAlerts(props: SearchAlertsProps) { + return supertest + .post('/api/detection_engine/signals/search') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, /** * Assigns users to alerts. */ @@ -270,6 +339,14 @@ export function SecuritySolutionApiProvider({ getService }: FtrProviderContext) .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send(props.body as object); }, + setAlertsStatus(props: SetAlertsStatusProps) { + return supertest + .post('/api/detection_engine/signals/status') + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2023-10-31') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send(props.body as object); + }, /** * Suggests user profiles. */ @@ -295,6 +372,9 @@ export function SecuritySolutionApiProvider({ getService }: FtrProviderContext) }; } +export interface AlertsMigrationCleanupProps { + body: AlertsMigrationCleanupRequestBodyInput; +} export interface BulkCreateRulesProps { body: BulkCreateRulesRequestBodyInput; } @@ -307,6 +387,9 @@ export interface BulkPatchRulesProps { export interface BulkUpdateRulesProps { body: BulkUpdateRulesRequestBodyInput; } +export interface CreateAlertsMigrationProps { + body: CreateAlertsMigrationRequestBodyInput; +} export interface CreateRuleProps { body: CreateRuleRequestBodyInput; } @@ -317,12 +400,18 @@ export interface ExportRulesProps { query: ExportRulesRequestQueryInput; body: ExportRulesRequestBodyInput; } +export interface FinalizeAlertsMigrationProps { + body: FinalizeAlertsMigrationRequestBodyInput; +} export interface FindRulesProps { query: FindRulesRequestQueryInput; } export interface GetAgentPolicySummaryProps { query: GetAgentPolicySummaryRequestQueryInput; } +export interface GetAlertsMigrationStatusProps { + query: GetAlertsMigrationStatusRequestQueryInput; +} export interface GetEndpointSuggestionsProps { params: GetEndpointSuggestionsRequestParamsInput; body: GetEndpointSuggestionsRequestBodyInput; @@ -341,6 +430,9 @@ export interface GetRuleExecutionResultsProps { export interface ImportRulesProps { query: ImportRulesRequestQueryInput; } +export interface ManageAlertTagsProps { + body: ManageAlertTagsRequestBodyInput; +} export interface PatchRuleProps { body: PatchRuleRequestBodyInput; } @@ -351,9 +443,15 @@ export interface PerformBulkActionProps { export interface ReadRuleProps { query: ReadRuleRequestQueryInput; } +export interface SearchAlertsProps { + body: SearchAlertsRequestBodyInput; +} export interface SetAlertAssigneesProps { body: SetAlertAssigneesRequestBodyInput; } +export interface SetAlertsStatusProps { + body: SetAlertsStatusRequestBodyInput; +} export interface SuggestUserProfilesProps { query: SuggestUserProfilesRequestQueryInput; } diff --git a/x-pack/test/cloud_security_posture_functional/page_objects/add_cis_integration_form_page.ts b/x-pack/test/cloud_security_posture_functional/page_objects/add_cis_integration_form_page.ts index 18df32bd71993..1a566a346ad4e 100644 --- a/x-pack/test/cloud_security_posture_functional/page_objects/add_cis_integration_form_page.ts +++ b/x-pack/test/cloud_security_posture_functional/page_objects/add_cis_integration_form_page.ts @@ -201,6 +201,10 @@ export function AddCisIntegrationFormPageProvider({ return await testSubjects.find('confirmModalTitleText'); }; + const checkIntegrationPliAuthBlockExists = async () => { + return await testSubjects.exists('cloud-security-posture-integration-pli-auth-block'); + }; + const fillInTextField = async (selector: string, text: string) => { const textField = await testSubjects.find(selector); await textField.type(text); @@ -282,5 +286,6 @@ export function AddCisIntegrationFormPageProvider({ selectValue, getValueInEditPage, isOptionChecked, + checkIntegrationPliAuthBlockExists, }; } diff --git a/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cnvm/cis_integration_cnvm.ts b/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cnvm/cis_integration_cnvm.ts index 1e5ff7868f902..1b57e9a65d41e 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cnvm/cis_integration_cnvm.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cnvm/cis_integration_cnvm.ts @@ -23,7 +23,8 @@ export default function (providerContext: FtrProviderContext) { await cisIntegration.navigateToAddIntegrationCspmPage(); }); - describe('CNVM AWS', () => { + // FLAKY: https://github.com/elastic/kibana/issues/186302 + describe.skip('CNVM AWS', () => { it('Hyperlink on PostInstallation Modal should have the correct URL', async () => { await cisIntegration.navigateToAddIntegrationCnvmPage(); await cisIntegration.clickSaveButton(); diff --git a/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts b/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts index 2dd8f36af154d..cae23e9bfe8a4 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_aws.ts @@ -38,7 +38,8 @@ export default function (providerContext: FtrProviderContext) { await cisIntegration.navigateToAddIntegrationCspmPage(); }); - describe('CIS_AWS Organization Cloud Formation', () => { + // FLAKY: https://github.com/elastic/kibana/issues/186438 + describe.skip('CIS_AWS Organization Cloud Formation', () => { it('Initial form state, AWS Org account, and CloudFormation should be selected by default', async () => { expect((await cisIntegration.isRadioButtonChecked('cloudbeat/cis_aws')) === true); expect((await cisIntegration.isRadioButtonChecked('organization-account')) === true); diff --git a/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts b/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts index b75c4aeb2b7f3..121bf7cc2e574 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/cspm/cis_integration_gcp.ts @@ -24,7 +24,8 @@ export default function (providerContext: FtrProviderContext) { const { getPageObjects } = providerContext; const pageObjects = getPageObjects(['cloudPostureDashboard', 'cisAddIntegration', 'header']); - describe('Test adding Cloud Security Posture Integrations CSPM GCP', function () { + // Failing: See https://github.com/elastic/kibana/issues/186440 + describe.skip('Test adding Cloud Security Posture Integrations CSPM GCP', function () { this.tags(['cloud_security_posture_cis_integration_cspm_gcp']); let cisIntegrationGcp: typeof pageObjects.cisAddIntegration.cisGcp; let cisIntegration: typeof pageObjects.cisAddIntegration; diff --git a/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts b/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts index db0778831964f..cd63bc117224d 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/cis_integrations/kspm/cis_integration_eks.ts @@ -50,7 +50,8 @@ export default function (providerContext: FtrProviderContext) { }); }); - describe('KSPM EKS Direct Access', async () => { + // FLAKY: https://github.com/elastic/kibana/issues/186315 + describe.skip('KSPM EKS Direct Access', async () => { it('KSPM EKS Direct Access Workflow', async () => { const directAccessKeyId = 'directAccessKeyIdTest'; const directAccessSecretKey = 'directAccessSecretKeyTest'; @@ -74,7 +75,8 @@ export default function (providerContext: FtrProviderContext) { }); }); - describe('KSPM EKS Temporary Keys', () => { + // FLAKY: https://github.com/elastic/kibana/issues/186389 + describe.skip('KSPM EKS Temporary Keys', () => { it('KSPM EKS Temporary Keys Workflow', async () => { const accessKeyId = 'accessKeyIdTest'; const accessKeySecretKey = 'accessKeySecretKeyTest'; @@ -107,7 +109,8 @@ export default function (providerContext: FtrProviderContext) { }); }); - describe('KSPM EKS Shared Credentials', () => { + // FLAKY: https://github.com/elastic/kibana/issues/186387 + describe.skip('KSPM EKS Shared Credentials', () => { it('KSPM EKS Shared Credentials Workflow', async () => { const sharedCredentialFile = 'sharedCredentialFileTest'; const sharedCredentialProfileName = 'sharedCredentialProfileNameTest'; diff --git a/x-pack/test/cloud_security_posture_functional/pages/findings.ts b/x-pack/test/cloud_security_posture_functional/pages/findings.ts index 6819da2a03e09..841799dd115f6 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/findings.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/findings.ts @@ -238,14 +238,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); }); - describe('DataTable features', () => { - it('Edit data view field option is Enabled', async () => { - await latestFindingsTable.toggleEditDataViewFieldsOption('result.evaluation'); - expect(await testSubjects.find('gridEditFieldButton')).to.be.ok(); - await latestFindingsTable.toggleEditDataViewFieldsOption('result.evaluation'); - }); - }); - describe('Findings - Fields selector', () => { const CSP_FIELDS_SELECTOR_MODAL = 'cloudSecurityFieldsSelectorModal'; const CSP_FIELDS_SELECTOR_OPEN_BUTTON = 'cloudSecurityFieldsSelectorOpenButton'; diff --git a/x-pack/test/cloud_security_posture_functional/pages/vulnerabilities.ts b/x-pack/test/cloud_security_posture_functional/pages/vulnerabilities.ts index d882d1765f752..c94fe9b5d046b 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/vulnerabilities.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/vulnerabilities.ts @@ -93,14 +93,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); }); - describe('DataTable features', () => { - it('Edit data view field option is Enabled', async () => { - await latestVulnerabilitiesTable.toggleEditDataViewFieldsOption('vulnerability.id'); - expect(await testSubjects.find('gridEditFieldButton')).to.be.ok(); - await latestVulnerabilitiesTable.toggleEditDataViewFieldsOption('vulnerability.id'); - }); - }); - describe('Vulnerabilities - Fields selector', () => { const CSP_FIELDS_SELECTOR_MODAL = 'cloudSecurityFieldsSelectorModal'; const CSP_FIELDS_SELECTOR_OPEN_BUTTON = 'cloudSecurityFieldsSelectorOpenButton'; diff --git a/x-pack/test/dataset_quality_api_integration/common/config.ts b/x-pack/test/dataset_quality_api_integration/common/config.ts index e10ad4273fe76..e5a45b72d7f06 100644 --- a/x-pack/test/dataset_quality_api_integration/common/config.ts +++ b/x-pack/test/dataset_quality_api_integration/common/config.ts @@ -48,6 +48,7 @@ export type CreateTestConfig = ReturnType; export type DatasetQualityApiClientKey = | 'noAccessUser' + | 'viewerUser' | 'readUser' | 'adminUser' | 'writeUser' @@ -120,10 +121,14 @@ export function createTestConfig( kibanaServer, username: DatasetQualityUsername.noAccessUser, }), - readUser: await getDatasetQualityApiClient({ + viewerUser: await getDatasetQualityApiClient({ kibanaServer, username: DatasetQualityUsername.viewerUser, }), + readUser: await getDatasetQualityApiClient({ + kibanaServer, + username: DatasetQualityUsername.readUser, + }), adminUser: await getDatasetQualityApiClient({ kibanaServer, username: 'elastic', diff --git a/x-pack/test/dataset_quality_api_integration/tests/data_streams/data_stream_details.spec.ts b/x-pack/test/dataset_quality_api_integration/tests/data_streams/data_stream_details.spec.ts index efa183d2d336b..dd34769fe717e 100644 --- a/x-pack/test/dataset_quality_api_integration/tests/data_streams/data_stream_details.spec.ts +++ b/x-pack/test/dataset_quality_api_integration/tests/data_streams/data_stream_details.spec.ts @@ -62,6 +62,14 @@ export default function ApiTest({ getService }: FtrProviderContext) { ]); }); + it('returns lastActivity as undefined when user does not have access to the data stream', async () => { + const resp = await callApiAs('viewerUser', `${type}-${dataset}-${namespace}`); + expect(resp.body.lastActivity).to.be(undefined); + + // userPrivileges.canMonitor should be false for readUser + expect(resp.body.userPrivileges?.canMonitor).to.be(false); + }); + it('returns error when dataStream param is not provided', async () => { const expectedMessage = 'Data Stream name cannot be empty'; const err = await expectToReject(() => diff --git a/x-pack/test/dataset_quality_api_integration/tests/data_streams/degraded_docs.spec.ts b/x-pack/test/dataset_quality_api_integration/tests/data_streams/degraded_docs.spec.ts index 8df33cbfa60f6..149f07a98a2f3 100644 --- a/x-pack/test/dataset_quality_api_integration/tests/data_streams/degraded_docs.spec.ts +++ b/x-pack/test/dataset_quality_api_integration/tests/data_streams/degraded_docs.spec.ts @@ -7,6 +7,8 @@ import { log, timerange } from '@kbn/apm-synthtrace-client'; import expect from '@kbn/expect'; +import { DatasetQualityApiError } from '../../common/dataset_quality_api_supertest'; +import { expectToReject } from '../../utils'; import { DatasetQualityApiClientKey } from '../../common/config'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -31,6 +33,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { } registry.when('Degraded docs', { config: 'basic' }, () => { + describe('authorization', () => { + it('should return a 403 when the user does not have sufficient privileges', async () => { + const err = await expectToReject(() => callApiAs('noAccessUser')); + expect(err.res.status).to.be(403); + }); + }); + describe('and there are log documents', () => { before(async () => { await synthtrace.index([ @@ -56,9 +65,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .message('This is a log message') .timestamp(timestamp) .dataset('synth.2') - .logLevel( - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?' - ) + .logLevel(MORE_THAN_1024_CHARS) .defaults({ 'log.file.path': '/my-service.log', }) @@ -103,5 +110,175 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(stats.body.degradedDocs.length).to.be(0); }); }); + + describe('when there are data streams of different spaces', () => { + const spaces = ['default', 'space1', 'space2']; + const datasetsWithNoDegradedDocs = ['nginx.access', 'apache.access', 'mysql.access']; + const datasetsWithDegradedDocs = ['nginx.error', 'apache.error', 'mysql.error']; + + before(async () => { + for (const space of spaces) { + for (const dataset of datasetsWithNoDegradedDocs) { + await synthtrace.index([ + timerange(start, end) + .interval('1m') + .rate(1) + .generator((timestamp) => + log + .create() + .message('This is a log message') + .timestamp(timestamp) + .dataset(dataset) + .namespace(space) + ), + ]); + } + + for (const dataset of datasetsWithDegradedDocs) { + await synthtrace.index([ + timerange(start, end) + .interval('1m') + .rate(2) + .generator((timestamp: number, index: number) => + log + .create() + .message('This is a log message') + .timestamp(timestamp) + .dataset(dataset) + .namespace(space) + .logLevel(index % 2 === 0 ? MORE_THAN_1024_CHARS : 'This is a log message') + ), + ]); + } + } + }); + + it('returns counts and list of datasets correctly', async () => { + const stats = await callApiAs('datasetQualityLogsUser'); + expect(stats.body.degradedDocs.length).to.be(18); + + const expected = { + degradedDocs: [ + { + dataset: 'logs-apache.access-default', + count: 0, + docsCount: 1, + percentage: 0, + }, + { + dataset: 'logs-apache.access-space1', + count: 0, + docsCount: 1, + percentage: 0, + }, + { + dataset: 'logs-apache.access-space2', + count: 0, + docsCount: 1, + percentage: 0, + }, + { + dataset: 'logs-apache.error-default', + count: 1, + docsCount: 2, + percentage: 50, + }, + { + dataset: 'logs-apache.error-space1', + count: 1, + docsCount: 2, + percentage: 50, + }, + { + dataset: 'logs-apache.error-space2', + count: 1, + docsCount: 2, + percentage: 50, + }, + { + dataset: 'logs-mysql.access-default', + count: 0, + docsCount: 1, + percentage: 0, + }, + { + dataset: 'logs-mysql.access-space1', + count: 0, + docsCount: 1, + percentage: 0, + }, + { + dataset: 'logs-mysql.access-space2', + count: 0, + docsCount: 1, + percentage: 0, + }, + { + dataset: 'logs-mysql.error-default', + count: 1, + docsCount: 2, + percentage: 50, + }, + { + dataset: 'logs-mysql.error-space1', + count: 1, + docsCount: 2, + percentage: 50, + }, + { + dataset: 'logs-mysql.error-space2', + count: 1, + docsCount: 2, + percentage: 50, + }, + { + dataset: 'logs-nginx.access-default', + count: 0, + docsCount: 1, + percentage: 0, + }, + { + dataset: 'logs-nginx.access-space1', + count: 0, + docsCount: 1, + percentage: 0, + }, + { + dataset: 'logs-nginx.access-space2', + count: 0, + docsCount: 1, + percentage: 0, + }, + { + dataset: 'logs-nginx.error-default', + count: 1, + docsCount: 2, + percentage: 50, + }, + { + dataset: 'logs-nginx.error-space1', + count: 1, + docsCount: 2, + percentage: 50, + }, + { + dataset: 'logs-nginx.error-space2', + count: 1, + docsCount: 2, + percentage: 50, + }, + ], + }; + + expect(stats.body).to.eql(expected); + }); + + after(async () => { + await synthtrace.clean(); + }); + }); }); } + +const MORE_THAN_1024_CHARS = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?'; diff --git a/x-pack/test/dataset_quality_api_integration/tests/data_streams/stats.spec.ts b/x-pack/test/dataset_quality_api_integration/tests/data_streams/stats.spec.ts index 39f8407305bae..b01dc1f2375aa 100644 --- a/x-pack/test/dataset_quality_api_integration/tests/data_streams/stats.spec.ts +++ b/x-pack/test/dataset_quality_api_integration/tests/data_streams/stats.spec.ts @@ -8,9 +8,7 @@ import { log, timerange } from '@kbn/apm-synthtrace-client'; import expect from '@kbn/expect'; import { DatasetQualityApiClientKey } from '../../common/config'; -import { DatasetQualityApiError } from '../../common/dataset_quality_api_supertest'; import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { expectToReject } from '../../utils'; import { cleanLogIndexTemplate, addIntegrationToLogIndexTemplate } from './es_utils'; export default function ApiTest({ getService }: FtrProviderContext) { @@ -25,20 +23,98 @@ export default function ApiTest({ getService }: FtrProviderContext) { params: { query: { type: 'logs', + datasetQuery: '-', }, }, }); } + async function ingestDocuments({ + from = '2023-11-20T15:00:00.000Z', + to = '2023-11-20T15:01:00.000Z', + interval = '1m', + rate = 1, + dataset = 'synth.1', + }: { from?: string; to?: string; interval?: string; rate?: number; dataset?: string } = {}) { + await synthtrace.index([ + timerange(from, to) + .interval(interval) + .rate(rate) + .generator((timestamp) => + log + .create() + .message('This is a log message') + .timestamp(timestamp) + .dataset(dataset) + .defaults({ + 'log.file.path': '/my-service.log', + }) + ), + ]); + } + registry.when('Api Key privileges check', { config: 'basic' }, () => { - describe('when missing required privileges', () => { - it('fails with a 500 error', async () => { - const err = await expectToReject( - async () => await callApiAs('readUser') - ); - - expect(err.res.status).to.be(500); - expect(err.res.body.message).to.contain('unauthorized'); + describe('index privileges', () => { + it('returns user authorization as false for noAccessUser', async () => { + const resp = await callApiAs('noAccessUser'); + + expect(resp.body.datasetUserPrivileges.canRead).to.be(false); + expect(resp.body.datasetUserPrivileges.canMonitor).to.be(false); + expect(resp.body.datasetUserPrivileges.canViewIntegrations).to.be(false); + expect(resp.body.dataStreamsStats).to.eql([]); + }); + + it('returns correct user privileges for an elevated user', async () => { + const resp = await callApiAs('adminUser'); + + expect(resp.body.datasetUserPrivileges).to.eql({ + canRead: true, + canMonitor: true, + canViewIntegrations: true, + }); + }); + + it('get empty stats for a readUser', async () => { + const resp = await callApiAs('readUser'); + + expect(resp.body.datasetUserPrivileges.canRead).to.be(true); + expect(resp.body.datasetUserPrivileges.canMonitor).to.be(false); + expect(resp.body.datasetUserPrivileges.canViewIntegrations).to.be(false); + expect(resp.body.dataStreamsStats).to.eql([]); + }); + + it('returns non empty stats for an authorized user', async () => { + await ingestDocuments(); + const stats = await callApiAs('datasetQualityLogsUser'); + + expect(stats.body.dataStreamsStats[0].size).not.empty(); + expect(stats.body.dataStreamsStats[0].sizeBytes).greaterThan(0); + expect(stats.body.dataStreamsStats[0].lastActivity).greaterThan(0); + }); + + it('get list of privileged data streams for datasetQualityLogsUser', async () => { + // Index only one document to logs-test-1-default and logs-test-1-default data stream using synthtrace + await ingestDocuments({ dataset: 'test.1' }); + await ingestDocuments({ dataset: 'test.2' }); + const resp = await callApiAs('datasetQualityLogsUser'); + + expect(resp.body.datasetUserPrivileges.canMonitor).to.be(true); + expect( + resp.body.dataStreamsStats + .map(({ name, userPrivileges: { canMonitor: hasPrivilege } }) => ({ + name, + hasPrivilege, + })) + .filter(({ name }) => name.includes('test')) + ).to.eql([ + { name: 'logs-test.1-default', hasPrivilege: true }, + { name: 'logs-test.2-default', hasPrivilege: true }, + ]); + }); + + after(async () => { + await synthtrace.clean(); + await cleanLogIndexTemplate({ esClient: es }); }); }); diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/agent_policies.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/agent_policies.ts new file mode 100644 index 0000000000000..7ab4e86448bde --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/agent_policies.ts @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { CreateAgentPolicyResponse } from '@kbn/fleet-plugin/common'; +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { skipIfNoDockerRegistry } from '../../helpers'; +import { SpaceTestApiClient } from './api_helper'; +import { cleanFleetIndices } from './helpers'; +import { setupTestSpaces, TEST_SPACE_1 } from './space_helpers'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const supertest = getService('supertest'); + const esClient = getService('es'); + const kibanaServer = getService('kibanaServer'); + + describe('agent policies', async function () { + skipIfNoDockerRegistry(providerContext); + const apiClient = new SpaceTestApiClient(supertest); + + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + }); + + setupTestSpaces(providerContext); + let defaultSpacePolicy1: CreateAgentPolicyResponse; + let spaceTest1Policy1: CreateAgentPolicyResponse; + let spaceTest1Policy2: CreateAgentPolicyResponse; + before(async () => { + const [_defaultSpacePolicy1, _spaceTest1Policy1, _spaceTest1Policy2] = await Promise.all([ + apiClient.createAgentPolicy(), + apiClient.createAgentPolicy(TEST_SPACE_1), + apiClient.createAgentPolicy(TEST_SPACE_1), + ]); + defaultSpacePolicy1 = _defaultSpacePolicy1; + spaceTest1Policy1 = _spaceTest1Policy1; + spaceTest1Policy2 = _spaceTest1Policy2; + }); + + describe('GET /agent_policies', () => { + it('should return policies in a specific space', async () => { + const agentPolicies = await apiClient.getAgentPolicies(TEST_SPACE_1); + expect(agentPolicies.total).to.eql(2); + const policyIds = agentPolicies.items?.map((item) => item.id); + expect(policyIds).to.contain(spaceTest1Policy1.item.id); + expect(policyIds).to.contain(spaceTest1Policy2.item.id); + expect(policyIds).not.to.contain(defaultSpacePolicy1.item.id); + }); + + it('should return policies in default space', async () => { + const agentPolicies = await apiClient.getAgentPolicies(); + expect(agentPolicies.total).to.eql(1); + const policyIds = agentPolicies.items?.map((item) => item.id); + expect(policyIds).not.to.contain(spaceTest1Policy1.item.id); + expect(policyIds).not.contain(spaceTest1Policy2.item.id); + expect(policyIds).to.contain(defaultSpacePolicy1.item.id); + }); + }); + + describe('GET /agent_policies/{id}', () => { + it('should allow to access a policy in a specific space', async () => { + await apiClient.getAgentPolicy(spaceTest1Policy1.item.id, TEST_SPACE_1); + }); + it('should not allow to get a policy from a different space from the default space', async () => { + let err: Error | undefined; + try { + await apiClient.getAgentPolicy(spaceTest1Policy1.item.id); + } catch (_err) { + err = _err; + } + + expect(err).to.be.an(Error); + expect(err?.message).to.match(/404 "Not Found"/); + }); + + it('should not allow to get an default space policy from a different space', async () => { + let err: Error | undefined; + try { + await apiClient.getAgentPolicy(defaultSpacePolicy1.item.id, TEST_SPACE_1); + } catch (_err) { + err = _err; + } + + expect(err).to.be.an(Error); + expect(err?.message).to.match(/404 "Not Found"/); + }); + }); + }); +} diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts new file mode 100644 index 0000000000000..73c2675d7b2d5 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { CreateAgentPolicyResponse } from '@kbn/fleet-plugin/common'; +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { skipIfNoDockerRegistry } from '../../helpers'; +import { SpaceTestApiClient } from './api_helper'; +import { cleanFleetIndices } from './helpers'; +import { setupTestSpaces, TEST_SPACE_1 } from './space_helpers'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const supertest = getService('supertest'); + const esClient = getService('es'); + const kibanaServer = getService('kibanaServer'); + const createFleetAgent = async (agentPolicyId: string, spaceId?: string) => { + const agentResponse = await esClient.index({ + index: '.fleet-agents', + refresh: true, + body: { + access_api_key_id: 'api-key-3', + active: true, + policy_id: agentPolicyId, + policy_revision_idx: 1, + last_checkin_status: 'online', + type: 'PERMANENT', + local_metadata: { + host: { hostname: 'host123' }, + elastic: { agent: { version: '8.15.0' } }, + }, + user_provided_metadata: {}, + enrolled_at: new Date().toISOString(), + last_checkin: new Date().toISOString(), + tags: ['tag1'], + namespaces: spaceId ? [spaceId] : undefined, + }, + }); + + return agentResponse._id; + }; + describe('agents', async function () { + skipIfNoDockerRegistry(providerContext); + const apiClient = new SpaceTestApiClient(supertest); + + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + }); + + setupTestSpaces(providerContext); + let defaultSpacePolicy1: CreateAgentPolicyResponse; + let spaceTest1Policy1: CreateAgentPolicyResponse; + let spaceTest1Policy2: CreateAgentPolicyResponse; + + let defaultSpaceAgent1: string; + let defaultSpaceAgent2: string; + let testSpaceAgent1: string; + let testSpaceAgent2: string; + + before(async () => { + const [_defaultSpacePolicy1, _spaceTest1Policy1, _spaceTest1Policy2] = await Promise.all([ + apiClient.createAgentPolicy(), + apiClient.createAgentPolicy(TEST_SPACE_1), + apiClient.createAgentPolicy(TEST_SPACE_1), + ]); + defaultSpacePolicy1 = _defaultSpacePolicy1; + spaceTest1Policy1 = _spaceTest1Policy1; + spaceTest1Policy2 = _spaceTest1Policy2; + + const [_defaultSpaceAgent1, _defaultSpaceAgent2, _testSpaceAgent1, _testSpaceAgent2] = + await Promise.all([ + createFleetAgent(defaultSpacePolicy1.item.id, 'default'), + createFleetAgent(defaultSpacePolicy1.item.id), + createFleetAgent(spaceTest1Policy1.item.id, TEST_SPACE_1), + createFleetAgent(spaceTest1Policy2.item.id, TEST_SPACE_1), + ]); + + defaultSpaceAgent1 = _defaultSpaceAgent1; + defaultSpaceAgent2 = _defaultSpaceAgent2; + testSpaceAgent1 = _testSpaceAgent1; + testSpaceAgent2 = _testSpaceAgent2; + }); + + describe('GET /agents', () => { + it('should return agents in a specific space', async () => { + const agents = await apiClient.getAgents(TEST_SPACE_1); + expect(agents.total).to.eql(2); + const agentIds = agents.items?.map((item) => item.id); + expect(agentIds).to.contain(testSpaceAgent1); + expect(agentIds).to.contain(testSpaceAgent2); + }); + + it('should return agents in default space', async () => { + const agents = await apiClient.getAgents(); + expect(agents.total).to.eql(2); + const agentIds = agents.items?.map((item) => item.id); + expect(agentIds).to.contain(defaultSpaceAgent1); + expect(agentIds).to.contain(defaultSpaceAgent2); + }); + }); + + describe('GET /agents/{id}', () => { + it('should allow to retrieve agent in the same space', async () => { + await apiClient.getAgent(testSpaceAgent1, TEST_SPACE_1); + }); + + it('should not allow to get an agent from a different space from the default space', async () => { + let err: Error | undefined; + try { + await apiClient.getAgent(testSpaceAgent1); + } catch (_err) { + err = _err; + } + + expect(err).to.be.an(Error); + expect(err?.message).to.match(/404 "Not Found"/); + }); + }); + }); +} diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts new file mode 100644 index 0000000000000..4b166d040625b --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts @@ -0,0 +1,176 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { v4 as uuidV4 } from 'uuid'; +import type { Agent } from 'supertest'; +import { + CreateAgentPolicyResponse, + GetAgentPoliciesResponse, + GetAgentsResponse, + GetOneAgentPolicyResponse, + GetOneAgentResponse, +} from '@kbn/fleet-plugin/common'; +import { + GetEnrollmentAPIKeysResponse, + GetOneEnrollmentAPIKeyResponse, + PostEnrollmentAPIKeyResponse, + PostEnrollmentAPIKeyRequest, + GetEnrollmentSettingsResponse, +} from '@kbn/fleet-plugin/common/types'; +import { + GetUninstallTokenResponse, + GetUninstallTokensMetadataResponse, +} from '@kbn/fleet-plugin/common/types/rest_spec/uninstall_token'; + +export class SpaceTestApiClient { + constructor(private readonly supertest: Agent) {} + private getBaseUrl(spaceId?: string) { + return spaceId ? `/s/${spaceId}` : ''; + } + async setup(spaceId?: string): Promise { + const { body: res } = await this.supertest + .post(`${this.getBaseUrl(spaceId)}/api/fleet/setup`) + .set('kbn-xsrf', 'xxxx') + .send({}) + .expect(200); + + return res; + } + // Agent policies + async createAgentPolicy(spaceId?: string): Promise { + const { body: res } = await this.supertest + .post(`${this.getBaseUrl(spaceId)}/api/fleet/agent_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: `test ${uuidV4()}`, + description: '', + namespace: 'default', + inactivity_timeout: 24 * 1000, + }) + .expect(200); + + return res; + } + async createFleetServerPolicy(spaceId?: string): Promise { + const { body: res } = await this.supertest + .post(`${this.getBaseUrl(spaceId)}/api/fleet/agent_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: `test ${uuidV4()}`, + description: '', + namespace: 'default', + inactivity_timeout: 24 * 1000, + has_fleet_server: true, + force: true, + }) + .expect(200); + + return res; + } + async deleteAgentPolicy(agentPolicyId: string, spaceId?: string) { + await this.supertest + .post(`${this.getBaseUrl(spaceId)}/api/fleet/agent_policies/delete`) + .send({ + agentPolicyId, + }) + .set('kbn-xsrf', 'xxxx') + .expect(200); + } + async getAgentPolicy(policyId: string, spaceId?: string): Promise { + const { body: res } = await this.supertest + .get(`${this.getBaseUrl(spaceId)}/api/fleet/agent_policies/${policyId}`) + .expect(200); + + return res; + } + async getAgentPolicies(spaceId?: string): Promise { + const { body: res } = await this.supertest + .get(`${this.getBaseUrl(spaceId)}/api/fleet/agent_policies`) + .expect(200); + + return res; + } + // Enrollmennt API Keys + async getEnrollmentApiKey( + keyId: string, + spaceId?: string + ): Promise { + const { body: res } = await this.supertest + .get(`${this.getBaseUrl(spaceId)}/api/fleet/enrollment_api_keys/${keyId}`) + .expect(200); + + return res; + } + async getEnrollmentApiKeys(spaceId?: string): Promise { + const { body: res } = await this.supertest + .get(`${this.getBaseUrl(spaceId)}/api/fleet/enrollment_api_keys`) + .expect(200); + + return res; + } + async deleteEnrollmentApiKey( + keyId: string, + spaceId?: string + ): Promise { + const { body: res } = await this.supertest + .delete(`${this.getBaseUrl(spaceId)}/api/fleet/enrollment_api_keys/${keyId}`) + .set('kbn-xsrf', 'xxxx') + .expect(200); + + return res; + } + async postEnrollmentApiKeys( + body: PostEnrollmentAPIKeyRequest['body'], + spaceId?: string + ): Promise { + const { body: res } = await this.supertest + .post(`${this.getBaseUrl(spaceId)}/api/fleet/enrollment_api_keys`) + .set('kbn-xsrf', 'xxxx') + .send(body) + .expect(200); + + return res; + } + // Uninstall tokens + async getUninstallTokens(spaceId?: string): Promise { + const { body: res } = await this.supertest + .get(`${this.getBaseUrl(spaceId)}/api/fleet/uninstall_tokens`) + .expect(200); + + return res; + } + async getUninstallToken(tokenId: string, spaceId?: string): Promise { + const { body: res } = await this.supertest + .get(`${this.getBaseUrl(spaceId)}/api/fleet/uninstall_tokens/${tokenId}`) + .expect(200); + + return res; + } + // Agents + async getAgent(agentId: string, spaceId?: string): Promise { + const { body: res } = await this.supertest + .get(`${this.getBaseUrl(spaceId)}/api/fleet/agents/${agentId}`) + .expect(200); + + return res; + } + async getAgents(spaceId?: string): Promise { + const { body: res } = await this.supertest + .get(`${this.getBaseUrl(spaceId)}/api/fleet/agents`) + .expect(200); + + return res; + } + // Enrollment Settings + async getEnrollmentSettings(spaceId?: string): Promise { + const { body: res } = await this.supertest + .get(`${this.getBaseUrl(spaceId)}/internal/fleet/settings/enrollment`) + .expect(200); + + return res; + } +} diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/enrollment_api_keys.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/enrollment_api_keys.ts new file mode 100644 index 0000000000000..13238acb3917c --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/enrollment_api_keys.ts @@ -0,0 +1,196 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { CreateAgentPolicyResponse } from '@kbn/fleet-plugin/common'; +import { type EnrollmentAPIKey } from '@kbn/fleet-plugin/common/types'; +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { skipIfNoDockerRegistry } from '../../helpers'; +import { SpaceTestApiClient } from './api_helper'; +import { cleanFleetIndices } from './helpers'; +import { setupTestSpaces, TEST_SPACE_1 } from './space_helpers'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const supertest = getService('supertest'); + const esClient = getService('es'); + const kibanaServer = getService('kibanaServer'); + + describe('enrollment api keys', async function () { + skipIfNoDockerRegistry(providerContext); + const apiClient = new SpaceTestApiClient(supertest); + + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + }); + + setupTestSpaces(providerContext); + + let defaultSpacePolicy1: CreateAgentPolicyResponse; + let spaceTest1Policy1: CreateAgentPolicyResponse; + let spaceTest1Policy2: CreateAgentPolicyResponse; + let defaultSpaceEnrollmentKey1: EnrollmentAPIKey; + let spaceTest1EnrollmentKey1: EnrollmentAPIKey; + // Create agent policies it should create a enrollment key for every keys + before(async () => { + const [_defaultSpacePolicy1, _spaceTest1Policy1, _spaceTest1Policy2] = await Promise.all([ + apiClient.createAgentPolicy(), + apiClient.createAgentPolicy(TEST_SPACE_1), + apiClient.createAgentPolicy(TEST_SPACE_1), + ]); + defaultSpacePolicy1 = _defaultSpacePolicy1; + spaceTest1Policy1 = _spaceTest1Policy1; + spaceTest1Policy2 = _spaceTest1Policy2; + + const space1ApiKeys = await apiClient.getEnrollmentApiKeys(TEST_SPACE_1); + const defaultSpaceApiKeys = await apiClient.getEnrollmentApiKeys(); + defaultSpaceEnrollmentKey1 = defaultSpaceApiKeys.items[0]; + spaceTest1EnrollmentKey1 = space1ApiKeys.items[0]; + }); + + describe('read APIs', () => { + describe('GET /enrollment_api_keys', () => { + it('should return enrolmment keys in a specific space', async () => { + const apiKeys = await apiClient.getEnrollmentApiKeys(TEST_SPACE_1); + expect(apiKeys.total).to.eql(2); + const policyIds = apiKeys.items?.map((item) => item.policy_id); + expect(policyIds).to.contain(spaceTest1Policy1.item.id); + expect(policyIds).to.contain(spaceTest1Policy2.item.id); + expect(policyIds).not.to.contain(defaultSpacePolicy1.item.id); + }); + + it('should return enrolmment keys in default space', async () => { + const apiKeys = await apiClient.getEnrollmentApiKeys(); + expect(apiKeys.total).to.eql(1); + const policyIds = apiKeys.items?.map((item) => item.policy_id); + expect(policyIds).not.to.contain(spaceTest1Policy1.item.id); + expect(policyIds).not.contain(spaceTest1Policy2.item.id); + expect(policyIds).to.contain(defaultSpacePolicy1.item.id); + }); + }); + + describe('GET /enrollment_api_keys/{id}', () => { + it('should allow to access a enrollment keu in a specific space', async () => { + await apiClient.getEnrollmentApiKey(spaceTest1EnrollmentKey1.id, TEST_SPACE_1); + }); + it('should not allow to get an enrolmment key from a different space from the default space', async () => { + let err: Error | undefined; + try { + await apiClient.getEnrollmentApiKey(spaceTest1EnrollmentKey1.id); + } catch (_err) { + err = _err; + } + + expect(err).to.be.an(Error); + expect(err?.message).to.match(/404 "Not Found"/); + }); + + it('should not allow to get an default space enrolmment key from a different space', async () => { + let err: Error | undefined; + try { + await apiClient.getEnrollmentApiKey(defaultSpaceEnrollmentKey1.id, TEST_SPACE_1); + } catch (_err) { + err = _err; + } + + expect(err).to.be.an(Error); + expect(err?.message).to.match(/404 "Not Found"/); + }); + }); + }); + + describe('write APIs', () => { + describe('POST /enrollment_api_keys', () => { + it('should allow to create an enrollment api key for a policy in the default space', async () => { + const res = await apiClient.postEnrollmentApiKeys({ + policy_id: defaultSpacePolicy1.item.id, + }); + expect(res.item).to.have.key('id'); + }); + it('should allow to create an enrollment api key for a policy in the same space', async () => { + const res = await apiClient.postEnrollmentApiKeys( + { + policy_id: spaceTest1Policy1.item.id, + }, + TEST_SPACE_1 + ); + expect(res.item).to.have.key('id'); + }); + + it('should not allow to create an enrollment api key for a policy in a different space', async () => { + let err: Error | undefined; + try { + await apiClient.postEnrollmentApiKeys( + { + policy_id: defaultSpacePolicy1.item.id, + }, + TEST_SPACE_1 + ); + } catch (_err) { + err = _err; + } + expect(err).to.be.an(Error); + expect(err?.message).to.match(/404 "Not Found"/); + }); + + it('should not allow to create an enrollment api key for a policy from a different space in the default space', async () => { + let err: Error | undefined; + try { + await apiClient.postEnrollmentApiKeys({ + policy_id: spaceTest1Policy1.item.id, + }); + } catch (_err) { + err = _err; + } + expect(err).to.be.an(Error); + expect(err?.message).to.match(/404 "Not Found"/); + }); + }); + describe('DELETE /enrollment_api_keys', () => { + it('should not allow to delete an enrollment api key in a different space', async () => { + let err: Error | undefined; + try { + await apiClient.deleteEnrollmentApiKey(defaultSpaceEnrollmentKey1.id, TEST_SPACE_1); + } catch (_err) { + err = _err; + } + expect(err).to.be.an(Error); + expect(err?.message).to.match(/404 "Not Found"/); + }); + + it('should not allow to delete an enrollment api key from a different space in the default space', async () => { + let err: Error | undefined; + try { + await apiClient.deleteEnrollmentApiKey(spaceTest1EnrollmentKey1.id); + } catch (_err) { + err = _err; + } + expect(err).to.be.an(Error); + expect(err?.message).to.match(/404 "Not Found"/); + }); + it('should allow to delete an enrollment api key in the default space', async () => { + await apiClient.deleteEnrollmentApiKey(defaultSpaceEnrollmentKey1.id); + }); + it('should allow to create an enrollment api key in the same space', async () => { + await apiClient.deleteEnrollmentApiKey(spaceTest1EnrollmentKey1.id, TEST_SPACE_1); + }); + }); + }); + }); +} diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/enrollment_settings.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/enrollment_settings.ts new file mode 100644 index 0000000000000..d5eb41afb2104 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/enrollment_settings.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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { skipIfNoDockerRegistry } from '../../helpers'; +import { SpaceTestApiClient } from './api_helper'; +import { cleanFleetIndices } from './helpers'; +import { setupTestSpaces, TEST_SPACE_1 } from './space_helpers'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const supertest = getService('supertest'); + const esClient = getService('es'); + const kibanaServer = getService('kibanaServer'); + const createFleetAgent = async (agentPolicyId: string, spaceId?: string) => { + const agentResponse = await esClient.index({ + index: '.fleet-agents', + refresh: true, + body: { + access_api_key_id: 'api-key-3', + active: true, + policy_id: agentPolicyId, + policy_revision_idx: 1, + last_checkin_status: 'online', + type: 'PERMANENT', + local_metadata: { + host: { hostname: 'host123' }, + elastic: { agent: { version: '8.15.0' } }, + }, + user_provided_metadata: {}, + enrolled_at: new Date().toISOString(), + last_checkin: new Date().toISOString(), + tags: ['tag1'], + namespaces: spaceId ? [spaceId] : undefined, + }, + }); + + return agentResponse._id; + }; + describe('enrollment_settings', async function () { + skipIfNoDockerRegistry(providerContext); + const apiClient = new SpaceTestApiClient(supertest); + + describe('Without Fleet server setup', () => { + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + }); + + setupTestSpaces(providerContext); + + before(async () => { + await apiClient.setup(); + }); + + describe('GET /enrollments/settings', () => { + it('in default space it should not return an active fleet server', async () => { + const res = await apiClient.getEnrollmentSettings(); + expect(res.fleet_server.has_active).to.be(false); + }); + + it('in a specific spaceit should not return an active fleet server', async () => { + const res = await apiClient.getEnrollmentSettings(TEST_SPACE_1); + expect(res.fleet_server.has_active).to.be(false); + }); + }); + }); + + describe('With Fleet server setup in a specific space', () => { + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + }); + + setupTestSpaces(providerContext); + + before(async () => { + await apiClient.setup(); + const testSpaceFleetServerPolicy = await apiClient.createFleetServerPolicy(TEST_SPACE_1); + await createFleetAgent(testSpaceFleetServerPolicy.item.id, TEST_SPACE_1); + }); + + describe('GET /enrollments/settings', () => { + it('in default space it should return all policies and active fleet server', async () => { + const res = await apiClient.getEnrollmentSettings(); + expect(res.fleet_server.has_active).to.be(true); + }); + + it('in a specific space it should return all policies and active fleet server', async () => { + const res = await apiClient.getEnrollmentSettings(TEST_SPACE_1); + expect(res.fleet_server.has_active).to.be(true); + }); + }); + }); + + describe('With Fleet server setup in default space', () => { + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + }); + + setupTestSpaces(providerContext); + + before(async () => { + await apiClient.setup(); + const defaultFleetServerPolicy = await apiClient.createFleetServerPolicy(); + await createFleetAgent(defaultFleetServerPolicy.item.id); + }); + + describe('GET /enrollments/settings', () => { + it('in default space it should return all policies and active fleet server', async () => { + const res = await apiClient.getEnrollmentSettings(); + expect(res.fleet_server.has_active).to.be(true); + }); + + it('in a specific space it should return all policies and active fleet server', async () => { + const res = await apiClient.getEnrollmentSettings(TEST_SPACE_1); + expect(res.fleet_server.has_active).to.be(true); + }); + }); + }); + }); +} diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/helpers.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/helpers.ts new file mode 100644 index 0000000000000..fe731e323ce12 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/helpers.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 { Client } from '@elastic/elasticsearch'; + +export async function cleanFleetIndices(esClient: Client) { + await Promise.all([ + esClient.deleteByQuery({ + index: '.fleet-enrollment-api-keys', + q: '*', + ignore_unavailable: true, + refresh: true, + }), + esClient.deleteByQuery({ + index: '.fleet-agents', + q: '*', + ignore_unavailable: true, + refresh: true, + }), + ]); +} diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/index.js b/x-pack/test/fleet_api_integration/apis/space_awareness/index.js new file mode 100644 index 0000000000000..3a3d9ea907150 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/index.js @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export default function loadTests({ loadTestFile }) { + describe('Space awareness', () => { + loadTestFile(require.resolve('./enrollment_api_keys')); + loadTestFile(require.resolve('./uninstall_tokens')); + loadTestFile(require.resolve('./agent_policies')); + loadTestFile(require.resolve('./agents')); + loadTestFile(require.resolve('./enrollment_settings')); + }); +} diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/space_helpers.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/space_helpers.ts new file mode 100644 index 0000000000000..b529c6390517a --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/space_helpers.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 { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; + +export const TEST_SPACE_1 = 'test1'; + +export function setupTestSpaces(providerContex: FtrProviderContext) { + const kibanaServer = providerContex.getService('kibanaServer'); + before(async () => + Promise.all([ + kibanaServer.spaces + .create({ + id: TEST_SPACE_1, + name: TEST_SPACE_1, + }) + .catch((err) => {}), + ]) + ); +} diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/uninstall_tokens.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/uninstall_tokens.ts new file mode 100644 index 0000000000000..12bbc8bec3b37 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/uninstall_tokens.ts @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { CreateAgentPolicyResponse } from '@kbn/fleet-plugin/common'; +import { UninstallTokenMetadata } from '@kbn/fleet-plugin/common/types/models/uninstall_token'; +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { skipIfNoDockerRegistry } from '../../helpers'; +import { SpaceTestApiClient } from './api_helper'; +import { cleanFleetIndices } from './helpers'; +import { setupTestSpaces, TEST_SPACE_1 } from './space_helpers'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const supertest = getService('supertest'); + const esClient = getService('es'); + const kibanaServer = getService('kibanaServer'); + + describe('uninstall tokens', async function () { + skipIfNoDockerRegistry(providerContext); + const apiClient = new SpaceTestApiClient(supertest); + + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.savedObjects.cleanStandardList({ + space: TEST_SPACE_1, + }); + await cleanFleetIndices(esClient); + }); + + setupTestSpaces(providerContext); + let defaultSpacePolicy1: CreateAgentPolicyResponse; + let spaceTest1Policy1: CreateAgentPolicyResponse; + let spaceTest1Policy2: CreateAgentPolicyResponse; + let defaultSpaceToken: UninstallTokenMetadata; + let spaceTest1Token: UninstallTokenMetadata; + // Create agent policies it should create am uninstall token for every keys + before(async () => { + const [_defaultSpacePolicy1, _spaceTest1Policy1, _spaceTest1Policy2] = await Promise.all([ + apiClient.createAgentPolicy(), + apiClient.createAgentPolicy(TEST_SPACE_1), + apiClient.createAgentPolicy(TEST_SPACE_1), + ]); + defaultSpacePolicy1 = _defaultSpacePolicy1; + spaceTest1Policy1 = _spaceTest1Policy1; + spaceTest1Policy2 = _spaceTest1Policy2; + + const space1Tokens = await apiClient.getUninstallTokens(TEST_SPACE_1); + const defaultSpaceTokens = await apiClient.getUninstallTokens(); + defaultSpaceToken = defaultSpaceTokens.items[0]; + spaceTest1Token = space1Tokens.items[0]; + }); + + describe('GET /uninstall_tokens', () => { + it('should return uninstall_tokens in a specific space', async () => { + const tokens = await apiClient.getUninstallTokens(TEST_SPACE_1); + expect(tokens.total).to.eql(2); + const policyIds = tokens.items?.map((item) => item.policy_id); + expect(policyIds).to.contain(spaceTest1Policy1.item.id); + expect(policyIds).to.contain(spaceTest1Policy2.item.id); + expect(policyIds).not.to.contain(defaultSpacePolicy1.item.id); + }); + + it('should return uninstall_tokens in default space', async () => { + const tokens = await apiClient.getUninstallTokens(); + expect(tokens.total).to.eql(1); + const policyIds = tokens.items?.map((item) => item.policy_id); + expect(policyIds).not.to.contain(spaceTest1Policy1.item.id); + expect(policyIds).not.contain(spaceTest1Policy2.item.id); + expect(policyIds).to.contain(defaultSpacePolicy1.item.id); + }); + }); + + describe('GET /uninstall_tokens/{id}', () => { + it('should allow to access a uninstall token in a specific space', async () => { + await apiClient.getUninstallToken(spaceTest1Token.id, TEST_SPACE_1); + }); + it('should not allow to get an uninstall token from a different space from the default space', async () => { + let err: Error | undefined; + try { + await apiClient.getUninstallToken(spaceTest1Token.id); + } catch (_err) { + err = _err; + } + + expect(err).to.be.an(Error); + expect(err?.message).to.match(/404 "Not Found"/); + }); + + it('should not allow to get an default space uninstall token from a different space', async () => { + let err: Error | undefined; + try { + await apiClient.getUninstallToken(defaultSpaceToken.id, TEST_SPACE_1); + } catch (_err) { + err = _err; + } + + expect(err).to.be.an(Error); + expect(err?.message).to.match(/404 "Not Found"/); + }); + }); + }); +} diff --git a/x-pack/test/fleet_api_integration/config.space_awareness.ts b/x-pack/test/fleet_api_integration/config.space_awareness.ts new file mode 100644 index 0000000000000..f77d3826964ed --- /dev/null +++ b/x-pack/test/fleet_api_integration/config.space_awareness.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 { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const baseFleetApiConfig = await readConfigFile(require.resolve('./config.base.ts')); + + const serverArgs: string[] = [...baseFleetApiConfig.get('kbnTestServer.serverArgs')]; + + const enableExperimentalIndex = serverArgs.findIndex((val) => + val.includes('xpack.fleet.enableExperimental') + ); + serverArgs[enableExperimentalIndex] = `--xpack.fleet.enableExperimental=${JSON.stringify([ + 'outputSecretsStorage', + 'agentTamperProtectionEnabled', + 'enableStrictKQLValidation', + 'subfeaturePrivileges', + 'enablePackagesStateMachine', + 'useSpaceAwareness', + ])}`; + + return { + ...baseFleetApiConfig.getAll(), + kbnTestServer: { + ...baseFleetApiConfig.get('kbnTestServer'), + serverArgs, + }, + testFiles: [require.resolve('./apis/space_awareness')], + junit: { + reportName: 'X-Pack Fleet Agent Policy API Integration Tests', + }, + }; +} diff --git a/x-pack/test/fleet_cypress/artifact_manager.ts b/x-pack/test/fleet_cypress/artifact_manager.ts index 17ba9b0a5517d..0fe6609f28efc 100644 --- a/x-pack/test/fleet_cypress/artifact_manager.ts +++ b/x-pack/test/fleet_cypress/artifact_manager.ts @@ -7,8 +7,14 @@ import axios from 'axios'; import { last } from 'lodash'; +import pRetry from 'p-retry'; + +const DEFAULT_VERSION = '8.15.0-SNAPSHOT'; export async function getLatestVersion(): Promise { - const response: any = await axios('https://artifacts-api.elastic.co/v1/versions'); - return last(response.data.versions as string[]) || '8.1.0-SNAPSHOT'; + return pRetry(() => axios('https://artifacts-api.elastic.co/v1/versions'), { + maxRetryTime: 60 * 1000, // 1 minute + }) + .then((response) => last(response.data.versions as string[]) || DEFAULT_VERSION) + .catch(() => DEFAULT_VERSION); } diff --git a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/__mocks__/analysis_table_textfield_zerodocsfallback.ts b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/__mocks__/analysis_table_textfield_zerodocsfallback.ts index 75c624a67321c..6242ac96d0a21 100644 --- a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/__mocks__/analysis_table_textfield_zerodocsfallback.ts +++ b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/__mocks__/analysis_table_textfield_zerodocsfallback.ts @@ -10,35 +10,35 @@ export const analysisTableTextfieldZerodocsfallback = [ fieldName: 'message', fieldValue: 'Paul [11/19/2022, 8:00:34 AM] "GET /home.php HTTP/1.1" 200', logRate: 'Chart type:bar chart', - pValue: '1.00', + pValue: '', impact: '', }, { fieldName: 'response_code', fieldValue: '500', logRate: 'Chart type:bar chart', - pValue: '1.00', + pValue: '', impact: '', }, { fieldName: 'url', fieldValue: 'home.php', logRate: 'Chart type:bar chart', - pValue: '1.00', + pValue: '', impact: '', }, { fieldName: 'user', fieldValue: 'Paul', logRate: 'Chart type:bar chart', - pValue: '1.00', + pValue: '', impact: '', }, { fieldName: 'version', fieldValue: 'v1.0.0', logRate: 'Chart type:bar chart', - pValue: '1.00', + pValue: '', impact: '', }, ]; diff --git a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/__mocks__/analysis_table_zerodocsfallback.ts b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/__mocks__/analysis_table_zerodocsfallback.ts index 3d4f806db8f49..4743557dc0797 100644 --- a/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/__mocks__/analysis_table_zerodocsfallback.ts +++ b/x-pack/test/functional/apps/aiops/log_rate_analysis/test_data/__mocks__/analysis_table_zerodocsfallback.ts @@ -10,28 +10,28 @@ export const analysisTableZerodocsfallback = [ fieldName: 'response_code', fieldValue: '500', logRate: 'Chart type:bar chart', - pValue: '1.00', + pValue: '', impact: '', }, { fieldName: 'url', fieldValue: 'home.php', logRate: 'Chart type:bar chart', - pValue: '1.00', + pValue: '', impact: '', }, { fieldName: 'user', fieldValue: 'Paul', logRate: 'Chart type:bar chart', - pValue: '1.00', + pValue: '', impact: '', }, { fieldName: 'version', fieldValue: 'v1.0.0', logRate: 'Chart type:bar chart', - pValue: '1.00', + pValue: '', impact: '', }, ]; diff --git a/x-pack/test/functional/apps/dataset_quality/data/logs_data.ts b/x-pack/test/functional/apps/dataset_quality/data/logs_data.ts index 399030d1dd377..168aeb4b4df21 100644 --- a/x-pack/test/functional/apps/dataset_quality/data/logs_data.ts +++ b/x-pack/test/functional/apps/dataset_quality/data/logs_data.ts @@ -191,6 +191,7 @@ export function createDegradedFieldsRecord({ export const datasetNames = ['synth.1', 'synth.2', 'synth.3']; export const defaultNamespace = 'default'; +export const productionNamespace = 'production'; // Logs Data logic const MESSAGE_LOG_LEVELS: MessageWithLevel[] = [ diff --git a/x-pack/test/functional/apps/dataset_quality/dataset_quality_flyout.ts b/x-pack/test/functional/apps/dataset_quality/dataset_quality_flyout.ts index b3a80bd673d8a..59e11b225772a 100644 --- a/x-pack/test/functional/apps/dataset_quality/dataset_quality_flyout.ts +++ b/x-pack/test/functional/apps/dataset_quality/dataset_quality_flyout.ts @@ -12,6 +12,7 @@ import { datasetNames, getInitialTestLogs, getLogsForDataset, + productionNamespace, } from './data'; const integrationActions = { @@ -32,22 +33,70 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid const retry = getService('retry'); const browser = getService('browser'); const to = '2024-01-01T12:00:00.000Z'; + const apacheAccessDatasetName = 'apache.access'; + const apacheAccessDatasetHumanName = 'Apache access logs'; + const apacheIntegrationId = 'apache'; + const apachePkg = { + name: 'apache', + version: '1.14.0', + }; + + const bitbucketDatasetName = 'atlassian_bitbucket.audit'; + const bitbucketDatasetHumanName = 'Bitbucket Audit Logs'; + const bitbucketPkg = { + name: 'atlassian_bitbucket', + version: '1.14.0', + }; + + const degradedDatasetName = datasetNames[2]; + + describe('Flyout', () => { + before(async () => { + // Install Apache Integration and ingest logs for it + await PageObjects.observabilityLogsExplorer.installPackage(apachePkg); + + // Install Bitbucket Integration (package which does not has Dashboards) and ingest logs for it + await PageObjects.observabilityLogsExplorer.installPackage(bitbucketPkg); + + await synthtrace.index([ + // Ingest basic logs + getInitialTestLogs({ to, count: 4 }), + // Ingest Degraded Logs + createDegradedFieldsRecord({ + to: new Date().toISOString(), + count: 2, + dataset: degradedDatasetName, + }), + // Index 15 logs for `logs-apache.access` dataset + getLogsForDataset({ + to: new Date().toISOString(), + count: 15, + dataset: apacheAccessDatasetName, + namespace: productionNamespace, + }), + // Index degraded docs for Apache integration + getLogsForDataset({ + to: new Date().toISOString(), + count: 1, + dataset: apacheAccessDatasetName, + namespace: productionNamespace, + isMalformed: true, + }), + // Index logs for Bitbucket integration + getLogsForDataset({ to, count: 10, dataset: bitbucketDatasetName }), + ]); + + await PageObjects.datasetQuality.navigateTo(); + }); - describe('Dataset quality flyout', () => { - // FLAKY: https://github.com/elastic/kibana/issues/182154 - // Added this sub describe block so that the existing flaky tests can be skipped and new ones can be added in the other describe block - describe.skip('Other dataset quality flyout tests', () => { - before(async () => { - await synthtrace.index(getInitialTestLogs({ to, count: 4 })); - await PageObjects.datasetQuality.navigateTo(); - }); - - after(async () => { - await synthtrace.clean(); - await PageObjects.observabilityLogsExplorer.removeInstalledPackages(); - }); + after(async () => { + await PageObjects.observabilityLogsExplorer.uninstallPackage(apachePkg); + await PageObjects.observabilityLogsExplorer.uninstallPackage(bitbucketPkg); + await synthtrace.clean(); + }); - it('opens the flyout for the right dataset', async () => { + describe('open flyout', () => { + it('should open the flyout for the right dataset', async () => { const testDatasetName = datasetNames[1]; await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName); @@ -55,42 +104,12 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid await testSubjects.existOrFail( PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutTitle ); - }); - - it('shows the correct last activity', async () => { - const testDatasetName = datasetNames[0]; - // Update last activity for the dataset await PageObjects.datasetQuality.closeFlyout(); - await synthtrace.index( - getLogsForDataset({ to: new Date().toISOString(), count: 1, dataset: testDatasetName }) - ); - await PageObjects.datasetQuality.refreshTable(); - - const cols = await PageObjects.datasetQuality.parseDatasetTable(); - - const datasetNameCol = cols['Data Set Name']; - const datasetNameColCellTexts = await datasetNameCol.getCellTexts(); - - const testDatasetRowIndex = datasetNameColCellTexts.findIndex( - (dName: string) => dName === testDatasetName - ); - - const lastActivityText = (await cols['Last Activity'].getCellTexts())[testDatasetRowIndex]; - - await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName); - - const lastActivityTextExists = await PageObjects.datasetQuality.doestTextExistInFlyout( - lastActivityText, - `[data-test-subj=${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutFieldValue}]` - ); - - expect(lastActivityTextExists).to.eql(true); }); it('reflects the breakdown field state in url', async () => { - const testDatasetName = datasetNames[0]; - await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName); + await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); const breakdownField = 'service.name'; await PageObjects.datasetQuality.selectBreakdownField(breakdownField); @@ -109,25 +128,25 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid const currentUrl = await browser.getCurrentUrl(); expect(currentUrl).to.not.contain('breakdownField'); }); + await PageObjects.datasetQuality.closeFlyout(); }); + }); - it('shows the integration details', async () => { - const apacheAccessDatasetName = 'apache.access'; - const apacheAccessDatasetHumanName = 'Apache access logs'; - const apacheIntegrationId = 'apache'; - - await PageObjects.observabilityLogsExplorer.navigateTo(); + describe('integrations', () => { + it('should hide the integration section for non integrations', async () => { + const testDatasetName = datasetNames[1]; - // Add initial integrations - await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); + await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName); - // Index 10 logs for `logs-apache.access` dataset - await synthtrace.index( - getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName }) + await testSubjects.missingOrFail( + PageObjects.datasetQuality.testSubjectSelectors + .datasetQualityFlyoutFieldsListIntegrationDetails ); - await PageObjects.datasetQuality.navigateTo(); + await PageObjects.datasetQuality.closeFlyout(); + }); + it('should shows the integration section for integrations', async () => { await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); const integrationNameElements = await PageObjects.datasetQuality.getFlyoutElementsByText( @@ -135,124 +154,141 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid apacheIntegrationId ); - await PageObjects.datasetQuality.closeFlyout(); + await testSubjects.existOrFail( + PageObjects.datasetQuality.testSubjectSelectors + .datasetQualityFlyoutFieldsListIntegrationDetails + ); expect(integrationNameElements.length).to.eql(1); + + await PageObjects.datasetQuality.closeFlyout(); }); - it('goes to log explorer page when open button is clicked', async () => { - const testDatasetName = datasetNames[2]; - await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName); + it('should show the integration actions menu with correct actions', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); + await PageObjects.datasetQuality.openIntegrationActionsMenu(); - await (await PageObjects.datasetQuality.getFlyoutLogsExplorerButton()).click(); + const actions = await Promise.all( + Object.values(integrationActions).map((action) => + PageObjects.datasetQuality.getIntegrationActionButtonByAction(action) + ) + ); - // Confirm dataset selector text in observability logs explorer - const datasetSelectorText = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - expect(datasetSelectorText).to.eql(testDatasetName); + expect(actions.length).to.eql(3); + await PageObjects.datasetQuality.closeFlyout(); + }); + + it('should hide integration dashboard for integrations without dashboards', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(bitbucketDatasetHumanName); + await PageObjects.datasetQuality.openIntegrationActionsMenu(); + + await testSubjects.missingOrFail( + PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutIntegrationAction( + integrationActions.viewDashboards + ) + ); + await PageObjects.datasetQuality.closeFlyout(); }); - it('shows summary KPIs', async () => { + it('Should navigate to integration overview page on clicking integration overview action', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(bitbucketDatasetHumanName); + await PageObjects.datasetQuality.openIntegrationActionsMenu(); + + const action = await PageObjects.datasetQuality.getIntegrationActionButtonByAction( + integrationActions.overview + ); + + await action.click(); + + await retry.tryForTime(5000, async () => { + const currentUrl = await browser.getCurrentUrl(); + const parsedUrl = new URL(currentUrl); + + expect(parsedUrl.pathname).to.contain('/app/integrations/detail/atlassian_bitbucket'); + }); + await PageObjects.datasetQuality.navigateTo(); + }); - const apacheAccessDatasetHumanName = 'Apache access logs'; + it('should navigate to index template page in clicking Integration template', async () => { await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); + await PageObjects.datasetQuality.openIntegrationActionsMenu(); + + const action = await PageObjects.datasetQuality.getIntegrationActionButtonByAction( + integrationActions.template + ); + + await action.click(); - const summary = await PageObjects.datasetQuality.parseFlyoutKpis(); - expect(summary).to.eql({ - docsCountTotal: '0', - size: '0.0 B', - services: '0', - hosts: '0', - degradedDocs: '0', + await retry.tryForTime(5000, async () => { + const currentUrl = await browser.getCurrentUrl(); + const parsedUrl = new URL(currentUrl); + expect(parsedUrl.pathname).to.contain( + `/app/management/data/index_management/templates/logs-${apacheAccessDatasetName}` + ); }); + await PageObjects.datasetQuality.navigateTo(); }); - it('shows the updated KPIs', async () => { - const apacheAccessDatasetName = 'apache.access'; - const apacheAccessDatasetHumanName = 'Apache access logs'; + it('should navigate to the selected dashboard on clicking integration dashboard action ', async () => { await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); + await PageObjects.datasetQuality.openIntegrationActionsMenu(); - const summaryBefore = await PageObjects.datasetQuality.parseFlyoutKpis(); - - // Set time range to 3 days ago - const flyoutBodyContainer = await testSubjects.find( - PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutBody + const action = await PageObjects.datasetQuality.getIntegrationActionButtonByAction( + integrationActions.viewDashboards ); - await PageObjects.datasetQuality.setDatePickerLastXUnits(flyoutBodyContainer, 3, 'd'); - // Index 2 doc 2 days ago - const time2DaysAgo = Date.now() - 2 * 24 * 60 * 60 * 1000; - await synthtrace.index( - getLogsForDataset({ - to: time2DaysAgo, - count: 2, - dataset: apacheAccessDatasetName, - isMalformed: false, - }) - ); + await action.click(); - // Index 5 degraded docs 2 days ago - await synthtrace.index( - getLogsForDataset({ - to: time2DaysAgo, - count: 5, - dataset: apacheAccessDatasetName, - isMalformed: true, - }) - ); + const dashboardButtons = await PageObjects.datasetQuality.getIntegrationDashboardButtons(); + const firstDashboardButton = await dashboardButtons[0]; + const dashboardText = await firstDashboardButton.getVisibleText(); - await PageObjects.datasetQuality.refreshFlyout(); - const summaryAfter = await PageObjects.datasetQuality.parseFlyoutKpis(); + await firstDashboardButton.click(); - expect(parseInt(summaryAfter.docsCountTotal, 10)).to.be.greaterThan( - parseInt(summaryBefore.docsCountTotal, 10) - ); + const breadcrumbText = await testSubjects.getVisibleText('breadcrumb last'); - expect(parseInt(summaryAfter.degradedDocs, 10)).to.be.greaterThan( - parseInt(summaryBefore.degradedDocs, 10) - ); + expect(breadcrumbText).to.eql(dashboardText); - expect(parseInt(summaryAfter.size, 10)).to.be.greaterThan(parseInt(summaryBefore.size, 10)); - expect(parseInt(summaryAfter.services, 10)).to.be.greaterThan( - parseInt(summaryBefore.services, 10) - ); - expect(parseInt(summaryAfter.hosts, 10)).to.be.greaterThan( - parseInt(summaryBefore.hosts, 10) - ); + await PageObjects.datasetQuality.navigateTo(); }); + }); - it('shows the right number of services', async () => { - const apacheAccessDatasetName = 'apache.access'; - const apacheAccessDatasetHumanName = 'Apache access logs'; + describe('summary panel', () => { + it('should show summary KPIs', async () => { await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); - const summaryBefore = await PageObjects.datasetQuality.parseFlyoutKpis(); - const testServices = ['test-srv-1', 'test-srv-2']; + const { docsCountTotal, degradedDocs, services, hosts, size } = + await PageObjects.datasetQuality.parseFlyoutKpis(); + expect(parseInt(docsCountTotal, 10)).to.be(226); + expect(parseInt(degradedDocs, 10)).to.be(1); + expect(parseInt(services, 10)).to.be(3); + expect(parseInt(hosts, 10)).to.be(52); + expect(parseInt(size, 10)).to.be.greaterThan(0); - // Index 2 docs with different services - const timeNow = Date.now(); - await synthtrace.index( - getLogsForDataset({ - to: timeNow, - count: 2, - dataset: apacheAccessDatasetName, - isMalformed: false, - services: testServices, - }) - ); + await PageObjects.datasetQuality.closeFlyout(); + }); + }); - await PageObjects.datasetQuality.refreshFlyout(); - const summaryAfter = await PageObjects.datasetQuality.parseFlyoutKpis(); + describe('navigation', () => { + it('should go to log explorer page when the open in log explorer button is clicked', async () => { + const testDatasetName = datasetNames[2]; + await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName); - expect(parseInt(summaryAfter.services, 10)).to.eql( - parseInt(summaryBefore.services, 10) + testServices.length - ); + const logExplorerButton = await PageObjects.datasetQuality.getFlyoutLogsExplorerButton(); + + await logExplorerButton.click(); + + // Confirm dataset selector text in observability logs explorer + const datasetSelectorText = + await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); + expect(datasetSelectorText).to.eql(testDatasetName); + + // Should bring back the test to the dataset quality page + await PageObjects.datasetQuality.navigateTo(); }); - it('goes to log explorer for degraded docs when show all is clicked', async () => { - const apacheAccessDatasetName = 'apache.access'; - const apacheAccessDatasetHumanName = 'Apache access logs'; + it('should go log explorer for degraded docs when the show all button is clicked', async () => { await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); const degradedDocsShowAllSelector = `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutKpiLink}-${PageObjects.datasetQuality.texts.degradedDocs}`; @@ -266,11 +302,13 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid await browser.closeCurrentWindow(); await browser.switchTab(0); + + await PageObjects.datasetQuality.closeFlyout(); }); // Blocked by https://github.com/elastic/kibana/issues/181705 + // Its a test written ahead of its time. it.skip('goes to infra hosts for hosts when show all is clicked', async () => { - const apacheAccessDatasetHumanName = 'Apache access logs'; await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); const hostsShowAllSelector = `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutKpiLink}-${PageObjects.datasetQuality.texts.hosts}`; @@ -286,307 +324,132 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid await browser.closeCurrentWindow(); await browser.switchTab(0); - }); - it('Integration actions menu is present with correct actions', async () => { - const apacheAccessDatasetName = 'apache.access'; - const apacheAccessDatasetHumanName = 'Apache access logs'; - - await PageObjects.observabilityLogsExplorer.navigateTo(); + await PageObjects.datasetQuality.closeFlyout(); + }); + }); - // Add initial integrations - await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); + describe('degraded fields table', () => { + it(' should show empty degraded fields table when no degraded fields are present', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(datasetNames[0]); - // Index 10 logs for `logs-apache.access` dataset - await synthtrace.index( - getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName }) + await testSubjects.existOrFail( + PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutDegradedTableNoData ); - await PageObjects.datasetQuality.navigateTo(); + await PageObjects.datasetQuality.closeFlyout(); + }); - await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); - await PageObjects.datasetQuality.openIntegrationActionsMenu(); + it('should show the degraded fields table with data when present', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); - const actions = await Promise.all( - Object.values(integrationActions).map((action) => - PageObjects.datasetQuality.getIntegrationActionButtonByAction(action) - ) + await testSubjects.existOrFail( + PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutDegradedFieldTable ); - expect(actions.length).to.eql(3); - }); + const rows = + await PageObjects.datasetQuality.getDatasetQualityFlyoutDegradedFieldTableRows(); - it('Integration dashboard action hidden for integrations without dashboards', async () => { - const bitbucketDatasetName = 'atlassian_bitbucket.audit'; - const bitbucketDatasetHumanName = 'Bitbucket Audit Logs'; + expect(rows.length).to.eql(2); - await PageObjects.observabilityLogsExplorer.navigateTo(); + await PageObjects.datasetQuality.closeFlyout(); + }); - // Add initial integrations - await PageObjects.observabilityLogsExplorer.installPackage({ - name: 'atlassian_bitbucket', - version: '1.14.0', - }); + it('should display Spark Plot for every row of degraded fields', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); - // Index 10 logs for `atlassian_bitbucket.audit` dataset - await synthtrace.index(getLogsForDataset({ to, count: 10, dataset: bitbucketDatasetName })); + const rows = + await PageObjects.datasetQuality.getDatasetQualityFlyoutDegradedFieldTableRows(); - await PageObjects.datasetQuality.navigateTo(); + const sparkPlots = await testSubjects.findAll( + PageObjects.datasetQuality.testSubjectSelectors.datasetQualitySparkPlot + ); - await PageObjects.datasetQuality.openDatasetFlyout(bitbucketDatasetHumanName); - await PageObjects.datasetQuality.openIntegrationActionsMenu(); + expect(rows.length).to.be(sparkPlots.length); - await testSubjects.missingOrFail( - PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutIntegrationAction( - integrationActions.viewDashboards - ) - ); + await PageObjects.datasetQuality.closeFlyout(); }); - it('Integration overview action should navigate to the integration overview page', async () => { - const bitbucketDatasetName = 'atlassian_bitbucket.audit'; - const bitbucketDatasetHumanName = 'Bitbucket Audit Logs'; + it('should sort the table when the count table header is clicked', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); - await PageObjects.observabilityLogsExplorer.navigateTo(); + const table = await PageObjects.datasetQuality.parseDegradedFieldTable(); - // Add initial integrations - await PageObjects.observabilityLogsExplorer.installPackage({ - name: 'atlassian_bitbucket', - version: '1.14.0', - }); + const countColumn = table['Docs count']; + const cellTexts = await countColumn.getCellTexts(); - // Index 10 logs for `atlassian_bitbucket.audit` dataset - await synthtrace.index(getLogsForDataset({ to, count: 10, dataset: bitbucketDatasetName })); + await countColumn.sort('ascending'); + const sortedCellTexts = await countColumn.getCellTexts(); - await PageObjects.datasetQuality.navigateTo(); + expect(cellTexts.reverse()).to.eql(sortedCellTexts); - await PageObjects.datasetQuality.openDatasetFlyout(bitbucketDatasetHumanName); - await PageObjects.datasetQuality.openIntegrationActionsMenu(); + await PageObjects.datasetQuality.closeFlyout(); + }); - const action = await PageObjects.datasetQuality.getIntegrationActionButtonByAction( - integrationActions.overview - ); + it('should update the URL when the table is sorted', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); - await action.click(); + const table = await PageObjects.datasetQuality.parseDegradedFieldTable(); + const countColumn = table['Docs count']; await retry.tryForTime(5000, async () => { const currentUrl = await browser.getCurrentUrl(); const parsedUrl = new URL(currentUrl); + const pageState = parsedUrl.searchParams.get('pageState'); - expect(parsedUrl.pathname).to.contain('/app/integrations/detail/atlassian_bitbucket'); + expect(decodeURIComponent(pageState as string)).to.contain( + 'sort:(direction:desc,field:count)' + ); }); - }); - - it('Integration template action should navigate to the index template page', async () => { - const apacheAccessDatasetName = 'apache.access'; - const apacheAccessDatasetHumanName = 'Apache access logs'; - await PageObjects.observabilityLogsExplorer.navigateTo(); - - // Add initial integrations - await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); - - // Index 10 logs for `logs-apache.access` dataset - await synthtrace.index( - getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName }) - ); - - await PageObjects.datasetQuality.navigateTo(); - - await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); - await PageObjects.datasetQuality.openIntegrationActionsMenu(); + countColumn.sort('ascending'); await retry.tryForTime(5000, async () => { - const action = await PageObjects.datasetQuality.getIntegrationActionButtonByAction( - integrationActions.template - ); - - await action.click(); - const currentUrl = await browser.getCurrentUrl(); const parsedUrl = new URL(currentUrl); - expect(parsedUrl.pathname).to.contain( - `/app/management/data/index_management/templates/logs-${apacheAccessDatasetName}` - ); - }); - }); - - it('Integration dashboard action should navigate to the selected dashboard', async () => { - const apacheAccessDatasetName = 'apache.access'; - const apacheAccessDatasetHumanName = 'Apache access logs'; - - await PageObjects.observabilityLogsExplorer.navigateTo(); - - // Add initial integrations - await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); - - // Index 10 logs for `logs-apache.access` dataset - await synthtrace.index( - getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName }) - ); - - await PageObjects.datasetQuality.navigateTo(); - - await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); - await PageObjects.datasetQuality.openIntegrationActionsMenu(); - - const action = await PageObjects.datasetQuality.getIntegrationActionButtonByAction( - integrationActions.viewDashboards - ); - - await action.click(); - - const dashboardButtons = await PageObjects.datasetQuality.getIntegrationDashboardButtons(); - const firstDashboardButton = await dashboardButtons[0]; - const dashboardText = await firstDashboardButton.getVisibleText(); - - await firstDashboardButton.click(); - - const breadcrumbText = await testSubjects.getVisibleText('breadcrumb last'); - - expect(breadcrumbText).to.eql(dashboardText); - }); - }); - - // The above describe block has some failing/flaky tests which will - // be fixed as part of the tech debt mentioned here - // https://github.com/elastic/kibana/issues/184145 - // Until then, the below describe block is added to cover the tests for the - // newly added degraded Fields Table. This must be merged under the above - // describe block once the tech debt is fixed. - describe('Dataset quality flyout with degraded fields', () => { - const goodDatasetName = 'good'; - const degradedDatasetName = 'degraded'; - const today = new Date().toISOString(); - - describe('Degraded Fields Table with common data', () => { - before(async () => { - await synthtrace.index([ - getLogsForDataset({ - to: today, - count: 2, - dataset: goodDatasetName, - isMalformed: false, - }), - createDegradedFieldsRecord({ - to: today, - count: 2, - dataset: degradedDatasetName, - }), - ]); - await PageObjects.datasetQuality.navigateTo(); - }); + const pageState = parsedUrl.searchParams.get('pageState'); - after(async () => { - await synthtrace.clean(); - }); - - it('shows the degraded fields table with no data when no degraded fields are present', async () => { - await PageObjects.datasetQuality.openDatasetFlyout(goodDatasetName); - - await testSubjects.existOrFail( - PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutDegradedTableNoData - ); - - await PageObjects.datasetQuality.closeFlyout(); - }); - - it('should load the degraded fields table with data', async () => { - await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); - - await testSubjects.existOrFail( - PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutDegradedFieldTable + expect(decodeURIComponent(pageState as string)).to.contain( + 'sort:(direction:asc,field:count)' ); - - const rows = - await PageObjects.datasetQuality.getDatasetQualityFlyoutDegradedFieldTableRows(); - - expect(rows.length).to.eql(2); - - await PageObjects.datasetQuality.closeFlyout(); }); - it('should display Spark Plot for every row of degraded fields', async () => { - await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); - - const rows = - await PageObjects.datasetQuality.getDatasetQualityFlyoutDegradedFieldTableRows(); - - const sparkPlots = await testSubjects.findAll( - PageObjects.datasetQuality.testSubjectSelectors.datasetQualitySparkPlot - ); - - expect(rows.length).to.be(sparkPlots.length); - - await PageObjects.datasetQuality.closeFlyout(); - }); - - it('should sort the table when the count table header is clicked', async () => { - await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); - - const table = await PageObjects.datasetQuality.parseDegradedFieldTable(); - - const countColumn = table['Docs count']; - const cellTexts = await countColumn.getCellTexts(); - - await countColumn.sort('ascending'); - const sortedCellTexts = await countColumn.getCellTexts(); - - expect(cellTexts.reverse()).to.eql(sortedCellTexts); - - await PageObjects.datasetQuality.closeFlyout(); - }); + await PageObjects.datasetQuality.closeFlyout(); }); - describe('Degraded Fields Table with data ingestion', () => { - before(async () => { - await synthtrace.index([ - getLogsForDataset({ - to: today, - count: 2, - dataset: goodDatasetName, - isMalformed: false, - }), - createDegradedFieldsRecord({ - to: today, - count: 2, - dataset: degradedDatasetName, - }), - ]); - await PageObjects.datasetQuality.navigateTo(); - }); - - after(async () => { - await synthtrace.clean(); - }); + // This is the only test which ingest data during the test. + // This block tests the refresh behavior of the degraded fields table. + // Even though this test ingest data, it can also be freely moved inside + // this describe block, and it won't affect any of the existing tests + it('should update the table when new data is ingested and the flyout is refreshed using the time selector', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); - it('should update the table when new data is ingested and the flyout is refreshed using the time selector', async () => { - await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); + const table = await PageObjects.datasetQuality.parseDegradedFieldTable(); - const table = await PageObjects.datasetQuality.parseDegradedFieldTable(); + const countColumn = table['Docs count']; + const cellTexts = await countColumn.getCellTexts(); - const countColumn = table['Docs count']; - const cellTexts = await countColumn.getCellTexts(); + await synthtrace.index([ + createDegradedFieldsRecord({ + to: new Date().toISOString(), + count: 2, + dataset: degradedDatasetName, + }), + ]); - await synthtrace.index([ - createDegradedFieldsRecord({ - to: today, - count: 2, - dataset: degradedDatasetName, - }), - ]); + await PageObjects.datasetQuality.refreshFlyout(); - await PageObjects.datasetQuality.refreshFlyout(); + const updatedTable = await PageObjects.datasetQuality.parseDegradedFieldTable(); + const updatedCountColumn = updatedTable['Docs count']; - const updatedCellTexts = await countColumn.getCellTexts(); + const updatedCellTexts = await updatedCountColumn.getCellTexts(); - const singleValuePreviously = parseInt(cellTexts[0], 10); - const singleValueNow = parseInt(updatedCellTexts[0], 10); + const singleValuePreviously = parseInt(cellTexts[0], 10); + const singleValueNow = parseInt(updatedCellTexts[0], 10); - expect(singleValueNow).to.be(singleValuePreviously * 2); + expect(singleValueNow).to.be.greaterThan(singleValuePreviously); - await PageObjects.datasetQuality.closeFlyout(); - }); + await PageObjects.datasetQuality.closeFlyout(); }); }); }); diff --git a/x-pack/test/functional/apps/dataset_quality/dataset_quality_privileges.ts b/x-pack/test/functional/apps/dataset_quality/dataset_quality_privileges.ts new file mode 100644 index 0000000000000..c7c3cbf709931 --- /dev/null +++ b/x-pack/test/functional/apps/dataset_quality/dataset_quality_privileges.ts @@ -0,0 +1,246 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { DatasetQualityFtrProviderContext } from './config'; +import { getInitialTestLogs, getLogsForDataset } from './data'; + +export default function ({ getService, getPageObjects }: DatasetQualityFtrProviderContext) { + const PageObjects = getPageObjects([ + 'common', + 'security', + 'navigationalSearch', + 'observabilityLogsExplorer', + 'datasetQuality', + ]); + const security = getService('security'); + const synthtrace = getService('logSynthtraceEsClient'); + const testSubjects = getService('testSubjects'); + const find = getService('find'); + const to = new Date(new Date().setDate(new Date().getDate() - 1)).toISOString(); + + const apacheAccessDatasetName = 'apache.access'; + const apacheAccessDatasetHumanName = 'Apache access logs'; + + describe('Dataset quality handles user privileges', () => { + before(async () => { + await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); + }); + + after(async () => { + await PageObjects.observabilityLogsExplorer.removeInstalledPackages(); + }); + + describe('User cannot read any logs-*', () => { + before(async () => { + // Index logs for synth-* and apache.access datasets + await synthtrace.index(getInitialTestLogs({ to, count: 4 })); + + await createDatasetQualityUserWithRole(security, 'dataset_quality_no_read', []); + + // Logout in order to re-login with a different user + await PageObjects.security.forceLogout(); + await PageObjects.security.login( + 'dataset_quality_no_read', + 'dataset_quality_no_read-password', + { + expectSpaceSelector: false, + } + ); + + await PageObjects.datasetQuality.navigateTo(); + }); + + after(async () => { + await synthtrace.clean(); + + // Cleanup the user and role + await PageObjects.security.forceLogout(); + await deleteDatasetQualityUserWithRole(security, 'dataset_quality_no_read'); + }); + + it('shows empty state as user cannot read any dataset', async () => { + // Existence of `datasetQualityNoPrivilegesEmptyState` cannot be asserted as without the logs-*-* read privilege, + // only the management home screen is shown + await testSubjects.existOrFail( + PageObjects.datasetQuality.testSubjectSelectors.managementHome + ); + }); + }); + + describe('User can read logs-*', () => { + before(async () => { + await createDatasetQualityUserWithRole(security, 'dataset_quality_limited_user', [ + { names: ['logs-*'], privileges: ['read', 'view_index_metadata'] }, + { names: ['logs-synth*'], privileges: ['read'] }, // No monitor privilege + { names: ['logs-apache*'], privileges: ['monitor'] }, + ]); + + // Logout in order to re-login with a different user + await PageObjects.security.forceLogout(); + await PageObjects.security.login( + 'dataset_quality_limited_user', + 'dataset_quality_limited_user-password', + { + expectSpaceSelector: false, + } + ); + }); + + after(async () => { + // Cleanup the user and role + await PageObjects.security.forceLogout(); + await deleteDatasetQualityUserWithRole(security, 'dataset_quality_limited_user'); + }); + + describe('User cannot monitor any data stream', () => { + before(async () => { + // Index logs for synth-* and apache.access datasets + await synthtrace.index(getInitialTestLogs({ to, count: 4 })); + + await PageObjects.datasetQuality.navigateTo(); + }); + + after(async () => { + await synthtrace.clean(); + }); + + it('Active and Estimated data are not available due to underprivileged user', async () => { + await testSubjects.existOrFail( + `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityInsufficientPrivileges}-${PageObjects.datasetQuality.texts.activeDatasets}` + ); + await testSubjects.existOrFail( + `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityInsufficientPrivileges}-${PageObjects.datasetQuality.texts.estimatedData}` + ); + }); + + it('"Show inactive datasets" is hidden when lastActivity is not available', async () => { + await find.waitForDeletedByCssSelector( + PageObjects.datasetQuality.selectors.showInactiveDatasetsNamesSwitch + ); + }); + + it('does not show size and last activity columns for underprivileged data stream', async () => { + const cols = await PageObjects.datasetQuality.getDatasetTableHeaderTexts(); + + expect(cols).to.not.contain('Size'); + expect(cols).to.not.contain('Last Activity'); + }); + }); + + describe('User can monitor some data streams', () => { + before(async () => { + // Index logs for synth-* and apache.access datasets + await synthtrace.index(getInitialTestLogs({ to, count: 4 })); + await synthtrace.index( + getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName }) + ); + + await PageObjects.datasetQuality.navigateTo(); + }); + + after(async () => { + await synthtrace.clean(); + }); + + it('shows underprivileged warning when size and last activity cannot be accessed for some data streams', async () => { + await PageObjects.datasetQuality.refreshTable(); + + const datasetWithMonitorPrivilege = apacheAccessDatasetHumanName; + const datasetWithoutMonitorPrivilege = 'synth.1'; + + // "Size" and "Last Activity" should be available for `apacheAccessDatasetName` + await testSubjects.missingOrFail( + `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityInsufficientPrivileges}-sizeBytes-${datasetWithMonitorPrivilege}` + ); + await testSubjects.missingOrFail( + `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityInsufficientPrivileges}-lastActivity-${datasetWithMonitorPrivilege}` + ); + + // "Size" and "Last Activity" should not be available for `datasetWithoutMonitorPrivilege` + await testSubjects.existOrFail( + `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityInsufficientPrivileges}-sizeBytes-${datasetWithoutMonitorPrivilege}` + ); + await testSubjects.existOrFail( + `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityInsufficientPrivileges}-lastActivity-${datasetWithoutMonitorPrivilege}` + ); + }); + + it('flyout shows insufficient privileges warning for underprivileged data stream', async () => { + await PageObjects.datasetQuality.openDatasetFlyout('synth.1'); + + await testSubjects.existOrFail( + `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityInsufficientPrivileges}-Size` + ); + + await PageObjects.datasetQuality.closeFlyout(); + }); + + it('"View dashboards" and "See integration" are hidden for underprivileged user', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); + await PageObjects.datasetQuality.openIntegrationActionsMenu(); + + // "See Integration" is hidden + await testSubjects.missingOrFail( + PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutIntegrationAction( + 'Overview' + ) + ); + + // "View Dashboards" is hidden + await testSubjects.missingOrFail( + PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutIntegrationAction( + 'ViewDashboards' + ) + ); + + await PageObjects.datasetQuality.closeFlyout(); + }); + }); + }); + }); +} + +async function createDatasetQualityUserWithRole( + security: ReturnType, + username: string, + indices: Array<{ names: string[]; privileges: string[] }> +) { + const role = `${username}-role`; + const password = `${username}-password`; + const name = `${username}-name`; + + await security.role.create(role, { + elasticsearch: { + cluster: ['monitor'], + indices, + }, + kibana: [ + { + feature: { + discover: ['all'], + fleet: ['none'], + }, + spaces: ['*'], + }, + ], + }); + + return security.user.create(username, { + password, + roles: [role], + full_name: name, + }); +} + +async function deleteDatasetQualityUserWithRole( + security: ReturnType, + username: string +) { + await security.user.delete(username); + await security.role.delete(`${username}-role`); +} diff --git a/x-pack/test/functional/apps/dataset_quality/dataset_quality_summary.ts b/x-pack/test/functional/apps/dataset_quality/dataset_quality_summary.ts index c3bc9ea3145a5..be70275f0874d 100644 --- a/x-pack/test/functional/apps/dataset_quality/dataset_quality_summary.ts +++ b/x-pack/test/functional/apps/dataset_quality/dataset_quality_summary.ts @@ -17,21 +17,48 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid 'datasetQuality', ]); const synthtrace = getService('logSynthtraceEsClient'); - const browser = getService('browser'); - const retry = getService('retry'); const to = '2024-01-01T12:00:00.000Z'; + const ingestDataForSummary = async () => { + // Ingest documents for 3 type of datasets + return synthtrace.index([ + // Ingest good data to all 3 datasets + getInitialTestLogs({ to, count: 4 }), + // Ingesting poor data to one dataset + getLogsForDataset({ + to: Date.now(), + count: 1, + dataset: datasetNames[1], + isMalformed: true, + }), + // Ingesting degraded docs into another dataset by ingesting malformed 1st and then good data + getLogsForDataset({ + to: Date.now(), + count: 1, + dataset: datasetNames[2], + isMalformed: true, + }), + getLogsForDataset({ + to: Date.now(), + count: 10, + dataset: datasetNames[2], + isMalformed: false, + }), + ]); + }; + describe('Dataset quality summary', () => { before(async () => { - await synthtrace.index(getInitialTestLogs({ to, count: 4 })); await PageObjects.datasetQuality.navigateTo(); }); - after(async () => { + afterEach(async () => { await synthtrace.clean(); }); - it('shows poor, degraded and good count', async () => { + it('shows poor, degraded and good count as 0 and all dataset as healthy', async () => { + await synthtrace.index(getInitialTestLogs({ to, count: 4 })); + await PageObjects.datasetQuality.refreshTable(); const summary = await PageObjects.datasetQuality.parseSummaryPanel(); expect(summary).to.eql({ datasetHealthPoor: '0', @@ -42,92 +69,21 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid }); }); - it('updates the poor count when degraded docs are ingested', async () => { - // Index malformed document with current timestamp - await synthtrace.index( - getLogsForDataset({ - to: Date.now(), - count: 1, - dataset: datasetNames[2], - isMalformed: true, - }) - ); - - await browser.refresh(); - await PageObjects.datasetQuality.waitUntilSummaryPanelLoaded(); - - await retry.try(async () => { - const summary = await PageObjects.datasetQuality.parseSummaryPanel(); - const { estimatedData, ...restOfSummary } = summary; - expect(restOfSummary).to.eql({ - datasetHealthPoor: '1', - datasetHealthDegraded: '0', - datasetHealthGood: '2', - activeDatasets: '1 of 3', - }); - }); - }); - - it('updates the degraded count when degraded docs are ingested', async () => { - // Index malformed document with current timestamp - await synthtrace.index( - getLogsForDataset({ - to: Date.now(), - count: 1, - dataset: datasetNames[1], - isMalformed: true, - }) - ); - - // Index healthy documents - await synthtrace.index( - getLogsForDataset({ - to: Date.now(), - count: 10, - dataset: datasetNames[1], - isMalformed: false, - }) - ); + it('shows updated count for poor, degraded and good datasets, estimated size and updates active datasets', async () => { + await ingestDataForSummary(); + await PageObjects.datasetQuality.refreshTable(); - await browser.refresh(); - await PageObjects.datasetQuality.waitUntilSummaryPanelLoaded(); - - await retry.try(async () => { - const { estimatedData, ...restOfSummary } = - await PageObjects.datasetQuality.parseSummaryPanel(); - expect(restOfSummary).to.eql({ - datasetHealthPoor: '1', - datasetHealthDegraded: '1', - datasetHealthGood: '1', - activeDatasets: '2 of 3', - }); + const summary = await PageObjects.datasetQuality.parseSummaryPanel(); + const { estimatedData, ...restOfSummary } = summary; + expect(restOfSummary).to.eql({ + datasetHealthPoor: '1', + datasetHealthDegraded: '1', + datasetHealthGood: '1', + activeDatasets: '2 of 3', }); - }); - - it('updates active datasets and estimated data KPIs', async () => { - const { estimatedData: existingEstimatedData } = - await PageObjects.datasetQuality.parseSummaryPanel(); - // Index document at current time to mark dataset as active - await synthtrace.index( - getLogsForDataset({ - to: Date.now(), - count: 4, - dataset: datasetNames[0], - isMalformed: false, - }) - ); - - await browser.refresh(); // Summary panel doesn't update reactively - await PageObjects.datasetQuality.waitUntilSummaryPanelLoaded(); - - await retry.try(async () => { - const { activeDatasets: updatedActiveDatasets, estimatedData: updatedEstimatedData } = - await PageObjects.datasetQuality.parseSummaryPanel(); - - expect(updatedActiveDatasets).to.eql('3 of 3'); - expect(updatedEstimatedData).to.not.eql(existingEstimatedData); - }); + const sizeInNumber = parseFloat(estimatedData.split(' ')[0]); + expect(sizeInNumber).to.be.greaterThan(0); }); }); } diff --git a/x-pack/test/functional/apps/dataset_quality/dataset_quality_table.ts b/x-pack/test/functional/apps/dataset_quality/dataset_quality_table.ts index 51707db1f852c..fb6c6ed9b519f 100644 --- a/x-pack/test/functional/apps/dataset_quality/dataset_quality_table.ts +++ b/x-pack/test/functional/apps/dataset_quality/dataset_quality_table.ts @@ -7,7 +7,13 @@ import expect from '@kbn/expect'; import { DatasetQualityFtrProviderContext } from './config'; -import { datasetNames, defaultNamespace, getInitialTestLogs, getLogsForDataset } from './data'; +import { + datasetNames, + defaultNamespace, + getInitialTestLogs, + getLogsForDataset, + productionNamespace, +} from './data'; export default function ({ getService, getPageObjects }: DatasetQualityFtrProviderContext) { const PageObjects = getPageObjects([ @@ -17,40 +23,78 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid 'datasetQuality', ]); const synthtrace = getService('logSynthtraceEsClient'); - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); const to = '2024-01-01T12:00:00.000Z'; + const apacheAccessDatasetName = 'apache.access'; + const apacheAccessDatasetHumanName = 'Apache access logs'; + const pkg = { + name: 'apache', + version: '1.14.0', + }; describe('Dataset quality table', () => { before(async () => { - await synthtrace.index(getInitialTestLogs({ to, count: 4 })); + // Install Integration and ingest logs for it + await PageObjects.observabilityLogsExplorer.installPackage(pkg); + // Ingest basic logs + await synthtrace.index([ + // Ingest basic logs + getInitialTestLogs({ to, count: 4 }), + // Ingest Degraded Logs + getLogsForDataset({ + to: new Date().toISOString(), + count: 1, + dataset: datasetNames[2], + isMalformed: true, + }), + // Index 10 logs for `logs-apache.access` dataset + getLogsForDataset({ + to, + count: 10, + dataset: apacheAccessDatasetName, + namespace: productionNamespace, + }), + ]); await PageObjects.datasetQuality.navigateTo(); }); after(async () => { await synthtrace.clean(); - await PageObjects.observabilityLogsExplorer.removeInstalledPackages(); + await PageObjects.observabilityLogsExplorer.uninstallPackage(pkg); }); - it('shows the right number of rows in correct order', async () => { + it('shows sort by dataset name and show namespace', async () => { const cols = await PageObjects.datasetQuality.parseDatasetTable(); - const datasetNameCol = cols['Data Set Name']; await datasetNameCol.sort('descending'); const datasetNameColCellTexts = await datasetNameCol.getCellTexts(); - expect(datasetNameColCellTexts).to.eql([...datasetNames].reverse()); + expect(datasetNameColCellTexts).to.eql( + [apacheAccessDatasetHumanName, ...datasetNames].reverse() + ); const namespaceCol = cols.Namespace; const namespaceColCellTexts = await namespaceCol.getCellTexts(); - expect(namespaceColCellTexts).to.eql([defaultNamespace, defaultNamespace, defaultNamespace]); + expect(namespaceColCellTexts).to.eql([ + defaultNamespace, + defaultNamespace, + defaultNamespace, + productionNamespace, + ]); - const degradedDocsCol = cols['Degraded Docs (%)']; - const degradedDocsColCellTexts = await degradedDocsCol.getCellTexts(); - expect(degradedDocsColCellTexts).to.eql(['0%', '0%', '0%']); + // Cleaning the sort + await datasetNameCol.sort('ascending'); + }); + it('shows the last activity', async () => { + const cols = await PageObjects.datasetQuality.parseDatasetTable(); const lastActivityCol = cols['Last Activity']; - const lastActivityColCellTexts = await lastActivityCol.getCellTexts(); - expect(lastActivityColCellTexts).to.eql([ + const activityCells = await lastActivityCol.getCellTexts(); + const lastActivityCell = activityCells[activityCells.length - 1]; + const restActivityCells = activityCells.slice(0, -1); + + // The first cell of lastActivity should have data + expect(lastActivityCell).to.not.eql(PageObjects.datasetQuality.texts.noActivityText); + // The rest of the rows must show no activity + expect(restActivityCells).to.eql([ PageObjects.datasetQuality.texts.noActivityText, PageObjects.datasetQuality.texts.noActivityText, PageObjects.datasetQuality.texts.noActivityText, @@ -59,111 +103,30 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid it('shows degraded docs percentage', async () => { const cols = await PageObjects.datasetQuality.parseDatasetTable(); - const datasetNameCol = cols['Data Set Name']; - await datasetNameCol.sort('ascending'); const degradedDocsCol = cols['Degraded Docs (%)']; const degradedDocsColCellTexts = await degradedDocsCol.getCellTexts(); - expect(degradedDocsColCellTexts).to.eql(['0%', '0%', '0%']); - - // Index malformed document with current timestamp - await synthtrace.index( - getLogsForDataset({ - to: Date.now(), - count: 1, - dataset: datasetNames[2], - isMalformed: true, - }) - ); - - // Set time range to Last 5 minute - const filtersContainer = await testSubjects.find( - PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFiltersContainer - ); - await PageObjects.datasetQuality.setDatePickerLastXUnits(filtersContainer, 5, 'm'); - - const updatedDegradedDocsColCellTexts = await degradedDocsCol.getCellTexts(); - expect(updatedDegradedDocsColCellTexts[2]).to.not.eql('0%'); + expect(degradedDocsColCellTexts).to.eql(['0%', '0%', '0%', '100%']); }); - it('shows the updated size of the index', async () => { - const testDatasetIndex = 2; + it('shows the value in the size column', async () => { const cols = await PageObjects.datasetQuality.parseDatasetTable(); - const datasetNameCol = cols['Data Set Name']; - await datasetNameCol.sort('ascending'); - const datasetNameColCellTexts = await datasetNameCol.getCellTexts(); - - const datasetToUpdateRowIndex = datasetNameColCellTexts.findIndex( - (dName: string) => dName === datasetNames[testDatasetIndex] - ); const sizeColCellTexts = await cols.Size.getCellTexts(); - const beforeSize = sizeColCellTexts[datasetToUpdateRowIndex]; + const sizeGreaterThanZero = sizeColCellTexts[3]; + const sizeEqualToZero = sizeColCellTexts[2]; - // Index documents with current timestamp - await synthtrace.index( - getLogsForDataset({ - to: Date.now(), - count: 4, - dataset: datasetNames[testDatasetIndex], - isMalformed: false, - }) - ); - - const colsAfterUpdate = await PageObjects.datasetQuality.parseDatasetTable(); - - // Assert that size has changed - await retry.tryForTime(15000, async () => { - // Refresh the table - await PageObjects.datasetQuality.refreshTable(); - const updatedSizeColCellTexts = await colsAfterUpdate.Size.getCellTexts(); - expect(updatedSizeColCellTexts[datasetToUpdateRowIndex]).to.not.eql(beforeSize); - }); - }); - - it('sorts by dataset name', async () => { - // const header = await PageObjects.datasetQuality.getDatasetTableHeader('Data Set Name'); - const cols = await PageObjects.datasetQuality.parseDatasetTable(); - const datasetNameCol = cols['Data Set Name']; - - // Sort ascending - await datasetNameCol.sort('ascending'); - const cellTexts = await datasetNameCol.getCellTexts(); - - const datasetNamesAsc = [...datasetNames].sort(); - - expect(cellTexts).to.eql(datasetNamesAsc); + expect(sizeGreaterThanZero).to.not.eql('0.0 KB'); + expect(sizeEqualToZero).to.eql('0.0 B'); }); it('shows dataset from integration', async () => { - const apacheAccessDatasetName = 'apache.access'; - const apacheAccessDatasetHumanName = 'Apache access logs'; - - await PageObjects.observabilityLogsExplorer.navigateTo(); - - // Add initial integrations - await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); - - // Index 10 logs for `logs-apache.access` dataset - await synthtrace.index( - getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName }) - ); - - await PageObjects.datasetQuality.navigateTo(); - const cols = await PageObjects.datasetQuality.parseDatasetTable(); const datasetNameCol = cols['Data Set Name']; - // Sort ascending - await datasetNameCol.sort('ascending'); const datasetNameColCellTexts = await datasetNameCol.getCellTexts(); - const datasetNamesAsc = [...datasetNames, apacheAccessDatasetHumanName].sort(); - - // Assert there are 4 rows - expect(datasetNameColCellTexts.length).to.eql(4); - - expect(datasetNameColCellTexts).to.eql(datasetNamesAsc); + expect(datasetNameColCellTexts[0]).to.eql(apacheAccessDatasetHumanName); }); it('goes to log explorer page when opened', async () => { @@ -179,50 +142,12 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid const datasetSelectorText = await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); expect(datasetSelectorText).to.eql(datasetName); - }); - it('shows the last activity when in time range', async () => { + // Return to Dataset Quality Page await PageObjects.datasetQuality.navigateTo(); - const cols = await PageObjects.datasetQuality.parseDatasetTable(); - const lastActivityCol = cols['Last Activity']; - const datasetNameCol = cols['Data Set Name']; - - // Set time range to Last 1 minute - const filtersContainer = await testSubjects.find( - PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFiltersContainer - ); - - await PageObjects.datasetQuality.setDatePickerLastXUnits(filtersContainer, 1, 's'); - const lastActivityColCellTexts = await lastActivityCol.getCellTexts(); - expect(lastActivityColCellTexts).to.eql([ - PageObjects.datasetQuality.texts.noActivityText, - PageObjects.datasetQuality.texts.noActivityText, - PageObjects.datasetQuality.texts.noActivityText, - PageObjects.datasetQuality.texts.noActivityText, - ]); - - const datasetToUpdate = datasetNames[0]; - await synthtrace.index( - getLogsForDataset({ to: new Date().toISOString(), count: 1, dataset: datasetToUpdate }) - ); - const datasetNameColCellTexts = await datasetNameCol.getCellTexts(); - const datasetToUpdateRowIndex = datasetNameColCellTexts.findIndex( - (dName: string) => dName === datasetToUpdate - ); - - await PageObjects.datasetQuality.setDatePickerLastXUnits(filtersContainer, 1, 'h'); - - await retry.tryForTime(5000, async () => { - const updatedLastActivityColCellTexts = await lastActivityCol.getCellTexts(); - expect(updatedLastActivityColCellTexts[datasetToUpdateRowIndex]).to.not.eql( - PageObjects.datasetQuality.texts.noActivityText - ); - }); }); it('hides inactive datasets', async () => { - await PageObjects.datasetQuality.waitUntilTableLoaded(); - // Get number of rows with Last Activity not equal to "No activity in the selected timeframe" const cols = await PageObjects.datasetQuality.parseDatasetTable(); const lastActivityCol = cols['Last Activity']; diff --git a/x-pack/test/functional/apps/dataset_quality/dataset_quality_table_filters.ts b/x-pack/test/functional/apps/dataset_quality/dataset_quality_table_filters.ts index ac118a5c1d1cc..3c8c3e702d576 100644 --- a/x-pack/test/functional/apps/dataset_quality/dataset_quality_table_filters.ts +++ b/x-pack/test/functional/apps/dataset_quality/dataset_quality_table_filters.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { DatasetQualityFtrProviderContext } from './config'; -import { datasetNames, defaultNamespace, getInitialTestLogs, getLogsForDataset } from './data'; +import { datasetNames, getInitialTestLogs, getLogsForDataset, productionNamespace } from './data'; export default function ({ getService, getPageObjects }: DatasetQualityFtrProviderContext) { const PageObjects = getPageObjects([ @@ -19,55 +19,73 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid const synthtrace = getService('logSynthtraceEsClient'); const testSubjects = getService('testSubjects'); const to = '2024-01-01T12:00:00.000Z'; + const apacheAccessDatasetName = 'apache.access'; + const apacheAccessDatasetHumanName = 'Apache access logs'; + const apacheIntegrationName = 'Apache HTTP Server'; + const pkg = { + name: 'apache', + version: '1.14.0', + }; + const allDatasetNames = [apacheAccessDatasetHumanName, ...datasetNames]; describe('Dataset quality table filters', () => { before(async () => { - await synthtrace.index(getInitialTestLogs({ to, count: 4 })); + // Install Integration and ingest logs for it + await PageObjects.observabilityLogsExplorer.installPackage(pkg); + // Ingest basic logs + await synthtrace.index([ + // Ingest basic logs + getInitialTestLogs({ to, count: 4 }), + // Ingest Degraded Logs + getLogsForDataset({ + to: new Date().toISOString(), + count: 1, + dataset: datasetNames[2], + isMalformed: true, + }), + // Index 10 logs for `logs-apache.access` dataset + getLogsForDataset({ + to, + count: 10, + dataset: apacheAccessDatasetName, + namespace: productionNamespace, + }), + ]); await PageObjects.datasetQuality.navigateTo(); }); after(async () => { + await PageObjects.observabilityLogsExplorer.uninstallPackage(pkg); await synthtrace.clean(); - await PageObjects.observabilityLogsExplorer.removeInstalledPackages(); - }); - - it('hides inactive datasets when toggled', async () => { - const initialRows = await PageObjects.datasetQuality.getDatasetTableRows(); - expect(initialRows.length).to.eql(3); - - await PageObjects.datasetQuality.toggleShowInactiveDatasets(); - - const afterToggleRows = await PageObjects.datasetQuality.getDatasetTableRows(); - expect(afterToggleRows.length).to.eql(1); - - await PageObjects.datasetQuality.toggleShowInactiveDatasets(); - - const afterReToggleRows = await PageObjects.datasetQuality.getDatasetTableRows(); - expect(afterReToggleRows.length).to.eql(3); }); it('shows full dataset names when toggled', async () => { const cols = await PageObjects.datasetQuality.parseDatasetTable(); const datasetNameCol = cols['Data Set Name']; const datasetNameColCellTexts = await datasetNameCol.getCellTexts(); - expect(datasetNameColCellTexts).to.eql(datasetNames); + expect(datasetNameColCellTexts).to.eql(allDatasetNames); await PageObjects.datasetQuality.toggleShowFullDatasetNames(); const datasetNameColCellTextsAfterToggle = await datasetNameCol.getCellTexts(); - const duplicateNames = datasetNames.map((name) => `${name}\n${name}`); + const duplicateNames = [ + `${apacheAccessDatasetHumanName}\n${apacheAccessDatasetName}`, + ...datasetNames.map((name) => `${name}\n${name}`), + ]; + expect(datasetNameColCellTextsAfterToggle).to.eql(duplicateNames); + // resetting the toggle await PageObjects.datasetQuality.toggleShowFullDatasetNames(); const datasetNameColCellTextsAfterReToggle = await datasetNameCol.getCellTexts(); - expect(datasetNameColCellTextsAfterReToggle).to.eql(datasetNames); + expect(datasetNameColCellTextsAfterReToggle).to.eql(allDatasetNames); }); it('searches the datasets', async () => { const cols = await PageObjects.datasetQuality.parseDatasetTable(); const datasetNameCol = cols['Data Set Name']; const datasetNameColCellTexts = await datasetNameCol.getCellTexts(); - expect(datasetNameColCellTexts).to.eql(datasetNames); + expect(datasetNameColCellTexts).to.eql(allDatasetNames); // Search for a dataset await testSubjects.setValue( @@ -79,33 +97,16 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid const datasetNameColAfterSearch = colsAfterSearch['Data Set Name']; const datasetNameColCellTextsAfterSearch = await datasetNameColAfterSearch.getCellTexts(); expect(datasetNameColCellTextsAfterSearch).to.eql([datasetNames[2]]); - await testSubjects.setValue( - PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFilterBarFieldSearch, - '' - ); + + // Reset the search field + await testSubjects.click('clearSearchButton'); }); it('filters for integration', async () => { - const apacheAccessDatasetName = 'apache.access'; - const apacheAccessDatasetHumanName = 'Apache access logs'; - const apacheIntegrationName = 'Apache HTTP Server'; - - await PageObjects.observabilityLogsExplorer.navigateTo(); - - // Add initial integrations - await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); - - // Index 10 logs for `logs-apache.access` dataset - await synthtrace.index( - getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName }) - ); - - await PageObjects.datasetQuality.navigateTo(); - const cols = await PageObjects.datasetQuality.parseDatasetTable(); const datasetNameCol = cols['Data Set Name']; const datasetNameColCellTexts = await datasetNameCol.getCellTexts(); - expect(datasetNameColCellTexts).to.eql([apacheAccessDatasetHumanName, ...datasetNames]); + expect(datasetNameColCellTexts).to.eql(allDatasetNames); // Filter for integration await PageObjects.datasetQuality.filterForIntegrations([apacheIntegrationName]); @@ -113,65 +114,33 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid const colsAfterFilter = await PageObjects.datasetQuality.parseDatasetTable(); const datasetNameColAfterFilter = colsAfterFilter['Data Set Name']; const datasetNameColCellTextsAfterFilter = await datasetNameColAfterFilter.getCellTexts(); + expect(datasetNameColCellTextsAfterFilter).to.eql([apacheAccessDatasetHumanName]); + // Reset the filter by selecting from the dropdown again + await PageObjects.datasetQuality.filterForIntegrations([apacheIntegrationName]); }); it('filters for namespace', async () => { - const apacheAccessDatasetName = 'apache.access'; - const datasetNamespace = 'prod'; - - await PageObjects.observabilityLogsExplorer.navigateTo(); - - // Add initial integrations - await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); - - // Index 10 logs for `logs-apache.access` dataset - await synthtrace.index( - getLogsForDataset({ - to, - count: 10, - dataset: apacheAccessDatasetName, - namespace: datasetNamespace, - }) - ); - - await PageObjects.datasetQuality.navigateTo(); - // Get default namespaces const cols = await PageObjects.datasetQuality.parseDatasetTable(); const namespaceCol = cols.Namespace; const namespaceColCellTexts = await namespaceCol.getCellTexts(); - expect(namespaceColCellTexts).to.contain(defaultNamespace); + expect(namespaceColCellTexts).to.contain(productionNamespace); - // Filter for prod namespace - await PageObjects.datasetQuality.filterForNamespaces([datasetNamespace]); + // Filter for production namespace + await PageObjects.datasetQuality.filterForNamespaces([productionNamespace]); const colsAfterFilter = await PageObjects.datasetQuality.parseDatasetTable(); const namespaceColAfterFilter = colsAfterFilter.Namespace; const namespaceColCellTextsAfterFilter = await namespaceColAfterFilter.getCellTexts(); - expect(namespaceColCellTextsAfterFilter).to.eql([datasetNamespace]); + expect(namespaceColCellTextsAfterFilter).to.eql([productionNamespace]); + // Reset the namespace by selecting from the dropdown again + await PageObjects.datasetQuality.filterForNamespaces([productionNamespace]); }); it('filters for quality', async () => { - const apacheAccessDatasetName = 'apache.access'; const expectedQuality = 'Poor'; - - // Add initial integrations - await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); - - // Index 10 logs for `logs-apache.access` dataset - await synthtrace.index( - getLogsForDataset({ - to: new Date().toISOString(), - count: 10, - dataset: apacheAccessDatasetName, - isMalformed: true, - }) - ); - - await PageObjects.datasetQuality.navigateTo(); - // Get default quality const cols = await PageObjects.datasetQuality.parseDatasetTable(); const datasetQuality = cols['Data Set Quality']; @@ -186,6 +155,9 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid const datasetQualityCellTextsAfterFilter = await datasetQualityAfterFilter.getCellTexts(); expect(datasetQualityCellTextsAfterFilter).to.eql([expectedQuality]); + + // Reset the namespace by selecting from the dropdown again + await PageObjects.datasetQuality.filterForQualities([expectedQuality]); }); }); } diff --git a/x-pack/test/functional/apps/dataset_quality/home.ts b/x-pack/test/functional/apps/dataset_quality/home.ts index 7940333470137..e08a06620e605 100644 --- a/x-pack/test/functional/apps/dataset_quality/home.ts +++ b/x-pack/test/functional/apps/dataset_quality/home.ts @@ -6,6 +6,7 @@ */ import { DatasetQualityFtrProviderContext } from './config'; +import { getInitialTestLogs } from './data'; export default function ({ getService, getPageObjects }: DatasetQualityFtrProviderContext) { const PageObjects = getPageObjects([ @@ -16,13 +17,38 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid ]); const testSubjects = getService('testSubjects'); + const synthtrace = getService('logSynthtraceEsClient'); + const to = '2024-01-01T12:00:00.000Z'; describe('Dataset quality home', () => { - it('dataset quality table exists', async () => { - await PageObjects.datasetQuality.navigateTo(); - await testSubjects.existOrFail( - PageObjects.datasetQuality.testSubjectSelectors.datasetQualityTable - ); + describe('with no datasets available', () => { + before(async () => { + await PageObjects.datasetQuality.navigateTo(); + }); + + it('shows the empty state', async () => { + await testSubjects.existOrFail( + PageObjects.datasetQuality.testSubjectSelectors.datasetQualityNoDataEmptyState + ); + }); + }); + + describe('with datasets available', () => { + before(async () => { + await synthtrace.index(getInitialTestLogs({ to, count: 1 })); + await PageObjects.datasetQuality.navigateTo(); + }); + + after(async () => { + await synthtrace.clean(); + }); + + it('dataset quality table exists', async () => { + await PageObjects.datasetQuality.navigateTo(); + await testSubjects.existOrFail( + PageObjects.datasetQuality.testSubjectSelectors.datasetQualityTable + ); + }); }); }); } diff --git a/x-pack/test/functional/apps/dataset_quality/index.ts b/x-pack/test/functional/apps/dataset_quality/index.ts index 453d3673e7ef5..f22af151ca6c9 100644 --- a/x-pack/test/functional/apps/dataset_quality/index.ts +++ b/x-pack/test/functional/apps/dataset_quality/index.ts @@ -14,5 +14,6 @@ export default function ({ loadTestFile }: DatasetQualityFtrProviderContext) { loadTestFile(require.resolve('./dataset_quality_table')); loadTestFile(require.resolve('./dataset_quality_table_filters')); loadTestFile(require.resolve('./dataset_quality_flyout')); + loadTestFile(require.resolve('./dataset_quality_privileges')); }); } diff --git a/x-pack/test/functional/apps/dev_tools/searchprofiler_editor.ts b/x-pack/test/functional/apps/dev_tools/searchprofiler_editor.ts index b6b514fe2c75d..174f3d4527178 100644 --- a/x-pack/test/functional/apps/dev_tools/searchprofiler_editor.ts +++ b/x-pack/test/functional/apps/dev_tools/searchprofiler_editor.ts @@ -22,7 +22,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const es = getService('es'); const log = getService('log'); - describe('Search Profiler Editor', () => { + // Failing: See https://github.com/elastic/kibana/issues/186126 + describe.skip('Search Profiler Editor', () => { before(async () => { await security.testUser.setRoles(['global_devtools_read']); await PageObjects.common.navigateToApp('searchProfiler'); diff --git a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts index e543116b956c2..bfd732622b785 100644 --- a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts +++ b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts @@ -5,7 +5,9 @@ * 2.0. */ +import { DISCOVER_APP_LOCATOR } from '@kbn/discover-plugin/common'; import expect from '@kbn/expect'; +import { decompressFromBase64 } from 'lz-string'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { getSavedQuerySecurityUtils } from '../../saved_query_management/utils/saved_query_security'; @@ -35,6 +37,7 @@ export default function (ctx: FtrProviderContext) { const testSubjects = getService('testSubjects'); const appsMenu = getService('appsMenu'); const kibanaServer = getService('kibanaServer'); + const deployment = getService('deployment'); const logstashIndexName = 'logstash-2015.09.22'; async function setDiscoverTimeRange() { @@ -119,6 +122,19 @@ export default function (ctx: FtrProviderContext) { await globalNav.badgeMissingOrFail(); }); + it('Shows short urls for users with the right privileges', async () => { + let actualUrl: string = ''; + await PageObjects.share.clickShareTopNavButton(); + const re = new RegExp( + deployment.getHostPort().replace(':80', '').replace(':443', '') + '/app/r.*$' + ); + await retry.try(async () => { + actualUrl = await PageObjects.share.getSharedUrl(); + expect(actualUrl).to.match(re); + await PageObjects.share.closeShareModal(); + }); + }); + it('shows CSV reports', async () => { await PageObjects.share.clickShareTopNavButton(); await PageObjects.share.clickTab('Export'); @@ -190,6 +206,44 @@ export default function (ctx: FtrProviderContext) { await PageObjects.unifiedFieldList.expectMissingFieldListItemVisualize('bytes'); }); + it('should allow for copying the snapshot URL', async function () { + await PageObjects.share.clickShareTopNavButton(); + const actualUrl = await PageObjects.share.getSharedUrl(); + expect(actualUrl).to.contain(`?l=${DISCOVER_APP_LOCATOR}`); + const urlSearchParams = new URLSearchParams(actualUrl); + expect(JSON.parse(decompressFromBase64(urlSearchParams.get('lz')!)!)).to.eql({ + query: { + language: 'kuery', + query: '', + }, + sort: [['@timestamp', 'desc']], + columns: [], + interval: 'auto', + filters: [], + dataViewId: 'logstash-*', + timeRange: { + from: '2015-09-19T06:31:44.000Z', + to: '2015-09-23T18:31:44.000Z', + }, + refreshInterval: { + value: 60000, + pause: true, + }, + }); + await PageObjects.share.closeShareModal(); + }); + + it(`Doesn't show short urls for users without those privileges`, async () => { + await PageObjects.share.clickShareTopNavButton(); + let actualUrl: string = ''; + + await retry.try(async () => { + actualUrl = await PageObjects.share.getSharedUrl(); + // only shows in long urls + expect(actualUrl).to.contain(DISCOVER_APP_LOCATOR); + await PageObjects.share.closeShareModal(); + }); + }); savedQuerySecurityUtils.shouldDisallowSavingButAllowLoadingSavedQueries(); }); @@ -253,6 +307,19 @@ export default function (ctx: FtrProviderContext) { await PageObjects.unifiedFieldList.expectMissingFieldListItemVisualize('bytes'); }); + it('Shows short urls for users with the right privileges', async () => { + await PageObjects.share.clickShareTopNavButton(); + let actualUrl: string = ''; + const re = new RegExp( + deployment.getHostPort().replace(':80', '').replace(':443', '') + '/app/r.*$' + ); + await retry.try(async () => { + actualUrl = await PageObjects.share.getSharedUrl(); + expect(actualUrl).to.match(re); + await PageObjects.share.closeShareModal(); + }); + }); + savedQuerySecurityUtils.shouldDisallowSavingButAllowLoadingSavedQueries(); }); diff --git a/x-pack/test/functional/apps/discover/reporting.ts b/x-pack/test/functional/apps/discover/reporting.ts index bb5721b9c99fc..41882ef130790 100644 --- a/x-pack/test/functional/apps/discover/reporting.ts +++ b/x-pack/test/functional/apps/discover/reporting.ts @@ -6,8 +6,8 @@ */ import expect from '@kbn/expect'; -import { Key } from 'selenium-webdriver'; import moment from 'moment'; +import { Key } from 'selenium-webdriver'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { @@ -103,8 +103,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.selectIndexPattern('ecommerce'); }); - // Discover defaults to short urls - is this test helpful? Clarify in separate PR - xit('generates a report with single timefilter', async () => { + it('generates a report with single timefilter', async () => { await PageObjects.discover.clickNewSearchButton(); await PageObjects.timePicker.setCommonlyUsedTime('Last_24 hours'); await PageObjects.discover.saveSearch('single-timefilter-search'); @@ -116,22 +115,23 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.share.clickShareTopNavButton(); await PageObjects.reporting.openExportTab(); const copyButton = await testSubjects.find('shareReportingCopyURL'); - const reportURL = (await copyButton.getAttribute('data-share-url')) ?? ''; + const reportURL = decodeURIComponent( + (await copyButton.getAttribute('data-share-url')) ?? '' + ); // get number of filters in URLs const timeFiltersNumberInReportURL = - decodeURIComponent(reportURL).split( - 'query:(range:(order_date:(format:strict_date_optional_time' - ).length - 1; + reportURL.split('query:(range:(order_date:(format:strict_date_optional_time').length - 1; const timeFiltersNumberInSharedURL = sharedURL.split('time:').length - 1; expect(timeFiltersNumberInSharedURL).to.be(1); expect(sharedURL.includes('time:(from:now-24h%2Fh,to:now))')).to.be(true); expect(timeFiltersNumberInReportURL).to.be(1); + expect( - decodeURIComponent(reportURL).includes( - 'query:(range:(order_date:(format:strict_date_optional_time' + reportURL.includes( + `query:(range:(order_date:(format:strict_date_optional_time,gte:now-24h/h,lte:now))))` ) ).to.be(true); @@ -298,6 +298,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await setupPage(); }); + afterEach(async () => { + await PageObjects.reporting.checkForReportingToasts(); + }); + it('generates a report with data', async () => { await PageObjects.discover.loadSavedSearch('Ecommerce Data'); await retry.try(async () => { diff --git a/x-pack/test/functional/apps/infra/helpers.ts b/x-pack/test/functional/apps/infra/helpers.ts index 2ddabf314390f..c313626b83826 100644 --- a/x-pack/test/functional/apps/infra/helpers.ts +++ b/x-pack/test/functional/apps/infra/helpers.ts @@ -60,7 +60,19 @@ export function generateDockerContainersData({ const containers = Array(count) .fill(0) - .map((_, idx) => infra.dockerContainer(`container-id-${idx}`)); + .map((_, idx) => + infra.dockerContainer(`container-id-${idx}`).defaults({ + 'container.name': `container-id-${idx}`, + 'container.id': `container-id-${idx}`, + 'container.runtime': 'docker', + 'container.image.name': 'image-1', + 'host.name': 'host-1', + 'cloud.instance.id': 'instance-1', + 'cloud.image.id': 'image-1', + 'cloud.provider': 'aws', + 'event.dataset': 'docker.container', + }) + ); return range .interval('30s') diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index 669547c1c671c..1681d397f932b 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -292,10 +292,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); [ - { metric: 'cpuUsage', value: '2,500.0%' }, - { metric: 'memoryUsage', value: '2,000.0%' }, + { metric: 'cpuUsage', value: '25.0%' }, + { metric: 'memoryUsage', value: '20.0%' }, ].forEach(({ metric, value }) => { - it.skip(`${metric} tile should show ${value}`, async () => { + it(`${metric} tile should show ${value}`, async () => { await retry.tryForTime(3 * 1000, async () => { const tileValue = await pageObjects.assetDetails.getAssetDetailsKPITileValue( metric @@ -309,13 +309,20 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { { metric: 'cpu', chartsCount: 1 }, { metric: 'memory', chartsCount: 1 }, ].forEach(({ metric, chartsCount }) => { - it.skip(`should render ${chartsCount} ${metric} chart(s) in the Metrics section`, async () => { + it(`should render ${chartsCount} ${metric} chart(s) in the Metrics section`, async () => { const containers = await pageObjects.assetDetails.getOverviewTabDockerMetricCharts( metric ); expect(containers.length).to.equal(chartsCount); }); }); + + it('should show alerts', async () => { + await pageObjects.header.waitUntilLoadingHasFinished(); + await pageObjects.assetDetails.overviewAlertsTitleExists(); + await pageObjects.assetDetails.overviewLinkToAlertsExist(); + await pageObjects.assetDetails.overviewOpenAlertsFlyoutExist(); + }); }); describe('Metadata Tab', () => { @@ -328,6 +335,16 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); + describe('Metrics Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickMetricsTab(); + }); + + it('should show metrics content', async () => { + await pageObjects.assetDetails.metricsChartsContentExists(); + }); + }); + describe('Logs Tab', () => { before(async () => { await pageObjects.assetDetails.clickLogsTab(); diff --git a/x-pack/test/functional/apps/infra/node_details.ts b/x-pack/test/functional/apps/infra/node_details.ts index 1cc435e417437..60b4e25bb8802 100644 --- a/x-pack/test/functional/apps/infra/node_details.ts +++ b/x-pack/test/functional/apps/infra/node_details.ts @@ -38,6 +38,8 @@ const START_HOST_KUBERNETES_SECTION_DATE = moment.utc( const END_HOST_KUBERNETES_SECTION_DATE = moment.utc( DATES.metricsAndLogs.hosts.kubernetesSectionEndDate ); +const START_CONTAINER_DATE = moment.utc(DATE_WITH_DOCKER_DATA_FROM); +const END_CONTAINER_DATE = moment.utc(DATE_WITH_DOCKER_DATA_TO); export default ({ getPageObjects, getService }: FtrProviderContext) => { const observability = getService('observability'); @@ -651,25 +653,54 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); describe('when container asset view is enabled', () => { - it('should show asset container details page', async () => { + before(async () => { await setInfrastructureContainerAssetViewUiSetting(true); await navigateToNodeDetails('container-id-0', 'container-id-0', 'container'); await pageObjects.header.waitUntilLoadingHasFinished(); - + await pageObjects.timePicker.setAbsoluteRange( + START_CONTAINER_DATE.format(DATE_PICKER_FORMAT), + END_CONTAINER_DATE.format(DATE_PICKER_FORMAT) + ); + }); + it('should show asset container details page', async () => { await pageObjects.assetDetails.getOverviewTab(); }); [ { metric: 'cpu', chartsCount: 1 }, { metric: 'memory', chartsCount: 1 }, + { metric: 'disk', chartsCount: 1 }, + { metric: 'network', chartsCount: 1 }, ].forEach(({ metric, chartsCount }) => { - it.skip(`should render ${chartsCount} ${metric} chart(s) in the Metrics section`, async () => { + it(`should render ${chartsCount} ${metric} chart(s) in the Metrics section`, async () => { const charts = await pageObjects.assetDetails.getOverviewTabDockerMetricCharts( metric ); expect(charts.length).to.equal(chartsCount); }); }); + + it('should show / hide alerts section with no alerts and show / hide closed section content', async () => { + await pageObjects.assetDetails.alertsSectionCollapsibleExist(); + // Collapsed by default + await pageObjects.assetDetails.alertsSectionClosedContentNoAlertsExist(); + // Expand + await pageObjects.assetDetails.alertsSectionCollapsibleClick(); + await pageObjects.assetDetails.alertsSectionClosedContentNoAlertsMissing(); + // Check if buttons exist + await pageObjects.assetDetails.overviewLinkToAlertsExist(); + await pageObjects.assetDetails.overviewOpenAlertsFlyoutExist(); + }); + + describe('Metadata Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickMetadataTab(); + }); + + it('should show metadata table', async () => { + await pageObjects.assetDetails.metadataTableExists(); + }); + }); describe('Logs Tab', () => { before(async () => { await pageObjects.assetDetails.clickLogsTab(); @@ -693,6 +724,28 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await searchInput.clearValue(); }); }); + + describe('Metrics Tab', () => { + before(async () => { + await pageObjects.assetDetails.clickMetricsTab(); + }); + + [ + { metric: 'cpu', chartsCount: 1 }, + { metric: 'memory', chartsCount: 1 }, + { metric: 'disk', chartsCount: 1 }, + { metric: 'network', chartsCount: 1 }, + ].forEach(({ metric, chartsCount }) => { + it(`should render ${chartsCount} ${metric} chart(s)`, async () => { + const charts = await pageObjects.assetDetails.getMetricsTabDockerCharts(metric); + expect(charts.length).to.equal(chartsCount); + }); + + it(`should render a quick access for ${metric} in the side panel`, async () => { + await pageObjects.assetDetails.quickAccessItemExists(metric); + }); + }); + }); }); }); }); diff --git a/x-pack/test/functional/apps/lens/group4/dashboard.ts b/x-pack/test/functional/apps/lens/group4/dashboard.ts index 33e3277855d2f..776a4416d7d4c 100644 --- a/x-pack/test/functional/apps/lens/group4/dashboard.ts +++ b/x-pack/test/functional/apps/lens/group4/dashboard.ts @@ -321,5 +321,37 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // check the success state await PageObjects.dashboard.verifyNoRenderErrors(); }); + + it('should work in lens with by-value charts', async () => { + // create a new dashboard, then a new visualization in Lens. + await PageObjects.dashboard.navigateToApp(); + await PageObjects.dashboard.clickNewDashboard(); + await testSubjects.click('dashboardEditorMenuButton'); + await testSubjects.click('visType-lens'); + // Configure it and save to return to the dashboard. + await PageObjects.lens.waitForField('@timestamp'); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'average', + field: 'bytes', + }); + await PageObjects.lens.save('test', true); + // Edit the visualization now and get back to Lens editor + await testSubjects.click('embeddablePanelToggleMenuIcon'); + await testSubjects.click('embeddablePanelAction-ACTION_CONFIGURE_IN_LENS'); + await testSubjects.click('navigateToLensEditorLink'); + // Click on Share, then Copy link and paste the link in a new tab. + const url = await PageObjects.lens.getUrl(); + await browser.openNewTab(); + await browser.navigateTo(url); + expect(await PageObjects.lens.getTitle()).to.be('test'); + // need to make sure there aren't extra tabs or it will impact future test suites + // close any new tabs that were opened + const windowHandlers = await browser.getAllWindowHandles(); + if (windowHandlers.length > 1) { + await browser.closeCurrentWindow(); + await browser.switchToWindow(windowHandlers[0]); + } + }); }); } diff --git a/x-pack/test/functional/apps/lens/group4/share.ts b/x-pack/test/functional/apps/lens/group4/share.ts index 9e99c4d3c328f..ac3c4a3b22b78 100644 --- a/x-pack/test/functional/apps/lens/group4/share.ts +++ b/x-pack/test/functional/apps/lens/group4/share.ts @@ -61,12 +61,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await PageObjects.lens.isShareActionEnabled('link')); }); - xit('should preserve filter and query when sharing', async () => { + it('should preserve filter and query when sharing', async () => { await filterBarService.addFilter({ field: 'bytes', operation: 'is', value: '1' }); await queryBar.setQuery('host.keyword www.elastic.co'); await queryBar.submitQuery(); - await PageObjects.lens.waitForVisualization('xyVisChart'); - + await PageObjects.lens.save('new'); const url = await PageObjects.lens.getUrl(); await PageObjects.lens.closeShareModal(); await browser.openNewTab(); diff --git a/x-pack/test/functional/apps/lens/group6/legend_statistics.ts b/x-pack/test/functional/apps/lens/group6/legend_statistics.ts index c5cf63c92b7bd..a6e8cf8a86510 100644 --- a/x-pack/test/functional/apps/lens/group6/legend_statistics.ts +++ b/x-pack/test/functional/apps/lens/group6/legend_statistics.ts @@ -26,12 +26,23 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const text = await legendElement.getVisibleText(); expect(text).to.eql(expectedText); } + async function expectLegendTableToHaveText(expectedText: string) { + const legendElement = await find.byCssSelector('.echLegendTable'); + const text = await legendElement.getVisibleText(); + expect(text).to.eql(expectedText); + } - describe('lens persisted and runtime state differences properties', () => { + describe('lens legend statistics', () => { before(async () => { await kibanaServer.importExport.load( 'x-pack/test/functional/fixtures/kbn_archiver/lens/legend_statistics' ); + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json' + ); + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/default' + ); await kibanaServer.uiSettings.update({ 'timepicker:timeDefaults': '{ "from": "2015-09-18T19:37:13.000Z", "to": "2015-09-22T23:30:30.000Z"}', @@ -39,40 +50,72 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visualize.gotoVisualizationLandingPage({ forceRefresh: true }); }); - after(async () => { - await kibanaServer.uiSettings.unset('timepicker:timeDefaults'); - }); - describe('xy chart', () => { - it('shows values in legend for legacy valuesInLegend===true property and saves it correctly', async () => { - const title = 'xyValuesInLegendTrue'; - await loadSavedLens(title); - await expectLegendOneItem('Count of records', '2'); - await PageObjects.lens.save(title); - await loadSavedLens(title); - await expectLegendOneItem('Count of records', '2'); - }); + describe('xy chart legend statistics', () => { + it('shows table with legend statistics', async () => { + await loadSavedLens('lnsXYvis'); - it('does not show values in legend for legacy valuesInLegend===false prop', async () => { - await loadSavedLens('xyValuesInLegendFalse'); - await expectLegendOneItem('Count of records'); - }); - it('shows values in legend for legendStats===["values"] prop', async () => { - await loadSavedLens('xyLegendStats'); - await expectLegendOneItem('Count of records', '2'); + await PageObjects.lens.toggleToolbarPopover('lnsLegendButton'); + await PageObjects.lens.selectOptionFromComboBox('lnsLegendStatisticsSelect', [ + 'average', + 'minimum', + 'maximum', + ]); + + const tableText = `Avg +Min +Max +97.220.3.248 +19,755 +19,755 +19,755 +169.228.188.120 +18,994 +18,994 +18,994 +78.83.247.30 +17,246 +17,246 +17,246`; + + await expectLegendTableToHaveText(tableText); }); }); - describe('waffle chart', () => { - it('waffleshows values in legend for legacy valuesInLegend===true property', async () => { - await loadSavedLens('waffleValuesInLegendTrue'); - await expectLegendOneItem('Count of records', '14,003'); + describe('lens persisted and runtime state differences properties', () => { + after(async () => { + await kibanaServer.uiSettings.unset('timepicker:timeDefaults'); }); - it('shows values in legend for legacy showValuesInLegend===false prop', async () => { - await loadSavedLens('waffleValuesInLegendFalse'); - await expectLegendOneItem('Count of records', undefined); + describe('xy chart', () => { + it('shows values in legend for legacy valuesInLegend===true property and saves it correctly', async () => { + const title = 'xyValuesInLegendTrue'; + await loadSavedLens(title); + await expectLegendOneItem('Count of records', '2'); + await PageObjects.lens.save(title); + await loadSavedLens(title); + await expectLegendOneItem('Count of records', '2'); + }); + + it('does not show values in legend for legacy valuesInLegend===false prop', async () => { + await loadSavedLens('xyValuesInLegendFalse'); + await expectLegendOneItem('Count of records'); + }); + it('shows values in legend for legendStats===["values"] prop', async () => { + await loadSavedLens('xyLegendStats'); + await expectLegendOneItem('Count of records', '2'); + }); }); - it('shows values in legend for legendStats===["values"] prop', async () => { - await loadSavedLens('waffleLegendStats'); - await expectLegendOneItem('Count of records', '14,003'); + describe('waffle chart', () => { + it('waffleshows values in legend for legacy valuesInLegend===true property', async () => { + await loadSavedLens('waffleValuesInLegendTrue'); + await expectLegendOneItem('Count of records', '14,003'); + }); + it('shows values in legend for legacy showValuesInLegend===false prop', async () => { + await loadSavedLens('waffleValuesInLegendFalse'); + await expectLegendOneItem('Count of records', undefined); + }); + it('shows values in legend for legendStats===["values"] prop', async () => { + await loadSavedLens('waffleLegendStats'); + await expectLegendOneItem('Count of records', '14,003'); + }); }); }); }); diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_result_views/forecasts.ts b/x-pack/test/functional/apps/ml/anomaly_detection_result_views/forecasts.ts index 3a60e8fca97c2..bdd3052f54c0a 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_result_views/forecasts.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_result_views/forecasts.ts @@ -39,7 +39,8 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); - describe('forecasts', function () { + // Failing: See https://github.com/elastic/kibana/issues/164381 + describe.skip('forecasts', function () { this.tags(['ml']); describe('with single metric job', function () { diff --git a/x-pack/test/functional/apps/observability_logs_explorer/data_source_selector.ts b/x-pack/test/functional/apps/observability_logs_explorer/data_source_selector.ts index 100d547cdb07d..d4774b1ef6601 100644 --- a/x-pack/test/functional/apps/observability_logs_explorer/data_source_selector.ts +++ b/x-pack/test/functional/apps/observability_logs_explorer/data_source_selector.ts @@ -654,7 +654,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { PageObjects.observabilityLogsExplorer.getPanelEntries(menu) ); - expect(menuEntries.length).to.be(1); + expect(menuEntries.length).to.be(2); expect(await menuEntries[0].getVisibleText()).to.be(sortedExpectedDataViews[0]); }); diff --git a/x-pack/test/functional/apps/reporting_management/config.ts b/x-pack/test/functional/apps/reporting_management/config.ts index d0d07ff200281..af7abdc5122e1 100644 --- a/x-pack/test/functional/apps/reporting_management/config.ts +++ b/x-pack/test/functional/apps/reporting_management/config.ts @@ -13,5 +13,13 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { return { ...functionalConfig.getAll(), testFiles: [require.resolve('.')], + kbnTestServer: { + ...functionalConfig.get('kbnTestServer'), + serverArgs: [ + ...functionalConfig.get('kbnTestServer.serverArgs'), + // disabling the monaco editor to run tests for ace + `--console.dev.enableMonaco=false`, + ], + }, }; } diff --git a/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js b/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js index 799274968167c..b1c37234cdf65 100644 --- a/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js +++ b/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js @@ -8,6 +8,7 @@ import datemath from '@kbn/datemath'; import expect from '@kbn/expect'; import mockRolledUpData, { mockIndices } from './hybrid_index_helper'; +import { MOCK_ROLLUP_INDEX_NAME, createMockRollupIndex } from './test_helpers'; export default function ({ getService, getPageObjects }) { const es = getService('es'); @@ -17,9 +18,7 @@ export default function ({ getService, getPageObjects }) { const PageObjects = getPageObjects(['common', 'settings']); const esDeleteAllIndices = getService('esDeleteAllIndices'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/183975 - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/183976 - describe.skip('hybrid index pattern', function () { + describe('hybrid index pattern', function () { //Since rollups can only be created once with the same name (even if you delete it), //we add the Date.now() to avoid name collision if you run the tests locally back to back. const rollupJobName = `hybrid-index-pattern-test-rollup-job-${Date.now()}`; @@ -43,6 +42,10 @@ export default function ({ getService, getPageObjects }) { await kibanaServer.uiSettings.replace({ defaultIndex: 'rollup', }); + + // From 8.15, Es only allows creating a new rollup job when there is existing rollup usage in the cluster + // We will simulate rollup usage by creating a mock-up rollup index + await createMockRollupIndex(es); }); it('create hybrid index pattern', async () => { @@ -107,7 +110,7 @@ export default function ({ getService, getPageObjects }) { // ensure all fields are available await PageObjects.settings.clickIndexPatternByName(rollupIndexPatternName); const fields = await PageObjects.settings.getFieldNames(); - expect(fields).to.eql(['@timestamp', '_id', '_index', '_score', '_source']); + expect(fields).to.eql(['@timestamp', '_id', '_ignored', '_index', '_score', '_source']); }); after(async () => { @@ -118,6 +121,7 @@ export default function ({ getService, getPageObjects }) { rollupTargetIndexName, `${regularIndexPrefix}*`, `${rollupSourceIndexPrefix}*`, + MOCK_ROLLUP_INDEX_NAME, ]); await kibanaServer.savedObjects.cleanStandardList(); }); diff --git a/x-pack/test/functional/apps/rollup_job/rollup_jobs.js b/x-pack/test/functional/apps/rollup_job/rollup_jobs.js index 3c33c9c31bc6b..bbf9d56e494ee 100644 --- a/x-pack/test/functional/apps/rollup_job/rollup_jobs.js +++ b/x-pack/test/functional/apps/rollup_job/rollup_jobs.js @@ -8,7 +8,7 @@ import datemath from '@kbn/datemath'; import expect from '@kbn/expect'; import { mockIndices } from './hybrid_index_helper'; -// import { FtrProviderContext } from '../../ftr_provider_context'; +import { MOCK_ROLLUP_INDEX_NAME, createMockRollupIndex } from './test_helpers'; export default function ({ getService, getPageObjects }) { const config = getService('config'); @@ -17,18 +17,14 @@ export default function ({ getService, getPageObjects }) { const esDeleteAllIndices = getService('esDeleteAllIndices'); const kibanaServer = getService('kibanaServer'); const es = getService('es'); + const testSubjects = getService('testSubjects'); const isRunningCcs = config.get('esTestCluster.ccs') ? true : false; let remoteEs; if (isRunningCcs) { remoteEs = getService('remoteEs'); } - // FLAKY: https://github.com/elastic/kibana/issues/183925 - // FLAKY: https://github.com/elastic/kibana/issues/183926 - // FLAKY: https://github.com/elastic/kibana/issues/183927 - // FLAKY: https://github.com/elastic/kibana/issues/183928 - // FLAKY: https://github.com/elastic/kibana/issues/104569 - describe.skip('rollup job', function () { + describe('rollup job', function () { // Since rollups can only be created once with the same name (even if you delete it), // we add the Date.now() to avoid name collision. const rollupJobName = 'rollup-to-be-' + Date.now(); @@ -52,6 +48,14 @@ export default function ({ getService, getPageObjects }) { // await security.testUser.setRoles(['manage_rollups_role', 'global_ccr_role']); await security.testUser.setRoles(['superuser']); await PageObjects.common.navigateToApp('rollupJob'); + + // From 8.15, Es only allows creating a new rollup job when there is existing rollup usage in the cluster + // We will simulate rollup usage by creating a mock-up rollup index + await createMockRollupIndex(es); + }); + + it('shows deprecation prompt when there are no existing rollup jobs', async () => { + expect(await testSubjects.exists('jobListDeprecatedPrompt')).to.be(true); }); it('create new rollup job', async () => { @@ -88,7 +92,7 @@ export default function ({ getService, getPageObjects }) { }); // Delete all data indices that were created. - await esDeleteAllIndices([targetIndexName], false); + await esDeleteAllIndices([targetIndexName, MOCK_ROLLUP_INDEX_NAME], false); if (isRunningCcs) { await esDeleteAllIndices([indexPatternToUse], true); } else { diff --git a/x-pack/test/functional/apps/rollup_job/test_helpers.ts b/x-pack/test/functional/apps/rollup_job/test_helpers.ts new file mode 100644 index 0000000000000..263a40a1a4708 --- /dev/null +++ b/x-pack/test/functional/apps/rollup_job/test_helpers.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import Client from '@elastic/elasticsearch/lib/client'; + +export const MOCK_ROLLUP_INDEX_NAME = 'mock-rollup-index'; + +export const createMockRollupIndex = async (es: Client) => { + await es.indices.create({ + index: MOCK_ROLLUP_INDEX_NAME, + mappings: { + _meta: { + _rollup: { + logs_job: { + id: 'mockRollupJob', + index_pattern: MOCK_ROLLUP_INDEX_NAME, + rollup_index: 'rollup_index', + cron: '0 0 0 ? * 7', + page_size: 1000, + groups: { + date_histogram: { + interval: '24h', + delay: '1d', + time_zone: 'UTC', + field: 'testCreatedField', + }, + terms: { + fields: ['testTotalField', 'testTagField'], + }, + histogram: { + interval: '7', + fields: ['testTotalField'], + }, + }, + }, + }, + 'rollup-version': '', + }, + }, + }); +}; diff --git a/x-pack/test/functional/apps/rollup_job/tsvb.js b/x-pack/test/functional/apps/rollup_job/tsvb.js index e7ad485224389..1220e3c51845a 100644 --- a/x-pack/test/functional/apps/rollup_job/tsvb.js +++ b/x-pack/test/functional/apps/rollup_job/tsvb.js @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import mockRolledUpData from './hybrid_index_helper'; +import { MOCK_ROLLUP_INDEX_NAME, createMockRollupIndex } from './test_helpers'; export default function ({ getService, getPageObjects }) { const es = getService('es'); @@ -24,9 +25,7 @@ export default function ({ getService, getPageObjects }) { const fromTime = 'Oct 15, 2019 @ 00:00:01.000'; const toTime = 'Oct 15, 2019 @ 19:31:44.000'; - // FLAKY: https://github.com/elastic/kibana/issues/56816 - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/168267 - describe.skip('tsvb integration', function () { + describe('tsvb integration', function () { //Since rollups can only be created once with the same name (even if you delete it), //we add the Date.now() to avoid name collision if you run the tests locally back to back. const rollupJobName = `tsvb-test-rollup-job-${Date.now()}`; @@ -49,6 +48,10 @@ export default function ({ getService, getPageObjects }) { 'metrics:allowStringIndices': true, 'timepicker:timeDefaults': `{ "from": "${fromTime}", "to": "${toTime}"}`, }); + + // From 8.15, Es only allows creating a new rollup job when there is existing rollup usage in the cluster + // We will simulate rollup usage by creating a mock-up rollup index + await createMockRollupIndex(es); }); it('create rollup tsvb', async () => { @@ -108,7 +111,11 @@ export default function ({ getService, getPageObjects }) { method: 'DELETE', }); - await esDeleteAllIndices([rollupTargetIndexName, rollupSourceIndexName]); + await esDeleteAllIndices([ + rollupTargetIndexName, + rollupSourceIndexName, + MOCK_ROLLUP_INDEX_NAME, + ]); await kibanaServer.importExport.unload( 'x-pack/test/functional/fixtures/kbn_archiver/rollup/rollup.json' ); diff --git a/x-pack/test/functional/apps/spaces/create_edit_space.ts b/x-pack/test/functional/apps/spaces/create_edit_space.ts new file mode 100644 index 0000000000000..cfffc752cca0c --- /dev/null +++ b/x-pack/test/functional/apps/spaces/create_edit_space.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['common', 'settings', 'security', 'spaceSelector']); + const testSubjects = getService('testSubjects'); + + describe('edit space', () => { + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + }); + + describe('solution view', () => { + it('does not show solution view panel', async () => { + await PageObjects.common.navigateToUrl('management', 'kibana/spaces/edit/default', { + shouldUseHashForSubUrl: false, + }); + + await testSubjects.existOrFail('spaces-edit-page'); + await testSubjects.existOrFail('spaces-edit-page > generalPanel'); + await testSubjects.missingOrFail('spaces-edit-page > navigationPanel'); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/spaces/index.ts b/x-pack/test/functional/apps/spaces/index.ts index c951609d6a33f..aa034f4be012b 100644 --- a/x-pack/test/functional/apps/spaces/index.ts +++ b/x-pack/test/functional/apps/spaces/index.ts @@ -13,5 +13,6 @@ export default function spacesApp({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./feature_controls/spaces_security')); loadTestFile(require.resolve('./spaces_selection')); loadTestFile(require.resolve('./enter_space')); + loadTestFile(require.resolve('./create_edit_space')); }); } diff --git a/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/config.ts b/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/config.ts new file mode 100644 index 0000000000000..0d286f5ffa621 --- /dev/null +++ b/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/config.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 { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js')); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('.')], + kbnTestServer: { + ...functionalConfig.get('kbnTestServer'), + serverArgs: [ + ...functionalConfig.get('kbnTestServer.serverArgs'), + '--xpack.cloud_integrations.experiments.enabled=true', + '--xpack.cloud_integrations.experiments.launch_darkly.sdk_key=a_string', + '--xpack.cloud_integrations.experiments.launch_darkly.client_id=a_string', + '--xpack.cloud_integrations.experiments.flag_overrides.solutionNavEnabled=true', + // Note: the base64 string in the cloud.id config contains the ES endpoint required in the functional tests + '--xpack.cloud.id=ftr_fake_cloud_id:aGVsbG8uY29tOjQ0MyRFUzEyM2FiYyRrYm4xMjNhYmM=', + '--xpack.cloud.base_url=https://cloud.elastic.co', + '--xpack.cloud.deployment_url=/deployments/deploymentId', + ], + }, + }; +} diff --git a/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/create_edit_space.ts b/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/create_edit_space.ts new file mode 100644 index 0000000000000..b6b5eff6ef5f2 --- /dev/null +++ b/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/create_edit_space.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['common', 'settings', 'security', 'spaceSelector']); + const testSubjects = getService('testSubjects'); + const find = getService('find'); + + describe('edit space', () => { + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + }); + + describe('solution view', () => { + it('does show the solution view panel', async () => { + await PageObjects.common.navigateToUrl('management', 'kibana/spaces/edit/default', { + shouldUseHashForSubUrl: false, + }); + + await testSubjects.existOrFail('spaces-edit-page'); + await testSubjects.existOrFail('spaces-edit-page > generalPanel'); + await testSubjects.existOrFail('spaces-edit-page > navigationPanel'); + }); + + it('changes the space solution and updates the side navigation', async () => { + await PageObjects.common.navigateToUrl('management', 'kibana/spaces/edit/default', { + shouldUseHashForSubUrl: false, + }); + + // Make sure we are on the classic side nav + await testSubjects.existOrFail('mgtSideBarNav'); + await testSubjects.missingOrFail('searchSideNav'); + + // change to Enterprise Search + await PageObjects.spaceSelector.changeSolutionView('es'); + await PageObjects.spaceSelector.clickSaveSpaceCreation(); + await PageObjects.spaceSelector.confirmModal(); + + await find.waitForDeletedByCssSelector('.kibanaWelcomeLogo'); + + // Search side nav is loaded + await testSubjects.existOrFail('searchSideNav'); + await testSubjects.missingOrFail('mgtSideBarNav'); + + // change back to classic + await PageObjects.common.navigateToUrl('management', 'kibana/spaces/edit/default', { + shouldUseHashForSubUrl: false, + }); + await PageObjects.spaceSelector.changeSolutionView('classic'); + await PageObjects.spaceSelector.clickSaveSpaceCreation(); + await PageObjects.spaceSelector.confirmModal(); + + await testSubjects.existOrFail('mgtSideBarNav'); + await testSubjects.missingOrFail('searchSideNav'); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/index.ts b/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/index.ts new file mode 100644 index 0000000000000..99ce8f2ab16e7 --- /dev/null +++ b/x-pack/test/functional/apps/spaces/solution_view_flag_enabled/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function spacesApp({ loadTestFile }: FtrProviderContext) { + describe('Spaces app (with solution view)', function spacesAppTestSuite() { + loadTestFile(require.resolve('./create_edit_space')); + }); +} diff --git a/x-pack/test/functional/page_objects/asset_details.ts b/x-pack/test/functional/page_objects/asset_details.ts index 32efd9f315ad2..3956ded602570 100644 --- a/x-pack/test/functional/page_objects/asset_details.ts +++ b/x-pack/test/functional/page_objects/asset_details.ts @@ -71,12 +71,12 @@ export function AssetDetailsProvider({ getService }: FtrProviderContext) { return servicesWithIconsAndNames; }, - async clickOverviewLinkToAlerts() { - return testSubjects.click('infraAssetDetailsAlertsShowAllButton'); + async overviewLinkToAlertsExist() { + return testSubjects.existOrFail('infraAssetDetailsAlertsTabAlertsShowAllButton'); }, - async clickOverviewOpenAlertsFlyout() { - return testSubjects.click('infraAssetDetailsCreateAlertsRuleButton'); + async overviewOpenAlertsFlyoutExist() { + return testSubjects.existOrFail('infraAssetDetailsAlertsTabCreateAlertsRuleButton'); }, async clickShowAllMetadataOverviewTab() { @@ -227,6 +227,14 @@ export function AssetDetailsProvider({ getService }: FtrProviderContext) { return section.findAllByCssSelector('[data-test-subj*="infraAssetDetailsMetricChart"]'); }, + async getMetricsTabDockerCharts(metric: string) { + const container = await testSubjects.find('infraAssetDetailsMetricsTabContent'); + const section = await container.findByTestSubject( + `infraAssetDetailsDockerChartsSection${metric}` + ); + return section.findAllByCssSelector('[data-test-subj*="infraAssetDetailsMetricChart"]'); + }, + async quickAccessItemExists(metric: string) { return testSubjects.click(`infraMetricsQuickAccessItem${metric}`); }, diff --git a/x-pack/test/functional/page_objects/dataset_quality.ts b/x-pack/test/functional/page_objects/dataset_quality.ts index c91ce73a05d49..d9b274e158536 100644 --- a/x-pack/test/functional/page_objects/dataset_quality.ts +++ b/x-pack/test/functional/page_objects/dataset_quality.ts @@ -51,12 +51,26 @@ type SummaryPanelKpi = Record< type FlyoutKpi = Record<'docsCountTotal' | 'size' | 'services' | 'hosts' | 'degradedDocs', string>; +const texts = { + noActivityText: 'No activity in the selected timeframe', + datasetHealthPoor: 'Poor', + datasetHealthDegraded: 'Degraded', + datasetHealthGood: 'Good', + activeDatasets: 'Active Data Sets', + estimatedData: 'Estimated Data', + docsCountTotal: 'Docs count (total)', + size: 'Size', + services: 'Services', + hosts: 'Hosts', + degradedDocs: 'Degraded docs', +}; + export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProviderContext) { const PageObjects = getPageObjects(['common']); const testSubjects = getService('testSubjects'); const euiSelectable = getService('selectable'); - const retry = getService('retry'); const find = getService('find'); + const retry = getService('retry'); const selectors = { datasetQualityTable: '[data-test-subj="datasetQualityTable"]', @@ -80,6 +94,8 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv datasetQualitySparkPlot: 'datasetQualitySparkPlot', datasetQualityHeaderButton: 'datasetQualityHeaderButton', datasetQualityFlyoutFieldValue: 'datasetQualityFlyoutFieldValue', + datasetQualityFlyoutFieldsListIntegrationDetails: + 'datasetQualityFlyoutFieldsList-integration_details', datasetQualityFlyoutIntegrationActionsButton: 'datasetQualityFlyoutIntegrationActionsButton', datasetQualityFlyoutIntegrationAction: (action: string) => `datasetQualityFlyoutIntegrationAction${action}`, @@ -93,6 +109,9 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv datasetQualityDatasetHealthKpi: 'datasetQualityDatasetHealthKpi', datasetQualityFlyoutKpiValue: 'datasetQualityFlyoutKpiValue', datasetQualityFlyoutKpiLink: 'datasetQualityFlyoutKpiLink', + datasetQualityInsufficientPrivileges: 'datasetQualityInsufficientPrivileges', + datasetQualityNoDataEmptyState: 'datasetQualityNoDataEmptyState', + datasetQualityNoPrivilegesEmptyState: 'datasetQualityNoPrivilegesEmptyState', superDatePickerToggleQuickMenuButton: 'superDatePickerToggleQuickMenuButton', superDatePickerApplyTimeButton: 'superDatePickerApplyTimeButton', @@ -102,6 +121,7 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv unifiedHistogramBreakdownSelectorSelectorSearch: 'unifiedHistogramBreakdownSelectorSelectorSearch', unifiedHistogramBreakdownSelectorSelectable: 'unifiedHistogramBreakdownSelectorSelectable', + managementHome: 'managementHome', }; return { @@ -143,13 +163,17 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv await find.waitForDeletedByCssSelector('.euiFlyoutBody .euiBasicTable-loading', 20 * 1000); }, - async waitUntilSummaryPanelLoaded() { + async waitUntilSummaryPanelLoaded(isStateful: boolean = true) { await testSubjects.missingOrFail(`datasetQuality-${texts.activeDatasets}-loading`); - await testSubjects.missingOrFail(`datasetQuality-${texts.estimatedData}-loading`); + if (isStateful) { + await testSubjects.missingOrFail(`datasetQuality-${texts.estimatedData}-loading`); + } }, async parseSummaryPanel(excludeKeys: string[] = []): Promise { - await this.waitUntilSummaryPanelLoaded(); + const isStateful = !excludeKeys.includes('estimatedData'); + + await this.waitUntilSummaryPanelLoaded(isStateful); const kpiTitleAndKeys = [ { title: texts.datasetHealthPoor, key: 'datasetHealthPoor' }, @@ -211,9 +235,15 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv return tBody.findAllByTagName('tr'); }, + async getDatasetTableHeaderTexts() { + const table = await this.getDatasetsTable(); + return getDatasetTableHeaderTexts(table); + }, + async parseDatasetTable() { + await this.waitUntilTableLoaded(); const table = await this.getDatasetsTable(); - return parseDatasetTable(table, [ + return this.parseTable(table, [ '0', 'Data Set Name', 'Namespace', @@ -226,8 +256,9 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv }, async parseDegradedFieldTable() { + await this.waitUntilTableInFlyoutLoaded(); const table = await this.getDatasetQualityFlyoutDegradedFieldTable(); - return parseDatasetTable(table, ['Field', 'Docs count', 'Last Occurrence']); + return this.parseTable(table, ['Field', 'Docs count', 'Last Occurrence']); }, async filterForIntegrations(integrations: string[]) { @@ -263,29 +294,32 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv }, async openDatasetFlyout(datasetName: string) { + await this.waitUntilTableLoaded(); const cols = await this.parseDatasetTable(); const datasetNameCol = cols['Data Set Name']; const datasetNameColCellTexts = await datasetNameCol.getCellTexts(); const testDatasetRowIndex = datasetNameColCellTexts.findIndex( (dName) => dName === datasetName ); - const expanderColumn = cols['0']; - let expanderButtons: WebElementWrapper[]; - await retry.try(async () => { - expanderButtons = await expanderColumn.getCellChildren( - `[data-test-subj=${testSubjectSelectors.datasetQualityExpandButton}]` - ); - expect(expanderButtons.length).to.be.greaterThan(0); - - // Check if 'title' attribute is "Expand" or "Collapse" - const isCollapsed = - (await expanderButtons[testDatasetRowIndex].getAttribute('title')) === 'Expand'; - - // Open if collapsed - if (isCollapsed) { - await expanderButtons[testDatasetRowIndex].click(); - } - }); + + expect(testDatasetRowIndex).to.be.greaterThan(-1); + + const expandColumn = cols['0']; + const expandButtons = await expandColumn.getCellChildren( + `[data-test-subj=${testSubjectSelectors.datasetQualityExpandButton}]` + ); + + expect(expandButtons.length).to.be.greaterThan(0); + + const datasetExpandButton = expandButtons[testDatasetRowIndex]; + + // Check if 'title' attribute is "Expand" or "Collapse" + const isCollapsed = (await datasetExpandButton.getAttribute('title')) === 'Expand'; + + // Open if collapsed + if (isCollapsed) { + await datasetExpandButton.click(); + } }, async closeFlyout() { @@ -421,87 +455,93 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv fieldText ); }, + + async parseTable(tableWrapper: WebElementWrapper, columnNamesOrIndexes: string[]) { + const headerElementWrappers = await tableWrapper.findAllByCssSelector('thead th, thead td'); + + const result: Record< + string, + { + columnNameOrIndex: string; + sortDirection?: 'ascending' | 'descending'; + headerElement: WebElementWrapper; + cellElements: WebElementWrapper[]; + cellContentElements: WebElementWrapper[]; + getSortDirection: () => Promise<'ascending' | 'descending' | undefined>; + sort: (sortDirection: 'ascending' | 'descending') => Promise; + getCellTexts: (selector?: string) => Promise; + getCellChildren: (selector: string) => Promise; + } + > = {}; + + for (let i = 0; i < headerElementWrappers.length; i++) { + const tdSelector = `table > tbody > tr td:nth-child(${i + 1})`; + const cellContentSelector = `${tdSelector} .euiTableCellContent`; + const thWrapper = headerElementWrappers[i]; + const columnName = await thWrapper.getVisibleText(); + const columnIndex = `${i}`; + const columnNameOrIndex = columnNamesOrIndexes.includes(columnName) + ? columnName + : columnNamesOrIndexes.includes(columnIndex) + ? columnIndex + : undefined; + + if (columnNameOrIndex) { + const headerElement = thWrapper; + + const tdWrappers = await tableWrapper.findAllByCssSelector(tdSelector); + const cellContentWrappers = await tableWrapper.findAllByCssSelector(cellContentSelector); + + const getSortDirection = () => + headerElement.getAttribute('aria-sort') as Promise< + 'ascending' | 'descending' | undefined + >; + + result[columnNameOrIndex] = { + columnNameOrIndex, + headerElement, + cellElements: tdWrappers, + cellContentElements: cellContentWrappers, + getSortDirection, + sort: async (sortDirection: 'ascending' | 'descending') => { + await retry.tryForTime(5000, async () => { + while ((await getSortDirection()) !== sortDirection) { + await headerElement.click(); + } + }); + }, + getCellTexts: async (textContainerSelector?: string) => { + const cellContentContainerWrappers = textContainerSelector + ? await tableWrapper.findAllByCssSelector(`${tdSelector} ${textContainerSelector}`) + : cellContentWrappers; + + const cellContentContainerWrapperTexts: string[] = []; + for (let j = 0; j < cellContentContainerWrappers.length; j++) { + const cellContentContainerWrapper = cellContentContainerWrappers[j]; + const cellContentContainerWrapperText = + await cellContentContainerWrapper.getVisibleText(); + cellContentContainerWrapperTexts.push(cellContentContainerWrapperText); + } + + return cellContentContainerWrapperTexts; + }, + getCellChildren: (childSelector: string) => { + return tableWrapper.findAllByCssSelector(`${cellContentSelector} ${childSelector}`); + }, + }; + } + } + + return result; + }, }; } -async function parseDatasetTable(tableWrapper: WebElementWrapper, columnNamesOrIndexes: string[]) { +async function getDatasetTableHeaderTexts(tableWrapper: WebElementWrapper) { const headerElementWrappers = await tableWrapper.findAllByCssSelector('thead th, thead td'); - - const result: Record< - string, - { - columnNameOrIndex: string; - sortDirection?: 'ascending' | 'descending'; - headerElement: WebElementWrapper; - cellElements: WebElementWrapper[]; - cellContentElements: WebElementWrapper[]; - getSortDirection: () => Promise<'ascending' | 'descending' | undefined>; - sort: (sortDirection: 'ascending' | 'descending') => Promise; - getCellTexts: (selector?: string) => Promise; - getCellChildren: (selector: string) => Promise; - } - > = {}; - - for (let i = 0; i < headerElementWrappers.length; i++) { - const tdSelector = `table > tbody > tr td:nth-child(${i + 1})`; - const cellContentSelector = `${tdSelector} .euiTableCellContent`; - const thWrapper = headerElementWrappers[i]; - const columnName = await thWrapper.getVisibleText(); - const columnIndex = `${i}`; - const columnNameOrIndex = columnNamesOrIndexes.includes(columnName) - ? columnName - : columnNamesOrIndexes.includes(columnIndex) - ? columnIndex - : undefined; - - if (columnNameOrIndex) { - const headerElement = thWrapper; - - const tdWrappers = await tableWrapper.findAllByCssSelector(tdSelector); - const cellContentWrappers = await tableWrapper.findAllByCssSelector(cellContentSelector); - - const getSortDirection = () => - headerElement.getAttribute('aria-sort') as Promise<'ascending' | 'descending' | undefined>; - - result[columnNameOrIndex] = { - columnNameOrIndex, - headerElement, - cellElements: tdWrappers, - cellContentElements: cellContentWrappers, - getSortDirection, - sort: async (sortDirection: 'ascending' | 'descending') => { - if ((await getSortDirection()) !== sortDirection) { - await headerElement.click(); - } - - // Sorting twice if the sort was in neutral state - if ((await getSortDirection()) !== sortDirection) { - await headerElement.click(); - } - }, - getCellTexts: async (textContainerSelector?: string) => { - const cellContentContainerWrappers = textContainerSelector - ? await tableWrapper.findAllByCssSelector(`${tdSelector} ${textContainerSelector}`) - : cellContentWrappers; - - const cellContentContainerWrapperTexts: string[] = []; - for (let j = 0; j < cellContentContainerWrappers.length; j++) { - const cellContentContainerWrapper = cellContentContainerWrappers[j]; - const cellContentContainerWrapperText = - await cellContentContainerWrapper.getVisibleText(); - cellContentContainerWrapperTexts.push(cellContentContainerWrapperText); - } - - return cellContentContainerWrapperTexts; - }, - getCellChildren: (childSelector: string) => { - return tableWrapper.findAllByCssSelector(`${cellContentSelector} ${childSelector}`); - }, - }; - } - } - - return result; + return Promise.all( + headerElementWrappers.map((headerElementWrapper) => headerElementWrapper.getVisibleText()) + ); } /** @@ -528,17 +568,3 @@ export async function getAllByText(container: WebElementWrapper, selector: strin return matchingElements; } - -const texts = { - noActivityText: 'No activity in the selected timeframe', - datasetHealthPoor: 'Poor', - datasetHealthDegraded: 'Degraded', - datasetHealthGood: 'Good', - activeDatasets: 'Active Data Sets', - estimatedData: 'Estimated Data', - docsCountTotal: 'Docs count (total)', - size: 'Size', - services: 'Services', - hosts: 'Hosts', - degradedDocs: 'Degraded docs', -}; diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index 9a5680b8cdf3c..913396d5b64dc 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -120,10 +120,13 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont }); }, - async selectOptionFromComboBox(testTargetId: string, name: string) { + async selectOptionFromComboBox(testTargetId: string, name: string | string[]) { const target = await testSubjects.find(testTargetId, 1000); await comboBox.openOptionsList(target); - await comboBox.setElement(target, name); + const names = Array.isArray(name) ? name : [name]; + for (const option of names) { + await comboBox.setElement(target, option); + } }, async configureQueryAnnotation(opts: { diff --git a/x-pack/test/functional/page_objects/rollup_page.ts b/x-pack/test/functional/page_objects/rollup_page.ts index f485de8ebac0c..90ba69f5410fb 100644 --- a/x-pack/test/functional/page_objects/rollup_page.ts +++ b/x-pack/test/functional/page_objects/rollup_page.ts @@ -13,6 +13,7 @@ export class RollupPageObject extends FtrService { private readonly log = this.ctx.getService('log'); private readonly find = this.ctx.getService('find'); private readonly header = this.ctx.getPageObject('header'); + private readonly common = this.ctx.getPageObject('common'); private readonly retry = this.ctx.getService('retry'); async createNewRollUpJob( @@ -26,7 +27,7 @@ export class RollupPageObject extends FtrService { ) { let stepNum = 1; // Step 1 - await this.testSubjects.click('createRollupJobButton'); + await this.common.navigateToUrlWithBrowserHistory('rollupJob', '/create'); await this.verifyStepIsActive(stepNum); await this.addRollupNameandIndexPattern(jobName, indexPattern); await this.verifyIndexPatternAccepted(); diff --git a/x-pack/test/functional/page_objects/space_selector_page.ts b/x-pack/test/functional/page_objects/space_selector_page.ts index 8c56dee81f435..a9bea52bcd2e8 100644 --- a/x-pack/test/functional/page_objects/space_selector_page.ts +++ b/x-pack/test/functional/page_objects/space_selector_page.ts @@ -120,6 +120,22 @@ export class SpaceSelectorPageObject extends FtrService { await this.testSubjects.setValue('euiColorPickerAnchor', hexValue); } + async openSolutionViewSelect() { + const solutionViewSelect = await this.testSubjects.find('solutionViewSelect'); + const classes = await solutionViewSelect.getAttribute('class'); + + const isOpen = classes?.includes('isOpen') ?? false; + if (!isOpen) { + await solutionViewSelect.click(); + } + } + + async changeSolutionView(solution: 'es' | 'oblt' | 'security' | 'classic') { + await this.openSolutionViewSelect(); + const serialized = solution.charAt(0).toUpperCase() + solution.slice(1); + await this.testSubjects.click(`solutionView${serialized}Option`); + } + async clickShowFeatures() { await this.testSubjects.click('show-hide-section-link'); } @@ -208,6 +224,11 @@ export class SpaceSelectorPageObject extends FtrService { await this.testSubjects.click('confirmModalConfirmButton'); } + // Generic for any confirm modal + async confirmModal() { + await this.testSubjects.click('confirmModalConfirmButton'); + } + async clickOnSpaceb() { await this.testSubjects.click('space-avatar-space_b'); } diff --git a/x-pack/test/functional/services/cases/list.ts b/x-pack/test/functional/services/cases/list.ts index b449b6f18438a..34db5e7345b3c 100644 --- a/x-pack/test/functional/services/cases/list.ts +++ b/x-pack/test/functional/services/cases/list.ts @@ -24,6 +24,7 @@ export function CasesTableServiceProvider( const browser = getService('browser'); const retry = getService('retry'); const config = getService('config'); + const toasts = getService('toasts'); const assertCaseExists = (index: number, totalCases: number) => { if (index > totalCases - 1) { @@ -49,6 +50,7 @@ export function CasesTableServiceProvider( }, async deleteCase(index: number = 0) { + await toasts.dismissAll(); this.openRowActions(index); await testSubjects.existOrFail('cases-bulk-action-delete'); await testSubjects.click('cases-bulk-action-delete'); diff --git a/x-pack/test/functional_with_es_ssl/apps/cases/group2/attachment_framework.ts b/x-pack/test/functional_with_es_ssl/apps/cases/group2/attachment_framework.ts index 876e4d2123019..8c4dd47532255 100644 --- a/x-pack/test/functional_with_es_ssl/apps/cases/group2/attachment_framework.ts +++ b/x-pack/test/functional_with_es_ssl/apps/cases/group2/attachment_framework.ts @@ -263,7 +263,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); }); - describe('Modal', () => { + // FLAKY: https://github.com/elastic/kibana/issues/157642 + describe.skip('Modal', () => { const createdCases = new Map(); const openModal = async () => { diff --git a/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/ml/alert_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/ml/alert_flyout.ts index 58c72ef9d1a27..7802a25f53e2f 100644 --- a/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/ml/alert_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/discover_ml_uptime/ml/alert_flyout.ts @@ -67,7 +67,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { let testJobId = ''; - describe('anomaly detection alert', function () { + // Failing: See https://github.com/elastic/kibana/issues/186261 + describe.skip('anomaly detection alert', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/ecommerce'); await ml.testResources.createDataViewIfNeeded('ft_ecommerce', 'order_date'); diff --git a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts index 0f14b6f10fef0..809f7307ab8aa 100644 --- a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts +++ b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts @@ -18,7 +18,7 @@ import { EphemeralTask, } from '@kbn/task-manager-plugin/server'; import { DEFAULT_MAX_WORKERS } from '@kbn/task-manager-plugin/server/config'; -import { TaskPriority } from '@kbn/task-manager-plugin/server/task'; +import { getDeleteTaskRunResult, TaskPriority } from '@kbn/task-manager-plugin/server/task'; import { initRoutes } from './init_routes'; // this plugin's dependendencies @@ -167,6 +167,45 @@ export class SampleTaskManagerFixturePlugin }, }), }, + sampleRecurringTaskThatDeletesItself: { + title: 'Sample Recurring Task that Times Out', + description: 'A sample task that requests deletion.', + stateSchemaByVersion: { + 1: { + up: (state: Record) => ({ count: state.count }), + schema: schema.object({ + count: schema.maybe(schema.number()), + }), + }, + }, + createTaskRunner: ({ taskInstance }: { taskInstance: ConcreteTaskInstance }) => ({ + async run() { + const { state } = taskInstance; + const prevState = state || { count: 0 }; + + const count = (prevState.count || 0) + 1; + + const [{ elasticsearch }] = await core.getStartServices(); + await elasticsearch.client.asInternalUser.index({ + index: '.kibana_task_manager_test_result', + body: { + type: 'task', + taskId: taskInstance.id, + state: JSON.stringify(state), + ranAt: new Date(), + }, + refresh: true, + }); + + if (count === 5) { + return getDeleteTaskRunResult(); + } + return { + state: { count }, + }; + }, + }), + }, sampleAdHocTaskTimingOut: { title: 'Sample Ad-Hoc Task that Times Out', description: 'A sample task that times out.', diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts index ff650c32de9af..26f4df962231f 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts @@ -25,6 +25,7 @@ export default function ({ getService }: FtrProviderContext) { 'sampleOneTimeTaskThrowingError', 'sampleRecurringTaskTimingOut', 'sampleRecurringTaskWhichHangs', + 'sampleRecurringTaskThatDeletesItself', 'sampleTask', 'sampleTaskWithLimitedConcurrency', 'sampleTaskWithSingleConcurrency', diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts index a430993933c8b..148939a58f910 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts @@ -296,6 +296,20 @@ export default function ({ getService }: FtrProviderContext) { }); }); + it('should remove recurring task if task requests deletion', async () => { + await scheduleTask({ + taskType: 'sampleRecurringTaskThatDeletesItself', + schedule: { interval: '1s' }, + params: {}, + }); + + await retry.try(async () => { + const history = await historyDocs(); + expect(history.length).to.eql(5); + expect((await currentTasks()).docs).to.eql([]); + }); + }); + it('should use a given ID as the task document ID', async () => { const result = await scheduleTask({ id: 'test-task-for-sample-task-plugin-to-test-task-manager', diff --git a/x-pack/test/saved_object_tagging/functional/tests/feature_control.ts b/x-pack/test/saved_object_tagging/functional/tests/feature_control.ts index f226423100451..463c2f5be82f4 100644 --- a/x-pack/test/saved_object_tagging/functional/tests/feature_control.ts +++ b/x-pack/test/saved_object_tagging/functional/tests/feature_control.ts @@ -45,8 +45,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const unselectTags = async () => { if (await tagManagementPage.isSelectionColumnDisplayed()) { - await tagManagementPage.selectAllTagRows(); - await tagManagementPage.selectAllTagRows(); + await tagManagementPage.clickOnBulkAction('clear_selection'); } }; diff --git a/x-pack/test/security_solution_api_integration/README.md b/x-pack/test/security_solution_api_integration/README.md index 1cfc87a1420c9..eeb0682c1358e 100644 --- a/x-pack/test/security_solution_api_integration/README.md +++ b/x-pack/test/security_solution_api_integration/README.md @@ -47,6 +47,13 @@ ex: 3. In these new configuration files, include references to the base configurations located under the config directory to inherit CI configurations, environment variables, and other settings. 4. Append a new entry in the `ftr_configs.yml` file to enable the execution of the newly added tests within the CI pipeline. +## Adding tests for MKI which rely onto NON default project configuration + +The default project type configuration in Serverless is complete. If for the needs of a test suite a different configuration is required, e.g. [PLI - Essentials](https://github.com/elastic/kibana/blob/36578e82fa0a0440c1657a0ca688106c895d5e4e/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/basic_license_essentials_tier/configs/serverless.config.ts#L13), the already mentioned configuration in the permalink **does not work** for MKI. The override is needed to be added in the `./scripts/api_configs.json` file under the key with exact same name as the one of the script in `package.json` file which is running. + +There are already configurations in the `./scripts/api_configs.json` which you can follow in order to add yours when it is needed. The currently supported configuration, allows **ONLY** the PLIs to be configured. Thus, experimental feature flags **are not yet supported** and the test should be skipped until further notice. + +**NOTE**: If a target script living in `package.json` file, does not require any further configuration, then the entry in `./scripts/api_configs.json` file, **can be omitted!** # Testing locally diff --git a/x-pack/test/security_solution_api_integration/package.json b/x-pack/test/security_solution_api_integration/package.json index acc78fba0fddf..52ff9a233b477 100644 --- a/x-pack/test/security_solution_api_integration/package.json +++ b/x-pack/test/security_solution_api_integration/package.json @@ -297,11 +297,11 @@ "rule_read:basic:server:ess": "npm run initialize-server:rm:basic_essentials rule_read ess", "rule_read:basic:runner:ess": "npm run run-tests:rm:basic_essentials rule_read ess essEnv", - "rules_management:essentials:server:serverless": "npm run initialize-server:rm:basic_essentials rule_management serverless", - "rules_management:essentials:runner:serverless": "npm run run-tests:rm:basic_essentials rule_management serverless serverlessEnv", - "rules_management:essentials:qa:serverless": "npm run run-tests:rm:basic_essentials rule_management serverless qaPeriodicEnv", - "rules_management:essentials:qa:serverless:release": "npm run run-tests:rm:basic_essentials rule_management serverless qaEnv", - "rules_management:basic:server:ess": "npm run initialize-server:rm:basic_essentials rule_management ess", - "rules_management:basic:runner:ess": "npm run run-tests:rm:basic_essentials rule_management ess essEnv" + "rules_management:essentials:server:serverless": "npm run initialize-server:rm:basic_essentials rule_management serverless", + "rules_management:essentials:runner:serverless": "npm run run-tests:rm:basic_essentials rule_management serverless serverlessEnv", + "rules_management:essentials:qa:serverless": "npm run run-tests:rm:basic_essentials rule_management serverless qaPeriodicEnv", + "rules_management:essentials:qa:serverless:release": "npm run run-tests:rm:basic_essentials rule_management serverless qaEnv", + "rules_management:basic:server:ess": "npm run initialize-server:rm:basic_essentials rule_management ess", + "rules_management:basic:runner:ess": "npm run run-tests:rm:basic_essentials rule_management ess essEnv" } } diff --git a/x-pack/test/security_solution_api_integration/scripts/api_configs.json b/x-pack/test/security_solution_api_integration/scripts/api_configs.json new file mode 100644 index 0000000000000..6a25037ccb905 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/scripts/api_configs.json @@ -0,0 +1,198 @@ +{ + "nlp_cleanup_task:essentials:qa:serverless": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" }, + { "product_line": "cloud", "product_tier": "essentials" } + ] + }, + "nlp_cleanup_task:essentials:qa:serverless:release": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" }, + { "product_line": "cloud", "product_tier": "essentials" } + ] + }, + "entity_analytics:essentials:qa:serverless": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" }, + { "product_line": "cloud", "product_tier": "essentials" } + ] + }, + "entity_analytics:essentials:qa:serverless:release": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" }, + { "product_line": "cloud", "product_tier": "essentials" } + ] + }, + "exception_workflows:essentials:qa:serverless": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "exception_workflows:essentials:qa:serverless:release": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "exception_operators_date_numeric_types:essentials:qa:serverless": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "exception_operators_date_numeric_types:essentials:qa:serverless:release": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "exception_operators_keyword:essentials:qa:serverless": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "exception_operators_keyword:essentials:qa:serverless:release": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "exception_operators_ips:essentials:qa:serverless": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "exception_operators_ips:essentials:qa:serverless:release": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "exception_operators_long:essentials:qa:serverless": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "exception_operators_long:essentials:qa:serverless:release": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "exception_operators_text:essentials:qa:serverless": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "exception_operators_text:essentials:qa:serverless:release": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "alerts:essentials:qa:serverless": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "alerts:essentials:qa:serverless:release": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "rule_creation:essentials:qa:serverless": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "rule_creation:essentials:qa:serverless:release": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "rule_update:essentials:qa:serverless": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "rule_update:essentials:qa:serverless:release": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "rule_patch:essentials:qa:serverless": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "rule_patch:essentials:qa:serverless:release": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "rule_delete:essentials:qa:serverless": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "rule_delete:essentials:qa:serverless:release": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "rule_import_export:essentials:qa:serverless": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "rule_import_export:essentials:qa:serverless:release": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "rules_management:essentials:qa:serverless": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "rules_management:essentials:qa:serverless:release": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "rule_read:essentials:qa:serverless": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + }, + "rule_read:essentials:qa:serverless:release": { + "productTypes": [ + { "product_line": "security", "product_tier": "essentials" }, + { "product_line": "endpoint", "product_tier": "essentials" } + ] + } +} diff --git a/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api_ftr_execution.ts b/x-pack/test/security_solution_api_integration/scripts/mki_api_ftr_execution.ts similarity index 77% rename from .buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api_ftr_execution.ts rename to x-pack/test/security_solution_api_integration/scripts/mki_api_ftr_execution.ts index f1e957873de0a..3dff7e2c1d071 100644 --- a/.buildkite/scripts/pipelines/security_solution_quality_gate/api_integration/api_ftr_execution.ts +++ b/x-pack/test/security_solution_api_integration/scripts/mki_api_ftr_execution.ts @@ -1,17 +1,19 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ import { run } from '@kbn/dev-cli-runner'; import { ToolingLog } from '@kbn/tooling-log'; import { exec } from 'child_process'; import crypto from 'crypto'; - -import type { ProjectHandler } from '@kbn/security-solution-plugin/scripts/run_cypress/project_handler/project_handler'; +import fs from 'fs'; +import type { + ProductType, + ProjectHandler, +} from '@kbn/security-solution-plugin/scripts/run_cypress/project_handler/project_handler'; import { CloudHandler } from '@kbn/security-solution-plugin/scripts/run_cypress/project_handler/cloud_project_handler'; import { ProxyHandler } from '@kbn/security-solution-plugin/scripts/run_cypress/project_handler/proxy_project_handler'; import { @@ -25,28 +27,33 @@ const BASE_ENV_URL = `${process.env.QA_CONSOLE_URL}`; const PROJECT_NAME_PREFIX = 'kibana-ftr-api-integration-security-solution'; // Function to execute a command and return a Promise with the status code -function executeCommand(command: string, envVars: any, workDir: string): Promise { +function executeCommand( + command: string, + envVars: any, + log: ToolingLog, + workDir?: string +): Promise { return new Promise((resolve, reject) => { - const childProcess = exec(command, { env: envVars, cwd: workDir }, (error, stdout, stderr) => { + const childProcess = exec(command, { env: envVars }, (error, stdout, stderr) => { if (error) { - console.error(`exec error: ${error}`); + log.error(`exec error: ${error}`); process.exitCode = error.code; } }); // Listen and print stdout data childProcess.stdout?.on('data', (data) => { - console.log(data); + log.info(data); }); // Listen and print stderr data childProcess.stderr?.on('data', (data) => { - console.log(data); + log.info(data); }); // Listen for process exit childProcess.on('exit', (code) => { - console.log(`Node process for target ${process.env.TARGET_SCRIPT} exits with code : ${code}`); + log.info(`Node process for target ${process.env.TARGET_SCRIPT} exits with code : ${code}`); if (code !== 0) { reject(code); return; @@ -56,6 +63,24 @@ function executeCommand(command: string, envVars: any, workDir: string): Promise }); } +async function parseProductTypes(log: ToolingLog): Promise { + if (!process.env.TARGET_SCRIPT) { + log.error('TARGET_SCRIPT environment variable is not provided. Aborting...'); + return process.exit(1); + } + + const apiConfigs = JSON.parse(await fs.promises.readFile('./scripts/api_configs.json', 'utf8')); + try { + const productTypes: ProductType[] = apiConfigs[process.env.TARGET_SCRIPT] + .productTypes as ProductType[]; + return productTypes && productTypes.length > 0 ? productTypes : undefined; + } catch (err) { + // If the configuration for the script is not needed, it can be omitted from the json file. + log.warning(`Extended configuration was not found for script : ${process.env.TARGET_SCRIPT}`); + return undefined; + } +} + export const cli = () => { run( async (context) => { @@ -89,14 +114,13 @@ export const cli = () => { const id = crypto.randomBytes(8).toString('hex'); const PROJECT_NAME = `${PROJECT_NAME_PREFIX}-${id}`; + const productTypes = await parseProductTypes(log); // Creating project for the test to run - const project = await cloudHandler.createSecurityProject(PROJECT_NAME); - log.info(project); + const project = await cloudHandler.createSecurityProject(PROJECT_NAME, productTypes); if (!project) { log.error('Failed to create project.'); - return process.exit(1); } let statusCode: number = 0; @@ -132,7 +156,6 @@ export const cli = () => { const testCloud = 1; const testEsUrl = `https://${credentials.username}:${credentials.password}@${FORMATTED_ES_URL}`; const testKibanaUrl = `https://${credentials.username}:${credentials.password}@${FORMATTED_KB_URL}`; - const workDir = 'x-pack/test/security_solution_api_integration'; const envVars = { ...process.env, TEST_CLOUD: testCloud.toString(), @@ -140,7 +163,7 @@ export const cli = () => { TEST_KIBANA_URL: testKibanaUrl, }; - statusCode = await executeCommand(command, envVars, workDir); + statusCode = await executeCommand(command, envVars, log); } catch (err) { log.error('An error occured when running the test script.'); log.error(err.message); diff --git a/x-pack/test/security_solution_api_integration/scripts/mki_start_api_ftr_execution.js b/x-pack/test/security_solution_api_integration/scripts/mki_start_api_ftr_execution.js new file mode 100644 index 0000000000000..587ce8e13f0b6 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/scripts/mki_start_api_ftr_execution.js @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +require('../../../../src/setup_node_env'); +require('./mki_api_ftr_execution').cli(); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/ess_specific_index_logic/migrations/create_alerts_migrations.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/ess_specific_index_logic/migrations/create_alerts_migrations.ts index 389527a532b40..12058ababe0dc 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/ess_specific_index_logic/migrations/create_alerts_migrations.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/ess_specific_index_logic/migrations/create_alerts_migrations.ts @@ -70,11 +70,14 @@ export default ({ getService }: FtrProviderContext): void => { // Finalize the migration after each test so that the .siem-signals alias gets added to the migrated index - // this allows deleteSignalsIndex to find and delete the migrated index await sleep(5000); // Allow the migration to complete - await supertest - .post(DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL) - .set('kbn-xsrf', 'true') - .send({ migration_ids: createdMigrations.map((m) => m.migration_id) }) - .expect(200); + + if (createdMigrations.length > 0) { + await supertest + .post(DETECTION_ENGINE_SIGNALS_FINALIZE_MIGRATION_URL) + .set('kbn-xsrf', 'true') + .send({ migration_ids: createdMigrations.map((m) => m.migration_id) }) + .expect(200); + } await esArchiver.unload('x-pack/test/functional/es_archives/signals/outdated_signals_index'); await esArchiver.unload('x-pack/test/functional/es_archives/signals/legacy_signals_index'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/field_aliases.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/field_aliases.ts index b6c6d265c14da..732bb54385a8a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/field_aliases.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/field_aliases.ts @@ -39,12 +39,9 @@ export default ({ getService }: FtrProviderContext) => { }); beforeEach(async () => { - await createAlertsIndex(supertest, log); - }); - - afterEach(async () => { await deleteAllAlerts(supertest, log, es); await deleteAllRules(supertest, log); + await createAlertsIndex(supertest, log); }); it('should keep the original alias value such as "host_alias" from a source index when the value is indexed', async () => { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/query_alerts.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/query_alerts.ts index 95764a6894fd4..63875d58f5d90 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/query_alerts.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/query_alerts.ts @@ -35,6 +35,10 @@ export default ({ getService }: FtrProviderContext) => { const es = getService('es'); describe('@ess @serverless @serverlessQA query_signals_route and find_alerts_route', () => { + beforeEach(async () => { + await deleteAllAlerts(supertest, log, es); + }); + describe('validation checks', () => { it('should not give errors when querying and the alerts index does exist and is empty', async () => { await createAlertsIndex(supertest, log); @@ -61,7 +65,7 @@ export default ({ getService }: FtrProviderContext) => { }); describe('runtime fields', () => { - before(async () => { + beforeEach(async () => { await esArchiver.load( 'x-pack/test/functional/es_archives/security_solution/alerts/8.8.0_multiple_docs', { @@ -71,7 +75,7 @@ export default ({ getService }: FtrProviderContext) => { ); await createAlertsIndex(supertest, log); }); - after(async () => { + afterEach(async () => { await deleteAllAlerts(supertest, log, es); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/set_alert_tags.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/set_alert_tags.ts index e0b09a111ae0c..961150d0908c2 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/set_alert_tags.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/set_alert_tags.ts @@ -5,7 +5,7 @@ * 2.0. */ -import expect from '@kbn/expect'; +import expect from 'expect'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { @@ -49,9 +49,10 @@ export default ({ getService }: FtrProviderContext) => { .send(setAlertTags({ tagsToAdd: [], tagsToRemove: [], ids: [] })) .expect(400); - expect(body).to.eql({ - message: ['No alert ids were provided'], - status_code: 400, + expect(body).toEqual({ + error: 'Bad Request', + message: '[request body]: ids: Array must contain at least 1 element(s)', + statusCode: 400, }); }); @@ -62,7 +63,7 @@ export default ({ getService }: FtrProviderContext) => { .send(setAlertTags({ tagsToAdd: ['test-1'], tagsToRemove: ['test-1'], ids: ['123'] })) .expect(400); - expect(body).to.eql({ + expect(body).toEqual({ message: [ 'Duplicate tags ["test-1"] were found in the tags_to_add and tags_to_remove parameters.', ], @@ -119,7 +120,7 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); body.hits.hits.map((alert) => { - expect(alert._source?.['kibana.alert.workflow_tags']).to.eql(['tag-1']); + expect(alert._source?.['kibana.alert.workflow_tags']).toEqual(['tag-1']); }); }); @@ -165,7 +166,7 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); body.hits.hits.map((alert) => { - expect(alert._source?.['kibana.alert.workflow_tags']).to.eql(['tag-1']); + expect(alert._source?.['kibana.alert.workflow_tags']).toEqual(['tag-1']); }); }); @@ -211,7 +212,7 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); body.hits.hits.map((alert) => { - expect(alert._source?.['kibana.alert.workflow_tags']).to.eql(['tag-1']); + expect(alert._source?.['kibana.alert.workflow_tags']).toEqual(['tag-1']); }); }); @@ -245,7 +246,7 @@ export default ({ getService }: FtrProviderContext) => { .expect(200); body.hits.hits.map((alert) => { - expect(alert._source?.['kibana.alert.workflow_tags']).to.eql([]); + expect(alert._source?.['kibana.alert.workflow_tags']).toEqual([]); }); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts index d6f464b63d78d..9e2638981a9e8 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts @@ -5,7 +5,7 @@ * 2.0. */ -import expect from '@kbn/expect'; +import expect from 'expect'; import { v4 as uuidv4 } from 'uuid'; import { NewTermsRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; @@ -80,8 +80,7 @@ export default ({ getService }: FtrProviderContext) => { return testId; }; - // Failing: See https://github.com/elastic/kibana/issues/180236 - describe.skip('@ess @serverless @serverlessQA New terms type rules', () => { + describe('@ess @serverless @serverlessQA New terms type rules', () => { before(async () => { await esArchiver.load(path); await esArchiver.load('x-pack/test/functional/es_archives/security_solution/new_terms'); @@ -110,8 +109,8 @@ export default ({ getService }: FtrProviderContext) => { const createdRule = await createRule(supertest, log, rule); const alerts = await getAlerts(supertest, log, es, createdRule); - expect(alerts.hits.hits.length).eql(1); - expect(removeRandomValuedPropertiesFromAlert(alerts.hits.hits[0]._source)).eql({ + expect(alerts.hits.hits.length).toEqual(1); + expect(removeRandomValuedPropertiesFromAlert(alerts.hits.hits[0]._source)).toEqual({ 'kibana.alert.new_terms': ['zeek-newyork-sha-aa8df15'], 'kibana.alert.rule.category': 'New Terms Rule', 'kibana.alert.rule.consumer': 'siem', @@ -145,12 +144,17 @@ export default ({ getService }: FtrProviderContext) => { version: '18.10 (Cosmic Cuttlefish)', }, }, - message: - 'Login by user root (UID: 0) on pts/0 (PID: 20638) from 8.42.77.171 (IP: 8.42.77.171)', + message: expect.stringMatching( + /Login by user (root|bob) \(UID: (0|1)\) on pts\/0 \(PID: 20638\) from 8\.42\.77\.171 \(IP: 8\.42\.77\.171\)/ + ), process: { pid: 20638 }, service: { type: 'system' }, source: { ip: '8.42.77.171' }, - user: { id: 0, name: 'root', terminal: 'pts/0' }, + user: { + id: expect.any(Number), + name: expect.stringMatching(/(root|bob)/), + terminal: 'pts/0', + }, 'event.action': 'user_login', 'event.category': 'authentication', 'event.dataset': 'login', @@ -162,7 +166,7 @@ export default ({ getService }: FtrProviderContext) => { 'kibana.alert.original_time': '2019-02-19T20:42:08.230Z', 'kibana.alert.ancestors': [ { - id: 'x07wJ2oB9v5HJNSHhyxi', + id: expect.any(String), type: 'event', index: 'auditbeat-8.0.0-2019.02.19-000001', depth: 0, @@ -173,8 +177,9 @@ export default ({ getService }: FtrProviderContext) => { 'kibana.alert.workflow_tags': [], 'kibana.alert.workflow_assignee_ids': [], 'kibana.alert.depth': 1, - 'kibana.alert.reason': - 'authentication event with source 8.42.77.171 by root on zeek-newyork-sha-aa8df15 created high alert Query with a rule id.', + 'kibana.alert.reason': expect.stringMatching( + /authentication event with source 8\.42\.77\.171 by (root|bob) on zeek-newyork-sha-aa8df15 created high alert Query with a rule id\./ + ), 'kibana.alert.severity': 'high', 'kibana.alert.risk_score': 55, 'kibana.alert.rule.parameters': { @@ -203,6 +208,9 @@ export default ({ getService }: FtrProviderContext) => { history_window_start: '2019-01-19T20:42:00.000Z', index: ['auditbeat-*'], language: 'kuery', + rule_source: { + type: 'internal', + }, }, 'kibana.alert.rule.actions': [], 'kibana.alert.rule.author': [], @@ -249,7 +257,7 @@ export default ({ getService }: FtrProviderContext) => { }; const { logs } = await previewRule({ supertest, rule }); - expect(logs[0].warnings).contain(getMaxAlertsWarning()); + expect(logs[0].warnings).toContain(getMaxAlertsWarning()); }); it("doesn't generate max alerts warning when circuit breaker is met but not exceeded", async () => { @@ -262,7 +270,7 @@ export default ({ getService }: FtrProviderContext) => { }; const { logs } = await previewRule({ supertest, rule }); - expect(logs[0].warnings).not.contain(getMaxAlertsWarning()); + expect(logs[0].warnings).not.toContain(getMaxAlertsWarning()); }); it('should generate 3 alerts when 1 document has 3 new values', async () => { @@ -276,19 +284,19 @@ export default ({ getService }: FtrProviderContext) => { const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).eql(3); + expect(previewAlerts.length).toEqual(3); const previewAlertsOrderedByHostIp = orderBy( previewAlerts, '_source.kibana.alert.new_terms', 'asc' ); - expect(previewAlertsOrderedByHostIp[0]._source?.['kibana.alert.new_terms']).eql([ + expect(previewAlertsOrderedByHostIp[0]._source?.['kibana.alert.new_terms']).toEqual([ '10.10.0.6', ]); - expect(previewAlertsOrderedByHostIp[1]._source?.['kibana.alert.new_terms']).eql([ + expect(previewAlertsOrderedByHostIp[1]._source?.['kibana.alert.new_terms']).toEqual([ '157.230.208.30', ]); - expect(previewAlertsOrderedByHostIp[2]._source?.['kibana.alert.new_terms']).eql([ + expect(previewAlertsOrderedByHostIp[2]._source?.['kibana.alert.new_terms']).toEqual([ 'fe80::24ce:f7ff:fede:a571', ]); }); @@ -304,14 +312,14 @@ export default ({ getService }: FtrProviderContext) => { const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).eql(3); + expect(previewAlerts.length).toEqual(3); const newTerms = orderBy( previewAlerts.map((item) => item._source?.['kibana.alert.new_terms']), ['0', '1'] ); - expect(newTerms).eql([ + expect(newTerms).toEqual([ ['zeek-newyork-sha-aa8df15', '10.10.0.6'], ['zeek-newyork-sha-aa8df15', '157.230.208.30'], ['zeek-newyork-sha-aa8df15', 'fe80::24ce:f7ff:fede:a571'], @@ -359,7 +367,7 @@ export default ({ getService }: FtrProviderContext) => { es, previewId: hostIpPreview.previewId, }); - expect(hostIpPreviewAlerts.length).eql(0); + expect(hostIpPreviewAlerts.length).toEqual(0); // shouldn't be terms for 'host.name' const hostNamePreview = await previewRule({ @@ -370,14 +378,14 @@ export default ({ getService }: FtrProviderContext) => { es, previewId: hostNamePreview.previewId, }); - expect(hostNamePreviewAlerts.length).eql(0); + expect(hostNamePreviewAlerts.length).toEqual(0); const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).eql(1); + expect(previewAlerts.length).toEqual(1); - expect(previewAlerts[0]._source?.['kibana.alert.new_terms']).eql(['host-0', '127.0.0.2']); + expect(previewAlerts[0]._source?.['kibana.alert.new_terms']).toEqual(['host-0', '127.0.0.2']); }); it('should generate 5 alerts, 1 for each new unique combination in 2 fields', async () => { @@ -416,14 +424,14 @@ export default ({ getService }: FtrProviderContext) => { const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).eql(5); + expect(previewAlerts.length).toEqual(5); const newTerms = orderBy( previewAlerts.map((item) => item._source?.['kibana.alert.new_terms']), ['0', '1'] ); - expect(newTerms).eql([ + expect(newTerms).toEqual([ ['192.168.1.1', 'tag-new-1'], ['192.168.1.1', 'tag-new-3'], ['192.168.1.2', 'tag-2'], @@ -456,8 +464,8 @@ export default ({ getService }: FtrProviderContext) => { const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).eql(1); - expect(previewAlerts[0]._source?.['kibana.alert.new_terms']).eql(['user-0', '1']); + expect(previewAlerts.length).toEqual(1); + expect(previewAlerts[0]._source?.['kibana.alert.new_terms']).toEqual(['user-0', 1]); }); it('should generate 1 alert for unique combination of terms, one of which is a boolean', async () => { @@ -472,8 +480,8 @@ export default ({ getService }: FtrProviderContext) => { const { previewId } = await previewRule({ supertest, rule }); 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.length).toEqual(1); + expect(previewAlerts[0]._source?.['kibana.alert.new_terms']).toEqual(['user-0', false]); }); it('should generate alerts for every term when history window is small', async () => { @@ -488,15 +496,15 @@ export default ({ getService }: FtrProviderContext) => { const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).eql(5); + expect(previewAlerts.length).toEqual(5); const hostNames = previewAlerts .map((signal) => signal._source?.['kibana.alert.new_terms']) .sort(); - expect(hostNames[0]).eql(['suricata-sensor-amsterdam']); - expect(hostNames[1]).eql(['suricata-sensor-san-francisco']); - expect(hostNames[2]).eql(['zeek-newyork-sha-aa8df15']); - expect(hostNames[3]).eql(['zeek-sensor-amsterdam']); - expect(hostNames[4]).eql(['zeek-sensor-san-francisco']); + expect(hostNames[0]).toEqual(['suricata-sensor-amsterdam']); + expect(hostNames[1]).toEqual(['suricata-sensor-san-francisco']); + expect(hostNames[2]).toEqual(['zeek-newyork-sha-aa8df15']); + expect(hostNames[3]).toEqual(['zeek-sensor-amsterdam']); + expect(hostNames[4]).toEqual(['zeek-sensor-san-francisco']); }); // github.com/elastic/kibana/issues/149920 @@ -538,9 +546,9 @@ export default ({ getService }: FtrProviderContext) => { const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).eql(1); + expect(previewAlerts.length).toEqual(1); - expect(previewAlerts[0]._source?.['kibana.alert.new_terms']).eql(['host-0', '127.0.0.2']); + expect(previewAlerts[0]._source?.['kibana.alert.new_terms']).toEqual(['host-0', '127.0.0.2']); }); describe('null values', () => { it('should not generate alerts with null values for single field', async () => { @@ -555,7 +563,7 @@ export default ({ getService }: FtrProviderContext) => { const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).eql(0); + expect(previewAlerts.length).toEqual(0); }); it('should not generate alerts with null values for multiple fields', async () => { @@ -570,7 +578,7 @@ export default ({ getService }: FtrProviderContext) => { const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).eql(0); + expect(previewAlerts.length).toEqual(0); }); }); @@ -587,7 +595,7 @@ export default ({ getService }: FtrProviderContext) => { const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId, size: 100 }); - expect(previewAlerts.length).eql(20); + expect(previewAlerts.length).toEqual(20); }); // There is a limit in ES for a number of emitted values in runtime field (100) @@ -605,7 +613,7 @@ export default ({ getService }: FtrProviderContext) => { const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId, size: 200 }); - expect(previewAlerts.length).eql(100); + expect(previewAlerts.length).toEqual(100); }); // There is a limit in ES for a number of emitted values in runtime field (100) @@ -624,7 +632,7 @@ export default ({ getService }: FtrProviderContext) => { const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId, size: 200 }); - expect(previewAlerts.length).eql(100); + expect(previewAlerts.length).toEqual(100); }); it('should not miss alerts if rule execution value combinations number is greater than 100', async () => { @@ -672,7 +680,7 @@ export default ({ getService }: FtrProviderContext) => { const previewAlerts = await getPreviewAlerts({ es, previewId, size: 200 }); // 10 alerts (with host.names a-[0-9]) should be generated - expect(previewAlerts.length).eql(10); + expect(previewAlerts.length).toEqual(10); }); it('should not miss alerts for high cardinality values in arrays, over 10.000 composite page size', async () => { @@ -731,7 +739,7 @@ export default ({ getService }: FtrProviderContext) => { const previewAlerts = await getPreviewAlerts({ es, previewId, size: 200 }); // only 1 alert should be generated - expect(previewAlerts.length).eql(1); + expect(previewAlerts.length).toEqual(1); }); it('should not miss alerts for high cardinality values in arrays, over 10.000 composite page size spread over multiple pages', async () => { @@ -817,7 +825,7 @@ export default ({ getService }: FtrProviderContext) => { const previewAlerts = await getPreviewAlerts({ es, previewId, size: 200 }); // only 4 alerts should be generated - expect(previewAlerts.length).eql(4); + expect(previewAlerts.length).toEqual(4); }); it('should not generate false positive alerts if rule historical window combinations overlap execution ones, which have more than 100', async () => { @@ -867,7 +875,7 @@ export default ({ getService }: FtrProviderContext) => { const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId, size: 200 }); - expect(previewAlerts.length).eql(0); + expect(previewAlerts.length).toEqual(0); }); it('should not generate false positive alerts if rule historical window combinations overlap execution ones, which have precisely 100', async () => { @@ -911,7 +919,7 @@ export default ({ getService }: FtrProviderContext) => { const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId, size: 200 }); - expect(previewAlerts.length).eql(0); + expect(previewAlerts.length).toEqual(0); }); }); @@ -949,12 +957,12 @@ export default ({ getService }: FtrProviderContext) => { const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).eql(2); + expect(previewAlerts.length).toEqual(2); const hostNames = previewAlerts .map((signal) => signal._source?.['kibana.alert.new_terms']) .sort(); - expect(hostNames[0]).eql(['host-3']); - expect(hostNames[1]).eql(['host-4']); + expect(hostNames[0]).toEqual(['host-3']); + expect(hostNames[1]).toEqual(['host-4']); }); }); @@ -989,14 +997,14 @@ export default ({ getService }: FtrProviderContext) => { }); const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts.length).eql(4); + expect(previewAlerts.length).toEqual(4); const hostNames = previewAlerts .map((signal) => signal._source?.['kibana.alert.new_terms']) .sort(); - expect(hostNames[0]).eql(['suricata-sensor-amsterdam']); - expect(hostNames[1]).eql(['suricata-sensor-san-francisco']); - expect(hostNames[2]).eql(['zeek-newyork-sha-aa8df15']); - expect(hostNames[3]).eql(['zeek-sensor-amsterdam']); + expect(hostNames[0]).toEqual(['suricata-sensor-amsterdam']); + expect(hostNames[1]).toEqual(['suricata-sensor-san-francisco']); + expect(hostNames[2]).toEqual(['zeek-newyork-sha-aa8df15']); + expect(hostNames[3]).toEqual(['zeek-sensor-amsterdam']); }); }); @@ -1014,11 +1022,11 @@ export default ({ getService }: FtrProviderContext) => { const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId, size: maxAlerts * 2 }); - expect(previewAlerts.length).eql(maxAlerts); + expect(previewAlerts.length).toEqual(maxAlerts); const processPids = previewAlerts .map((signal) => signal._source?.['kibana.alert.new_terms']) .sort(); - expect(processPids[0]).eql([1]); + expect(processPids[0]).toEqual([1]); }); describe('alerts should be be enriched', () => { @@ -1041,13 +1049,14 @@ export default ({ getService }: FtrProviderContext) => { const { previewId } = await previewRule({ supertest, rule }); const previewAlerts = await getPreviewAlerts({ es, previewId }); - expect(previewAlerts[0]?._source?.host?.risk?.calculated_level).to.eql('Low'); - expect(previewAlerts[0]?._source?.host?.risk?.calculated_score_norm).to.eql(23); + expect(previewAlerts[0]?._source?.host?.risk?.calculated_level).toEqual('Low'); + expect(previewAlerts[0]?._source?.host?.risk?.calculated_score_norm).toEqual(23); }); }); describe('with asset criticality', async () => { before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/security_solution/ecs_compliant'); await esArchiver.load('x-pack/test/functional/es_archives/asset_criticality'); await kibanaServer.uiSettings.update({ [ENABLE_ASSET_CRITICALITY_SETTING]: true, @@ -1055,23 +1064,54 @@ export default ({ getService }: FtrProviderContext) => { }); after(async () => { + await esArchiver.unload( + 'x-pack/test/functional/es_archives/security_solution/ecs_compliant' + ); await esArchiver.unload('x-pack/test/functional/es_archives/asset_criticality'); }); + const { indexListOfDocuments } = dataGeneratorFactory({ + es, + index: 'ecs_compliant', + log, + }); + it('should be enriched alert with criticality_level', async () => { + const id = uuidv4(); + const timestamp = '2020-10-28T06:45:00.000Z'; + + const firstExecutionDocuments = [ + { + host: { name: 'zeek-newyork-sha-aa8df15', ip: '127.0.0.5' }, + user: { name: 'root' }, + id, + '@timestamp': timestamp, + }, + ]; + + await indexListOfDocuments([...firstExecutionDocuments]); + const rule: NewTermsRuleCreateProps = { ...getCreateNewTermsRulesSchemaMock('rule-1', true), new_terms_fields: ['host.name'], - from: '2019-02-19T20:42:00.000Z', - history_window_start: '2019-01-19T20:42:00.000Z', + query: `id: "${id}"`, + index: ['ecs_compliant'], + history_window_start: '2019-10-13T05:00:04.000Z', + from: 'now-35m', + interval: '30m', }; - const { previewId } = await previewRule({ supertest, rule }); + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T07:00:00.000Z'), + }); const previewAlerts = await getPreviewAlerts({ es, previewId }); + expect(previewAlerts.length).toBe(1); const fullAlert = previewAlerts[0]._source; - expect(fullAlert?.['host.asset.criticality']).to.eql('medium_impact'); - expect(fullAlert?.['user.asset.criticality']).to.eql('extreme_impact'); + expect(fullAlert?.['host.asset.criticality']).toBe('medium_impact'); + expect(fullAlert?.['user.asset.criticality']).toBe('extreme_impact'); }); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts index 5625d00b5293d..0656288e2b4ce 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/index.ts @@ -16,5 +16,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./upgrade_prebuilt_rules')); loadTestFile(require.resolve('./upgrade_prebuilt_rules_with_historical_versions')); loadTestFile(require.resolve('./fleet_integration')); + loadTestFile(require.resolve('./upgrade_review_prebuilt_rules')); }); }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.ts new file mode 100644 index 0000000000000..9ad266e20740d --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/prebuilt_rules/management/trial_license_complete_tier/upgrade_review_prebuilt_rules.ts @@ -0,0 +1,715 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from 'expect'; +import { + ThreeWayDiffOutcome, + ThreeWayMergeOutcome, +} from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { + deleteAllTimelines, + deleteAllPrebuiltRuleAssets, + createRuleAssetSavedObject, + installPrebuiltRules, + createPrebuiltRuleAssetSavedObjects, + reviewPrebuiltRulesToUpgrade, + patchRule, + createHistoricalPrebuiltRuleAssetSavedObjects, +} from '../../../../utils'; +import { deleteAllRules } from '../../../../../../../common/utils/security_solution'; + +export default ({ getService }: FtrProviderContext): void => { + const es = getService('es'); + const supertest = getService('supertest'); + const log = getService('log'); + + describe('@ess @serverless @skipInServerlessMKI review prebuilt rules updates from package with mock rule assets', () => { + beforeEach(async () => { + await deleteAllRules(supertest, log); + await deleteAllTimelines(es, log); + await deleteAllPrebuiltRuleAssets(es, log); + }); + + describe(`single line string fields`, () => { + const getRuleAssetSavedObjects = () => [ + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 1, name: 'A' }), + ]; + + describe("when rule field doesn't have an update and has no custom value", () => { + it('should not show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + // Increment the version of the installed rule, do NOT update the related single line string field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + name: 'A', + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that there is 1 rule eligable for update but single line string field is NOT returned + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect(reviewResponse.rules[0].diff.fields).toEqual({ + version: { + base_version: 1, + current_version: 1, + target_version: 2, + merged_version: 2, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + has_update: true, + }, + }); + expect(reviewResponse.rules[0].diff.has_conflict).toBe(false); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + }); + }); + + describe("when rule field doesn't have an update but has a custom value", () => { + it('should not show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + // Customize a single line string field on the installed rule + await patchRule(supertest, log, { + rule_id: 'rule-1', + name: 'B', + }); + + // Increment the version of the installed rule, do NOT update the related single line string field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + name: 'A', + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that single line string diff field is returned but field does not have an update + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect(reviewResponse.rules[0].diff.fields).toEqual({ + name: { + base_version: 'A', + current_version: 'B', + target_version: 'A', + merged_version: 'B', + diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_conflict: false, + has_update: false, + }, + version: { + base_version: 1, + current_version: 1, + target_version: 2, + merged_version: 2, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + has_update: true, + }, + }); + expect(reviewResponse.rules[0].diff.has_conflict).toBe(false); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + }); + }); + + describe('when rule field has an update but does not have a custom value', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + // Increment the version of the installed rule, update a single line string field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + name: 'B', + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect(reviewResponse.rules[0].diff.fields).toEqual({ + name: { + base_version: 'A', + current_version: 'A', + target_version: 'B', + merged_version: 'B', + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + has_update: true, + }, + version: { + base_version: 1, + current_version: 1, + target_version: 2, + merged_version: 2, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + has_update: true, + }, + }); + expect(reviewResponse.rules[0].diff.has_conflict).toBe(false); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + }); + + describe('when rule field has an update and a custom value that are the same', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + // Customize a single line string field on the installed rule + await patchRule(supertest, log, { + rule_id: 'rule-1', + name: 'B', + }); + + // Increment the version of the installed rule, update a single line string field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + name: 'B', + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect(reviewResponse.rules[0].diff.fields).toEqual({ + name: { + base_version: 'A', + current_version: 'B', + target_version: 'B', + merged_version: 'B', + diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_conflict: false, + has_update: false, + }, + version: { + base_version: 1, + current_version: 1, + target_version: 2, + merged_version: 2, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + has_update: true, + }, + }); + expect(reviewResponse.rules[0].diff.has_conflict).toBe(false); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + }); + }); + + describe('when rule field has an update and a custom value that are different', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + // Customize a single line string field on the installed rule + await patchRule(supertest, log, { + rule_id: 'rule-1', + name: 'B', + }); + + // Increment the version of the installed rule, update a single line string field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + name: 'C', + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + // and single line string field update has conflict + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect(reviewResponse.rules[0].diff.fields).toEqual({ + name: { + base_version: 'A', + current_version: 'B', + target_version: 'C', + merged_version: 'B', + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Conflict, + has_conflict: true, + has_update: true, + }, + version: { + base_version: 1, + current_version: 1, + target_version: 2, + merged_version: 2, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + has_update: true, + }, + }); + expect(reviewResponse.rules[0].diff.has_conflict).toBe(true); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + }); + }); + + describe('when rule base version does not exist', () => { + describe('when rule field has an update and a custom value that are the same', () => { + it('should not show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + // Clear previous rule assets + await deleteAllPrebuiltRuleAssets(es, log); + + // Customize a single line string field on the installed rule + await patchRule(supertest, log, { + rule_id: 'rule-1', + name: 'B', + }); + + // Increment the version of the installed rule, update a single line string field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + name: 'B', + }), + ]; + await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + // but does NOT contain single line string field + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect(reviewResponse.rules[0].diff.fields).toEqual({ + version: { + current_version: 1, + target_version: 2, + merged_version: 2, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + has_update: true, + }, + }); + expect(reviewResponse.rules[0].diff.has_conflict).toBe(false); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + }); + }); + + describe('when rule field has an update and a custom value that are different', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + // Clear previous rule assets + await deleteAllPrebuiltRuleAssets(es, log); + + // Customize a single line string field on the installed rule + await patchRule(supertest, log, { + rule_id: 'rule-1', + name: 'B', + }); + + // Increment the version of the installed rule, update a single line string field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + name: 'C', + }), + ]; + await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + // and single line string field update does not have a conflict + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect(reviewResponse.rules[0].diff.fields).toEqual({ + name: { + current_version: 'B', + target_version: 'C', + merged_version: 'C', + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + has_update: true, + }, + version: { + current_version: 1, + target_version: 2, + merged_version: 2, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + has_update: true, + }, + }); + expect(reviewResponse.rules[0].diff.has_conflict).toBe(false); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + }); + }); + }); + }); + }); + + describe(`number fields`, () => { + const getRuleAssetSavedObjects = () => [ + createRuleAssetSavedObject({ rule_id: 'rule-1', version: 1, risk_score: 1 }), + ]; + + describe("when rule field doesn't have an update and has no custom value", () => { + it('should not show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + // Increment the version of the installed rule, do NOT update the related number field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + risk_score: 1, + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that there is 1 rule eligable for update but number field is NOT returned + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect(reviewResponse.rules[0].diff.fields).toEqual({ + version: { + base_version: 1, + current_version: 1, + target_version: 2, + merged_version: 2, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + has_update: true, + }, + }); + expect(reviewResponse.rules[0].diff.has_conflict).toBe(false); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + }); + }); + + describe("when rule field doesn't have an update but has a custom value", () => { + it('should not show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + // Customize a number field on the installed rule + await patchRule(supertest, log, { + rule_id: 'rule-1', + risk_score: 2, + }); + + // Increment the version of the installed rule, do NOT update the related number field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + risk_score: 1, + version: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that number diff field is returned but field does not have an update + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect(reviewResponse.rules[0].diff.fields).toEqual({ + risk_score: { + base_version: 1, + current_version: 2, + target_version: 1, + merged_version: 2, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueNoUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_conflict: false, + has_update: false, + }, + version: { + base_version: 1, + current_version: 1, + target_version: 2, + merged_version: 2, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + has_update: true, + }, + }); + expect(reviewResponse.rules[0].diff.has_conflict).toBe(false); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + }); + }); + + describe('when rule field has an update but does not have a custom value', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + // Increment the version of the installed rule, update a number field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + risk_score: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect(reviewResponse.rules[0].diff.fields).toEqual({ + risk_score: { + base_version: 1, + current_version: 1, + target_version: 2, + merged_version: 2, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + has_update: true, + }, + version: { + base_version: 1, + current_version: 1, + target_version: 2, + merged_version: 2, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + has_update: true, + }, + }); + expect(reviewResponse.rules[0].diff.has_conflict).toBe(false); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + }); + + describe('when rule field has an update and a custom value that are the same', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + // Customize a number field on the installed rule + await patchRule(supertest, log, { + rule_id: 'rule-1', + risk_score: 2, + }); + + // Increment the version of the installed rule, update a number field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + risk_score: 2, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update and contains number field + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect(reviewResponse.rules[0].diff.fields).toEqual({ + risk_score: { + base_version: 1, + current_version: 2, + target_version: 2, + merged_version: 2, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueSameUpdate, + merge_outcome: ThreeWayMergeOutcome.Current, + has_conflict: false, + has_update: false, + }, + version: { + base_version: 1, + current_version: 1, + target_version: 2, + merged_version: 2, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + has_update: true, + }, + }); + expect(reviewResponse.rules[0].diff.has_conflict).toBe(false); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + }); + }); + + describe('when rule field has an update and a custom value that are different', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createHistoricalPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + // Customize a number field on the installed rule + await patchRule(supertest, log, { + rule_id: 'rule-1', + risk_score: 2, + }); + + // Increment the version of the installed rule, update a number field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + risk_score: 3, + }), + ]; + await createHistoricalPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + // and number field update has conflict + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect(reviewResponse.rules[0].diff.fields).toEqual({ + risk_score: { + base_version: 1, + current_version: 2, + target_version: 3, + merged_version: 2, + diff_outcome: ThreeWayDiffOutcome.CustomizedValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Conflict, + has_conflict: true, + has_update: true, + }, + version: { + base_version: 1, + current_version: 1, + target_version: 2, + merged_version: 2, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + has_update: true, + }, + }); + expect(reviewResponse.rules[0].diff.has_conflict).toBe(true); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + }); + }); + + describe('when rule base version does not exist', () => { + describe('when rule field has an update and a custom value that are the same', () => { + it('should not show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + // Clear previous rule assets + await deleteAllPrebuiltRuleAssets(es, log); + + // Customize a number field on the installed rule + await patchRule(supertest, log, { + rule_id: 'rule-1', + risk_score: 2, + }); + + // Increment the version of the installed rule, update a number field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + risk_score: 2, + }), + ]; + await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + // but does NOT contain number field + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect(reviewResponse.rules[0].diff.fields).toEqual({ + version: { + current_version: 1, + target_version: 2, + merged_version: 2, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + has_update: true, + }, + }); + expect(reviewResponse.rules[0].diff.has_conflict).toBe(false); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + }); + }); + + describe('when rule field has an update and a custom value that are different', () => { + it('should show in the upgrade/_review API response', async () => { + // Install base prebuilt detection rule + await createPrebuiltRuleAssetSavedObjects(es, getRuleAssetSavedObjects()); + await installPrebuiltRules(es, supertest); + + // Clear previous rule assets + await deleteAllPrebuiltRuleAssets(es, log); + + // Customize a number field on the installed rule + await patchRule(supertest, log, { + rule_id: 'rule-1', + risk_score: 2, + }); + + // Increment the version of the installed rule, update a number field, and create the new rule assets + const updatedRuleAssetSavedObjects = [ + createRuleAssetSavedObject({ + rule_id: 'rule-1', + version: 2, + risk_score: 3, + }), + ]; + await createPrebuiltRuleAssetSavedObjects(es, updatedRuleAssetSavedObjects); + + // Call the upgrade review prebuilt rules endpoint and check that one rule is eligible for update + // and number field update does not have a conflict + const reviewResponse = await reviewPrebuiltRulesToUpgrade(supertest); + expect(reviewResponse.rules[0].diff.fields).toEqual({ + risk_score: { + current_version: 2, + target_version: 3, + merged_version: 3, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + has_update: true, + }, + version: { + current_version: 1, + target_version: 2, + merged_version: 2, + diff_outcome: ThreeWayDiffOutcome.StockValueCanUpdate, + merge_outcome: ThreeWayMergeOutcome.Target, + has_conflict: false, + has_update: true, + }, + }); + expect(reviewResponse.rules[0].diff.has_conflict).toBe(false); + expect(reviewResponse.stats.num_rules_to_upgrade_total).toBe(1); + }); + }); + }); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts index 22ac52dc2a333..091707df88d0a 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/rules_management/rule_bulk_actions/trial_license_complete_tier/perform_bulk_action.ts @@ -18,7 +18,7 @@ import { 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'; import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock'; -import { WebhookAuthType } from '@kbn/stack-connectors-plugin/common/webhook/constants'; +import { AuthType } from '@kbn/stack-connectors-plugin/common/auth/constants'; import { BaseDefaultableFields } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { binaryToString, @@ -190,7 +190,7 @@ export default ({ getService }: FtrProviderContext): void => { attributes: { actionTypeId: '.webhook', config: { - authType: WebhookAuthType.Basic, + authType: AuthType.Basic, hasAuth: true, method: 'post', url: 'http://localhost', diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/user_roles/trial_license_complete_tier/read_privileges.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/user_roles/trial_license_complete_tier/read_privileges.ts index 7727958243823..51a7a9ab1330f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/user_roles/trial_license_complete_tier/read_privileges.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/user_roles/trial_license_complete_tier/read_privileges.ts @@ -19,8 +19,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); - // Failing ES Promotion: https://github.com/elastic/kibana/issues/174028 - describe.skip('@ess @serverless @skipInServerless read_privileges', () => { + describe('@ess @serverless @skipInServerless read_privileges', () => { it('should return expected privileges for elastic admin', async () => { const { body } = await supertest.get(DETECTION_ENGINE_PRIVILEGES_URL).send().expect(200); expect(body).to.eql({ diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/set_alert_tags.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/set_alert_tags.ts index b3ae1d4de0b5c..f826629741179 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/set_alert_tags.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/set_alert_tags.ts @@ -6,7 +6,7 @@ */ import { AlertTagIds } from '@kbn/security-solution-plugin/common/api/detection_engine'; -import { SetAlertTagsRequestBody } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { ManageAlertTagsRequestBodyInput } from '@kbn/security-solution-plugin/common/api/detection_engine'; export const setAlertTags = ({ tagsToAdd, @@ -16,7 +16,7 @@ export const setAlertTags = ({ tagsToAdd: string[]; tagsToRemove: string[]; ids: AlertTagIds; -}): SetAlertTagsRequestBody => ({ +}): ManageAlertTagsRequestBodyInput => ({ tags: { tags_to_add: tagsToAdd, tags_to_remove: tagsToRemove, diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts index 7a7f50c5741ec..fa3f3e0e67f38 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts @@ -6,6 +6,8 @@ */ import expect from '@kbn/expect'; +import { omit } from 'lodash'; +import { CreateAssetCriticalityRecord } from '@kbn/security-solution-plugin/common/api/entity_analytics'; import { cleanRiskEngine, cleanAssetCriticality, @@ -206,6 +208,108 @@ export default ({ getService }: FtrProviderContext) => { }); }); + describe('bulk upload', () => { + const expectAssetCriticalityDocMatching = async (expectedDoc: { + id_field: string; + id_value: string; + criticality_level: string; + }) => { + const esDoc = await getAssetCriticalityDoc({ + es, + idField: expectedDoc.id_field, + idValue: expectedDoc.id_value, + }); + + expect(omit(esDoc, '@timestamp')).to.eql(expectedDoc); + }; + + it('should return 400 if the records array is empty', async () => { + await assetCriticalityRoutes.bulkUpload([], { + expectStatusCode: 400, + }); + }); + + it('should return 400 if the records array is too large', async () => { + const records = new Array(1001).fill({ + id_field: 'host.name', + id_value: 'host-1', + criticality_level: 'high_impact', + }); + + await assetCriticalityRoutes.bulkUpload(records, { + expectStatusCode: 400, + }); + }); + + it('should return a 403 if the advanced setting is disabled', async () => { + await disableAssetCriticalityAdvancedSetting(kibanaServer, log); + + const validRecord: CreateAssetCriticalityRecord = { + id_field: 'host.name', + id_value: 'delete-me', + criticality_level: 'high_impact', + }; + + await assetCriticalityRoutes.bulkUpload([validRecord], { + expectStatusCode: 403, + }); + }); + + it('should correctly upload a valid record for one entity', async () => { + const validRecord: CreateAssetCriticalityRecord = { + id_field: 'host.name', + id_value: 'host-1', + criticality_level: 'high_impact', + }; + + const { body } = await assetCriticalityRoutes.bulkUpload([validRecord]); + expect(body.errors).to.eql([]); + expect(body.stats).to.eql({ + total: 1, + successful: 1, + failed: 0, + }); + + await expectAssetCriticalityDocMatching(validRecord); + }); + + it('should correctly upload valid records for multiple entities', async () => { + const validRecords: CreateAssetCriticalityRecord[] = Array.from({ length: 50 }, (_, i) => ({ + id_field: 'host.name', + id_value: `host-${i}`, + criticality_level: 'high_impact', + })); + + const { body } = await assetCriticalityRoutes.bulkUpload(validRecords); + expect(body.errors).to.eql([]); + expect(body.stats).to.eql({ + total: validRecords.length, + successful: validRecords.length, + failed: 0, + }); + + await Promise.all(validRecords.map(expectAssetCriticalityDocMatching)); + }); + + it('should return a 400 if a record is invalid', async () => { + const invalidRecord = { + id_field: 'host.name', + id_value: 'host-1', + criticality_level: 'invalid', + } as unknown as CreateAssetCriticalityRecord; + + const validRecord: CreateAssetCriticalityRecord = { + id_field: 'host.name', + id_value: 'host-2', + criticality_level: 'high_impact', + }; + + await assetCriticalityRoutes.bulkUpload([invalidRecord, validRecord], { + expectStatusCode: 400, + }); + }); + }); + describe('delete', () => { it('should correctly delete asset criticality', async () => { const assetCriticality = { diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts index 3a46aa56ef614..ac7dfd170e412 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts @@ -11,13 +11,18 @@ import { X_ELASTIC_INTERNAL_ORIGIN_REQUEST, } from '@kbn/core-http-common'; import { - ASSET_CRITICALITY_STATUS_URL, - ASSET_CRITICALITY_URL, - ASSET_CRITICALITY_PRIVILEGES_URL, - ASSET_CRITICALITY_CSV_UPLOAD_URL, + ASSET_CRITICALITY_PUBLIC_URL, + ASSET_CRITICALITY_PUBLIC_CSV_UPLOAD_URL, + ASSET_CRITICALITY_INTERNAL_STATUS_URL, + ASSET_CRITICALITY_INTERNAL_PRIVILEGES_URL, ENABLE_ASSET_CRITICALITY_SETTING, + API_VERSIONS, + ASSET_CRITICALITY_PUBLIC_BULK_UPLOAD_URL, } from '@kbn/security-solution-plugin/common/constants'; -import type { AssetCriticalityRecord } from '@kbn/security-solution-plugin/common/api/entity_analytics'; +import type { + AssetCriticalityRecord, + CreateAssetCriticalityRecord, +} from '@kbn/security-solution-plugin/common/api/entity_analytics'; import type { Client } from '@elastic/elasticsearch'; import type { ToolingLog } from '@kbn/tooling-log'; import querystring from 'querystring'; @@ -109,9 +114,9 @@ export const assetCriticalityRouteHelpersFactory = ( ) => ({ status: async () => await supertest - .get(routeWithNamespace(ASSET_CRITICALITY_STATUS_URL, namespace)) + .get(routeWithNamespace(ASSET_CRITICALITY_INTERNAL_STATUS_URL, namespace)) .set('kbn-xsrf', 'true') - .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.internal.v1) .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send() .expect(200), @@ -120,9 +125,9 @@ export const assetCriticalityRouteHelpersFactory = ( { expectStatusCode }: { expectStatusCode: number } = { expectStatusCode: 200 } ) => await supertest - .post(routeWithNamespace(ASSET_CRITICALITY_URL, namespace)) + .post(routeWithNamespace(ASSET_CRITICALITY_PUBLIC_URL, namespace)) .set('kbn-xsrf', 'true') - .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.public.v1) .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send(body) .expect(expectStatusCode), @@ -132,11 +137,11 @@ export const assetCriticalityRouteHelpersFactory = ( { expectStatusCode }: { expectStatusCode: number } = { expectStatusCode: 200 } ) => { const qs = querystring.stringify({ id_field: idField, id_value: idValue }); - const route = `${routeWithNamespace(ASSET_CRITICALITY_URL, namespace)}?${qs}`; + const route = `${routeWithNamespace(ASSET_CRITICALITY_PUBLIC_URL, namespace)}?${qs}`; return supertest .delete(route) .set('kbn-xsrf', 'true') - .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.public.v1) .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .expect(expectStatusCode); }, @@ -146,23 +151,35 @@ export const assetCriticalityRouteHelpersFactory = ( { expectStatusCode }: { expectStatusCode: number } = { expectStatusCode: 200 } ) => { const qs = querystring.stringify({ id_field: idField, id_value: idValue }); - const route = `${routeWithNamespace(ASSET_CRITICALITY_URL, namespace)}?${qs}`; + const route = `${routeWithNamespace(ASSET_CRITICALITY_PUBLIC_URL, namespace)}?${qs}`; return supertest .get(route) .set('kbn-xsrf', 'true') - .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.public.v1) .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .expect(expectStatusCode); }, + bulkUpload: async ( + records: CreateAssetCriticalityRecord[], + { expectStatusCode }: { expectStatusCode: number } = { expectStatusCode: 200 } + ) => { + return supertest + .post(routeWithNamespace(ASSET_CRITICALITY_PUBLIC_BULK_UPLOAD_URL, namespace)) + .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.public.v1) + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send({ records }) + .expect(expectStatusCode); + }, uploadCsv: async ( fileContent: string | Buffer, { expectStatusCode }: { expectStatusCode: number } = { expectStatusCode: 200 } ) => { const file = fileContent instanceof Buffer ? fileContent : Buffer.from(fileContent); return supertest - .post(routeWithNamespace(ASSET_CRITICALITY_CSV_UPLOAD_URL, namespace)) + .post(routeWithNamespace(ASSET_CRITICALITY_PUBLIC_CSV_UPLOAD_URL, namespace)) .set('kbn-xsrf', 'true') - .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.public.v1) .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .attach('file', file, { filename: 'asset_criticality.csv' }) .expect(expectStatusCode); @@ -175,9 +192,9 @@ export const assetCriticalityRouteHelpersFactoryNoAuth = ( ) => ({ privilegesForUser: async ({ username, password }: { username: string; password: string }) => await supertestWithoutAuth - .get(ASSET_CRITICALITY_PRIVILEGES_URL) + .get(ASSET_CRITICALITY_INTERNAL_PRIVILEGES_URL) .auth(username, password) - .set('elastic-api-version', '1') + .set('elastic-api-version', API_VERSIONS.internal.v1) .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send() .expect(200), diff --git a/x-pack/test/security_solution_api_integration/test_suites/genai/nlp_cleanup_task/basic_license_essentials_tier/task_execution.ts b/x-pack/test/security_solution_api_integration/test_suites/genai/nlp_cleanup_task/basic_license_essentials_tier/task_execution.ts index c560c1db035ac..1e018646dd610 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/genai/nlp_cleanup_task/basic_license_essentials_tier/task_execution.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/genai/nlp_cleanup_task/basic_license_essentials_tier/task_execution.ts @@ -25,7 +25,10 @@ export default ({ getService }: FtrProviderContext): void => { id: SUPPORTED_TRAINED_MODELS.TINY_ELSER.name, }; - describe('@serverless NLP Cleanup Task in Essentials Tier', () => { + // This started failing after merging with main, so skipping for now + // See https://github.com/elastic/kibana/pull/186219 for details, but issue appears to be with + // sporadic errors in loading `pt_tiny_elser` + describe.skip('@serverless NLP Cleanup Task in Essentials Tier', () => { describe('New Essentials Deployment', () => { it('registers and enables NLP Cleanup Task', async () => { const task = await kibanaServer.savedObjects.get({ diff --git a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/find_list_items.ts b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/find_list_items.ts index 0c8aa8aa50fa8..aed7d61acf7b2 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/find_list_items.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/lists_and_exception_lists/lists_items/trial_license_complete_tier/items/find_list_items.ts @@ -5,27 +5,20 @@ * 2.0. */ -import expect from '@kbn/expect'; +import expect from 'expect'; import { LIST_URL, LIST_ITEM_URL } from '@kbn/securitysolution-list-constants'; import { LIST_ITEM_ID, LIST_ID } from '@kbn/lists-plugin/common/constants.mock'; -import { getListItemResponseMockWithoutAutoGeneratedValues } from '@kbn/lists-plugin/common/schemas/response/list_item_schema.mock'; import { getCreateMinimalListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_list_item_schema.mock'; import { getCreateMinimalListSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_list_schema.mock'; -import { - createListsIndex, - deleteListsIndex, - removeListItemServerGeneratedProperties, -} from '../../../utils'; +import { createListsIndex, deleteListsIndex } from '../../../utils'; import { FtrProviderContext } from '../../../../../ftr_provider_context'; export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const log = getService('log'); - const config = getService('config'); - const ELASTICSEARCH_USERNAME = config.get('servers.kibana.username'); describe('@ess @serverless find_list_items', () => { describe('find list items', () => { @@ -44,9 +37,9 @@ export default ({ getService }: FtrProviderContext): void => { .send() .expect(400); - expect(body).to.eql({ + expect(body).toEqual({ error: 'Bad Request', - message: '[request query]: Invalid value "undefined" supplied to "list_id"', + message: '[request query]: list_id: Required', statusCode: 400, }); }); @@ -58,8 +51,8 @@ export default ({ getService }: FtrProviderContext): void => { .send() .expect(404); - expect(body).to.eql({ - message: 'list id: "some-list-item-id" does not exist', + expect(body).toEqual({ + message: expect.any(String), status_code: 404, }); }); @@ -77,7 +70,7 @@ export default ({ getService }: FtrProviderContext): void => { .send() .expect(200); - expect(body).to.eql({ + expect(body).toEqual({ cursor: 'WzBd', data: [], page: 1, @@ -87,17 +80,12 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should return a single list item when a single list item is loaded from a find with defaults added', async () => { - await supertest - .post(LIST_URL) - .set('kbn-xsrf', 'true') - .send(getCreateMinimalListSchemaMock()) - .expect(200); + const listMock = getCreateMinimalListSchemaMock(); + const listItemMock = getCreateMinimalListItemSchemaMock(); - await supertest - .post(LIST_ITEM_URL) - .set('kbn-xsrf', 'true') - .send(getCreateMinimalListItemSchemaMock()) - .expect(200); + await supertest.post(LIST_URL).set('kbn-xsrf', 'true').send(listMock).expect(200); + + await supertest.post(LIST_ITEM_URL).set('kbn-xsrf', 'true').send(listItemMock).expect(200); const { body } = await supertest .get(`${LIST_ITEM_URL}/_find?list_id=${LIST_ID}`) @@ -105,11 +93,14 @@ export default ({ getService }: FtrProviderContext): void => { .send() .expect(200); - body.data = [removeListItemServerGeneratedProperties(body.data[0])]; - // cursor is a constant changing value so we have to delete it as well. - delete body.cursor; - expect(body).to.eql({ - data: [getListItemResponseMockWithoutAutoGeneratedValues(ELASTICSEARCH_USERNAME)], + expect(body).toMatchObject({ + data: [ + expect.objectContaining({ + list_id: listItemMock.list_id, + value: listItemMock.value, + type: listMock.type, + }), + ], page: 1, per_page: 20, total: 1, diff --git a/x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/apps/endpoint/index.ts b/x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/apps/endpoint/index.ts index f4438be799e25..fffd4b65e1907 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/apps/endpoint/index.ts @@ -16,7 +16,8 @@ import { export default function (providerContext: FtrProviderContext) { const { loadTestFile, getService, getPageObjects } = providerContext; - describe('endpoint', function () { + // Flaky: https://github.com/elastic/kibana/issues/186089 + describe('@skipInServerless endpoint', function () { const ingestManager = getService('ingestManager'); const log = getService('log'); const endpointTestResources = getService('endpointTestResources'); diff --git a/x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/apps/integrations/index.ts b/x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/apps/integrations/index.ts index 7035c07f5305f..5f62b8c0f97cb 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/apps/integrations/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/apps/integrations/index.ts @@ -16,7 +16,8 @@ import { export default function (providerContext: FtrProviderContext) { const { loadTestFile, getService, getPageObjects } = providerContext; - describe('endpoint', function () { + // Flaky: https://github.com/elastic/kibana/issues/186086 + describe('@skipInServerless endpoint', function () { const ingestManager = getService('ingestManager'); const log = getService('log'); const endpointTestResources = getService('endpointTestResources'); diff --git a/x-pack/test/security_solution_api_integration/tsconfig.json b/x-pack/test/security_solution_api_integration/tsconfig.json index 269822e345de8..c9bcdf8ccde2b 100644 --- a/x-pack/test/security_solution_api_integration/tsconfig.json +++ b/x-pack/test/security_solution_api_integration/tsconfig.json @@ -47,5 +47,6 @@ "@kbn/timelines-plugin", "@kbn/ftr-common-functional-ui-services", "@kbn/test-subj-selector", + "@kbn/dev-cli-runner", ] } diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_details/backfill_group.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_details/backfill_group.cy.ts index 1b2cbd9add957..7413b8a8f02c7 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_details/backfill_group.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/rule_details/backfill_group.cy.ts @@ -34,7 +34,7 @@ import { describe( 'Backfill groups', { - tags: ['@ess', '@serverless'], + tags: ['@ess', '@serverless', '@skipInServerlessMKI'], env: { ftrConfig: { kbnServerArgs: [ diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/entity_analytics/new_risk_score.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/entity_analytics/new_risk_score.cy.ts index 894248f9ff716..da59afaec941c 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/entity_analytics/new_risk_score.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/dashboards/entity_analytics/new_risk_score.cy.ts @@ -103,6 +103,7 @@ describe('Entity Analytics Dashboard', { tags: ['@ess', '@serverless'] }, () => describe('With host risk data', () => { before(() => { + cy.task('esArchiverUnload', { archiveName: 'risk_scores_new' }); cy.task('esArchiverLoad', { archiveName: 'risk_scores_new' }); }); @@ -145,6 +146,7 @@ describe('Entity Analytics Dashboard', { tags: ['@ess', '@serverless'] }, () => describe('With alerts data', () => { before(() => { + deleteAlertsAndRules(); createRule(getNewRule()); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/enrichments.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/enrichments.cy.ts index aff9307111beb..813560274fcee 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/enrichments.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/entity_analytics/enrichments.cy.ts @@ -36,7 +36,7 @@ const ORIGINAL_HOST_RISK_LEVEL = 'Original host risk level'; describe( 'Enrichment', { - tags: ['@ess', '@serverless'], + tags: ['@ess'], env: { ftrConfig: { kbnServerArgs: [ 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 fae26fec77fa2..b8d222471c87e 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 @@ -15,6 +15,7 @@ import { LOCAL_QUERY_BAR_SELECTOR, RISK_SCORE_ERROR_PANEL, RISK_SCORE_STATUS, + LOCAL_QUERY_BAR_SEARCH_INPUT_SELECTOR, } from '../../screens/entity_analytics_management'; import { deleteRiskScore, installRiskScoreModule } from '../../tasks/api_calls/risk_scores'; @@ -31,7 +32,7 @@ import { interceptRiskInitError, } from '../../tasks/api_calls/risk_engine'; import { updateDateRangeInLocalDatePickers } from '../../tasks/date_picker'; -import { fillLocalSearchBar, submitLocalSearch } from '../../tasks/search_bar'; +import { submitLocalSearch } from '../../tasks/search_bar'; import { riskEngineStatusChange, upgradeRiskEngine, @@ -63,8 +64,7 @@ describe( cy.get(PAGE_TITLE).should('have.text', 'Entity Risk Score'); }); - // FLAKY: https://github.com/elastic/kibana/issues/184133 - describe.skip('Risk preview', () => { + describe('Risk preview', () => { it('risk scores reacts on change in datepicker', () => { const START_DATE = 'Jan 18, 2019 @ 20:33:29.186'; const END_DATE = 'Jan 19, 2019 @ 20:33:29.186'; @@ -81,8 +81,7 @@ describe( it('risk scores reacts on change in search bar query', () => { cy.get(HOST_RISK_PREVIEW_TABLE_ROWS).should('have.length', 5); cy.get(USER_RISK_PREVIEW_TABLE_ROWS).should('have.length', 5); - - fillLocalSearchBar('host.name: "test-host1"'); + cy.get(LOCAL_QUERY_BAR_SEARCH_INPUT_SELECTOR).type('host.name: "test-host1"'); submitLocalSearch(LOCAL_QUERY_BAR_SELECTOR); cy.get(HOST_RISK_PREVIEW_TABLE_ROWS).should('have.length', 1); 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 c7b54e26db890..2d0d80af9c242 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 @@ -41,6 +41,7 @@ import { DOCUMENT_DETAILS_FLYOUT_JSON_TAB, DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB, DOCUMENT_DETAILS_FLYOUT_TABLE_TAB, + DOCUMENT_DETAILS_FLYOUT_FOOTER_ISOLATE_HOST, } from '../../../../screens/expandable_flyout/alert_details_right_panel'; import { closeFlyout, @@ -200,10 +201,10 @@ describe('Alert details expandable flyout right panel', { tags: ['@ess', '@serve // cy.log('should isolate host'); - // TODO figure out why isolate host isn't showing up in the dropdown + // TODO this will change when respond is improved // https://github.com/elastic/security-team/issues/6302 - // openTakeActionButton(); - // cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ISOLATE_HOST).should('be.visible'); + openTakeActionButton(); + cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_ISOLATE_HOST).should('be.disabled'); cy.log('should respond'); @@ -214,6 +215,7 @@ describe('Alert details expandable flyout right panel', { tags: ['@ess', '@serve cy.log('should investigate in timeline'); + openTakeActionButton(); selectTakeActionItem(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE); cy.get(DOCUMENT_DETAILS_FLYOUT_FOOTER_INVESTIGATE_IN_TIMELINE_SECTION) .first() diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/table_row_actions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/table_row_actions.cy.ts new file mode 100644 index 0000000000000..3166f2e7f6b89 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/table_row_actions.cy.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getNewRule } from '../../../objects/rule'; +import { deleteAlertsAndRules } from '../../../tasks/api_calls/common'; +import { createRule } from '../../../tasks/api_calls/rules'; +import { login } from '../../../tasks/login'; +import { visitWithTimeRange } from '../../../tasks/navigation'; +import { openTimelineUsingToggle } from '../../../tasks/security_main'; +import { ALERTS_URL } from '../../../urls/navigation'; +import { + createNewTimeline, + executeTimelineKQL, + executeTimelineSearch, + openTimelineEventContextMenu, +} from '../../../tasks/timeline'; +import { MARK_ALERT_ACKNOWLEDGED_BTN } from '../../../screens/alerts'; +import { GET_TIMELINE_GRID_CELL } from '../../../screens/timeline'; + +describe( + 'Timeline table Row Actions', + { + tags: ['@ess', '@serverless', '@skipInServerlessMKI'], + }, + () => { + beforeEach(() => { + deleteAlertsAndRules(); + createRule(getNewRule()); + login(); + visitWithTimeRange(ALERTS_URL); + openTimelineUsingToggle(); + createNewTimeline(); + executeTimelineSearch('*'); + }); + + it('should refresh the table when alert status is changed', () => { + executeTimelineKQL('kibana.alert.workflow_status:open'); + cy.get(GET_TIMELINE_GRID_CELL('@timestamp')).should('have.length', 1); + openTimelineEventContextMenu(); + cy.get(MARK_ALERT_ACKNOWLEDGED_BTN).click(); + cy.get(GET_TIMELINE_GRID_CELL('@timestamp')).should('have.length', 0); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/unified_components/query_tab.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/unified_components/query_tab.cy.ts index fcde7a791782c..0edb0b6e82e17 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/unified_components/query_tab.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/unified_components/query_tab.cy.ts @@ -5,6 +5,11 @@ * 2.0. */ +import { + INSPECT_MODAL, + INSPECT_MODAL_REQUEST_TAB, + INSPECT_MODAL_RESPONSE_TAB, +} from '../../../../screens/inspect'; import { closeTimelineFlyout, openEventDetailsFlyout, @@ -17,22 +22,21 @@ import { TIMELINE_DETAILS_FLYOUT, USER_DETAILS_FLYOUT, } from '../../../../screens/unified_timeline'; -import { - ROW_ADD_NOTES_BUTTON, - ADD_NOTE_CONTAINER, - RESOLVER_GRAPH_CONTAINER, -} from '../../../../screens/timeline'; -import { OPEN_ANALYZER_BTN } from '../../../../screens/alerts'; import { GET_DISCOVER_DATA_GRID_CELL_HEADER } from '../../../../screens/discover'; import { addFieldToTable, removeFieldFromTable } from '../../../../tasks/discover'; import { login } from '../../../../tasks/login'; import { visitWithTimeRange } from '../../../../tasks/navigation'; import { openTimelineUsingToggle } from '../../../../tasks/security_main'; -import { createNewTimeline, executeTimelineSearch } from '../../../../tasks/timeline'; +import { + createNewTimeline, + executeTimelineSearch, + openTimelineInspectButton, +} from '../../../../tasks/timeline'; import { ALERTS_URL } from '../../../../urls/navigation'; +import { openTab } from '../../../../tasks/inspect'; +import { CODE_BLOCK } from '../../../../screens/common'; -// FLAKY: https://github.com/elastic/kibana/issues/181882 -describe.skip( +describe( 'Unsaved Timeline query tab', { tags: ['@ess', '@serverless', '@skipInServerlessMKI'], @@ -55,6 +59,32 @@ describe.skip( executeTimelineSearch('*'); }); + it('should be able inspect without any issues', () => { + openTimelineInspectButton(); + cy.get(INSPECT_MODAL).should('be.visible'); + openTab(INSPECT_MODAL_REQUEST_TAB); + cy.get(INSPECT_MODAL_REQUEST_TAB).should('have.attr', 'aria-selected', 'true'); + cy.get(INSPECT_MODAL).within(() => { + cy.get(CODE_BLOCK) + .should('be.visible') + .then(($codeEditor) => { + const { height } = $codeEditor[0].getBoundingClientRect(); + expect(height).to.be.gt(100); + }); + }); + + openTab(INSPECT_MODAL_RESPONSE_TAB); + cy.get(INSPECT_MODAL_RESPONSE_TAB).should('have.attr', 'aria-selected', 'true'); + cy.get(INSPECT_MODAL).within(() => { + cy.get(CODE_BLOCK) + .should('be.visible') + .then(($codeEditor) => { + const { height } = $codeEditor[0].getBoundingClientRect(); + expect(height).to.be.gt(100); + }); + }); + }); + it('should be able to add/remove columns correctly', () => { cy.get(GET_UNIFIED_DATA_GRID_CELL_HEADER('agent.type')).should('not.exist'); addFieldToTable('agent.type'); @@ -63,19 +93,8 @@ describe.skip( cy.get(GET_DISCOVER_DATA_GRID_CELL_HEADER('agent.type')).should('not.exist'); }); - it('should render the add note button and display the markdown editor', () => { - cy.get(ROW_ADD_NOTES_BUTTON).should('be.visible').realClick(); - cy.get(ADD_NOTE_CONTAINER).should('be.visible'); - }); - - it('should render the analyze event button and display the process analyzer visualization', () => { - cy.get(OPEN_ANALYZER_BTN).should('be.visible').realClick(); - cy.get(RESOLVER_GRAPH_CONTAINER).should('be.visible'); - }); - - // these tests are skipped until we implement the expandable flyout in the unified table for timeline context('flyout', () => { - it.skip('should be able to open/close details details/host/user flyout', () => { + it('should be able to open/close details details/host/user flyout', () => { cy.log('Event Details Flyout'); openEventDetailsFlyout(0); cy.get(TIMELINE_DETAILS_FLYOUT).should('be.visible'); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/unified_components/table_row_actions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/unified_components/table_row_actions.cy.ts new file mode 100644 index 0000000000000..39fda4d67f244 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/unified_components/table_row_actions.cy.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { getNewRule } from '../../../../objects/rule'; +import { createRule } from '../../../../tasks/api_calls/rules'; +import { MARK_ALERT_ACKNOWLEDGED_BTN } from '../../../../screens/alerts'; +import { GET_UNIFIED_DATA_GRID_CELL } from '../../../../screens/unified_timeline'; +import { login } from '../../../../tasks/login'; +import { visitWithTimeRange } from '../../../../tasks/navigation'; +import { openTimelineUsingToggle } from '../../../../tasks/security_main'; +import { + createNewTimeline, + executeTimelineKQL, + executeTimelineSearch, + openTimelineEventContextMenu, +} from '../../../../tasks/timeline'; +import { ALERTS_URL } from '../../../../urls/navigation'; + +describe( + 'Unified Timeline table Row Actions', + { + tags: ['@ess', '@serverless', '@skipInServerlessMKI'], + env: { + ftrConfig: { + kbnServerArgs: [ + `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'unifiedComponentsInTimelineEnabled', + ])}`, + ], + }, + }, + }, + () => { + beforeEach(() => { + deleteAlertsAndRules(); + createRule(getNewRule()); + login(); + visitWithTimeRange(ALERTS_URL); + openTimelineUsingToggle(); + createNewTimeline(); + executeTimelineSearch('*'); + }); + + it('should refresh the table when alert status is changed', () => { + executeTimelineKQL('kibana.alert.workflow_status:open'); + cy.get(GET_UNIFIED_DATA_GRID_CELL('@timestamp', 0)).should('be.visible'); + openTimelineEventContextMenu(); + cy.get(MARK_ALERT_ACKNOWLEDGED_BTN).click(); + cy.get(GET_UNIFIED_DATA_GRID_CELL('@timestamp', 0)).should('not.exist'); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/common.ts b/x-pack/test/security_solution_cypress/cypress/screens/common.ts index b121badc9e20d..d59da7fabcbd6 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/common.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/common.ts @@ -10,3 +10,5 @@ export const TOOLTIP = '[role="tooltip"]'; export const BASIC_TABLE_LOADING = '.euiBasicTable.euiBasicTable-loading'; export const COMBO_BOX_OPTION = '.euiComboBoxOptionsList button[role="option"]'; + +export const CODE_BLOCK = '.euiCodeBlock'; diff --git a/x-pack/test/security_solution_cypress/cypress/screens/entity_analytics_management.ts b/x-pack/test/security_solution_cypress/cypress/screens/entity_analytics_management.ts index 481c12fd9232e..e1d62ffb420c8 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/entity_analytics_management.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/entity_analytics_management.ts @@ -23,6 +23,9 @@ export const RISK_PREVIEW_ERROR_BUTTON = '[data-test-subj="risk-preview-error-bu export const LOCAL_QUERY_BAR_SELECTOR = getDataTestSubjectSelector('risk-score-preview-search-bar'); +export const LOCAL_QUERY_BAR_SEARCH_INPUT_SELECTOR = + '[data-test-subj="risk-score-preview-search-bar-input"]'; + export const RISK_SCORE_ERROR_PANEL = '[data-test-subj="risk-score-error-panel"]'; export const RISK_SCORE_UPDATE_CANCEL = '[data-test-subj="risk-score-update-cancel"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/screens/inspect.ts b/x-pack/test/security_solution_cypress/cypress/screens/inspect.ts index 083e9d2dd2517..057d79216caa6 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/inspect.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/inspect.ts @@ -35,9 +35,13 @@ import { EVENTS_HISTOGRAM, EVENTS_TAB } from './users/user_events'; import { HTTP_TAB, HTTP_TABLE } from './network/http'; import { TLS_TAB, TLS_TABLE } from './network/tls'; import { RISK_SCORE_TAB, RISK_SCORE_TAB_CONTENT } from './users/user_risk_score'; +import { getDataTestSubjectSelector } from '../helpers/common'; export const INSPECT_BUTTON_ICON = '[data-test-subj="inspect-icon-button"]'; export const INSPECT_MODAL = '[data-test-subj="modal-inspect-euiModal"]'; +export const INSPECT_MODAL_REQUEST_TAB = getDataTestSubjectSelector('modal-inspect-request-tab'); +export const INSPECT_MODAL_RESPONSE_TAB = getDataTestSubjectSelector('modal-inspect-response-tab'); +export const INSPECT_MODAL_STATS_TAB = getDataTestSubjectSelector('modal-inspect-statistics-tab'); export const INSPECT_MODAL_INDEX_PATTERN = '[data-test-subj="index-pattern-description"]'; export const EMBEDDABLE_PANEL_TOGGLE_ICON = '[data-test-subj="embeddablePanelToggleMenuIcon"]'; export const EMBEDDABLE_PANEL_INSPECT = '[data-test-subj="embeddablePanelAction-inspect"]'; diff --git a/x-pack/test/security_solution_cypress/cypress/screens/unified_timeline.ts b/x-pack/test/security_solution_cypress/cypress/screens/unified_timeline.ts index 36aeb7d616128..afed0a9244542 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/unified_timeline.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/unified_timeline.ts @@ -13,7 +13,7 @@ export const HOST_DETAILS_LINK = getDataTestSubjectSelector('host-details-button export const USER_DETAILS_LINK = getDataTestSubjectSelector('users-link-anchor'); -export const TIMELINE_DETAILS_FLYOUT = getDataTestSubjectSelector('timeline:details-panel:flyout'); +export const TIMELINE_DETAILS_FLYOUT = getDataTestSubjectSelector('securitySolutionFlyoutBody'); export const HOST_DETAILS_FLYOUT = getDataTestSubjectSelector('host-panel-header'); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/entity_analytics.ts b/x-pack/test/security_solution_cypress/cypress/tasks/entity_analytics.ts index bb02c196ac92e..5d84c28100fee 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/entity_analytics.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/entity_analytics.ts @@ -26,13 +26,13 @@ import { } from '../screens/entity_analytics_management'; import { visitWithTimeRange } from './navigation'; import { GET_DATE_PICKER_APPLY_BUTTON, GLOBAL_FILTERS_CONTAINER } from '../screens/date_picker'; -import { LOADING_SPINNER } from '../screens/loading'; +import { REFRESH_BUTTON } from '../screens/security_header'; export const updateDashboardTimeRange = () => { // eslint-disable-next-line cypress/no-force cy.get(GET_DATE_PICKER_APPLY_BUTTON(GLOBAL_FILTERS_CONTAINER)).click({ force: true }); // Force to fix global timerange flakiness - cy.get(LOADING_SPINNER).should('exist'); - cy.get(LOADING_SPINNER).should('not.exist'); + cy.get(REFRESH_BUTTON).click(); + cy.get(REFRESH_BUTTON).should('not.have.attr', 'aria-label', 'Needs updating'); }; export const waitForAnomaliesToBeLoaded = () => { diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/inspect.ts b/x-pack/test/security_solution_cypress/cypress/tasks/inspect.ts index c100de3112606..5011410d8f480 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/inspect.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/inspect.ts @@ -57,6 +57,5 @@ export const openLensVisualizationsInspectModal = ( }; export const openTab = (tab: string) => { - cy.get(tab).invoke('show'); - cy.get(tab).click({ force: true }); + cy.get(tab).click(); }; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/timeline.ts b/x-pack/test/security_solution_cypress/cypress/tasks/timeline.ts index 75c78c24e4dc6..e2bf51be199c9 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/timeline.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/timeline.ts @@ -89,12 +89,14 @@ import { BOTTOM_BAR_TIMELINE_PLUS_ICON, BOTTOM_BAR_CREATE_NEW_TIMELINE, BOTTOM_BAR_CREATE_NEW_TIMELINE_TEMPLATE, + TIMELINE_FLYOUT, } from '../screens/timeline'; import { REFRESH_BUTTON, TIMELINE, TIMELINES_TAB_TEMPLATE } from '../screens/timelines'; import { drag, drop, waitForTabToBeLoaded } from './common'; import { closeFieldsBrowser, filterFieldsBrowser } from './fields_browser'; +import { TIMELINE_CONTEXT_MENU_BTN } from '../screens/alerts'; const hostExistsQuery = 'host.name: *'; @@ -505,3 +507,23 @@ export const selectKqlSearchMode = () => { cy.get(TIMELINE_SEARCH_OR_FILTER).click(); cy.get(TIMELINE_KQLMODE_SEARCH).click(); }; + +export const openTimelineEventContextMenu = (rowIndex: number = 0) => { + cy.get(TIMELINE_FLYOUT).within(() => { + const togglePopover = () => { + cy.get(TIMELINE_CONTEXT_MENU_BTN).eq(rowIndex).should('be.visible'); + cy.get(TIMELINE_CONTEXT_MENU_BTN).eq(rowIndex).click(); + cy.get(TIMELINE_CONTEXT_MENU_BTN) + .first() + .should('be.visible') + .then(($btnEl) => { + if ($btnEl.attr('data-popover-open') !== 'true') { + cy.log(`${TIMELINE_CONTEXT_MENU_BTN} was flaky, attempting to re-open popover`); + togglePopover(); + } + }); + }; + + togglePopover(); + }); +}; diff --git a/x-pack/test/spaces_api_integration/common/suites/create.ts b/x-pack/test/spaces_api_integration/common/suites/create.ts index c7aab659ce960..fc2bd1b841ccc 100644 --- a/x-pack/test/spaces_api_integration/common/suites/create.ts +++ b/x-pack/test/spaces_api_integration/common/suites/create.ts @@ -71,7 +71,7 @@ export function createTestSuiteFactory(esArchiver: any, supertest: SuperTest; diff --git a/x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget/kibana.jsonc b/x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget/kibana.jsonc new file mode 100644 index 0000000000000..f030d59f9cde3 --- /dev/null +++ b/x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget/kibana.jsonc @@ -0,0 +1,13 @@ +{ + "type": "plugin", + "id": "@kbn/sample-task-plugin-mget", + "owner": "@elastic/response-ops", + "plugin": { + "id": "sampleTaskPluginMget", + "server": true, + "browser": false, + "requiredPlugins": [ + "taskManager" + ] + } +} diff --git a/x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget/package.json b/x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget/package.json new file mode 100644 index 0000000000000..201d6a964ea1f --- /dev/null +++ b/x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget/package.json @@ -0,0 +1,14 @@ +{ + "name": "@kbn/sample-task-plugin-mget", + "version": "1.0.0", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "main": "target/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget", + "scripts": { + "kbn": "node ../../../../../scripts/kbn.js", + "build": "rm -rf './target' && ../../../../../node_modules/.bin/tsc" + }, + "license": "Elastic License 2.0" +} \ No newline at end of file diff --git a/x-pack/plugins/lists/common/api/values/import_list_item/import_list_item_route.ts b/x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget/server/index.ts similarity index 50% rename from x-pack/plugins/lists/common/api/values/import_list_item/import_list_item_route.ts rename to x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget/server/index.ts index 3ea57eda532fc..d1354da22390b 100644 --- a/x-pack/plugins/lists/common/api/values/import_list_item/import_list_item_route.ts +++ b/x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget/server/index.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { importListItemQuerySchema, listSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { PluginInitializerContext } from '@kbn/core/server'; +import { SampleTaskManagerFixturePlugin } from './plugin'; -export { - importListItemQuerySchema as importListItemRequestQuery, - listSchema as importListItemResponse, +export const plugin = async (initContext: PluginInitializerContext) => { + return new SampleTaskManagerFixturePlugin(initContext); }; diff --git a/x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget/server/init_routes.ts b/x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget/server/init_routes.ts new file mode 100644 index 0000000000000..3273fe855ad31 --- /dev/null +++ b/x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget/server/init_routes.ts @@ -0,0 +1,404 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; +import { + RequestHandlerContext, + KibanaRequest, + KibanaResponseFactory, + IKibanaResponse, + IRouter, + IScopedClusterClient, + Logger, +} from '@kbn/core/server'; +import { EventEmitter } from 'events'; +import { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; + +const scope = 'testing'; +const taskManagerQuery = { + bool: { + filter: { + bool: { + must: [ + { + term: { + 'task.scope': scope, + }, + }, + ], + }, + }, + }, +}; + +export function initRoutes( + logger: Logger, + router: IRouter, + taskManagerStart: Promise, + taskTestingEvents: EventEmitter +) { + async function ensureIndexIsRefreshed(client: IScopedClusterClient) { + return await client.asInternalUser.indices.refresh({ + index: '.kibana_task_manager', + }); + } + + logger.info('Initializing task manager testing routes'); + router.post( + { + path: `/api/sample_tasks/schedule`, + validate: { + body: schema.object({ + task: schema.object({ + enabled: schema.boolean({ defaultValue: true }), + taskType: schema.string(), + schedule: schema.maybe( + schema.object({ + interval: schema.string(), + }) + ), + interval: schema.maybe(schema.string()), + params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), + state: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), + id: schema.maybe(schema.string()), + timeoutOverride: schema.maybe(schema.string()), + }), + }), + }, + }, + async function ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> { + const taskManager = await taskManagerStart; + const { task: taskFields } = req.body; + const task = { + ...taskFields, + scope: [scope], + }; + + const taskResult = await taskManager.schedule(task, { req }); + + return res.ok({ body: taskResult }); + } + ); + + router.post( + { + path: `/api/sample_tasks/run_soon`, + validate: { + body: schema.object({ + task: schema.object({ + id: schema.string({}), + }), + }), + }, + }, + async function ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> { + const { + task: { id }, + } = req.body; + try { + const taskManager = await taskManagerStart; + return res.ok({ body: await taskManager.runSoon(id) }); + } catch (err) { + return res.ok({ body: { id, error: `${err}` } }); + } + } + ); + + router.post( + { + path: `/api/sample_tasks/bulk_enable`, + validate: { + body: schema.object({ + taskIds: schema.arrayOf(schema.string()), + runSoon: schema.boolean({ defaultValue: true }), + }), + }, + }, + async function ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ) { + const { taskIds, runSoon } = req.body; + try { + const taskManager = await taskManagerStart; + return res.ok({ body: await taskManager.bulkEnable(taskIds, runSoon) }); + } catch (err) { + return res.ok({ body: { taskIds, error: `${err}` } }); + } + } + ); + + router.post( + { + path: `/api/sample_tasks/bulk_disable`, + validate: { + body: schema.object({ + taskIds: schema.arrayOf(schema.string()), + }), + }, + }, + async function ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ) { + const { taskIds } = req.body; + try { + const taskManager = await taskManagerStart; + return res.ok({ body: await taskManager.bulkDisable(taskIds) }); + } catch (err) { + return res.ok({ body: { taskIds, error: `${err}` } }); + } + } + ); + + router.post( + { + path: `/api/sample_tasks/bulk_update_schedules`, + validate: { + body: schema.object({ + taskIds: schema.arrayOf(schema.string()), + schedule: schema.object({ interval: schema.string() }), + }), + }, + }, + async function ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ) { + const { taskIds, schedule } = req.body; + try { + const taskManager = await taskManagerStart; + return res.ok({ body: await taskManager.bulkUpdateSchedules(taskIds, schedule) }); + } catch (err) { + return res.ok({ body: { taskIds, error: `${err}` } }); + } + } + ); + + router.post( + { + path: `/api/sample_tasks/ephemeral_run_now`, + validate: { + body: schema.object({ + task: schema.object({ + taskType: schema.string(), + state: schema.recordOf(schema.string(), schema.any()), + params: schema.recordOf(schema.string(), schema.any()), + }), + }), + }, + }, + async function ( + context: RequestHandlerContext, + req: KibanaRequest< + any, + any, + { + task: { + taskType: string; + params: Record; + state: Record; + }; + }, + any + >, + res: KibanaResponseFactory + ): Promise> { + const { task } = req.body; + try { + const taskManager = await taskManagerStart; + return res.ok({ body: await taskManager.ephemeralRunNow(task) }); + } catch (err) { + return res.ok({ body: { task, error: `${err}` } }); + } + } + ); + + router.post( + { + path: `/api/sample_tasks/ensure_scheduled`, + validate: { + body: schema.object({ + task: schema.object({ + taskType: schema.string(), + params: schema.object({}), + state: schema.maybe(schema.object({})), + id: schema.maybe(schema.string()), + }), + }), + }, + }, + async function ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> { + try { + const { task: taskFields } = req.body; + const task = { + ...taskFields, + scope: [scope], + }; + + const taskManager = await taskManagerStart; + const taskResult = await taskManager.ensureScheduled(task, { req }); + + return res.ok({ body: taskResult }); + } catch (err) { + return res.ok({ body: err }); + } + } + ); + + router.post( + { + path: `/api/sample_tasks/event`, + validate: { + body: schema.object({ + event: schema.string(), + data: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), + }), + }, + }, + async function ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> { + try { + const { event, data } = req.body; + taskTestingEvents.emit(event, data); + return res.ok({ body: event }); + } catch (err) { + return res.ok({ body: err }); + } + } + ); + + router.get( + { + path: `/api/sample_tasks`, + validate: {}, + }, + async function ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> { + try { + const taskManager = await taskManagerStart; + return res.ok({ + body: await taskManager.fetch({ + size: 20, + query: taskManagerQuery, + }), + }); + } catch (err) { + return res.ok({ body: err }); + } + } + ); + + router.get( + { + path: `/api/sample_tasks/task/{taskId}`, + validate: { + params: schema.object({ + taskId: schema.string(), + }), + }, + }, + async function ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> { + try { + await ensureIndexIsRefreshed((await context.core).elasticsearch.client); + const taskManager = await taskManagerStart; + return res.ok({ body: await taskManager.get(req.params.taskId) }); + } catch ({ isBoom, output, message }) { + return res.ok({ body: isBoom ? output.payload : { message } }); + } + } + ); + + router.get( + { + path: `/api/ensure_tasks_index_refreshed`, + validate: {}, + }, + async function ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> { + await ensureIndexIsRefreshed((await context.core).elasticsearch.client); + return res.ok({ body: {} }); + } + ); + + router.delete( + { + path: `/api/sample_tasks`, + validate: {}, + }, + async function ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> { + try { + await ensureIndexIsRefreshed((await context.core).elasticsearch.client); + let tasksFound = 0; + const taskManager = await taskManagerStart; + do { + const { docs: tasks } = await taskManager.fetch({ + query: taskManagerQuery, + }); + tasksFound = tasks.length; + await Promise.all(tasks.map((task) => taskManager.remove(task.id))); + } while (tasksFound > 0); + return res.ok({ body: 'OK' }); + } catch ({ isBoom, output, message }) { + return res.ok({ body: isBoom ? output.payload : { message } }); + } + } + ); + + router.get( + { + path: '/api/registered_tasks', + validate: {}, + }, + async ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> => { + try { + const tm = await taskManagerStart; + return res.ok({ + body: tm.getRegisteredTypes(), + }); + } catch (err) { + return res.badRequest({ body: err }); + } + } + ); +} diff --git a/x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget/server/plugin.ts b/x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget/server/plugin.ts new file mode 100644 index 0000000000000..9f6944e4cff3a --- /dev/null +++ b/x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget/server/plugin.ts @@ -0,0 +1,409 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { random } from 'lodash'; +import { schema } from '@kbn/config-schema'; +import { Plugin, CoreSetup, CoreStart, Logger, PluginInitializerContext } from '@kbn/core/server'; +import { throwRetryableError } from '@kbn/task-manager-plugin/server/task_running'; +import { EventEmitter } from 'events'; +import { firstValueFrom, Subject } from 'rxjs'; +import { + TaskManagerSetupContract, + TaskManagerStartContract, + ConcreteTaskInstance, + EphemeralTask, +} from '@kbn/task-manager-plugin/server'; +import { DEFAULT_MAX_WORKERS } from '@kbn/task-manager-plugin/server/config'; +import { TaskPriority } from '@kbn/task-manager-plugin/server/task'; +import { initRoutes } from './init_routes'; + +// this plugin's dependendencies +export interface SampleTaskManagerFixtureSetupDeps { + taskManager: TaskManagerSetupContract; +} +export interface SampleTaskManagerFixtureStartDeps { + taskManager: TaskManagerStartContract; +} + +export class SampleTaskManagerFixturePlugin + implements + Plugin +{ + taskManagerStart$: Subject = new Subject(); + taskManagerStart: Promise = firstValueFrom(this.taskManagerStart$); + logger: Logger; + + constructor(initContext: PluginInitializerContext) { + this.logger = initContext.logger.get(); + } + + public setup(core: CoreSetup, { taskManager }: SampleTaskManagerFixtureSetupDeps) { + const taskTestingEvents = new EventEmitter(); + taskTestingEvents.setMaxListeners(DEFAULT_MAX_WORKERS * 2); + + const tmStart = this.taskManagerStart; + + const defaultSampleTaskConfig = { + timeout: '1m', + // This task allows tests to specify its behavior (whether it reschedules itself, whether it errors, etc) + // taskInstance.params has the following optional fields: + // nextRunMilliseconds: number - If specified, the run method will return a runAt that is now + nextRunMilliseconds + // failWith: string - If specified, the task will throw an error with the specified message + // failOn: number - If specified, the task will only throw the `failWith` error when `count` equals to the failOn value + // waitForParams : boolean - should the task stall ands wait to receive params asynchronously before using the default params + // waitForEvent : string - if provided, the task will stall (after completing the run) and wait for an asyn event before completing + createTaskRunner: ({ taskInstance }: { taskInstance: ConcreteTaskInstance }) => ({ + async run() { + const { params, state, id } = taskInstance; + const prevState = state || { count: 0 }; + + const count = (prevState.count || 0) + 1; + + const runParams = { + ...params, + // if this task requires custom params provided async - wait for them + ...(params.waitForParams ? await once(taskTestingEvents, id) : {}), + }; + + if (runParams.failWith) { + if (!runParams.failOn || (runParams.failOn && count === runParams.failOn)) { + throw new Error(runParams.failWith); + } + } + + const [{ elasticsearch }] = await core.getStartServices(); + await elasticsearch.client.asInternalUser.index({ + index: '.kibana_task_manager_test_result', + body: { + type: 'task', + taskId: taskInstance.id, + params: JSON.stringify(runParams), + state: JSON.stringify(state), + ranAt: new Date(), + }, + refresh: true, + }); + + // Stall task run until a certain event is triggered + if (runParams.waitForEvent) { + await once(taskTestingEvents, runParams.waitForEvent); + } + + return { + state: { count }, + runAt: millisecondsFromNow(runParams.nextRunMilliseconds), + }; + }, + }), + }; + + taskManager.registerTaskDefinitions({ + sampleTask: { + ...defaultSampleTaskConfig, + title: 'Sample Task', + description: 'A sample task for testing the task_manager.', + stateSchemaByVersion: { + 1: { + up: (state: Record) => ({ count: state.count }), + schema: schema.object({ + count: schema.maybe(schema.number()), + }), + }, + }, + }, + singleAttemptSampleTask: { + ...defaultSampleTaskConfig, + title: 'Failing Sample Task', + description: + 'A sample task for testing the task_manager that fails on the first attempt to run.', + // fail after the first failed run + maxAttempts: 1, + stateSchemaByVersion: { + 1: { + up: (state: Record) => ({ count: state.count }), + schema: schema.object({ + count: schema.maybe(schema.number()), + }), + }, + }, + }, + sampleTaskWithSingleConcurrency: { + ...defaultSampleTaskConfig, + title: 'Sample Task With Single Concurrency', + maxConcurrency: 1, + timeout: '60s', + description: 'A sample task that can only have one concurrent instance.', + stateSchemaByVersion: { + 1: { + up: (state: Record) => ({ count: state.count }), + schema: schema.object({ + count: schema.maybe(schema.number()), + }), + }, + }, + }, + sampleTaskWithLimitedConcurrency: { + ...defaultSampleTaskConfig, + title: 'Sample Task With Max Concurrency of 2', + maxConcurrency: 2, + timeout: '60s', + description: 'A sample task that can only have two concurrent instance.', + stateSchemaByVersion: { + 1: { + up: (state: Record) => ({ count: state.count }), + schema: schema.object({ + count: schema.maybe(schema.number()), + }), + }, + }, + }, + sampleRecurringTaskTimingOut: { + title: 'Sample Recurring Task that Times Out', + description: 'A sample task that times out each run.', + maxAttempts: 3, + timeout: '1s', + createTaskRunner: () => ({ + async run() { + return await new Promise((resolve) => {}); + }, + }), + }, + sampleAdHocTaskTimingOut: { + title: 'Sample Ad-Hoc Task that Times Out', + description: 'A sample task that times out.', + maxAttempts: 3, + timeout: '1s', + createTaskRunner: ({ taskInstance }: { taskInstance: ConcreteTaskInstance }) => { + let isCancelled: boolean = false; + return { + async run() { + // wait for 15 seconds + await new Promise((r) => setTimeout(r, 15000)); + + if (!isCancelled) { + const [{ elasticsearch }] = await core.getStartServices(); + await elasticsearch.client.asInternalUser.index({ + index: '.kibana_task_manager_test_result', + body: { + type: 'task', + taskType: 'sampleAdHocTaskTimingOut', + taskId: taskInstance.id, + }, + refresh: true, + }); + } + }, + async cancel() { + isCancelled = true; + }, + }; + }, + }, + sampleRecurringTaskWhichHangs: { + title: 'Sample Recurring Task that Hangs for a minute', + description: 'A sample task that Hangs for a minute on each run.', + maxAttempts: 3, + timeout: '60s', + createTaskRunner: () => ({ + async run() { + return await new Promise((resolve) => {}); + }, + }), + }, + sampleOneTimeTaskThrowingError: { + title: 'Sample One-Time Task that throws an error', + description: 'A sample task that throws an error each run.', + maxAttempts: 3, + createTaskRunner: () => ({ + async run() { + throwRetryableError(new Error('Error'), new Date(Date.now() + random(2, 5) * 1000)); + }, + }), + }, + taskToDisable: { + title: 'Task used for testing it being disabled', + description: '', + maxAttempts: 1, + paramsSchema: schema.object({}), + createTaskRunner: () => ({ + async run() {}, + }), + }, + lowPriorityTask: { + title: 'Task used for testing priority claiming', + priority: TaskPriority.Low, + createTaskRunner: ({ taskInstance }: { taskInstance: ConcreteTaskInstance }) => ({ + async run() { + const { state, schedule } = taskInstance; + const prevState = state || { count: 0 }; + + const count = (prevState.count || 0) + 1; + + const [{ elasticsearch }] = await core.getStartServices(); + await elasticsearch.client.asInternalUser.index({ + index: '.kibana_task_manager_test_result', + body: { + type: 'task', + taskType: 'lowPriorityTask', + taskId: taskInstance.id, + state: JSON.stringify(state), + ranAt: new Date(), + }, + refresh: true, + }); + + return { + state: { count }, + schedule, + }; + }, + }), + }, + }); + + const taskWithTiming = { + createTaskRunner: ({ taskInstance }: { taskInstance: ConcreteTaskInstance }) => ({ + async run() { + const stopTiming = startTaskTimer(); + + const { + params: { delay = 0 }, + state: { timings = [] }, + } = taskInstance; + + if (delay) { + await new Promise((resolve) => { + setTimeout(resolve, delay); + }); + } + + return { + state: { timings: [...timings, stopTiming()] }, + }; + }, + }), + }; + + taskManager.registerTaskDefinitions({ + timedTask: { + title: 'Task With Tracked Timings', + timeout: '60s', + description: 'A task that tracks its execution timing.', + ...taskWithTiming, + }, + timedTaskWithSingleConcurrency: { + title: 'Task With Tracked Timings and Single Concurrency', + maxConcurrency: 1, + timeout: '60s', + description: + 'A task that can only have one concurrent instance and tracks its execution timing.', + ...taskWithTiming, + }, + timedTaskWithLimitedConcurrency: { + title: 'Task With Tracked Timings and Limited Concurrency', + maxConcurrency: 2, + timeout: '60s', + description: + 'A task that can only have two concurrent instance and tracks its execution timing.', + ...taskWithTiming, + }, + taskWhichExecutesOtherTasksEphemerally: { + title: 'Task Which Executes Other Tasks Ephemerally', + description: 'A sample task used to validate how ephemeral tasks are executed.', + maxAttempts: 1, + timeout: '60s', + createTaskRunner: ({ taskInstance }: { taskInstance: ConcreteTaskInstance }) => ({ + async run() { + const { + params: { tasks = [] }, + } = taskInstance; + + const tm = await tmStart; + const executions = await Promise.all( + (tasks as EphemeralTask[]).map(async (task) => { + return tm + .ephemeralRunNow(task) + .then((result) => ({ + result, + })) + .catch((error) => ({ + error, + })); + }) + ); + + return { + state: { executions }, + }; + }, + }), + }, + }); + + taskManager.addMiddleware({ + async beforeSave({ taskInstance, ...opts }) { + const modifiedInstance = { + ...taskInstance, + params: { + originalParams: taskInstance.params, + superFly: 'My middleware param!', + }, + }; + + return { + ...opts, + taskInstance: modifiedInstance, + }; + }, + + async beforeRun({ taskInstance, ...opts }) { + return { + ...opts, + taskInstance: { + ...taskInstance, + params: taskInstance.params.originalParams, + }, + }; + }, + + async beforeMarkRunning(context) { + if (context.taskInstance?.params?.originalParams?.throwOnMarkAsRunning) { + throw new Error(`Sample task ${context.taskInstance.id} threw on MarkAsRunning`); + } + return context; + }, + }); + initRoutes(this.logger, core.http.createRouter(), this.taskManagerStart, taskTestingEvents); + } + + public start(core: CoreStart, { taskManager }: SampleTaskManagerFixtureStartDeps) { + this.taskManagerStart$.next(taskManager); + this.taskManagerStart$.complete(); + } + public stop() {} +} + +function millisecondsFromNow(ms: number) { + if (!ms) { + return; + } + + const dt = new Date(); + dt.setTime(dt.getTime() + ms); + return dt; +} + +const once = function (emitter: EventEmitter, event: string): Promise> { + return new Promise((resolve) => { + emitter.once(event, (data) => resolve(data || {})); + }); +}; + +function startTaskTimer(): () => { start: number; stop: number } { + const start = Date.now(); + return () => ({ start, stop: Date.now() }); +} diff --git a/packages/analytics/shippers/elastic_v3/common/tsconfig.json b/x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget/tsconfig.json similarity index 55% rename from packages/analytics/shippers/elastic_v3/common/tsconfig.json rename to x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget/tsconfig.json index 698191a0c3816..6038cbe20ae83 100644 --- a/packages/analytics/shippers/elastic_v3/common/tsconfig.json +++ b/x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget/tsconfig.json @@ -1,19 +1,18 @@ { "extends": "../../../../../tsconfig.base.json", "compilerOptions": { - "outDir": "target/types", - "types": [ - "jest", - "node" - ] + "outDir": "target/types" }, "include": [ - "**/*.ts" - ], - "kbn_references": [ - "@kbn/analytics-client" + "**/*.ts", + "**/*.tsx", ], "exclude": [ "target/**/*", + ], + "kbn_references": [ + "@kbn/core", + "@kbn/task-manager-plugin", + "@kbn/config-schema", ] } diff --git a/x-pack/test/task_manager_claimer_mget/services.ts b/x-pack/test/task_manager_claimer_mget/services.ts new file mode 100644 index 0000000000000..59a0aff5ec00e --- /dev/null +++ b/x-pack/test/task_manager_claimer_mget/services.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 { services } from '../api_integration/services'; diff --git a/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/background_task_utilization_route.ts b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/background_task_utilization_route.ts new file mode 100644 index 0000000000000..9c9dcbbe15126 --- /dev/null +++ b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/background_task_utilization_route.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import url from 'url'; +import supertest from 'supertest'; +import { MonitoredUtilization } from '@kbn/task-manager-plugin/server/routes/background_task_utilization'; +import { MonitoredStat } from '@kbn/task-manager-plugin/server/monitoring/monitoring_stats_stream'; +import { BackgroundTaskUtilizationStat } from '@kbn/task-manager-plugin/server/monitoring/background_task_utilization_statistics'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const config = getService('config'); + const retry = getService('retry'); + const request = supertest(url.format(config.get('servers.kibana'))); + + const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + + function getUtilizationRequest(isInternal: boolean = true) { + return request + .get(`/${isInternal ? 'internal' : 'api'}/task_manager/_background_task_utilization`) + .set('kbn-xsrf', 'foo'); + } + + function getUtilization(isInternal: boolean = true): Promise { + return getUtilizationRequest(isInternal) + .expect(200) + .then((response) => response.body); + } + + function getBackgroundTaskUtilization(isInternal: boolean = true): Promise { + return retry.try(async () => { + const utilization = await getUtilization(isInternal); + + if (utilization.stats) { + return utilization; + } + + await delay(500); + throw new Error('Stats have not run yet'); + }); + } + + describe('background task utilization', () => { + it('should return the task manager background task utilization for recurring stats', async () => { + const { + value: { + recurring: { ran }, + }, + } = (await getBackgroundTaskUtilization(true)) + .stats as MonitoredStat; + const serviceTime = ran.service_time; + expect(typeof serviceTime.actual).to.eql('number'); + expect(typeof serviceTime.adjusted).to.eql('number'); + expect(typeof serviceTime.task_counter).to.eql('number'); + }); + + it('should return the task manager background task utilization for adhoc stats', async () => { + const { + value: { + adhoc: { created, ran }, + }, + } = (await getBackgroundTaskUtilization(true)) + .stats as MonitoredStat; + const serviceTime = ran.service_time; + expect(typeof created.counter).to.eql('number'); + + expect(typeof serviceTime.actual).to.eql('number'); + expect(typeof serviceTime.adjusted).to.eql('number'); + expect(typeof serviceTime.task_counter).to.eql('number'); + }); + + it('should include load stat', async () => { + const { + value: { load }, + } = (await getBackgroundTaskUtilization(true)) + .stats as MonitoredStat; + expect(typeof load).to.eql('number'); + }); + + it('should return expected fields for internal route', async () => { + const monitoredStat = (await getBackgroundTaskUtilization(true)).stats; + expect(monitoredStat?.timestamp).not.to.be(undefined); + expect(monitoredStat?.value).not.to.be(undefined); + expect(monitoredStat?.value?.adhoc).not.to.be(undefined); + expect(monitoredStat?.value?.recurring).not.to.be(undefined); + expect(monitoredStat?.value?.load).not.to.be(undefined); + }); + + it('should return expected fields for public route', async () => { + const monitoredStat = (await getBackgroundTaskUtilization(false)).stats; + expect(monitoredStat?.timestamp).not.to.be(undefined); + expect(monitoredStat?.value).not.to.be(undefined); + expect(monitoredStat?.value?.adhoc).to.be(undefined); + expect(monitoredStat?.value?.recurring).to.be(undefined); + expect(monitoredStat?.value?.load).not.to.be(undefined); + }); + }); +} diff --git a/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/health_route.ts b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/health_route.ts new file mode 100644 index 0000000000000..066a004df3814 --- /dev/null +++ b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/health_route.ts @@ -0,0 +1,339 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 url from 'url'; +import { keyBy, mapValues } from 'lodash'; +import supertest from 'supertest'; +import { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +interface MonitoringStats { + last_update: string; + status: string; + stats: { + configuration: { + timestamp: string; + value: Record; + }; + workload: { + timestamp: string; + value: { + count: number; + task_types: Record; + schedule: Array<[string, number]>; + overdue: number; + non_recurring: number; + owner_ids: number; + estimated_schedule_density: number[]; + capacity_requirements: { + per_minute: number; + per_hour: number; + per_day: number; + }; + }; + }; + runtime: { + timestamp: string; + value: { + drift: Record; + drift_by_type: Record>; + load: Record; + execution: { + duration: Record>; + persistence: Record; + result_frequency_percent_as_number: Record>; + }; + polling: { + last_successful_poll: string; + last_polling_delay: string; + duration: Record; + claim_duration: Record; + result_frequency_percent_as_number: Record; + }; + }; + }; + capacity_estimation: { + timestamp: string; + value: { + observed: { + observed_kibana_instances: number; + max_throughput_per_minute: number; + max_throughput_per_minute_per_kibana: number; + minutes_to_drain_overdue: number; + avg_required_throughput_per_minute: number; + avg_required_throughput_per_minute_per_kibana: number; + avg_recurring_required_throughput_per_minute: number; + avg_recurring_required_throughput_per_minute_per_kibana: number; + }; + proposed: { + min_required_kibana: number; + avg_recurring_required_throughput_per_minute_per_kibana: number; + avg_required_throughput_per_minute: number; + avg_required_throughput_per_minute_per_kibana: number; + }; + }; + }; + }; +} + +export default function ({ getService }: FtrProviderContext) { + const config = getService('config'); + const retry = getService('retry'); + const request = supertest(url.format(config.get('servers.kibana'))); + + const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + + function getHealthRequest() { + return request.get('/api/task_manager/_health').set('kbn-xsrf', 'foo'); + } + + function getHealth(): Promise { + return getHealthRequest() + .expect(200) + .then((response) => response.body); + } + + function getHealthForSampleTask(): Promise { + return retry.try(async () => { + const health = await getHealth(); + + // only return health stats once they contain sampleTask, if requested + if (health.stats.runtime.value.drift_by_type.sampleTask) { + return health; + } + + // if sampleTask is not in the metrics, wait a bit and retry + await delay(500); + throw new Error('sampleTask has not yet run'); + }); + } + + function scheduleTask(task: Partial): Promise { + return request + .post('/api/sample_tasks/schedule') + .set('kbn-xsrf', 'xxx') + .send({ task }) + .expect(200) + .then((response: { body: ConcreteTaskInstance }) => response.body); + } + + const monitoredAggregatedStatsRefreshRate = 5000; + + describe('health', () => { + it('should return basic configuration of task manager', async () => { + const health = await getHealth(); + expect(health.status).to.eql('OK'); + expect(health.stats.configuration.value).to.eql({ + poll_interval: 3000, + monitored_aggregated_stats_refresh_rate: monitoredAggregatedStatsRefreshRate, + monitored_stats_running_average_window: 50, + monitored_task_execution_thresholds: { + custom: {}, + default: { + error_threshold: 90, + warn_threshold: 80, + }, + }, + request_capacity: 1000, + max_workers: 10, + }); + }); + + it('should return the task manager workload', async () => { + const health = await getHealth(); + const { + status, + stats: { workload }, + } = health; + + expect(status).to.eql('OK'); + + const sumSampleTaskInWorkload = + ( + workload.value.task_types as { + sampleTask?: { count: number }; + } + ).sampleTask?.count ?? 0; + const scheduledWorkload = mapValues( + keyBy(workload.value.schedule as Array<[string, number]>, ([interval, count]) => interval), + ([, count]) => count + ) as unknown as { '37m': number | undefined; '37s': number | undefined }; + + await scheduleTask({ + taskType: 'sampleTask', + schedule: { interval: '37s' }, + }); + + await scheduleTask({ + taskType: 'sampleTask', + schedule: { interval: '37m' }, + }); + + await retry.try(async () => { + // workload is configured to refresh every 5s in FTs + await delay(monitoredAggregatedStatsRefreshRate); + + const workloadAfterScheduling = (await getHealthForSampleTask()).stats.workload.value; + + expect( + (workloadAfterScheduling.task_types as { sampleTask: { count: number } }).sampleTask.count + ).to.eql(sumSampleTaskInWorkload + 2); + + const schedulesWorkloadAfterScheduling = mapValues( + keyBy( + workloadAfterScheduling.schedule as Array<[string, number]>, + ([interval]) => interval + ), + ([, count]) => count + ) as unknown as { + '37m': number; + '37s': number; + }; + expect(schedulesWorkloadAfterScheduling['37s']).to.eql(1 + (scheduledWorkload['37s'] ?? 0)); + expect(schedulesWorkloadAfterScheduling['37m']).to.eql(1 + (scheduledWorkload['37m'] ?? 0)); + }); + }); + + it('should return a breakdown of idleTasks in the task manager workload', async () => { + const { + capacity_estimation: { + value: { observed, proposed }, + }, + } = (await getHealth()).stats; + + expect(typeof observed.observed_kibana_instances).to.eql('number'); + expect(typeof observed.max_throughput_per_minute).to.eql('number'); + expect(typeof observed.max_throughput_per_minute_per_kibana).to.eql('number'); + expect(typeof observed.minutes_to_drain_overdue).to.eql('number'); + expect(typeof observed.avg_required_throughput_per_minute).to.eql('number'); + expect(typeof observed.avg_required_throughput_per_minute_per_kibana).to.eql('number'); + expect(typeof observed.avg_recurring_required_throughput_per_minute).to.eql('number'); + expect(typeof observed.avg_recurring_required_throughput_per_minute_per_kibana).to.eql( + 'number' + ); + + expect(typeof proposed.min_required_kibana).to.eql('number'); + expect(typeof proposed.avg_recurring_required_throughput_per_minute_per_kibana).to.eql( + 'number' + ); + expect(typeof proposed.avg_required_throughput_per_minute_per_kibana).to.eql('number'); + }); + + it('should return an estimation of task manager capacity as an array', async () => { + const { + workload: { value: workload }, + } = (await getHealth()).stats; + + expect(typeof workload.overdue).to.eql('number'); + + expect(typeof workload.non_recurring).to.eql('number'); + expect(typeof workload.owner_ids).to.eql('number'); + + expect(typeof workload.capacity_requirements.per_minute).to.eql('number'); + expect(typeof workload.capacity_requirements.per_hour).to.eql('number'); + expect(typeof workload.capacity_requirements.per_day).to.eql('number'); + + expect(Array.isArray(workload.estimated_schedule_density)).to.eql(true); + }); + + it('should return the task manager runtime stats', async () => { + await scheduleTask({ + taskType: 'sampleTask', + schedule: { interval: '5s' }, + }); + + const { + runtime: { + // eslint-disable-next-line @typescript-eslint/naming-convention + value: { drift, drift_by_type, load, polling, execution }, + }, + } = (await getHealthForSampleTask()).stats; + + expect(isNaN(Date.parse(polling.last_successful_poll as string))).to.eql(false); + expect(isNaN(Date.parse(polling.last_polling_delay as string))).to.eql(false); + expect(typeof polling.result_frequency_percent_as_number.NoTasksClaimed).to.eql('number'); + expect(typeof polling.result_frequency_percent_as_number.RanOutOfCapacity).to.eql('number'); + expect(typeof polling.result_frequency_percent_as_number.PoolFilled).to.eql('number'); + expect(typeof polling.result_frequency_percent_as_number.NoAvailableWorkers).to.eql('number'); + expect(typeof polling.result_frequency_percent_as_number.RunningAtCapacity).to.eql('number'); + expect(typeof polling.result_frequency_percent_as_number.Failed).to.eql('number'); + + expect(typeof polling.duration.p50).to.eql('number'); + expect(typeof polling.duration.p90).to.eql('number'); + expect(typeof polling.duration.p95).to.eql('number'); + expect(typeof polling.duration.p99).to.eql('number'); + + expect(typeof polling.claim_duration.p50).to.eql('number'); + expect(typeof polling.claim_duration.p90).to.eql('number'); + expect(typeof polling.claim_duration.p95).to.eql('number'); + expect(typeof polling.claim_duration.p99).to.eql('number'); + + expect(typeof drift.p50).to.eql('number'); + expect(typeof drift.p90).to.eql('number'); + expect(typeof drift.p95).to.eql('number'); + expect(typeof drift.p99).to.eql('number'); + + expect(typeof drift_by_type.sampleTask.p50).to.eql('number'); + expect(typeof drift_by_type.sampleTask.p90).to.eql('number'); + expect(typeof drift_by_type.sampleTask.p95).to.eql('number'); + expect(typeof drift_by_type.sampleTask.p99).to.eql('number'); + + expect(typeof load.p50).to.eql('number'); + expect(typeof load.p90).to.eql('number'); + expect(typeof load.p95).to.eql('number'); + expect(typeof load.p99).to.eql('number'); + + expect(typeof execution.duration.sampleTask.p50).to.eql('number'); + expect(typeof execution.duration.sampleTask.p90).to.eql('number'); + expect(typeof execution.duration.sampleTask.p95).to.eql('number'); + expect(typeof execution.duration.sampleTask.p99).to.eql('number'); + + expect(typeof execution.persistence.ephemeral).to.eql('number'); + expect(typeof execution.persistence.non_recurring).to.eql('number'); + expect(typeof execution.persistence.recurring).to.eql('number'); + + expect(typeof execution.result_frequency_percent_as_number.sampleTask.Success).to.eql( + 'number' + ); + expect(typeof execution.result_frequency_percent_as_number.sampleTask.RetryScheduled).to.eql( + 'number' + ); + expect(typeof execution.result_frequency_percent_as_number.sampleTask.Failed).to.eql( + 'number' + ); + }); + + it('should exclude disabled tasks', async () => { + const interval = '9s'; + await scheduleTask({ + enabled: false, + taskType: 'taskToDisable', + schedule: { interval }, + }); + + const timestamp = new Date(); + + const health = await retry.try(async () => { + const result = await getHealth(); + expect(new Date(result.stats.runtime.timestamp).getTime()).to.be.greaterThan( + timestamp.getTime() + ); + expect(new Date(result.stats.workload.timestamp).getTime()).to.be.greaterThan( + timestamp.getTime() + ); + return result; + }); + + expect(health.stats.runtime.value.execution.duration.taskToDisable).to.eql(undefined); + expect(health.stats.workload.value.task_types.taskToDisable).to.eql(undefined); + expect(health.stats.workload.value.schedule.find(([val]) => val === interval)).to.eql( + undefined + ); + }); + }); +} diff --git a/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/index.ts b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/index.ts new file mode 100644 index 0000000000000..66ba9a0108afc --- /dev/null +++ b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; 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('task_manager with mget task claimer', function taskManagerSuite() { + loadTestFile(require.resolve('./task_priority')); + loadTestFile(require.resolve('./background_task_utilization_route')); + loadTestFile(require.resolve('./metrics_route')); + loadTestFile(require.resolve('./health_route')); + loadTestFile(require.resolve('./task_management')); + loadTestFile(require.resolve('./task_management_scheduled_at')); + loadTestFile(require.resolve('./task_management_removed_types')); + + loadTestFile(require.resolve('./migrations')); + }); +} diff --git a/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/metrics_route.ts b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/metrics_route.ts new file mode 100644 index 0000000000000..4fad194abf368 --- /dev/null +++ b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/metrics_route.ts @@ -0,0 +1,328 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 url from 'url'; +import supertest from 'supertest'; +import { NodeMetrics } from '@kbn/task-manager-plugin/server/routes/metrics'; +import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const config = getService('config'); + const retry = getService('retry'); + const request = supertest(url.format(config.get('servers.kibana'))); + const es = getService('es'); + + const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + + function getMetricsRequest(reset: boolean = false) { + return request + .get(`/api/task_manager/metrics${reset ? '' : '?reset=false'}`) + .set('kbn-xsrf', 'foo') + .expect(200) + .then((response) => response.body); + } + + function getMetrics( + reset: boolean = false, + callback?: (metrics: NodeMetrics) => boolean + ): Promise { + return retry.try(async () => { + const metrics = await getMetricsRequest(reset); + + if (metrics.metrics) { + if ((callback && callback(metrics)) || !callback) { + return metrics; + } + } + + await delay(500); + throw new Error('Expected metrics not received'); + }); + } + + describe('task manager metrics', () => { + describe('task claim', () => { + it('should increment task claim success/total counters', async () => { + // counters are reset every 30 seconds, so wait until the start of a + // fresh counter cycle to make sure values are incrementing + const initialMetrics = ( + await getMetrics(false, (metrics) => metrics?.metrics?.task_claim?.value.total === 1) + ).metrics; + expect(initialMetrics).not.to.be(null); + expect(initialMetrics?.task_claim).not.to.be(null); + expect(initialMetrics?.task_claim?.value).not.to.be(null); + + let previousTaskClaimSuccess = initialMetrics?.task_claim?.value.total!; + let previousTaskClaimTotal = initialMetrics?.task_claim?.value.success!; + let previousTaskClaimTimestamp: string = initialMetrics?.task_claim?.timestamp!; + + for (let i = 0; i < 5; ++i) { + const metrics = ( + await getMetrics( + false, + (m: NodeMetrics) => m.metrics?.task_claim?.timestamp !== previousTaskClaimTimestamp + ) + ).metrics; + expect(metrics).not.to.be(null); + expect(metrics?.task_claim).not.to.be(null); + expect(metrics?.task_claim?.value).not.to.be(null); + + expect(metrics?.task_claim?.value.success).to.be.greaterThan(previousTaskClaimSuccess); + expect(metrics?.task_claim?.value.total).to.be.greaterThan(previousTaskClaimTotal); + + previousTaskClaimTimestamp = metrics?.task_claim?.timestamp!; + previousTaskClaimSuccess = metrics?.task_claim?.value.success!; + previousTaskClaimTotal = metrics?.task_claim?.value.total!; + + // check that duration histogram exists + expect(metrics?.task_claim?.value.duration).not.to.be(null); + expect(Array.isArray(metrics?.task_claim?.value.duration.counts)).to.be(true); + expect(Array.isArray(metrics?.task_claim?.value.duration.values)).to.be(true); + } + }); + + it('should reset task claim success/total counters at an interval', async () => { + const initialCounterValue = 7; + const initialMetrics = ( + await getMetrics( + false, + (metrics) => metrics?.metrics?.task_claim?.value.total === initialCounterValue + ) + ).metrics; + expect(initialMetrics).not.to.be(null); + expect(initialMetrics?.task_claim).not.to.be(null); + expect(initialMetrics?.task_claim?.value).not.to.be(null); + + // retry until counter value resets + const resetMetrics = ( + await getMetrics(false, (m: NodeMetrics) => m?.metrics?.task_claim?.value.total === 1) + ).metrics; + expect(resetMetrics).not.to.be(null); + expect(resetMetrics?.task_claim).not.to.be(null); + expect(resetMetrics?.task_claim?.value).not.to.be(null); + }); + + it('should increment task run framework_error counter', async () => { + const initialCounterValue = 1; + const initialMetrics = ( + await getMetrics( + false, + (metrics) => metrics?.metrics?.task_claim?.value.total === initialCounterValue + ) + ).metrics; + expect(initialMetrics).not.to.be(null); + expect(initialMetrics?.task_claim).not.to.be(null); + expect(initialMetrics?.task_claim?.value).not.to.be(null); + + let previousTaskClaimTimestamp: string = initialMetrics?.task_claim?.timestamp!; + + for (let i = 0; i < 5; ++i) { + const metrics = ( + await getMetrics( + true, + (m: NodeMetrics) => m.metrics?.task_claim?.timestamp !== previousTaskClaimTimestamp + ) + ).metrics; + expect(metrics).not.to.be(null); + expect(metrics?.task_claim).not.to.be(null); + expect(metrics?.task_claim?.value).not.to.be(null); + + expect(metrics?.task_claim?.value.success).to.equal(1); + expect(metrics?.task_claim?.value.total).to.equal(1); + + previousTaskClaimTimestamp = metrics?.task_claim?.timestamp!; + + // check that duration histogram exists + expect(metrics?.task_claim?.value.duration).not.to.be(null); + expect(Array.isArray(metrics?.task_claim?.value.duration.counts)).to.be(true); + expect(Array.isArray(metrics?.task_claim?.value.duration.values)).to.be(true); + } + }); + }); + + describe('task run', () => { + let ruleId: string | null = null; + before(async () => { + // create a rule that fires actions + const rule = await request + .post(`/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send({ + enabled: true, + name: 'test rule', + tags: [], + rule_type_id: '.es-query', + consumer: 'alerts', + // set schedule long so we can control when it runs + schedule: { interval: '1d' }, + actions: [], + params: { + aggType: 'count', + esQuery: '{\n "query":{\n "match_all" : {}\n }\n}', + excludeHitsFromPreviousRun: false, + groupBy: 'all', + index: ['.kibana-event-log*'], + searchType: 'esQuery', + size: 100, + termSize: 5, + threshold: [0], + thresholdComparator: '>', + timeField: '@timestamp', + timeWindowSize: 5, + timeWindowUnit: 'm', + }, + }) + .expect(200) + .then((response) => response.body); + + ruleId = rule.id; + }); + + after(async () => { + // delete rule + await request.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo').expect(204); + }); + + it('should increment task run success/not_timed_out/total counters', async () => { + const initialMetrics = ( + await getMetrics( + false, + (metrics) => + metrics?.metrics?.task_run?.value.by_type.alerting?.total === 1 && + metrics?.metrics?.task_run?.value.by_type.alerting?.not_timed_out === 1 && + metrics?.metrics?.task_run?.value.by_type.alerting?.success === 1 && + metrics?.metrics?.task_run?.value.by_type.alerting?.user_errors === 0 && + metrics?.metrics?.task_run?.value.by_type.alerting?.framework_errors === 0 + ) + ).metrics; + expect(initialMetrics).not.to.be(null); + expect(initialMetrics?.task_claim).not.to.be(null); + expect(initialMetrics?.task_claim?.value).not.to.be(null); + + for (let i = 0; i < 1; ++i) { + // run the rule and expect counters to increment + await request + .post('/api/sample_tasks/run_soon') + .set('kbn-xsrf', 'xxx') + .send({ task: { id: ruleId } }) + .expect(200); + + const metrics = ( + await getMetrics( + false, + (m) => + m?.metrics?.task_run?.value.by_type.alerting?.total === i + 2 && + m?.metrics?.task_run?.value.by_type.alerting?.not_timed_out === i + 2 && + m?.metrics?.task_run?.value.by_type.alerting?.success === i + 2 && + m?.metrics?.task_run?.value.by_type.alerting?.user_errors === 0 && + m?.metrics?.task_run?.value.by_type.alerting?.framework_errors === 0 + ) + ).metrics; + + // check that delay histogram exists + expect(metrics?.task_run?.value?.overall?.delay).not.to.be(null); + expect(Array.isArray(metrics?.task_run?.value?.overall?.delay.counts)).to.be(true); + expect(Array.isArray(metrics?.task_run?.value?.overall?.delay.values)).to.be(true); + } + + // counter should reset on its own + await getMetrics( + false, + (metrics) => + metrics?.metrics?.task_run?.value.by_type.alerting?.total === 0 && + metrics?.metrics?.task_run?.value.by_type.alerting?.not_timed_out === 0 && + metrics?.metrics?.task_run?.value.by_type.alerting?.success === 0 + ); + }); + + it('should increment task run framework_error counter', async () => { + // modify the rule to get it fire a decryption error + await es.updateByQuery({ + index: ALERTING_CASES_SAVED_OBJECT_INDEX, + body: { + script: { + lang: 'painless', + source: 'ctx._source.alert.params.foo = "bar"', + }, + query: { ids: { values: [`alert:${ruleId}`] } }, + }, + refresh: true, + conflicts: 'proceed', + }); + + // run the rule and expect counters to increment + await request + .post('/api/sample_tasks/run_soon') + .set('kbn-xsrf', 'xxx') + .send({ task: { id: ruleId } }) + .expect(200); + + const metrics = ( + await getMetrics(true, (m) => m?.metrics?.task_run?.value.overall.framework_errors! === 1) + ).metrics; + + const total = metrics?.task_run?.value.overall.total || 0; + const success = metrics?.task_run?.value.overall.success || 0; + + expect(total - success).to.be(1); + }); + + it('should increment task run user_errors counter', async () => { + // modify the rule to get it fire a validation error + await es.updateByQuery({ + index: ALERTING_CASES_SAVED_OBJECT_INDEX, + body: { + script: { + lang: 'painless', + source: 'ctx._source.alert.params.foo = "bar"', + }, + query: { ids: { values: [`alert:${ruleId}`] } }, + }, + refresh: true, + conflicts: 'proceed', + }); + + // update apiKey to fix decryption error + await request + .post(`/api/alerts/alert/${ruleId}/_update_api_key`) + .set('kbn-xsrf', 'xxx') + .expect(204); + + // run the rule and expect counters to increment + await request + .post('/api/sample_tasks/run_soon') + .set('kbn-xsrf', 'xxx') + .send({ task: { id: ruleId } }) + .expect(200); + + const metrics = ( + await getMetrics(true, (m) => m?.metrics?.task_run?.value.overall.user_errors! === 1) + ).metrics; + + const total = metrics?.task_run?.value.overall.total || 0; + const success = metrics?.task_run?.value.overall.success || 0; + + expect(total - success).to.be(1); + }); + }); + + describe('task overdue', () => { + it('histograms should exist', async () => { + const metrics = (await getMetrics(false)).metrics; + expect(metrics).not.to.be(null); + expect(metrics?.task_overdue).not.to.be(null); + expect(metrics?.task_overdue?.value).not.to.be(null); + expect(metrics?.task_overdue?.value.overall).not.to.be(null); + expect(metrics?.task_overdue?.value.overall.overdue_by).not.to.be(null); + expect(Array.isArray(metrics?.task_overdue?.value.overall.overdue_by.counts)).to.be(true); + expect(Array.isArray(metrics?.task_overdue?.value.overall.overdue_by.values)).to.be(true); + }); + }); + }); +} diff --git a/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/migrations.ts b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/migrations.ts new file mode 100644 index 0000000000000..8497f8bdc9678 --- /dev/null +++ b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/migrations.ts @@ -0,0 +1,276 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { TransportResult } from '@elastic/elasticsearch'; +import { + ConcreteTaskInstance, + SerializedConcreteTaskInstance, + TaskInstanceWithDeprecatedFields, + TaskStatus, +} from '@kbn/task-manager-plugin/server/task'; +import { SavedObjectsUtils } from '@kbn/core/server'; +import type { RuleTaskState, WrappedLifecycleRuleState } from '@kbn/alerting-state-types'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +export default function createGetTests({ getService }: FtrProviderContext) { + const es = getService('es'); + const esArchiver = getService('esArchiver'); + const ALERT_ID = '0359d7fcc04da9878ee9aadbda38ba55'; + const ACTION_TASK_PARAMS_ID = '6e96ac5e648f57523879661ea72525b7'; + + describe('migrations', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/task_manager_tasks'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/task_manager_tasks'); + }); + + it('8.0.0 migrates actions tasks with legacy id to saved object ids', async () => { + // NOTE: We hae to use elastic search directly against the ".kibana" index because alerts do not expose the references which we want to test exists + const response = await es.get<{ task: TaskInstanceWithDeprecatedFields }>( + { + index: '.kibana_task_manager', + id: 'task:be7e1250-3322-11eb-94c1-db6995e84f6a', + }, + { + meta: true, + } + ); + expect(response.statusCode).to.eql(200); + expect(response.body._source?.task.params).to.eql( + `{"spaceId":"user1","alertId":"${SavedObjectsUtils.getConvertedObjectId( + 'user1', + 'alert', + ALERT_ID + )}"}` + ); + }); + + it('8.0.0 migrates actions tasks from legacy id to saved object ids', async () => { + const searchResult: TransportResult< + estypes.SearchResponse<{ task: TaskInstanceWithDeprecatedFields }>, + unknown + > = await es.search( + { + index: '.kibana_task_manager', + body: { + query: { + term: { + _id: 'task:be7e1250-3322-11eb-94c1-db6995e8389f', + }, + }, + }, + }, + { meta: true } + ); + expect(searchResult.statusCode).to.equal(200); + expect((searchResult.body.hits.total as estypes.SearchTotalHits).value).to.equal(1); + const hit = searchResult.body.hits.hits[0]; + expect(hit!._source!.task.params!).to.equal( + `{"spaceId":"user1","actionTaskParamsId":"${SavedObjectsUtils.getConvertedObjectId( + 'user1', + 'action_task_params', + ACTION_TASK_PARAMS_ID + )}"}` + ); + }); + + it('8.2.0 migrates alerting tasks that has no schedule.interval', async () => { + const searchResult: TransportResult< + estypes.SearchResponse<{ task: ConcreteTaskInstance }>, + unknown + > = await es.search( + { + index: '.kibana_task_manager', + body: { + query: { + term: { + _id: 'task:d33d7590-8377-11ec-8c11-2dfe94229b95', + }, + }, + }, + }, + { meta: true } + ); + expect(searchResult.statusCode).to.equal(200); + expect((searchResult.body.hits.total as estypes.SearchTotalHits).value).to.equal(1); + const hit = searchResult.body.hits.hits[0]; + expect(hit!._source!.task.attempts).to.be(0); + expect(hit!._source!.task.status).to.be(TaskStatus.Idle); + }); + + it('8.2.0 migrates tasks with unrecognized status to idle if task type is removed', async () => { + const response = await es.get<{ task: ConcreteTaskInstance }>( + { + index: '.kibana_task_manager', + id: 'task:ce7e1250-3322-11eb-94c1-db6995e84f6d', + }, + { + meta: true, + } + ); + expect(response.statusCode).to.eql(200); + expect(response.body._source?.task.taskType).to.eql( + `alerting:0359d7fcc04da9878ee9aadbda38ba55` + ); + expect(response.body._source?.task.status).to.eql(`idle`); + }); + + it('8.2.0 does not migrate tasks with unrecognized status if task type is valid', async () => { + const response = await es.get<{ task: ConcreteTaskInstance }>( + { + index: '.kibana_task_manager', + id: 'task:fe7e1250-3322-11eb-94c1-db6395e84f6e', + }, + { + meta: true, + } + ); + expect(response.statusCode).to.eql(200); + expect(response.body._source?.task.taskType).to.eql(`sampleTaskRemovedType`); + expect(response.body._source?.task.status).to.eql(`unrecognized`); + }); + + it('8.5.0 migrates active tasks to set enabled to true', async () => { + const response = await es.search<{ task: ConcreteTaskInstance }>( + { + index: '.kibana_task_manager', + size: 100, + body: { + query: { + match_all: {}, + }, + }, + }, + { + meta: true, + } + ); + expect(response.statusCode).to.eql(200); + const tasks = response.body.hits.hits; + tasks + .filter( + (task) => + task._source?.task.status !== 'failed' && task._source?.task.status !== 'unrecognized' + ) + .forEach((task) => { + expect(task._source?.task.enabled).to.eql(true); + }); + }); + + it('8.5.0 does not migrates failed and unrecognized', async () => { + const response = await es.search<{ task: ConcreteTaskInstance }>( + { + index: '.kibana_task_manager', + size: 100, + body: { + query: { + match_all: {}, + }, + }, + }, + { + meta: true, + } + ); + expect(response.statusCode).to.eql(200); + const tasks = response.body.hits.hits; + tasks + .filter( + (task) => + task._source?.task.status === 'failed' || task._source?.task.status === 'unrecognized' + ) + .forEach((task) => { + expect(task._source?.task.enabled).to.be(undefined); + }); + }); + + describe('8.8.0', async () => { + it('adds UUIDs to all alerts', async () => { + const response = await es.search<{ task: SerializedConcreteTaskInstance }>( + { + index: '.kibana_task_manager', + size: 100, + body: { query: { match_all: {} } }, + }, + { meta: true } + ); + expect(response.statusCode).to.eql(200); + const tasks = response.body.hits.hits; + tasks.forEach((task) => { + const stateString = task._source?.task.state; + expect(stateString).to.be.ok(); + const state: RuleTaskState = JSON.parse(stateString!); + const uuids = new Set(); + + for (const alert of Object.values(state.alertInstances || {})) { + const uuid = alert?.meta?.uuid || 'uuid-is-missing'; + expect(uuid).to.match(/^.{8}-.{4}-.{4}-.{4}-.{12}$/); + expect(uuids.has(uuid)).to.be(false); + uuids.add(uuid); + } + + for (const alert of Object.values(state.alertRecoveredInstances || {})) { + const uuid = alert?.meta?.uuid || 'uuid-is-missing'; + expect(uuid).to.match(/^.{8}-.{4}-.{4}-.{4}-.{12}$/); + expect(uuids.has(uuid)).to.be(false); + uuids.add(uuid); + } + }); + }); + + it('copies UUIDs from rule registry wrapper to alerting framework', async () => { + const response = await es.search<{ task: SerializedConcreteTaskInstance }>( + { + index: '.kibana_task_manager', + size: 100, + body: { query: { match_all: {} } }, + }, + { meta: true } + ); + expect(response.statusCode).to.eql(200); + const tasks = response.body.hits.hits; + tasks.forEach((task) => { + const stateString = task._source?.task.state; + expect(stateString).to.be.ok(); + + const state: RuleTaskState = JSON.parse(stateString!); + if (!state?.alertTypeState?.wrapped) return; + + const wrappedUUIDs = new Map(); + const wrappedState = state.alertTypeState as WrappedLifecycleRuleState; + + for (const alert of Object.values(wrappedState.trackedAlerts || {})) { + const id = alert.alertId; + const uuid = alert.alertUuid; + wrappedUUIDs.set(id, uuid); + } + + for (const alert of Object.values(wrappedState.trackedAlertsRecovered || {})) { + const id = alert.alertId; + const uuid = alert.alertUuid; + wrappedUUIDs.set(id, uuid); + } + + for (const [id, alert] of Object.entries(state.alertInstances || {})) { + const uuid = alert?.meta?.uuid || 'uuid-is-missing'; + expect(uuid).to.be(wrappedUUIDs.get(id)); + } + + for (const [id, alert] of Object.entries(state.alertRecoveredInstances || {})) { + const uuid = alert?.meta?.uuid || 'uuid-is-missing'; + expect(uuid).to.be(wrappedUUIDs.get(id)); + } + }); + }); + }); + }); +} diff --git a/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/task_management.ts b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/task_management.ts new file mode 100644 index 0000000000000..c2d5e0edccf64 --- /dev/null +++ b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/task_management.ts @@ -0,0 +1,1170 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment'; +import { random } from 'lodash'; +import expect from '@kbn/expect'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { taskMappings as TaskManagerMapping } from '@kbn/task-manager-plugin/server/saved_objects/mappings'; +import { ConcreteTaskInstance, BulkUpdateTaskResult } from '@kbn/task-manager-plugin/server'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +const { properties: taskManagerIndexMapping } = TaskManagerMapping; + +export interface RawDoc { + _id: string; + _source: any; + _type?: string; +} +export interface SearchResults { + hits: { + hits: RawDoc[]; + }; +} + +type DeprecatedConcreteTaskInstance = Omit & { + interval: string; +}; + +type SerializedConcreteTaskInstance = Omit< + ConcreteTaskInstance, + 'state' | 'params' | 'scheduledAt' | 'startedAt' | 'retryAt' | 'runAt' +> & { + state: State; + params: Params; + scheduledAt: string; + startedAt: string | null; + retryAt: string | null; + runAt: string; +}; + +export default function ({ getService }: FtrProviderContext) { + const es = getService('es'); + const log = getService('log'); + const retry = getService('retry'); + const supertest = getService('supertest'); + const testHistoryIndex = '.kibana_task_manager_test_result'; + + describe('scheduling and running tasks', () => { + beforeEach(async () => { + // clean up before each test + return await supertest.delete('/api/sample_tasks').set('kbn-xsrf', 'xxx').expect(200); + }); + + beforeEach(async () => { + const exists = await es.indices.exists({ index: testHistoryIndex }); + if (exists) { + await es.deleteByQuery({ + index: testHistoryIndex, + refresh: true, + body: { query: { term: { type: 'task' } } }, + }); + } else { + await es.indices.create({ + index: testHistoryIndex, + body: { + mappings: { + properties: { + type: { + type: 'keyword', + }, + taskId: { + type: 'keyword', + }, + params: taskManagerIndexMapping.params, + state: taskManagerIndexMapping.state, + runAt: taskManagerIndexMapping.runAt, + } as Record, + }, + }, + }); + } + }); + + after(async () => { + // clean up after last test + return await supertest.delete('/api/sample_tasks').set('kbn-xsrf', 'xxx').expect(200); + }); + + function currentTasks(): Promise<{ + docs: Array>; + }> { + return supertest + .get('/api/sample_tasks') + .expect(200) + .then((response) => response.body); + } + + function currentTask( + task: string + ): Promise> { + return supertest + .get(`/api/sample_tasks/task/${task}`) + .send({ task }) + .expect((response) => { + expect(response.status).to.eql(200); + expect(typeof JSON.parse(response.text).id).to.eql(`string`); + }) + .then((response) => response.body); + } + + function currentTaskError( + task: string + ): Promise<{ + statusCode: number; + error: string; + message: string; + }> { + return supertest + .get(`/api/sample_tasks/task/${task}`) + .send({ task }) + .expect(function (response) { + expect(response.status).to.eql(200); + expect(typeof JSON.parse(response.text).message).to.eql(`string`); + }) + .then((response) => response.body); + } + + function ensureTasksIndexRefreshed() { + return supertest.get(`/api/ensure_tasks_index_refreshed`).send({}).expect(200); + } + + async function historyDocs(taskId?: string): Promise { + return es + .search({ + index: testHistoryIndex, + body: { + query: { + term: { type: 'task' }, + }, + }, + }) + .then((result) => + (result as unknown as SearchResults).hits.hits.filter((task) => + taskId ? task._source?.taskId === taskId : true + ) + ); + } + + function scheduleTask( + task: Partial + ): Promise { + return supertest + .post('/api/sample_tasks/schedule') + .set('kbn-xsrf', 'xxx') + .send({ task }) + .expect(200) + .then((response: { body: SerializedConcreteTaskInstance }) => { + log.debug(`Task Scheduled: ${response.body.id}`); + return response.body; + }); + } + + function runTaskSoon(task: { id: string }) { + return supertest + .post('/api/sample_tasks/run_soon') + .set('kbn-xsrf', 'xxx') + .send({ task }) + .expect(200) + .then((response) => response.body); + } + + function bulkEnable(taskIds: string[], runSoon: boolean) { + return supertest + .post('/api/sample_tasks/bulk_enable') + .set('kbn-xsrf', 'xxx') + .send({ taskIds, runSoon }) + .expect(200) + .then((response) => response.body); + } + + function bulkDisable(taskIds: string[]) { + return supertest + .post('/api/sample_tasks/bulk_disable') + .set('kbn-xsrf', 'xxx') + .send({ taskIds }) + .expect(200) + .then((response) => response.body); + } + + function bulkUpdateSchedules(taskIds: string[], schedule: { interval: string }) { + return supertest + .post('/api/sample_tasks/bulk_update_schedules') + .set('kbn-xsrf', 'xxx') + .send({ taskIds, schedule }) + .expect(200) + .then((response: { body: BulkUpdateTaskResult }) => response.body); + } + + // TODO: Add this back in with https://github.com/elastic/kibana/issues/106139 + // function runEphemeralTaskNow(task: { + // taskType: string; + // params: Record; + // state: Record; + // }) { + // return supertest + // .post('/api/sample_tasks/ephemeral_run_now') + // .set('kbn-xsrf', 'xxx') + // .send({ task }) + // .expect(200) + // .then((response) => response.body); + // } + + function scheduleTaskIfNotExists(task: Partial) { + return supertest + .post('/api/sample_tasks/ensure_scheduled') + .set('kbn-xsrf', 'xxx') + .send({ task }) + .expect(200) + .then((response: { body: ConcreteTaskInstance }) => response.body); + } + + function releaseTasksWaitingForEventToComplete(event: string) { + return supertest + .post('/api/sample_tasks/event') + .set('kbn-xsrf', 'xxx') + .send({ event }) + .expect(200); + } + + function getTaskById( + tasks: Array>, + id: string + ) { + return tasks.filter((task) => task.id === id)[0]; + } + + async function provideParamsToTasksWaitingForParams( + taskId: string, + data: Record = {} + ) { + // wait for task to start running and stall on waitForParams + await retry.try(async () => { + const tasks = (await currentTasks()).docs; + expect(getTaskById(tasks, taskId).status).to.eql('running'); + }); + + return supertest + .post('/api/sample_tasks/event') + .set('kbn-xsrf', 'xxx') + .send({ event: taskId, data }) + .expect(200); + } + + it('should support middleware', async () => { + const historyItem = random(1, 100); + + const scheduledTask = await scheduleTask({ + taskType: 'sampleTask', + schedule: { interval: '30m' }, + params: { historyItem }, + }); + log.debug(`Task created: ${scheduledTask.id}`); + + await retry.try(async () => { + expect((await historyDocs()).length).to.eql(1); + + const [task] = (await currentTasks<{ count: number }>()).docs; + log.debug(`Task found: ${task.id}`); + log.debug(`Task status: ${task.status}`); + log.debug(`Task state: ${JSON.stringify(task.state, null, 2)}`); + log.debug(`Task params: ${JSON.stringify(task.params, null, 2)}`); + + expect(task.state.count).to.eql(1); + + expect(task.params).to.eql({ + superFly: 'My middleware param!', + originalParams: { historyItem }, + }); + }); + }); + + it('should remove non-recurring tasks after they complete', async () => { + await scheduleTask({ + taskType: 'sampleTask', + params: {}, + }); + + await retry.try(async () => { + const history = await historyDocs(); + expect(history.length).to.eql(1); + expect((await currentTasks()).docs).to.eql([]); + }); + }); + + it('should use a given ID as the task document ID', async () => { + const result = await scheduleTask({ + id: 'test-task-for-sample-task-plugin-to-test-task-manager', + taskType: 'sampleTask', + params: {}, + }); + + expect(result.id).to.be('test-task-for-sample-task-plugin-to-test-task-manager'); + }); + + it('should allow a task with a given ID to be scheduled multiple times', async () => { + const result = await scheduleTaskIfNotExists({ + id: 'test-task-to-reschedule-in-task-manager', + taskType: 'sampleTask', + params: {}, + }); + + expect(result.id).to.be('test-task-to-reschedule-in-task-manager'); + + const rescheduleResult = await scheduleTaskIfNotExists({ + id: 'test-task-to-reschedule-in-task-manager', + taskType: 'sampleTask', + params: {}, + }); + + expect(rescheduleResult.id).to.be('test-task-to-reschedule-in-task-manager'); + }); + + it('should reschedule if task errors', async () => { + const task = await scheduleTask({ + taskType: 'sampleTask', + params: { failWith: 'Dangit!!!!!' }, + }); + + await retry.try(async () => { + const scheduledTask = await currentTask(task.id); + expect(scheduledTask.attempts).to.be.greaterThan(1); + expect(Date.parse(scheduledTask.runAt)).to.be.greaterThan( + Date.parse(task.runAt) + 30 * 1000 + ); + }); + }); + + it('should schedule the retry of recurring tasks to run at the next schedule when they time out', async () => { + const intervalInMinutes = 30; + const intervalInMilliseconds = intervalInMinutes * 60 * 1000; + const task = await scheduleTask({ + taskType: 'sampleRecurringTaskWhichHangs', + schedule: { interval: `${intervalInMinutes}m` }, + params: {}, + }); + + await retry.try(async () => { + const scheduledTask = await currentTask(task.id); + const retryAt = Date.parse(scheduledTask.retryAt!); + expect(isNaN(retryAt)).to.be(false); + + const buffer = 10000; // 10 second buffer + const retryDelay = retryAt - Date.parse(task.runAt); + expect(retryDelay).to.be.greaterThan(intervalInMilliseconds - buffer); + expect(retryDelay).to.be.lessThan(intervalInMilliseconds + buffer); + }); + }); + + it('should reschedule if task returns runAt', async () => { + const nextRunMilliseconds = random(60000, 200000); + const count = random(1, 20); + + const originalTask = await scheduleTask({ + taskType: 'sampleTask', + params: { nextRunMilliseconds }, + state: { count }, + }); + + await retry.try(async () => { + expect((await historyDocs(originalTask.id)).length).to.eql(1); + + const task = await currentTask<{ count: number }>(originalTask.id); + expect(task.attempts).to.eql(0); + expect(task.state.count).to.eql(count + 1); + + expectReschedule(Date.parse(originalTask.runAt), task, nextRunMilliseconds); + }); + }); + + it('should reschedule if task has an interval', async () => { + const interval = random(5, 200); + const intervalMilliseconds = interval * 60000; + + const originalTask = await scheduleTask({ + taskType: 'sampleTask', + schedule: { interval: `${interval}m` }, + params: {}, + }); + + await retry.try(async () => { + expect((await historyDocs()).length).to.eql(1); + + const [task] = (await currentTasks<{ count: number }>()).docs; + expect(task.attempts).to.eql(0); + expect(task.state.count).to.eql(1); + + expectReschedule(Date.parse(originalTask.runAt), task, intervalMilliseconds); + }); + }); + + it('should support the deprecated interval field', async () => { + const interval = random(5, 200); + const intervalMilliseconds = interval * 60000; + + const originalTask = await scheduleTask({ + taskType: 'sampleTask', + interval: `${interval}m`, + params: {}, + }); + + await retry.try(async () => { + expect((await historyDocs()).length).to.eql(1); + + const [task] = (await currentTasks<{ count: number }>()).docs; + expect(task.attempts).to.eql(0); + expect(task.state.count).to.eql(1); + + expectReschedule(Date.parse(originalTask.runAt), task, intervalMilliseconds); + }); + }); + + it('should return a task run result when asked to run a task now', async () => { + const originalTask = await scheduleTask({ + taskType: 'sampleTask', + schedule: { interval: `30m` }, + params: {}, + }); + + await retry.try(async () => { + const docs = await historyDocs(); + expect(docs.filter((taskDoc) => taskDoc._source.taskId === originalTask.id).length).to.eql( + 1 + ); + + const [task] = (await currentTasks<{ count: number }>()).docs.filter( + (taskDoc) => taskDoc.id === originalTask.id + ); + + expect(task.state.count).to.eql(1); + + // ensure this task shouldnt run for another half hour + expectReschedule(Date.parse(originalTask.runAt), task, 30 * 60000); + }); + + const now = Date.now(); + const runSoonResult = await runTaskSoon({ + id: originalTask.id, + }); + + expect(runSoonResult).to.eql({ id: originalTask.id }); + + await retry.try(async () => { + expect( + (await historyDocs()).filter((taskDoc) => taskDoc._source.taskId === originalTask.id) + .length + ).to.eql(2); + + const [task] = (await currentTasks<{ count: number }>()).docs.filter( + (taskDoc) => taskDoc.id === originalTask.id + ); + expect(task.state.count).to.eql(2); + + // ensure this task shouldnt run for another half hour + expectReschedule(now, task, 30 * 60000); + }); + }); + + // always failing + it.skip('should only run as many instances of a task as its maxConcurrency will allow', async () => { + // should run as there's only one and maxConcurrency on this TaskType is 1 + const firstWithSingleConcurrency = await scheduleTask({ + taskType: 'sampleTaskWithSingleConcurrency', + params: { + waitForEvent: 'releaseFirstWaveOfTasks', + }, + }); + + // should run as there's only two and maxConcurrency on this TaskType is 2 + const [firstLimitedConcurrency, secondLimitedConcurrency] = await Promise.all([ + scheduleTask({ + taskType: 'sampleTaskWithLimitedConcurrency', + params: { + waitForEvent: 'releaseFirstWaveOfTasks', + }, + }), + scheduleTask({ + taskType: 'sampleTaskWithLimitedConcurrency', + params: { + waitForEvent: 'releaseSecondWaveOfTasks', + }, + }), + ]); + + await retry.try(async () => { + expect((await historyDocs(firstWithSingleConcurrency.id)).length).to.eql(1); + expect((await historyDocs(firstLimitedConcurrency.id)).length).to.eql(1); + expect((await historyDocs(secondLimitedConcurrency.id)).length).to.eql(1); + }); + + // should not run as there one running and maxConcurrency on this TaskType is 1 + const secondWithSingleConcurrency = await scheduleTask({ + taskType: 'sampleTaskWithSingleConcurrency', + params: { + waitForEvent: 'releaseSecondWaveOfTasks', + }, + }); + + // should not run as there are two running and maxConcurrency on this TaskType is 2 + const thirdWithLimitedConcurrency = await scheduleTask({ + taskType: 'sampleTaskWithLimitedConcurrency', + params: { + waitForEvent: 'releaseSecondWaveOfTasks', + }, + }); + + // schedule a task that should get picked up before the two blocked tasks + const taskWithUnlimitedConcurrency = await scheduleTask({ + taskType: 'sampleTask', + params: {}, + }); + + await retry.try(async () => { + expect((await historyDocs(taskWithUnlimitedConcurrency.id)).length).to.eql(1); + expect((await currentTask(secondWithSingleConcurrency.id)).status).to.eql('idle'); + expect((await currentTask(thirdWithLimitedConcurrency.id)).status).to.eql('idle'); + }); + + // release the running SingleConcurrency task and only one of the LimitedConcurrency tasks + await releaseTasksWaitingForEventToComplete('releaseFirstWaveOfTasks'); + + await retry.try(async () => { + // ensure the completed tasks were deleted + expect((await currentTaskError(firstWithSingleConcurrency.id)).message).to.eql( + `Saved object [task/${firstWithSingleConcurrency.id}] not found` + ); + expect((await currentTaskError(firstLimitedConcurrency.id)).message).to.eql( + `Saved object [task/${firstLimitedConcurrency.id}] not found` + ); + + // ensure blocked tasks is still running + expect((await currentTask(secondLimitedConcurrency.id)).status).to.eql('running'); + + // ensure the blocked tasks begin running + expect((await currentTask(secondWithSingleConcurrency.id)).status).to.eql('running'); + expect((await currentTask(thirdWithLimitedConcurrency.id)).status).to.eql('running'); + }); + + // release blocked task + await releaseTasksWaitingForEventToComplete('releaseSecondWaveOfTasks'); + }); + + it('should increment attempts when task fails on markAsRunning', async () => { + const originalTask = await scheduleTask({ + taskType: 'sampleTask', + params: { throwOnMarkAsRunning: true }, + }); + + expect(originalTask.attempts).to.eql(0); + + // Wait for task manager to attempt running the task a second time + await retry.try(async () => { + const task = await currentTask(originalTask.id); + expect(task.attempts).to.eql(2); + }); + }); + + it('should return a task run error result when trying to run a non-existent task', async () => { + // runSoon should fail + const failedRunSoonResult = await runTaskSoon({ + id: 'i-dont-exist', + }); + expect(failedRunSoonResult).to.eql({ + error: `Error: Saved object [task/i-dont-exist] not found`, + id: 'i-dont-exist', + }); + }); + + it('should return a task run error result when trying to run a task now which is already running', async () => { + const longRunningTask = await scheduleTask({ + taskType: 'sampleTask', + schedule: { interval: '30m' }, + params: { + waitForParams: true, + }, + }); + + // tell the task to wait for the 'runSoonHasBeenAttempted' event + await provideParamsToTasksWaitingForParams(longRunningTask.id, { + waitForEvent: 'runSoonHasBeenAttempted', + }); + + await retry.try(async () => { + const docs = await historyDocs(); + expect( + docs.filter((taskDoc) => taskDoc._source.taskId === longRunningTask.id).length + ).to.eql(1); + + const task = await currentTask(longRunningTask.id); + expect(task.status).to.eql('running'); + }); + + await ensureTasksIndexRefreshed(); + + // first runSoon should fail + const failedRunSoonResult = await runTaskSoon({ + id: longRunningTask.id, + }); + + expect(failedRunSoonResult).to.eql({ + error: `Error: Failed to run task "${longRunningTask.id}" as it is currently running`, + id: longRunningTask.id, + }); + + // finish first run by emitting 'runSoonHasBeenAttempted' event + await releaseTasksWaitingForEventToComplete('runSoonHasBeenAttempted'); + await retry.try(async () => { + const tasks = (await currentTasks<{ count: number }>()).docs; + expect(getTaskById(tasks, longRunningTask.id).state.count).to.eql(1); + + const task = await currentTask(longRunningTask.id); + expect(task.status).to.eql('idle'); + }); + + await ensureTasksIndexRefreshed(); + + // second runSoon should be successful + const successfulRunSoonResult = runTaskSoon({ + id: longRunningTask.id, + }); + + await provideParamsToTasksWaitingForParams(longRunningTask.id); + + expect(await successfulRunSoonResult).to.eql({ id: longRunningTask.id }); + }); + + it('should disable and reenable task and run it when runSoon = true', async () => { + const historyItem = random(1, 100); + const scheduledTask = await scheduleTask({ + taskType: 'sampleTask', + schedule: { interval: '1h' }, + params: { historyItem }, + }); + + await retry.try(async () => { + expect((await historyDocs()).length).to.eql(1); + const task = await currentTask(scheduledTask.id); + + expect(task.enabled).to.eql(true); + }); + + await retry.try(async () => { + // disable the task + await bulkDisable([scheduledTask.id]); + const task = await currentTask(scheduledTask.id); + log.debug( + `bulkDisable:task(${scheduledTask.id}) enabled: ${task.enabled}, when runSoon = true` + ); + expect(task.enabled).to.eql(false); + }); + + // re-enable the task + await bulkEnable([scheduledTask.id], true); + + await retry.try(async () => { + const task = await currentTask(scheduledTask.id); + + expect(task.enabled).to.eql(true); + log.debug( + `bulkEnable:task(${scheduledTask.id}) enabled: ${task.enabled}, when runSoon = true` + ); + expect(Date.parse(task.scheduledAt)).to.be.greaterThan( + Date.parse(scheduledTask.scheduledAt) + ); + expect(Date.parse(task.runAt)).to.be.greaterThan(Date.parse(scheduledTask.runAt)); + }); + }); + + it('should disable and reenable task and not run it when runSoon = false', async () => { + const historyItem = random(1, 100); + const scheduledTask = await scheduleTask({ + taskType: 'sampleTask', + schedule: { interval: '1h' }, + params: { historyItem }, + }); + + await retry.try(async () => { + expect((await historyDocs()).length).to.eql(1); + + const task = await currentTask(scheduledTask.id); + expect(task.enabled).to.eql(true); + }); + + // disable the task + await bulkDisable([scheduledTask.id]); + + let disabledTask: SerializedConcreteTaskInstance; + await retry.try(async () => { + disabledTask = await currentTask(scheduledTask.id); + log.debug( + `bulkDisable:task(${scheduledTask.id}) enabled: ${disabledTask.enabled}, when runSoon = false` + ); + expect(disabledTask.enabled).to.eql(false); + }); + + // re-enable the task + await bulkEnable([scheduledTask.id], false); + + await retry.try(async () => { + const task = await currentTask(scheduledTask.id); + log.debug( + `bulkEnable:task(${scheduledTask.id}) enabled: ${task.enabled}, when runSoon = true` + ); + expect(task.enabled).to.eql(true); + expect(Date.parse(task.scheduledAt)).to.eql(Date.parse(disabledTask.scheduledAt)); + }); + }); + + function expectReschedule( + originalRunAt: number, + task: SerializedConcreteTaskInstance, + expectedDiff: number + ) { + const buffer = 10000; + expect(Date.parse(task.runAt) - originalRunAt).to.be.greaterThan(expectedDiff - buffer); + expect(Date.parse(task.runAt) - originalRunAt).to.be.lessThan(expectedDiff + buffer); + } + + it('should run tasks in parallel, allowing for long running tasks along side faster tasks', async () => { + /** + * It's worth noting this test relies on the /event endpoint that forces Task Manager to hold off + * on completing a task until a call is made by the test suite. + * If we begin testing with multiple Kibana instacnes in Parallel this will likely become flaky. + * If you end up here because the test is flaky, this might be why. + */ + const fastTask = await scheduleTask({ + taskType: 'sampleTask', + schedule: { interval: `1s` }, + params: {}, + }); + + const longRunningTask = await scheduleTask({ + taskType: 'sampleTask', + schedule: { interval: `1s` }, + params: { + waitForEvent: 'rescheduleHasHappened', + }, + }); + + await retry.try(async () => { + const tasks = (await currentTasks<{ count: number }>()).docs; + expect(getTaskById(tasks, fastTask.id).state.count).to.eql(2); + }); + + await releaseTasksWaitingForEventToComplete('rescheduleHasHappened'); + + await retry.try(async () => { + const tasks = (await currentTasks<{ count: number }>()).docs; + + expect(getTaskById(tasks, fastTask.id).state.count).to.greaterThan(2); + expect(getTaskById(tasks, longRunningTask.id).state.count).to.eql(1); + }); + }); + + it('should delete the task if it is still running but maxAttempts has been reached', async () => { + await scheduleTask({ + taskType: 'sampleOneTimeTaskThrowingError', + params: {}, + }); + + await retry.try(async () => { + const results = (await currentTasks()).docs; + expect(results.length).to.eql(0); + }); + }); + + // flaky + it.skip('should continue claiming recurring task even if maxAttempts has been reached', async () => { + const task = await scheduleTask({ + taskType: 'sampleRecurringTaskTimingOut', + schedule: { interval: '1s' }, + params: {}, + }); + + await retry.try(async () => { + const [scheduledTask] = (await currentTasks()).docs; + expect(scheduledTask.id).to.eql(task.id); + expect(scheduledTask.status).to.eql('claiming'); + expect(scheduledTask.attempts).to.be.greaterThan(3); + }); + }); + + it('should fail to schedule recurring task with timeout override', async () => { + const task = await scheduleTask({ + taskType: 'sampleRecurringTaskTimingOut', + schedule: { interval: '1s' }, + timeoutOverride: '30s', + params: {}, + }); + + expect(task.timeoutOverride).to.be(undefined); + }); + + it('should allow timeout override for ad hoc tasks', async () => { + const task = await scheduleTask({ + taskType: 'sampleAdHocTaskTimingOut', + timeoutOverride: '30s', + params: {}, + }); + + expect(task.timeoutOverride).to.be('30s'); + + // this task type is set to time out after 1s but the task runner + // will wait 15 seconds and then index a document if it hasn't timed out + // this test overrides the timeout to 30s and checks if the expected + // document was indexed. presence of indexed document means the task + // timeout override was respected + await retry.try(async () => { + const [scheduledTask] = (await currentTasks()).docs; + expect(scheduledTask?.id).to.eql(task.id); + }); + + await retry.try(async () => { + const docs: RawDoc[] = await historyDocs(task.id); + expect(docs.length).to.eql(1); + expect(docs[0]._source.taskType).to.eql('sampleAdHocTaskTimingOut'); + }); + }); + + it('should bulk update schedules for multiple tasks', async () => { + const initialTime = Date.now(); + const tasks = await Promise.all([ + scheduleTask({ + taskType: 'sampleTask', + schedule: { interval: '1h' }, + params: {}, + }), + + scheduleTask({ + taskType: 'sampleTask', + schedule: { interval: '5m' }, + params: {}, + }), + ]); + + const taskIds = tasks.map(({ id }) => id); + + await retry.try(async () => { + // ensure each task has ran at least once and been rescheduled for future run + for (const task of tasks) { + const { state } = await currentTask<{ count: number }>(task.id); + expect(state.count).to.be(1); + } + + // first task to be scheduled in 1h + expect(Date.parse((await currentTask(tasks[0].id)).runAt) - initialTime).to.be.greaterThan( + moment.duration(1, 'hour').asMilliseconds() + ); + + // second task to be scheduled in 5m + expect(Date.parse((await currentTask(tasks[1].id)).runAt) - initialTime).to.be.greaterThan( + moment.duration(5, 'minutes').asMilliseconds() + ); + }); + + await retry.try(async () => { + const updates = await bulkUpdateSchedules(taskIds, { interval: '3h' }); + + expect(updates.tasks.length).to.be(2); + expect(updates.errors.length).to.be(0); + }); + + await retry.try(async () => { + const updatedTasks = (await currentTasks()).docs; + + updatedTasks.forEach((task) => { + expect(task.schedule).to.eql({ interval: '3h' }); + // should be scheduled to run in 3 hours + expect(Date.parse(task.runAt) - initialTime).to.be.greaterThan( + moment.duration(3, 'hours').asMilliseconds() + ); + }); + }); + }); + + it('should not bulk update schedules for task in running status', async () => { + // this task should be in running status for 60s until it will be time outed + const longRunningTask = await scheduleTask({ + taskType: 'sampleRecurringTaskWhichHangs', + schedule: { interval: '1h' }, + params: {}, + }); + + runTaskSoon({ id: longRunningTask.id }); + + let scheduledRunAt: string; + // ensure task is running and store scheduled runAt + await retry.try(async () => { + const task = await currentTask(longRunningTask.id); + + expect(task.status).to.be('running'); + + scheduledRunAt = task.runAt; + }); + + await retry.try(async () => { + const updates = await bulkUpdateSchedules([longRunningTask.id], { interval: '3h' }); + + // length should be 0, as task in running status won't be updated + expect(updates.tasks.length).to.be(0); + expect(updates.errors.length).to.be(0); + }); + + // ensure task wasn't updated + await retry.try(async () => { + const task = await currentTask(longRunningTask.id); + + // interval shouldn't be changed + expect(task.schedule).to.eql({ interval: '1h' }); + + // scheduledRunAt shouldn't be changed + expect(task.runAt).to.eql(scheduledRunAt); + }); + }); + + // TODO: Add this back in with https://github.com/elastic/kibana/issues/106139 + // it('should return the resulting task state when asked to run an ephemeral task now', async () => { + // const ephemeralTask = await runEphemeralTaskNow({ + // taskType: 'sampleTask', + // params: {}, + // state: {}, + // }); + + // await retry.try(async () => { + // expect( + // (await historyDocs()).filter((taskDoc) => taskDoc._source.taskId === ephemeralTask.id) + // .length + // ).to.eql(1); + + // expect(ephemeralTask.state.count).to.eql(1); + // }); + + // const secondEphemeralTask = await runEphemeralTaskNow({ + // taskType: 'sampleTask', + // params: {}, + // // pass state from previous ephemeral run as input for the second run + // state: ephemeralTask.state, + // }); + + // // ensure state is cumulative + // expect(secondEphemeralTask.state.count).to.eql(2); + + // await retry.try(async () => { + // // ensure new id is produced for second task execution + // expect( + // (await historyDocs()).filter((taskDoc) => taskDoc._source.taskId === ephemeralTask.id) + // .length + // ).to.eql(1); + // expect( + // (await historyDocs()).filter( + // (taskDoc) => taskDoc._source.taskId === secondEphemeralTask.id + // ).length + // ).to.eql(1); + // }); + // }); + + // TODO: Add this back in with https://github.com/elastic/kibana/issues/106139 + // it('Epheemral task run should only run one instance of a task if its maxConcurrency is 1', async () => { + // const ephemeralTaskWithSingleConcurrency: { + // state: { + // executions: Array<{ + // result: { + // id: string; + // state: { + // timings: Array<{ + // start: number; + // stop: number; + // }>; + // }; + // }; + // }>; + // }; + // } = await runEphemeralTaskNow({ + // taskType: 'taskWhichExecutesOtherTasksEphemerally', + // params: { + // tasks: [ + // { + // taskType: 'timedTaskWithSingleConcurrency', + // params: { delay: 1000 }, + // state: {}, + // }, + // { + // taskType: 'timedTaskWithSingleConcurrency', + // params: { delay: 1000 }, + // state: {}, + // }, + // { + // taskType: 'timedTaskWithSingleConcurrency', + // params: { delay: 1000 }, + // state: {}, + // }, + // { + // taskType: 'timedTaskWithSingleConcurrency', + // params: { delay: 1000 }, + // state: {}, + // }, + // ], + // }, + // state: {}, + // }); + + // ensureOverlappingTasksDontExceedThreshold( + // ephemeralTaskWithSingleConcurrency.state.executions, + // // make sure each task intersects with any other task + // 0 + // ); + // }); + + // TODO: Add this back in with https://github.com/elastic/kibana/issues/106139 + // it('Ephemeral task run should only run as many instances of a task as its maxConcurrency will allow', async () => { + // const ephemeralTaskWithSingleConcurrency: { + // state: { + // executions: Array<{ + // result: { + // id: string; + // state: { + // timings: Array<{ + // start: number; + // stop: number; + // }>; + // }; + // }; + // }>; + // }; + // } = await runEphemeralTaskNow({ + // taskType: 'taskWhichExecutesOtherTasksEphemerally', + // params: { + // tasks: [ + // { + // taskType: 'timedTaskWithLimitedConcurrency', + // params: { delay: 100 }, + // state: {}, + // }, + // { + // taskType: 'timedTaskWithLimitedConcurrency', + // params: { delay: 100 }, + // state: {}, + // }, + // { + // taskType: 'timedTaskWithLimitedConcurrency', + // params: { delay: 100 }, + // state: {}, + // }, + // { + // taskType: 'timedTaskWithLimitedConcurrency', + // params: { delay: 100 }, + // state: {}, + // }, + // { + // taskType: 'timedTaskWithLimitedConcurrency', + // params: { delay: 100 }, + // state: {}, + // }, + // { + // taskType: 'timedTaskWithLimitedConcurrency', + // params: { delay: 100 }, + // state: {}, + // }, + // ], + // }, + // state: {}, + // }); + + // ensureOverlappingTasksDontExceedThreshold( + // ephemeralTaskWithSingleConcurrency.state.executions, + // // make sure each task intersects with, at most, 1 other task + // 1 + // ); + // }); + + // TODO: Add this back in with https://github.com/elastic/kibana/issues/106139 + // it('Ephemeral task executions cant exceed the max workes in Task Manager', async () => { + // const ephemeralTaskWithSingleConcurrency: { + // state: { + // executions: Array<{ + // result: { + // id: string; + // state: { + // timings: Array<{ + // start: number; + // stop: number; + // }>; + // }; + // }; + // }>; + // }; + // } = await runEphemeralTaskNow({ + // taskType: 'taskWhichExecutesOtherTasksEphemerally', + // params: { + // tasks: times(20, () => ({ + // taskType: 'timedTask', + // params: { delay: 100 }, + // state: {}, + // })), + // }, + // state: {}, + // }); + + // ensureOverlappingTasksDontExceedThreshold( + // ephemeralTaskWithSingleConcurrency.state.executions, + // // make sure each task intersects with, at most, 9 other tasks (as max workes is 10) + // 9 + // ); + // }); + }); + + // TODO: Add this back in with https://github.com/elastic/kibana/issues/106139 + // function ensureOverlappingTasksDontExceedThreshold( + // executions: Array<{ + // result: { + // id: string; + // state: { + // timings: Array<{ + // start: number; + // stop: number; + // }>; + // }; + // }; + // }>, + // threshold: number + // ) { + // const executionRanges = executions.map((execution) => ({ + // id: execution.result.id, + // range: range( + // // calculate range of milliseconds + // // in which the task was running (that should be good enough) + // execution.result.state.timings[0].start, + // execution.result.state.timings[0].stop + // ), + // })); + + // const intersections = new Map(); + // for (const currentExecution of executionRanges) { + // for (const executionToComparteTo of executionRanges) { + // if (currentExecution.id !== executionToComparteTo.id) { + // // find all executions that intersect + // if (intersection(currentExecution.range, executionToComparteTo.range).length) { + // intersections.set(currentExecution.id, [ + // ...(intersections.get(currentExecution.id) ?? []), + // executionToComparteTo.id, + // ]); + // } + // } + // } + // } + + // const tooManyIntersectingTasks = [...intersections.entries()].find( + // // make sure each task intersects with, at most, threshold of other task + // ([, intersectingTasks]) => intersectingTasks.length > threshold + // ); + // if (tooManyIntersectingTasks) { + // throw new Error( + // `Invalid execution found: ${tooManyIntersectingTasks[0]} overlaps with ${tooManyIntersectingTasks[1]}` + // ); + // } + // } +} diff --git a/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/task_management_removed_types.ts b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/task_management_removed_types.ts new file mode 100644 index 0000000000000..e13615cceab0c --- /dev/null +++ b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/task_management_removed_types.ts @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import url from 'url'; +import supertest from 'supertest'; +import { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export interface RawDoc { + _id: string; + _source: any; + _type?: string; +} +export interface SearchResults { + hits: { + hits: RawDoc[]; + }; +} + +type DeprecatedConcreteTaskInstance = Omit & { + interval: string; +}; + +type SerializedConcreteTaskInstance = Omit< + ConcreteTaskInstance, + 'state' | 'params' | 'scheduledAt' | 'startedAt' | 'retryAt' | 'runAt' +> & { + state: State; + params: Params; + scheduledAt: string; + startedAt: string | null; + retryAt: string | null; + runAt: string; +}; + +export default function ({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const retry = getService('retry'); + const config = getService('config'); + const request = supertest(url.format(config.get('servers.kibana'))); + + const UNREGISTERED_TASK_TYPE_ID = 'ce7e1250-3322-11eb-94c1-db6995e83f6b'; + const REMOVED_TASK_TYPE_ID = 'be7e1250-3322-11eb-94c1-db6995e83f6a'; + + describe('not registered task types', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/task_manager_removed_types'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/task_manager_removed_types'); + }); + + function scheduleTask( + task: Partial + ): Promise { + return request + .post('/api/sample_tasks/schedule') + .set('kbn-xsrf', 'xxx') + .send({ task }) + .expect(200) + .then((response: { body: SerializedConcreteTaskInstance }) => response.body); + } + + function currentTasks(): Promise<{ + docs: Array>; + }> { + return request + .get('/api/sample_tasks') + .expect(200) + .then((response) => response.body); + } + + // flaky + it.skip('should successfully schedule registered tasks, not claim unregistered tasks and mark removed task types as unrecognized', async () => { + const scheduledTask = await scheduleTask({ + taskType: 'sampleTask', + schedule: { interval: `1s` }, + params: {}, + }); + + await retry.try(async () => { + const tasks = (await currentTasks()).docs; + expect(tasks.length).to.eql(3); + + const taskIds = tasks.map((task) => task.id); + expect(taskIds).to.contain(scheduledTask.id); + expect(taskIds).to.contain(UNREGISTERED_TASK_TYPE_ID); + expect(taskIds).to.contain(REMOVED_TASK_TYPE_ID); + + const scheduledTaskInstance = tasks.find((task) => task.id === scheduledTask.id); + const unregisteredTaskInstance = tasks.find( + (task) => task.id === UNREGISTERED_TASK_TYPE_ID + ); + const removedTaskInstance = tasks.find((task) => task.id === REMOVED_TASK_TYPE_ID); + + expect(scheduledTaskInstance?.status).to.eql('claiming'); + expect(unregisteredTaskInstance?.status).to.eql('idle'); + expect(removedTaskInstance?.status).to.eql('unrecognized'); + }); + }); + }); +} diff --git a/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/task_management_scheduled_at.ts b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/task_management_scheduled_at.ts new file mode 100644 index 0000000000000..a70225035d03c --- /dev/null +++ b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/task_management_scheduled_at.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server/task'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +export default function createTaskManagementScheduledAtTests({ getService }: FtrProviderContext) { + const es = getService('es'); + const esArchiver = getService('esArchiver'); + const retry = getService('retry'); + + describe('task management scheduled at', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/task_manager_tasks'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/task_manager_tasks'); + await esArchiver.emptyKibanaIndex(); + }); + + it('sets scheduledAt to runAt if retryAt is null', async () => { + await retry.try(async () => { + const response = await es.get<{ task: ConcreteTaskInstance }>( + { + index: '.kibana_task_manager', + id: 'task:ge7e1250-3322-11eb-94c1-db6395e84f6g', + }, + { + meta: true, + } + ); + expect(response.statusCode).to.eql(200); + expect(response.body._source?.task.scheduledAt).to.eql('2020-11-30T16:00:00.000Z'); + }); + }); + + it('sets scheduledAt to retryAt if retryAt time has passed', async () => { + await retry.try(async () => { + const response = await es.get<{ task: ConcreteTaskInstance }>( + { + index: '.kibana_task_manager', + id: 'task:ie7e1250-3322-11eb-94c1-db6395e84f6i', + }, + { + meta: true, + } + ); + expect(response.statusCode).to.eql(200); + expect(response.body._source?.task.scheduledAt).to.eql('2020-11-30T17:00:00.000Z'); + }); + }); + }); +} diff --git a/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/task_priority.ts b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/task_priority.ts new file mode 100644 index 0000000000000..f8fc3f63987b9 --- /dev/null +++ b/x-pack/test/task_manager_claimer_mget/test_suites/task_manager/task_priority.ts @@ -0,0 +1,216 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server'; +import { taskMappings as TaskManagerMapping } from '@kbn/task-manager-plugin/server/saved_objects/mappings'; +import { asyncForEach } from '@kbn/std'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +const { properties: taskManagerIndexMapping } = TaskManagerMapping; + +export interface RawDoc { + _id: string; + _source: any; + _type?: string; +} +export interface SearchResults { + hits: { + hits: RawDoc[]; + }; +} + +type DeprecatedConcreteTaskInstance = Omit & { + interval: string; +}; + +type SerializedConcreteTaskInstance = Omit< + ConcreteTaskInstance, + 'state' | 'params' | 'scheduledAt' | 'startedAt' | 'retryAt' | 'runAt' +> & { + state: State; + params: Params; + scheduledAt: string; + startedAt: string | null; + retryAt: string | null; + runAt: string; +}; + +export default function ({ getService }: FtrProviderContext) { + const es = getService('es'); + const retry = getService('retry'); + const supertest = getService('supertest'); + + const testHistoryIndex = '.kibana_task_manager_test_result'; + + function scheduleTask( + task: Partial + ): Promise { + return supertest + .post('/api/sample_tasks/schedule') + .set('kbn-xsrf', 'xxx') + .send({ task }) + .expect(200) + .then((response: { body: SerializedConcreteTaskInstance }) => response.body); + } + + function currentTasks(): Promise<{ + docs: Array>; + }> { + return supertest + .get('/api/sample_tasks') + .expect(200) + .then((response) => response.body); + } + + async function historyDocs({ + taskId, + taskType, + }: { + taskId?: string; + taskType?: string; + }): Promise { + const filter: any[] = [{ term: { type: 'task' } }]; + if (taskId) { + filter.push({ term: { taskId } }); + } + if (taskType) { + filter.push({ term: { taskType } }); + } + return es + .search({ + index: testHistoryIndex, + body: { + query: { + bool: { + filter, + }, + }, + }, + }) + .then((result) => (result as unknown as SearchResults).hits.hits); + } + + describe('task priority', () => { + beforeEach(async () => { + const exists = await es.indices.exists({ index: testHistoryIndex }); + if (exists) { + await es.deleteByQuery({ + index: testHistoryIndex, + refresh: true, + body: { query: { term: { type: 'task' } } }, + }); + } else { + await es.indices.create({ + index: testHistoryIndex, + body: { + mappings: { + properties: { + type: { + type: 'keyword', + }, + taskId: { + type: 'keyword', + }, + params: taskManagerIndexMapping.params, + state: taskManagerIndexMapping.state, + runAt: taskManagerIndexMapping.runAt, + } as Record, + }, + }, + }); + } + }); + + afterEach(async () => { + await supertest.delete('/api/sample_tasks').set('kbn-xsrf', 'xxx').expect(200); + }); + + it('should claim low priority tasks if there is capacity', async () => { + // schedule 5 normal tasks and 1 low priority task + // setting the schedule long so they should only run once + const tasksToSchedule = []; + for (let i = 0; i < 5; i++) { + tasksToSchedule.push( + scheduleTask({ + taskType: 'sampleTask', + schedule: { interval: `1d` }, + params: {}, + }) + ); + } + tasksToSchedule.push( + scheduleTask({ + taskType: 'lowPriorityTask', + schedule: { interval: `1d` }, + params: {}, + }) + ); + const scheduledTasks = await Promise.all(tasksToSchedule); + + await retry.try(async () => { + const tasks = (await currentTasks()).docs; + expect(tasks.length).to.eql(6); + + const taskIds = tasks.map((task) => task.id); + const taskDocs: RawDoc[] = []; + await asyncForEach(scheduledTasks, async (scheduledTask) => { + expect(taskIds).to.contain(scheduledTask.id); + const doc: RawDoc[] = await historyDocs({ taskId: scheduledTask.id }); + expect(doc.length).to.eql(1); + taskDocs.push(...doc); + }); + + expect( + taskDocs.findIndex((taskDoc) => taskDoc._source.taskType === 'lowPriorityTask') + ).to.be.greaterThan(-1); + }); + }); + + it('should not claim low priority tasks when there is no capacity', async () => { + // schedule a bunch of normal priority tasks that run frequently + const tasksToSchedule = []; + for (let i = 0; i < 10; i++) { + tasksToSchedule.push( + scheduleTask({ + taskType: 'sampleTask', + schedule: { interval: `1s` }, + params: {}, + }) + ); + } + + // schedule a low priority task + tasksToSchedule.push( + scheduleTask({ + taskType: 'lowPriorityTask', + schedule: { interval: `1s` }, + params: {}, + }) + ); + const scheduledTasks = await Promise.all(tasksToSchedule); + + // make sure all tasks get created + await retry.try(async () => { + const tasks = (await currentTasks()).docs; + expect(tasks.length).to.eql(11); + + const taskIds = tasks.map((task) => task.id); + scheduledTasks.forEach((scheduledTask) => { + expect(taskIds).to.contain(scheduledTask.id); + }); + }); + + // wait for 30 seconds to let the multiple task claiming cycles run + await new Promise((r) => setTimeout(r, 30000)); + + const docs: RawDoc[] = await historyDocs({ taskType: 'lowPriorityTask' }); + expect(docs.length).to.eql(0); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/index_management/inference_endpoints.ts b/x-pack/test_serverless/api_integration/test_suites/common/index_management/inference_endpoints.ts index cacf294b4c81a..87b760d84540c 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/index_management/inference_endpoints.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/index_management/inference_endpoints.ts @@ -18,7 +18,8 @@ export default function ({ getService }: FtrProviderContext) { const taskType = 'sparse_embedding'; const service = 'elser'; - describe('Inference endpoints', function () { + // FLAKY: https://github.com/elastic/kibana/issues/185216 + describe.skip('Inference endpoints', function () { // test adds new trained model '.elser_model_2_linux-x86_64', but does not clean it. Follow up tests are affected this.tags(['failsOnMKI']); before(async () => { diff --git a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/index.ts b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/index.ts index fab6e47e87969..e5f3f4a86a923 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/index.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/index.ts @@ -22,7 +22,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./role_mappings')); loadTestFile(require.resolve('./sessions')); loadTestFile(require.resolve('./users')); - loadTestFile(require.resolve('./request_as_viewer')); loadTestFile(require.resolve('./user_profiles')); loadTestFile(require.resolve('./views')); loadTestFile(require.resolve('./feature_check')); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/request_as_viewer.ts b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/request_as_viewer.ts deleted file mode 100644 index 7931f28f55df7..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/request_as_viewer.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import type { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService }: FtrProviderContext) { - describe('security/me as viewer', () => { - const svlUserManager = getService('svlUserManager'); - const svlCommonApi = getService('svlCommonApi'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - let credentials: { Cookie: string }; - - before(async () => { - // get auth header for Viewer role - credentials = await svlUserManager.getApiCredentialsForRole('viewer'); - }); - - it('returns valid user data for authenticated request', async () => { - const { body, status } = await supertestWithoutAuth - .get('/internal/security/me') - .set(svlCommonApi.getInternalRequestHeader()) - .set(credentials); - - const userData = await svlUserManager.getUserData('viewer'); - - expect(status).to.be(200); - expect(body.full_name).to.be(userData.fullname); - expect(body.email).to.be(userData.email); - }); - }); -} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/bulk_delete.ts b/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/bulk_delete.ts new file mode 100644 index 0000000000000..e79e9cfc7a5d4 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/bulk_delete.ts @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { SavedObjectWithMetadata } from '@kbn/saved-objects-management-plugin/common'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { RoleCredentials } from '../../../../shared/services'; + +export default function ({ getService }: FtrProviderContext) { + const svlCommonApi = getService('svlCommonApi'); + const svlUserManager = getService('svlUserManager'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const kibanaServer = getService('kibanaServer'); + let roleAuthc: RoleCredentials; + + describe('_bulk_delete', () => { + const endpoint = '/internal/kibana/management/saved_objects/_bulk_delete'; + const validObject = { type: 'visualization', id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab' }; + const invalidObject = { type: 'wigwags', id: 'foo' }; + + before(async () => { + roleAuthc = await svlUserManager.createApiKeyForRole('admin'); + }); + + after(async () => { + await svlUserManager.invalidateApiKeyForRole(roleAuthc); + }); + + beforeEach(() => + kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ) + ); + afterEach(() => + kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ) + ); + + function expectSuccess(index: number, objs: SavedObjectWithMetadata[]) { + const { type, id, error } = objs[index]; + expect(type).to.eql(validObject.type); + expect(id).to.eql(validObject.id); + expect(error).to.equal(undefined); + } + + function expectBadRequest(index: number, objs: SavedObjectWithMetadata[]) { + const { type, id, error } = objs[index]; + expect(type).to.eql(invalidObject.type); + expect(id).to.eql(invalidObject.id); + expect(error).to.eql({ + message: `Unsupported saved object type: '${invalidObject.type}': Bad Request`, + statusCode: 400, + error: 'Bad Request', + }); + } + + it('should return 200 for an existing object', async () => + await supertestWithoutAuth + .post(endpoint) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send([validObject]) + .expect(200) + .then(({ body }) => { + expect(body).to.have.length(1); + expectSuccess(0, body); + })); + + it('should return error for invalid object type', async () => + await supertestWithoutAuth + .post(endpoint) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send([invalidObject]) + .expect(200) + .then(({ body }) => { + expect(body).to.have.length(1); + expectBadRequest(0, body); + })); + + it('should return mix of successes and errors', async () => + await supertestWithoutAuth + .post(endpoint) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send([validObject, invalidObject]) + .expect(200) + .then(({ body }) => { + expect(body).to.have.length(2); + expectSuccess(0, body); + expectBadRequest(1, body); + })); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/bulk_get.ts b/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/bulk_get.ts new file mode 100644 index 0000000000000..c17f1a5f51e51 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/bulk_get.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { SavedObjectWithMetadata } from '@kbn/saved-objects-management-plugin/common'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { RoleCredentials } from '../../../../shared/services'; + +export default function ({ getService }: FtrProviderContext) { + const svlCommonApi = getService('svlCommonApi'); + const svlUserManager = getService('svlUserManager'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const kibanaServer = getService('kibanaServer'); + let roleAuthc: RoleCredentials; + + const URL = '/api/kibana/management/saved_objects/_bulk_get'; + const validObject = { type: 'visualization', id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab' }; + const invalidObject = { type: 'wigwags', id: 'foo' }; + + describe('_bulk_get', () => { + before(async () => { + roleAuthc = await svlUserManager.createApiKeyForRole('admin'); + }); + + after(async () => { + await svlUserManager.invalidateApiKeyForRole(roleAuthc); + }); + + describe('get objects in bulk', () => { + before(() => + kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ) + ); + after(() => + kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ) + ); + + function expectSuccess(index: number, objs: SavedObjectWithMetadata[]) { + const { type, id, meta, error } = objs[index]; + expect(type).to.eql(validObject.type); + expect(id).to.eql(validObject.id); + expect(meta).to.not.equal(undefined); + expect(error).to.equal(undefined); + } + + function expectBadRequest(index: number, objs: SavedObjectWithMetadata[]) { + const { type, id, error } = objs[index]; + expect(type).to.eql(invalidObject.type); + expect(id).to.eql(invalidObject.id); + expect(error).to.eql({ + message: `Unsupported saved object type: '${invalidObject.type}': Bad Request`, + statusCode: 400, + error: 'Bad Request', + }); + } + + it('should return 200 for object that exists and inject metadata', async () => + await supertestWithoutAuth + .post(URL) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send([validObject]) + .expect(200) + .then(({ body }) => { + expect(body).to.have.length(1); + expectSuccess(0, body); + })); + + it('should return error for invalid object type', async () => + await supertestWithoutAuth + .post(URL) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send([invalidObject]) + .expect(200) + .then(({ body }) => { + expect(body).to.have.length(1); + expectBadRequest(0, body); + })); + + it('should return mix of successes and errors', async () => + await supertestWithoutAuth + .post(URL) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send([validObject, invalidObject]) + .expect(200) + .then(({ body }) => { + expect(body).to.have.length(2); + expectSuccess(0, body); + expectBadRequest(1, body); + })); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/find.ts b/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/find.ts new file mode 100644 index 0000000000000..b9b890383a69c --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/find.ts @@ -0,0 +1,308 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { RoleCredentials } from '../../../../shared/services'; + +export default function ({ getService }: FtrProviderContext) { + const svlCommonApi = getService('svlCommonApi'); + const svlUserManager = getService('svlUserManager'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const kibanaServer = getService('kibanaServer'); + let roleAuthc: RoleCredentials; + + describe('find', () => { + before(async () => { + roleAuthc = await svlUserManager.createApiKeyForRole('admin'); + }); + after(async () => { + await svlUserManager.invalidateApiKeyForRole(roleAuthc); + }); + + describe('with kibana index - basic', () => { + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + it('should return 200 with individual responses', async () => { + const { body } = await supertestWithoutAuth + .get('/api/kibana/management/saved_objects/_find?type=visualization') + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + expect(body.saved_objects.map((so: { id: string }) => so.id)).to.eql([ + 'dd7caf20-9efd-11e7-acb3-3dab96693fab', + ]); + }); + + describe('unknown type', () => { + it('should return 200 with empty response', async () => { + const { body } = await supertestWithoutAuth + .get('/api/kibana/management/saved_objects/_find?type=wigwags') + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + expect(body).to.eql({ + page: 1, + per_page: 20, + total: 0, + saved_objects: [], + }); + }); + }); + + describe('page beyond total', () => { + it('should return 200 with empty response', async () => { + const { body } = await supertestWithoutAuth + .get( + '/api/kibana/management/saved_objects/_find?type=visualization&page=100&perPage=100' + ) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + + expect(body).to.eql({ + page: 100, + per_page: 100, + total: 1, + saved_objects: [], + }); + }); + }); + + describe('unknown search field', () => { + it('should return 400 when using searchFields', async () => { + const { body } = await supertestWithoutAuth + .get('/api/kibana/management/saved_objects/_find?type=url&searchFields=a') + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(400); + expect(body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: '[request query.searchFields]: definition for this key is missing', + }); + }); + }); + }); + + describe('with kibana index - relationships', () => { + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/references.json' + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/references.json' + ); + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' + ); + }); + describe('`hasReference` and `hasReferenceOperator` parameters', () => { + it('search for a reference', async () => { + const { body } = await supertestWithoutAuth + .get('/api/kibana/management/saved_objects/_find') + .query({ + type: 'visualization', + hasReference: JSON.stringify({ type: 'ref-type', id: 'ref-1' }), + }) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + const objects = body.saved_objects; + expect(objects.map((obj: any) => obj.id)).to.eql(['only-ref-1', 'ref-1-and-ref-2']); + }); + }); + + it('search for multiple references with OR operator', async () => { + await supertestWithoutAuth + .get('/api/kibana/management/saved_objects/_find') + .query({ + type: 'visualization', + hasReference: JSON.stringify([ + { type: 'ref-type', id: 'ref-1' }, + { type: 'ref-type', id: 'ref-2' }, + ]), + hasReferenceOperator: 'OR', + }) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .then((response) => { + expect(response.status).to.eql(200); + expect(response.body.saved_objects.length).not.to.be(null); + expect(response.body.saved_objects.map((obj: any) => obj.id).length).to.be.greaterThan( + 0 + ); + }); + }); + + it('search for multiple references with AND operator', async () => { + const { body } = await supertestWithoutAuth + .get('/api/kibana/management/saved_objects/_find') + .query({ + type: 'visualization', + hasReference: JSON.stringify([ + { type: 'ref-type', id: 'ref-1' }, + { type: 'ref-type', id: 'ref-2' }, + ]), + hasReferenceOperator: 'AND', + }) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + const objects = body.saved_objects; + expect(objects.map((obj: any) => obj.id)).to.eql(['ref-1-and-ref-2']); + }); + + describe('`sortField` and `sortOrder` parameters', () => { + it('sort objects by "type" in "asc" order', async () => { + const { body } = await supertestWithoutAuth + .get('/api/kibana/management/saved_objects/_find') + .query({ + type: ['visualization', 'dashboard'], + sortField: 'type', + sortOrder: 'asc', + }) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + + const objects = body.saved_objects; + expect(objects.length).be.greaterThan(1); // Need more than 1 result for our test + expect(objects[0].type).to.be('dashboard'); + }); + // does not work in serverless mode + it('sort objects by "type" in "desc" order', async () => { + const { body } = await supertestWithoutAuth + .get('/api/kibana/management/saved_objects/_find') + .query({ + type: ['visualization', 'dashboard'], + sortField: 'type', + sortOrder: 'desc', + }) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + + const objects = body.saved_objects; + expect(objects[0].type).to.be('visualization'); + }); + }); + }); + + describe('meta attributes injected properly', () => { + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/search.json' + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/search.json' + ); + await kibanaServer.savedObjects.cleanStandardList(); + }); + + it('should inject meta attributes for searches', async () => + await supertestWithoutAuth + .get('/api/kibana/management/saved_objects/_find?type=search') + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200) + .then((response) => { + expect(response.body.saved_objects).to.have.length(1); + expect(response.body.saved_objects[0].meta).to.eql({ + icon: 'discoverApp', + title: 'OneRecord', + hiddenType: false, + inAppUrl: { + path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357', + uiCapabilitiesPath: 'discover.show', + }, + namespaceType: 'multiple-isolated', + }); + })); + + it('should inject meta attributes for dashboards', async () => + await supertestWithoutAuth + .get('/api/kibana/management/saved_objects/_find?type=dashboard') + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200) + .then((response) => { + expect(response.body.saved_objects).to.have.length(1); + expect(response.body.saved_objects[0].meta).to.eql({ + icon: 'dashboardApp', + title: 'Dashboard', + hiddenType: false, + inAppUrl: { + path: '/app/dashboards#/view/b70c7ae0-3224-11e8-a572-ffca06da1357', + uiCapabilitiesPath: 'dashboard.show', + }, + namespaceType: 'multiple-isolated', + }); + })); + + it('should inject meta attributes for visualizations', async () => + await supertestWithoutAuth + .get('/api/kibana/management/saved_objects/_find?type=visualization') + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200) + .then((response) => { + expect(response.body.saved_objects).to.have.length(2); + expect(response.body.saved_objects[0].meta).to.eql({ + icon: 'visualizeApp', + title: 'VisualizationFromSavedSearch', + namespaceType: 'multiple-isolated', + hiddenType: false, + }); + expect(response.body.saved_objects[1].meta).to.eql({ + icon: 'visualizeApp', + title: 'Visualization', + namespaceType: 'multiple-isolated', + hiddenType: false, + }); + })); + + it('should inject meta attributes for index patterns', async () => + await supertestWithoutAuth + .get('/api/kibana/management/saved_objects/_find?type=index-pattern') + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200) + .then((response) => { + expect(response.body.saved_objects).to.have.length(1); + expect(response.body.saved_objects[0].meta).to.eql({ + icon: 'indexPatternApp', + title: 'saved_objects*', + hiddenType: false, + editUrl: '/management/kibana/dataViews/dataView/8963ca30-3224-11e8-a572-ffca06da1357', + inAppUrl: { + path: '/app/management/kibana/dataViews/dataView/8963ca30-3224-11e8-a572-ffca06da1357', + uiCapabilitiesPath: 'management.kibana.indexPatterns', + }, + namespaceType: 'multiple', + }); + })); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/index.ts b/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/index.ts new file mode 100644 index 0000000000000..2da0263122550 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('saved objects management apis', () => { + loadTestFile(require.resolve('./find')); + loadTestFile(require.resolve('./bulk_get')); + loadTestFile(require.resolve('./bulk_delete')); + loadTestFile(require.resolve('./scroll_count')); + loadTestFile(require.resolve('./relationships')); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/relationships.ts b/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/relationships.ts new file mode 100644 index 0000000000000..dbffe02e290d6 --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/relationships.ts @@ -0,0 +1,473 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license 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 { schema } from '@kbn/config-schema'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { RoleCredentials } from '../../../../shared/services'; + +export default function ({ getService }: FtrProviderContext) { + const svlCommonApi = getService('svlCommonApi'); + const svlUserManager = getService('svlUserManager'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const kibanaServer = getService('kibanaServer'); + let roleAuthc: RoleCredentials; + + const relationSchema = schema.object({ + id: schema.string(), + type: schema.string(), + relationship: schema.oneOf([schema.literal('parent'), schema.literal('child')]), + meta: schema.object({ + title: schema.string(), + icon: schema.string(), + editUrl: schema.maybe(schema.string()), + // visualizations don't declare an inAppUrl + inAppUrl: schema.maybe( + schema.object({ + path: schema.string(), + uiCapabilitiesPath: schema.string(), + }) + ), + namespaceType: schema.string(), + hiddenType: schema.boolean(), + }), + }); + const invalidRelationSchema = schema.object({ + id: schema.string(), + type: schema.string(), + relationship: schema.oneOf([schema.literal('parent'), schema.literal('child')]), + error: schema.string(), + }); + const responseSchema = schema.object({ + relations: schema.arrayOf(relationSchema), + invalidRelations: schema.arrayOf(invalidRelationSchema), + }); + + describe('relationships', () => { + before(async () => { + roleAuthc = await svlUserManager.createApiKeyForRole('admin'); + + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/management/saved_objects/relationships.json' + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/management/saved_objects/relationships.json' + ); + await kibanaServer.savedObjects.cleanStandardList(); + + await svlUserManager.invalidateApiKeyForRole(roleAuthc); + }); + + const baseApiUrl = `/api/kibana/management/saved_objects/relationships`; + const defaultTypes = ['visualization', 'index-pattern', 'search', 'dashboard']; + + const relationshipsUrl = (type: string, id: string, types: string[] = defaultTypes) => { + const typesQuery = types.map((t) => `savedObjectTypes=${t}`).join('&'); + return `${baseApiUrl}/${type}/${id}?${typesQuery}`; + }; + + describe('validate response schema', () => { + it('search', async () => { + const resp = await supertestWithoutAuth + .get(relationshipsUrl('search', '960372e0-3224-11e8-a572-ffca06da1357')) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + + expect(() => { + responseSchema.validate(resp.body); + }).not.to.throwError(); + }); + it('dashboard', async () => { + const resp = await supertestWithoutAuth + .get(relationshipsUrl('dashboard', 'b70c7ae0-3224-11e8-a572-ffca06da1357')) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + + expect(() => { + responseSchema.validate(resp.body); + }).not.to.throwError(); + }); + it('visualization', async () => { + const resp = await supertestWithoutAuth + .get(relationshipsUrl('visualization', 'a42c0580-3224-11e8-a572-ffca06da1357')) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + + expect(() => { + responseSchema.validate(resp.body); + }).not.to.throwError(); + }); + it('index-pattern', async () => { + const resp = await supertestWithoutAuth + .get(relationshipsUrl('index-pattern', '8963ca30-3224-11e8-a572-ffca06da1357')) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + + expect(() => { + responseSchema.validate(resp.body); + }).not.to.throwError(); + }); + it('invalid-refs', async () => { + const resp = await supertestWithoutAuth + .get(relationshipsUrl('dashboard', 'invalid-refs')) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + + expect(() => { + responseSchema.validate(resp.body); + }).not.to.throwError(); + }); + }); + + describe('should work', () => { + it('for searches', async () => { + const resp = await supertestWithoutAuth + .get(relationshipsUrl('search', '960372e0-3224-11e8-a572-ffca06da1357')) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + expect(resp.body.relations).to.eql([ + { + id: '8963ca30-3224-11e8-a572-ffca06da1357', + type: 'index-pattern', + relationship: 'child', + meta: { + title: 'saved_objects*', + icon: 'indexPatternApp', + editUrl: '/management/kibana/dataViews/dataView/8963ca30-3224-11e8-a572-ffca06da1357', + inAppUrl: { + path: '/app/management/kibana/dataViews/dataView/8963ca30-3224-11e8-a572-ffca06da1357', + uiCapabilitiesPath: 'management.kibana.indexPatterns', + }, + namespaceType: 'multiple', + hiddenType: false, + }, + }, + { + id: 'a42c0580-3224-11e8-a572-ffca06da1357', + type: 'visualization', + relationship: 'parent', + meta: { + title: 'VisualizationFromSavedSearch', + icon: 'visualizeApp', + namespaceType: 'multiple-isolated', + hiddenType: false, + }, + }, + ]); + }); + it('for dashboards', async () => { + const resp = await supertestWithoutAuth + .get(relationshipsUrl('dashboard', 'b70c7ae0-3224-11e8-a572-ffca06da1357')) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + expect(resp.body.relations).to.eql([ + { + id: 'add810b0-3224-11e8-a572-ffca06da1357', + type: 'visualization', + relationship: 'child', + meta: { + icon: 'visualizeApp', + title: 'Visualization', + namespaceType: 'multiple-isolated', + hiddenType: false, + }, + }, + { + id: 'a42c0580-3224-11e8-a572-ffca06da1357', + type: 'visualization', + relationship: 'child', + meta: { + icon: 'visualizeApp', + title: 'VisualizationFromSavedSearch', + namespaceType: 'multiple-isolated', + hiddenType: false, + }, + }, + ]); + }); + it('for visualizations', async () => { + const resp = await supertestWithoutAuth + .get(relationshipsUrl('visualization', 'a42c0580-3224-11e8-a572-ffca06da1357')) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + + expect(resp.body.relations).to.eql([ + { + id: '960372e0-3224-11e8-a572-ffca06da1357', + type: 'search', + relationship: 'child', + meta: { + icon: 'discoverApp', + title: 'OneRecord', + inAppUrl: { + path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357', + uiCapabilitiesPath: 'discover.show', + }, + namespaceType: 'multiple-isolated', + hiddenType: false, + }, + }, + { + id: 'b70c7ae0-3224-11e8-a572-ffca06da1357', + type: 'dashboard', + relationship: 'parent', + meta: { + icon: 'dashboardApp', + title: 'Dashboard', + inAppUrl: { + path: '/app/dashboards#/view/b70c7ae0-3224-11e8-a572-ffca06da1357', + uiCapabilitiesPath: 'dashboard.show', + }, + namespaceType: 'multiple-isolated', + hiddenType: false, + }, + }, + ]); + }); + it('for index patterns', async () => { + const resp = await supertestWithoutAuth + .get(relationshipsUrl('index-pattern', '8963ca30-3224-11e8-a572-ffca06da1357')) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + + expect(resp.body.relations).to.eql([ + { + id: '960372e0-3224-11e8-a572-ffca06da1357', + type: 'search', + relationship: 'parent', + meta: { + icon: 'discoverApp', + title: 'OneRecord', + inAppUrl: { + path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357', + uiCapabilitiesPath: 'discover.show', + }, + namespaceType: 'multiple-isolated', + hiddenType: false, + }, + }, + { + id: 'add810b0-3224-11e8-a572-ffca06da1357', + type: 'visualization', + relationship: 'parent', + meta: { + icon: 'visualizeApp', + title: 'Visualization', + namespaceType: 'multiple-isolated', + hiddenType: false, + }, + }, + ]); + }); + }); + + describe('should filter based on savedObjectTypes', () => { + it('search', async () => { + const resp = await supertestWithoutAuth + .get( + relationshipsUrl('search', '960372e0-3224-11e8-a572-ffca06da1357', ['visualization']) + ) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + + expect(resp.body.relations).to.eql([ + { + id: '8963ca30-3224-11e8-a572-ffca06da1357', + type: 'index-pattern', + meta: { + icon: 'indexPatternApp', + title: 'saved_objects*', + editUrl: '/management/kibana/dataViews/dataView/8963ca30-3224-11e8-a572-ffca06da1357', + inAppUrl: { + path: '/app/management/kibana/dataViews/dataView/8963ca30-3224-11e8-a572-ffca06da1357', + uiCapabilitiesPath: 'management.kibana.indexPatterns', + }, + namespaceType: 'multiple', + hiddenType: false, + }, + relationship: 'child', + }, + { + id: 'a42c0580-3224-11e8-a572-ffca06da1357', + type: 'visualization', + meta: { + icon: 'visualizeApp', + title: 'VisualizationFromSavedSearch', + namespaceType: 'multiple-isolated', + hiddenType: false, + }, + relationship: 'parent', + }, + ]); + }); + it('dashboard', async () => { + const resp = await supertestWithoutAuth + .get(relationshipsUrl('dashboard', 'b70c7ae0-3224-11e8-a572-ffca06da1357', ['search'])) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + + expect(resp.body.relations).to.eql([ + { + id: 'add810b0-3224-11e8-a572-ffca06da1357', + type: 'visualization', + meta: { + icon: 'visualizeApp', + title: 'Visualization', + namespaceType: 'multiple-isolated', + hiddenType: false, + }, + relationship: 'child', + }, + { + id: 'a42c0580-3224-11e8-a572-ffca06da1357', + type: 'visualization', + meta: { + icon: 'visualizeApp', + title: 'VisualizationFromSavedSearch', + namespaceType: 'multiple-isolated', + hiddenType: false, + }, + relationship: 'child', + }, + ]); + }); + it('visualization', async () => { + const resp = await supertestWithoutAuth + .get( + relationshipsUrl('visualization', 'a42c0580-3224-11e8-a572-ffca06da1357', ['search']) + ) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + + expect(resp.body.relations).to.eql([ + { + id: '960372e0-3224-11e8-a572-ffca06da1357', + type: 'search', + meta: { + icon: 'discoverApp', + title: 'OneRecord', + inAppUrl: { + path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357', + uiCapabilitiesPath: 'discover.show', + }, + namespaceType: 'multiple-isolated', + hiddenType: false, + }, + relationship: 'child', + }, + ]); + }); + it('index-pattern', async () => { + const resp = await supertestWithoutAuth + .get( + relationshipsUrl('index-pattern', '8963ca30-3224-11e8-a572-ffca06da1357', ['search']) + ) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + + expect(resp.body.relations).to.eql([ + { + id: '960372e0-3224-11e8-a572-ffca06da1357', + type: 'search', + meta: { + icon: 'discoverApp', + title: 'OneRecord', + inAppUrl: { + path: '/app/discover#/view/960372e0-3224-11e8-a572-ffca06da1357', + uiCapabilitiesPath: 'discover.show', + }, + namespaceType: 'multiple-isolated', + hiddenType: false, + }, + relationship: 'parent', + }, + ]); + }); + }); + + describe('should return 404 no results for', () => { + it('a search', async () => { + await supertestWithoutAuth + .get(relationshipsUrl('search', 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(404); + }); + it('a dashboard', async () => { + await supertestWithoutAuth + .get(relationshipsUrl('dashboard', 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(404); + }); + it('a visualization', async () => { + await supertestWithoutAuth + .get(relationshipsUrl('visualization', 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(404); + }); + it('an index pattern', async () => { + await supertestWithoutAuth + .get(relationshipsUrl('index-pattern', 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(404); + }); + }); + + describe('invalid references', () => { + it('should return the invalid relations', async () => { + const resp = await supertestWithoutAuth + .get(relationshipsUrl('dashboard', 'invalid-refs')) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + + expect(resp.body).to.eql({ + invalidRelations: [ + { + error: 'Saved object [visualization/invalid-vis] not found', + id: 'invalid-vis', + relationship: 'child', + type: 'visualization', + }, + ], + relations: [ + { + id: 'add810b0-3224-11e8-a572-ffca06da1357', + meta: { + icon: 'visualizeApp', + namespaceType: 'multiple-isolated', + hiddenType: false, + title: 'Visualization', + }, + relationship: 'child', + type: 'visualization', + }, + ], + }); + }); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/scroll_count.ts b/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/scroll_count.ts new file mode 100644 index 0000000000000..8ba561daab9fc --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/common/saved_objects_management/scroll_count.ts @@ -0,0 +1,193 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { RoleCredentials } from '../../../../shared/services'; + +const apiUrl = '/api/kibana/management/saved_objects/scroll/counts'; +const defaultTypes = ['visualization', 'index-pattern', 'search', 'dashboard']; + +export default function ({ getService }: FtrProviderContext) { + const svlCommonApi = getService('svlCommonApi'); + const svlUserManager = getService('svlUserManager'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const kibanaServer = getService('kibanaServer'); + let roleAuthc: RoleCredentials; + + describe('scroll_count', () => { + before(async () => { + roleAuthc = await svlUserManager.createApiKeyForRole('admin'); + }); + + after(async () => { + await svlUserManager.invalidateApiKeyForRole(roleAuthc); + }); + describe('scroll_count with more than 10k objects', () => { + const importVisualizations = async ({ + startIdx = 1, + endIdx, + }: { + startIdx?: number; + endIdx: number; + }) => { + const fileChunks: string[] = []; + for (let i = startIdx; i <= endIdx; i++) { + const id = `test-vis-${i}`; + fileChunks.push( + JSON.stringify({ + type: 'visualization', + id, + attributes: { + title: `My visualization (${i})`, + uiStateJSON: '{}', + visState: '{}', + }, + references: [], + }) + ); + } + + await supertestWithoutAuth + .post(`/api/saved_objects/_import`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .attach('file', Buffer.from(fileChunks.join('\n'), 'utf8'), 'export.ndjson') + .expect(200); + }; + + const deleteVisualizations = async ({ + startIdx = 1, + endIdx, + }: { + startIdx?: number; + endIdx: number; + }) => { + const objsToDelete: any[] = []; + for (let i = startIdx; i <= endIdx; i++) { + const id = `test-vis-${i}`; + objsToDelete.push({ type: 'visualization', id }); + } + await kibanaServer.savedObjects.bulkDelete({ objects: objsToDelete }); + }; + + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await importVisualizations({ startIdx: 1, endIdx: 6000 }); + await importVisualizations({ startIdx: 6001, endIdx: 12000 }); + }); + + after(async () => { + // kibanaServer.savedObjects.cleanStandardList({}); times out for 12000 items + await deleteVisualizations({ startIdx: 1, endIdx: 3000 }); + await deleteVisualizations({ startIdx: 3001, endIdx: 6000 }); + await deleteVisualizations({ startIdx: 6001, endIdx: 9000 }); + await deleteVisualizations({ startIdx: 9001, endIdx: 12000 }); + }); + + it('returns the correct count for each included types', async () => { + const { body } = await supertestWithoutAuth + .post(apiUrl) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send({ + typesToInclude: ['visualization'], + }) + .expect(200); + + expect(body).to.eql({ + visualization: 12000, + }); + }); + }); + describe('with less than 10k objects', () => { + before(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/scroll_count.json' + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/scroll_count.json' + ); + await kibanaServer.savedObjects.cleanStandardList(); + }); + + it('returns the count for each included types', async () => { + const { body } = await supertestWithoutAuth + .post(apiUrl) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send({ + typesToInclude: defaultTypes, + }) + .expect(200); + + expect(body).to.eql({ + dashboard: 2, + 'index-pattern': 1, + search: 1, + visualization: 2, + }); + }); + + it('only returns count for types to include', async () => { + const { body } = await supertestWithoutAuth + .post(apiUrl) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send({ + typesToInclude: ['dashboard', 'search'], + }) + .expect(200); + + expect(body).to.eql({ + dashboard: 2, + search: 1, + }); + }); + + it('filters on title when `searchString` is provided', async () => { + const { body } = await supertestWithoutAuth + .post(apiUrl) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send({ + typesToInclude: defaultTypes, + searchString: 'Amazing', + }) + .expect(200); + + expect(body).to.eql({ + dashboard: 1, + visualization: 1, + 'index-pattern': 0, + search: 0, + }); + }); + + it('includes all requested types even when none match the search', async () => { + const { body } = await supertestWithoutAuth + .post(apiUrl) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send({ + typesToInclude: ['dashboard', 'search', 'visualization'], + searchString: 'nothing-will-match', + }) + .expect(200); + + expect(body).to.eql({ + dashboard: 0, + visualization: 0, + search: 0, + }); + }); + }); + }); +} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/search_oss/bsearch.ts b/x-pack/test_serverless/api_integration/test_suites/common/search_oss/bsearch.ts index ba1f974a01608..063f6623738bb 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/search_oss/bsearch.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/search_oss/bsearch.ts @@ -10,6 +10,7 @@ import request from 'superagent'; import { inflateResponse } from '@kbn/bfetch-plugin/public/streaming'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import { BFETCH_ROUTE_VERSION_LATEST } from '@kbn/bfetch-plugin/common'; +import { RoleCredentials } from '../../../../shared/services'; import type { FtrProviderContext } from '../../../ftr_provider_context'; import { painlessErrReq } from './painless_err_req'; import { verifyErrorResponse } from './verify_error'; @@ -24,18 +25,28 @@ function parseBfetchResponse(resp: request.Response, compressed: boolean = false } export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const svlCommonApi = getService('svlCommonApi'); + const svlUserManager = getService('svlUserManager'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + let roleAuthc: RoleCredentials; + describe('bsearch', () => { + before(async () => { + roleAuthc = await svlUserManager.createApiKeyForRole('admin'); + }); + after(async () => { + await svlUserManager.invalidateApiKeyForRole(roleAuthc); + }); describe('post', () => { it('should return 200 a single response', async () => { - const resp = await supertest + const resp = await supertestWithoutAuth .post(`/internal/bsearch`) .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) // TODO: API requests in Serverless require internal request headers .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) .send({ batch: [ { @@ -66,11 +77,12 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return 200 a single response from compressed', async () => { - const resp = await supertest + const resp = await supertestWithoutAuth .post(`/internal/bsearch?compress=true`) .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) // TODO: API requests in Serverless require internal request headers .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) .send({ batch: [ { @@ -101,11 +113,12 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return a batch of successful responses', async () => { - const resp = await supertest + const resp = await supertestWithoutAuth .post(`/internal/bsearch`) .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) // TODO: API requests in Serverless require internal request headers .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) .send({ batch: [ { @@ -146,11 +159,12 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return error for not found strategy', async () => { - const resp = await supertest + const resp = await supertestWithoutAuth .post(`/internal/bsearch`) .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) // TODO: API requests in Serverless require internal request headers .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) .send({ batch: [ { @@ -179,11 +193,12 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return 400 when index type is provided in "es" strategy', async () => { - const resp = await supertest + const resp = await supertestWithoutAuth .post(`/internal/bsearch`) .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) // TODO: API requests in Serverless require internal request headers .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) .send({ batch: [ { @@ -221,11 +236,12 @@ export default function ({ getService }: FtrProviderContext) { await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); }); it('should return 400 "search_phase_execution_exception" for Painless error in "es" strategy', async () => { - const resp = await supertest + const resp = await supertestWithoutAuth .post(`/internal/bsearch`) .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) // TODO: API requests in Serverless require internal request headers .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) .send({ batch: [ { diff --git a/x-pack/test_serverless/api_integration/test_suites/common/search_oss/search.ts b/x-pack/test_serverless/api_integration/test_suites/common/search_oss/search.ts index 85a57a2c2d272..51702ad4e272a 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/search_oss/search.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/search_oss/search.ts @@ -7,17 +7,27 @@ import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import expect from '@kbn/expect'; +import { RoleCredentials } from '../../../../shared/services'; import type { FtrProviderContext } from '../../../ftr_provider_context'; import { painlessErrReq } from './painless_err_req'; import { verifyErrorResponse } from './verify_error'; export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const svlCommonApi = getService('svlCommonApi'); const kibanaServer = getService('kibanaServer'); + const svlUserManager = getService('svlUserManager'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + let roleAuthc: RoleCredentials; + describe('search', () => { + before(async () => { + roleAuthc = await svlUserManager.createApiKeyForRole('admin'); + }); + after(async () => { + await svlUserManager.invalidateApiKeyForRole(roleAuthc); + }); before(async () => { // TODO: emptyKibanaIndex fails in Serverless with // "index_not_found_exception: no such index [.kibana_ingest]", @@ -31,11 +41,12 @@ export default function ({ getService }: FtrProviderContext) { }); describe('post', () => { it('should return 200 when correctly formatted searches are provided', async () => { - const resp = await supertest + const resp = await supertestWithoutAuth .post(`/internal/search/es`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') // TODO: API requests in Serverless require internal request headers .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) .send({ params: { body: { @@ -55,11 +66,12 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return 200 if terminated early', async () => { - const resp = await supertest + const resp = await supertestWithoutAuth .post(`/internal/search/es`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') // TODO: API requests in Serverless require internal request headers .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) .send({ params: { terminateAfter: 1, @@ -82,11 +94,12 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return 404 when if no strategy is provided', async () => { - const resp = await supertest + const resp = await supertestWithoutAuth .post(`/internal/search`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') // TODO: API requests in Serverless require internal request headers .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) .send({ body: { query: { @@ -100,11 +113,12 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return 404 when if unknown strategy is provided', async () => { - const resp = await supertest + const resp = await supertestWithoutAuth .post(`/internal/search/banana`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') // TODO: API requests in Serverless require internal request headers .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) .send({ body: { query: { @@ -120,11 +134,12 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return 400 with illegal ES argument', async () => { - const resp = await supertest + const resp = await supertestWithoutAuth .post(`/internal/search/es`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') // TODO: API requests in Serverless require internal request headers .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) .send({ params: { timeout: 1, // This should be a time range string! @@ -143,11 +158,12 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return 400 with a bad body', async () => { - const resp = await supertest + const resp = await supertestWithoutAuth .post(`/internal/search/es`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') // TODO: API requests in Serverless require internal request headers .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) .send({ params: { body: { @@ -162,11 +178,12 @@ export default function ({ getService }: FtrProviderContext) { }); it('should return 400 for a painless error', async () => { - const resp = await supertest + const resp = await supertestWithoutAuth .post(`/internal/search/es`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') // TODO: API requests in Serverless require internal request headers .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) .send(painlessErrReq) .expect(400); @@ -176,22 +193,24 @@ export default function ({ getService }: FtrProviderContext) { describe('delete', () => { it('should return 404 when no search id provided', async () => { - const resp = await supertest + const resp = await supertestWithoutAuth .delete(`/internal/search/es`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') // TODO: API requests in Serverless require internal request headers .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) .send() .expect(404); verifyErrorResponse(resp.body, 404); }); it('should return 400 when trying a delete on a non supporting strategy', async () => { - const resp = await supertest + const resp = await supertestWithoutAuth .delete(`/internal/search/es/123`) .set(ELASTIC_HTTP_VERSION_HEADER, '1') // TODO: API requests in Serverless require internal request headers .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) .send() .expect(400); verifyErrorResponse(resp.body, 400); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/common_configs/config.group1.ts b/x-pack/test_serverless/api_integration/test_suites/observability/common_configs/config.group1.ts index b0376b0925312..4d58d43ea7667 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/common_configs/config.group1.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/common_configs/config.group1.ts @@ -30,6 +30,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('../../common/grok_debugger'), require.resolve('../../common/painless_lab'), require.resolve('../../common/console'), + require.resolve('../../common/saved_objects_management'), require.resolve('../../common/telemetry'), ], junit: { diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/slos/create_slo.ts b/x-pack/test_serverless/api_integration/test_suites/observability/slos/create_slo.ts index 02ac42ad6e81a..197d68bdaa77f 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/slos/create_slo.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/slos/create_slo.ts @@ -149,10 +149,10 @@ export default function ({ getService }: FtrProviderContext) { const expectedTransforms: ExpectedTransforms = { count: 2, results: { - transform0: { id: 'slo-my-custom-id1-1', destIndex: '.slo-observability.sli-v3.2' }, + transform0: { id: 'slo-my-custom-id1-1', destIndex: '.slo-observability.sli-v3.3' }, transform1: { id: 'slo-summary-my-custom-id1-1', - destIndex: '.slo-observability.summary-v3.2', + destIndex: '.slo-observability.summary-v3.3', }, }, typeOfVersion: 'string', @@ -318,25 +318,25 @@ export default function ({ getService }: FtrProviderContext) { const expectedTransforms: ExpectedTransforms = { count: 8, results: { - transform0: { id: 'slo-my-custom-id1-1', destIndex: '.slo-observability.sli-v3.2' }, - transform1: { id: 'slo-my-custom-id2-1', destIndex: '.slo-observability.sli-v3.2' }, - transform2: { id: 'slo-my-custom-id3-1', destIndex: '.slo-observability.sli-v3.2' }, - transform3: { id: 'slo-my-custom-id4-1', destIndex: '.slo-observability.sli-v3.2' }, + transform0: { id: 'slo-my-custom-id1-1', destIndex: '.slo-observability.sli-v3.3' }, + transform1: { id: 'slo-my-custom-id2-1', destIndex: '.slo-observability.sli-v3.3' }, + transform2: { id: 'slo-my-custom-id3-1', destIndex: '.slo-observability.sli-v3.3' }, + transform3: { id: 'slo-my-custom-id4-1', destIndex: '.slo-observability.sli-v3.3' }, transform4: { id: 'slo-summary-my-custom-id1-1', - destIndex: '.slo-observability.summary-v3.2', + destIndex: '.slo-observability.summary-v3.3', }, transform5: { id: 'slo-summary-my-custom-id2-1', - destIndex: '.slo-observability.summary-v3.2', + destIndex: '.slo-observability.summary-v3.3', }, transform6: { id: 'slo-summary-my-custom-id3-1', - destIndex: '.slo-observability.summary-v3.2', + destIndex: '.slo-observability.summary-v3.3', }, transform7: { id: 'slo-summary-my-custom-id4-1', - destIndex: '.slo-observability.summary-v3.2', + destIndex: '.slo-observability.summary-v3.3', }, }, typeOfVersion: 'string', diff --git a/x-pack/test_serverless/api_integration/test_suites/search/common_configs/config.group1.ts b/x-pack/test_serverless/api_integration/test_suites/search/common_configs/config.group1.ts index d17e53d8c3372..4167364e44793 100644 --- a/x-pack/test_serverless/api_integration/test_suites/search/common_configs/config.group1.ts +++ b/x-pack/test_serverless/api_integration/test_suites/search/common_configs/config.group1.ts @@ -28,6 +28,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('../../common/core'), require.resolve('../../common/reporting'), require.resolve('../../common/console'), + require.resolve('../../common/saved_objects_management'), require.resolve('../../common/telemetry'), ], junit: { diff --git a/x-pack/test_serverless/api_integration/test_suites/security/common_configs/config.group1.ts b/x-pack/test_serverless/api_integration/test_suites/security/common_configs/config.group1.ts index c15cc90f48ed3..406de533a909d 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/common_configs/config.group1.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/common_configs/config.group1.ts @@ -30,6 +30,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('../../common/grok_debugger'), require.resolve('../../common/painless_lab'), require.resolve('../../common/console'), + require.resolve('../../common/saved_objects_management'), require.resolve('../../common/telemetry'), ], junit: { diff --git a/x-pack/test_serverless/functional/config.base.ts b/x-pack/test_serverless/functional/config.base.ts index 07aa6c9af5318..916be8ecd5cf3 100644 --- a/x-pack/test_serverless/functional/config.base.ts +++ b/x-pack/test_serverless/functional/config.base.ts @@ -16,7 +16,6 @@ import type { CreateTestConfigOptions } from '../shared/types'; export function createTestConfig(options: CreateTestConfigOptions) { return async ({ readConfigFile }: FtrConfigProviderContext) => { const svlSharedConfig = await readConfigFile(require.resolve('../shared/config.base.ts')); - return { ...svlSharedConfig.getAll(), @@ -110,6 +109,9 @@ export function createTestConfig(options: CreateTestConfigOptions) { maintenanceWindows: { pathname: '/app/management/insightsAndAlerting/maintenanceWindows', }, + fleet: { + pathname: '/app/fleet', + }, }, // choose where screenshots should be saved screenshots: { diff --git a/x-pack/test_serverless/functional/page_objects/index.ts b/x-pack/test_serverless/functional/page_objects/index.ts index f1604d48508e2..94e02f9c5e455 100644 --- a/x-pack/test_serverless/functional/page_objects/index.ts +++ b/x-pack/test_serverless/functional/page_objects/index.ts @@ -21,6 +21,7 @@ import { SvlRuleDetailsPageProvider } from './svl_rule_details_ui_page'; import { SvlSearchConnectorsPageProvider } from './svl_search_connectors_page'; import { SvlManagementPageProvider } from './svl_management_page'; import { SvlIngestPipelines } from './svl_ingest_pipelines'; +import { SvlSearchHomePageProvider } from './svl_search_homepage'; export const pageObjects = { ...xpackFunctionalPageObjects, @@ -38,4 +39,5 @@ export const pageObjects = { svlRuleDetailsUI: SvlRuleDetailsPageProvider, svlManagementPage: SvlManagementPageProvider, svlIngestPipelines: SvlIngestPipelines, + svlSearchHomePage: SvlSearchHomePageProvider, }; diff --git a/x-pack/test_serverless/functional/page_objects/svl_common_page.ts b/x-pack/test_serverless/functional/page_objects/svl_common_page.ts index 49113e5565435..827821c128daa 100644 --- a/x-pack/test_serverless/functional/page_objects/svl_common_page.ts +++ b/x-pack/test_serverless/functional/page_objects/svl_common_page.ts @@ -100,14 +100,14 @@ export function SvlCommonPageProvider({ getService, getPageObjects }: FtrProvide .set(svlCommonApi.getInternalRequestHeader()) .set({ Cookie: `sid=${browserCookies[0].value}` }); - const userData = await svlUserManager.getUserData(role); + const email = await svlUserManager.getEmail(role); // email returned from API call must match the email for the specified role - if (body.email === userData.email) { + if (body.email === email) { log.debug(`The new cookie is properly set for '${role}' role`); } else { log.debug(`API response body: ${JSON.stringify(body)}`); throw new Error( - `Cookie is not set properly, expected email is '${userData.email}', but found '${body.email}'` + `Cookie is not set properly, expected email is '${email}', but found '${body.email}'` ); } // Verifying that we are logged in diff --git a/x-pack/test_serverless/functional/page_objects/svl_search_homepage.ts b/x-pack/test_serverless/functional/page_objects/svl_search_homepage.ts new file mode 100644 index 0000000000000..eeb1b6de731f9 --- /dev/null +++ b/x-pack/test_serverless/functional/page_objects/svl_search_homepage.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../ftr_provider_context'; + +export function SvlSearchHomePageProvider({ getService }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + const browser = getService('browser'); + + return { + async expectToBeOnHomepage() { + expect(await browser.getCurrentUrl()).contain('/app/elasticsearch/home'); + }, + async expectToNotBeOnHomepage() { + expect(await browser.getCurrentUrl()).not.contain('/app/elasticsearch/home'); + }, + async expectHomepageHeader() { + await testSubjects.existOrFail('search-homepage-header', { timeout: 2000 }); + }, + }; +} diff --git a/x-pack/test_serverless/functional/services/index.ts b/x-pack/test_serverless/functional/services/index.ts index a1112232377cd..c63a16b4402f1 100644 --- a/x-pack/test_serverless/functional/services/index.ts +++ b/x-pack/test_serverless/functional/services/index.ts @@ -16,6 +16,7 @@ import { SvlCommonScreenshotsProvider } from './svl_common_screenshots'; import { SvlCasesServiceProvider } from '../../api_integration/services/svl_cases'; import { MachineLearningProvider } from './ml'; import { LogsSynthtraceProvider } from './log'; +import { UISettingsServiceProvider } from './ui_settings'; export const services = { // deployment agnostic FTR services @@ -30,6 +31,7 @@ export const services = { svlCommonScreenshots: SvlCommonScreenshotsProvider, svlCases: SvlCasesServiceProvider, svlMl: MachineLearningProvider, + uiSettings: UISettingsServiceProvider, // log services svlLogsSynthtraceClient: LogsSynthtraceProvider, }; diff --git a/x-pack/test_serverless/functional/services/ui_settings.ts b/x-pack/test_serverless/functional/services/ui_settings.ts new file mode 100644 index 0000000000000..337930790489d --- /dev/null +++ b/x-pack/test_serverless/functional/services/ui_settings.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 { FtrProviderContext } from '../ftr_provider_context'; +import { RoleCredentials } from '../../shared/services'; + +export function UISettingsServiceProvider({ getService }: FtrProviderContext) { + const svlCommonApi = getService('svlCommonApi'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + return { + async setUiSetting(role: RoleCredentials, settingId: string, value: unknown) { + await supertestWithoutAuth + .post(`/internal/kibana/settings/${settingId}`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(role.apiKeyHeader) + .send({ value }) + .expect(200); + }, + async deleteUISetting(role: RoleCredentials, settingId: string) { + await supertestWithoutAuth + .delete(`/internal/kibana/settings/${settingId}`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(role.apiKeyHeader) + .expect(200); + }, + }; +} diff --git a/x-pack/test_serverless/functional/test_suites/common/context/_filters.ts b/x-pack/test_serverless/functional/test_suites/common/context/_filters.ts index 47f864787e6c5..95197c1e20bd6 100644 --- a/x-pack/test_serverless/functional/test_suites/common/context/_filters.ts +++ b/x-pack/test_serverless/functional/test_suites/common/context/_filters.ts @@ -21,7 +21,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); const kibanaServer = getService('kibanaServer'); - const PageObjects = getPageObjects(['common', 'context', 'svlCommonPage']); + const PageObjects = getPageObjects(['common', 'context', 'svlCommonPage', 'discover']); const testSubjects = getService('testSubjects'); describe('context filters', function contextSize() { @@ -42,6 +42,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('inclusive filter should be addable via expanded data grid rows', async function () { await retry.waitFor(`filter ${TEST_ANCHOR_FILTER_FIELD} in filterbar`, async () => { await dataGrid.clickRowToggle({ isAnchorRow: true, renderMoreRows: true }); + await PageObjects.discover.findFieldByNameInDocViewer(TEST_ANCHOR_FILTER_FIELD); await dataGrid.clickFieldActionInFlyout( TEST_ANCHOR_FILTER_FIELD, 'addFilterForValueButton' diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/_data_source_profile.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/_data_source_profile.ts new file mode 100644 index 0000000000000..317835958b0a9 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/_data_source_profile.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import kbnRison from '@kbn/rison'; +import expect from '@kbn/expect'; +import type { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'timePicker', 'discover', 'unifiedFieldList']); + const testSubjects = getService('testSubjects'); + const dataViews = getService('dataViews'); + + describe('data source profile', () => { + describe('ES|QL mode', () => { + describe('cell renderers', () => { + it('should not render custom @timestamp or log.level', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-* | sort @timestamp desc' }, + }); + await PageObjects.common.navigateToApp('discover', { + hash: `/?_a=${state}`, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp'); + await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level'); + const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp', 2500); + expect(timestamps).to.have.length(0); + const logLevels = await testSubjects.findAll('exampleDataSourceProfileLogLevel', 2500); + expect(logLevels).to.have.length(0); + }); + + it('should not render custom @timestamp but should render custom log.level', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-logs | sort @timestamp desc' }, + }); + await PageObjects.common.navigateToApp('discover', { + hash: `/?_a=${state}`, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp'); + await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level'); + const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp', 2500); + expect(timestamps).to.have.length(0); + const logLevels = await testSubjects.findAll('exampleDataSourceProfileLogLevel'); + expect(logLevels).to.have.length(3); + expect(await logLevels[0].getVisibleText()).to.be('Debug'); + expect(await logLevels[2].getVisibleText()).to.be('Info'); + }); + }); + }); + + describe('data view mode', () => { + describe('cell renderers', () => { + it('should not render custom @timestamp or log.level', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-*'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp'); + await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level'); + const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp', 2500); + expect(timestamps).to.have.length(0); + const logLevels = await testSubjects.findAll('exampleDataSourceProfileLogLevel', 2500); + expect(logLevels).to.have.length(0); + }); + + it('should not render custom @timestamp but should render custom log.level', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-logs'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await PageObjects.unifiedFieldList.clickFieldListItemAdd('@timestamp'); + await PageObjects.unifiedFieldList.clickFieldListItemAdd('log.level'); + const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp', 2500); + expect(timestamps).to.have.length(0); + const logLevels = await testSubjects.findAll('exampleDataSourceProfileLogLevel'); + expect(logLevels).to.have.length(3); + expect(await logLevels[0].getVisibleText()).to.be('Debug'); + expect(await logLevels[2].getVisibleText()).to.be('Info'); + }); + }); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/_root_profile.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/_root_profile.ts new file mode 100644 index 0000000000000..649d0a67ee22f --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/_root_profile.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import kbnRison from '@kbn/rison'; +import expect from '@kbn/expect'; +import type { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'timePicker', 'discover']); + const testSubjects = getService('testSubjects'); + const dataViews = getService('dataViews'); + + describe('root profile', () => { + describe('ES|QL mode', () => { + describe('cell renderers', () => { + it('should not render custom @timestamp', async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from my-example-* | sort @timestamp desc' }, + }); + await PageObjects.common.navigateToApp('discover', { + hash: `/?_a=${state}`, + }); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp', 2500); + expect(timestamps).to.have.length(0); + }); + }); + }); + + describe('data view mode', () => { + describe('cell renderers', () => { + it('should not render custom @timestamp', async () => { + await PageObjects.common.navigateToApp('discover'); + await dataViews.switchTo('my-example-*'); + await PageObjects.discover.waitUntilSearchingHasFinished(); + const timestamps = await testSubjects.findAll('exampleRootProfileTimestamp', 2500); + expect(timestamps).to.have.length(0); + }); + }); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/index.ts b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/index.ts new file mode 100644 index 0000000000000..e6e128e4b6b59 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/common/discover/context_awareness/index.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects, loadTestFile }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['timePicker', 'svlCommonPage']); + const from = '2024-06-10T14:00:00.000Z'; + const to = '2024-06-10T16:30:00.000Z'; + + describe('discover/context_awareness', () => { + before(async () => { + await esArchiver.load('test/functional/fixtures/es_archiver/discover/context_awareness'); + await kibanaServer.importExport.load( + 'test/functional/fixtures/kbn_archiver/discover/context_awareness' + ); + await kibanaServer.uiSettings.update({ + 'timepicker:timeDefaults': `{ "from": "${from}", "to": "${to}"}`, + }); + await PageObjects.svlCommonPage.login(); + }); + + after(async () => { + await esArchiver.unload('test/functional/fixtures/es_archiver/discover/context_awareness'); + await kibanaServer.importExport.unload( + 'test/functional/fixtures/kbn_archiver/discover/context_awareness' + ); + await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings(); + }); + + loadTestFile(require.resolve('./_root_profile')); + loadTestFile(require.resolve('./_data_source_profile')); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/group2/_data_grid_doc_table.ts b/x-pack/test_serverless/functional/test_suites/common/discover/group2/_data_grid_doc_table.ts index 2ca4c5f856937..5b66781ff2ba7 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/group2/_data_grid_doc_table.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/group2/_data_grid_doc_table.ts @@ -164,7 +164,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.try(async function () { await dataGrid.clickRowToggle({ isAnchorRow: false, rowIndex: rowToInspect - 1 }); const detailsEl = await dataGrid.getDetailsRows(); - const defaultMessageEl = await detailsEl[0].findByTestSubject('docTableRowDetailsTitle'); + const defaultMessageEl = await detailsEl[0].findByTestSubject('docViewerRowDetailsTitle'); expect(defaultMessageEl).to.be.ok(); await dataGrid.closeFlyout(); }); @@ -186,9 +186,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should allow paginating docs in the flyout by clicking in the doc table', async function () { await retry.try(async function () { await dataGrid.clickRowToggle({ rowIndex: rowToInspect - 1 }); - await testSubjects.exists(`dscDocNavigationPage0`); + await testSubjects.exists(`docViewerFlyoutNavigationPage0`); await dataGrid.clickRowToggle({ rowIndex: rowToInspect }); - await testSubjects.exists(`dscDocNavigationPage1`); + await testSubjects.exists(`docViewerFlyoutNavigationPage1`); await dataGrid.closeFlyout(); }); }); diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/x_pack/reporting.ts b/x-pack/test_serverless/functional/test_suites/common/discover/x_pack/reporting.ts index 5a2129d75ec77..76c95ebbd890e 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover/x_pack/reporting.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover/x_pack/reporting.ts @@ -6,7 +6,6 @@ */ import expect from '@kbn/expect'; -import { Key } from 'selenium-webdriver'; import moment from 'moment'; import { FtrProviderContext } from '../../../../ftr_provider_context'; @@ -27,7 +26,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'share', ]); const filterBar = getService('filterBar'); - const testSubjects = getService('testSubjects'); const toasts = getService('toasts'); const setFieldsFromSource = async (setValue: boolean) => { @@ -115,42 +113,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.selectIndexPattern('ecommerce'); }); - // this test does not pass because of discover using short urls - investigate in separate PR - xit('generates a report with single timefilter', async () => { - await PageObjects.discover.clickNewSearchButton(); - await PageObjects.timePicker.setCommonlyUsedTime('Last_24 hours'); - await PageObjects.discover.saveSearch('single-timefilter-search'); - - // get shared URL value - const sharedURL = await browser.getCurrentUrl(); - - // click 'Copy POST URL' - await PageObjects.share.clickShareTopNavButton(); - await PageObjects.reporting.openExportTab(); - const copyButton = await testSubjects.find('shareReportingCopyURL'); - const reportURL = (await copyButton.getAttribute('data-share-url')) ?? ''; - - // get number of filters in URLs - const timeFiltersNumberInReportURL = - decodeURIComponent(reportURL).split( - 'query:(range:(order_date:(format:strict_date_optional_time' - ).length - 1; - const timeFiltersNumberInSharedURL = sharedURL.split('time:').length - 1; - - expect(timeFiltersNumberInSharedURL).to.be(1); - expect(sharedURL.includes('time:(from:now-24h%2Fh,to:now))')).to.be(true); - - expect(timeFiltersNumberInReportURL).to.be(1); - expect( - decodeURIComponent(reportURL).includes( - 'query:(range:(order_date:(format:strict_date_optional_time' - ) - ).to.be(true); - - // return keyboard state - await browser.getActions().keyUp(Key.CONTROL).perform(); - await browser.getActions().keyUp('v').perform(); - }); it('generates a report from a new search with data: default', async () => { await PageObjects.discover.clickNewSearchButton(); await PageObjects.reporting.setTimepickerInEcommerceDataRange(); diff --git a/x-pack/test_serverless/functional/test_suites/common/platform_security/index.ts b/x-pack/test_serverless/functional/test_suites/common/platform_security/index.ts index 7427384fc73bf..bbb66a9841868 100644 --- a/x-pack/test_serverless/functional/test_suites/common/platform_security/index.ts +++ b/x-pack/test_serverless/functional/test_suites/common/platform_security/index.ts @@ -9,7 +9,6 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('Serverless Common UI - Platform Security', function () { - loadTestFile(require.resolve('./viewer_role_login')); loadTestFile(require.resolve('./api_keys')); loadTestFile(require.resolve('./navigation/avatar_menu')); loadTestFile(require.resolve('./user_profiles/user_profiles')); diff --git a/x-pack/test_serverless/functional/test_suites/common/platform_security/user_profiles/user_profiles.ts b/x-pack/test_serverless/functional/test_suites/common/platform_security/user_profiles/user_profiles.ts index c9c61d5d3a6a9..50313bc4da7cf 100644 --- a/x-pack/test_serverless/functional/test_suites/common/platform_security/user_profiles/user_profiles.ts +++ b/x-pack/test_serverless/functional/test_suites/common/platform_security/user_profiles/user_profiles.ts @@ -33,7 +33,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const actualFullname = await pageObjects.userProfiles.getProfileFullname(); const actualEmail = await pageObjects.userProfiles.getProfileEmail(); - expect(actualFullname).to.be(userData.fullname); + expect(actualFullname).to.be(userData.full_name); expect(actualEmail).to.be(userData.email); }); diff --git a/x-pack/test_serverless/functional/test_suites/common/platform_security/viewer_role_login.ts b/x-pack/test_serverless/functional/test_suites/common/platform_security/viewer_role_login.ts deleted file mode 100644 index 767abe540c2f5..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/common/platform_security/viewer_role_login.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../ftr_provider_context'; - -const VIEWER_ROLE = 'viewer'; - -export default function ({ getPageObject, getService }: FtrProviderContext) { - describe(`Login as ${VIEWER_ROLE}`, function () { - const svlCommonPage = getPageObject('svlCommonPage'); - const testSubjects = getService('testSubjects'); - const svlUserManager = getService('svlUserManager'); - - before(async () => { - await svlCommonPage.loginWithRole(VIEWER_ROLE); - }); - - it('should be able to see correct profile', async () => { - await svlCommonPage.assertProjectHeaderExists(); - await svlCommonPage.assertUserAvatarExists(); - await svlCommonPage.clickUserAvatar(); - await svlCommonPage.assertUserMenuExists(); - const actualFullname = await testSubjects.getVisibleText('contextMenuPanelTitle'); - const userData = await svlUserManager.getUserData(VIEWER_ROLE); - expect(actualFullname).to.be(userData.fullname); - }); - }); -} diff --git a/x-pack/test_serverless/functional/test_suites/observability/config.context_awareness.ts b/x-pack/test_serverless/functional/test_suites/observability/config.context_awareness.ts new file mode 100644 index 0000000000000..76362cc111e6f --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/observability/config.context_awareness.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 { createTestConfig } from '../../config.base'; + +export default createTestConfig({ + serverlessProject: 'oblt', + testFiles: [require.resolve('../common/discover/context_awareness')], + junit: { + reportName: 'Serverless Observability Discover Context Awareness Functional Tests', + }, + kbnServerArgs: [ + '--discover.experimental.enabledProfiles=["example-root-profile","example-data-source-profile","example-document-profile"]', + ], + // include settings from project controller + // https://github.com/elastic/project-controller/blob/main/internal/project/observability/config/elasticsearch.yml + esServerArgs: ['xpack.ml.dfa.enabled=false'], +}); diff --git a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/data/logs_data.ts b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/data/logs_data.ts index 399030d1dd377..168aeb4b4df21 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/data/logs_data.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/data/logs_data.ts @@ -191,6 +191,7 @@ export function createDegradedFieldsRecord({ export const datasetNames = ['synth.1', 'synth.2', 'synth.3']; export const defaultNamespace = 'default'; +export const productionNamespace = 'production'; // Logs Data logic const MESSAGE_LOG_LEVELS: MessageWithLevel[] = [ diff --git a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_flyout.ts b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_flyout.ts index 0ca26b296fe13..24f6011c78855 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_flyout.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_flyout.ts @@ -12,6 +12,7 @@ import { getInitialTestLogs, getLogsForDataset, createDegradedFieldsRecord, + productionNamespace, } from './data'; const integrationActions = { @@ -36,25 +37,72 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const to = '2024-01-01T12:00:00.000Z'; const excludeKeysFromServerless = ['size']; // https://github.com/elastic/kibana/issues/178954 - describe('Dataset quality flyout', function () { - // FLAKY: https://github.com/elastic/kibana/issues/183771 - // Added this sub describe block so that the existing flaky tests can be skipped and new ones can be added in the other describe block - - describe.skip('Other dataset quality flyout tests', () => { - this.tags(['failsOnMKI']); // Failing https://github.com/elastic/kibana/issues/183495 - - before(async () => { - await PageObjects.svlCommonPage.loginWithRole('admin'); - await synthtrace.index(getInitialTestLogs({ to, count: 4 })); - await PageObjects.datasetQuality.navigateTo(); - }); + const apacheAccessDatasetName = 'apache.access'; + const apacheAccessDatasetHumanName = 'Apache access logs'; + const apacheIntegrationId = 'apache'; + const apachePkg = { + name: 'apache', + version: '1.14.0', + }; + + const bitbucketDatasetName = 'atlassian_bitbucket.audit'; + const bitbucketDatasetHumanName = 'Bitbucket Audit Logs'; + const bitbucketPkg = { + name: 'atlassian_bitbucket', + version: '1.14.0', + }; + + const degradedDatasetName = datasetNames[2]; + + describe('Flyout', function () { + before(async () => { + // Install Apache Integration and ingest logs for it + await PageObjects.observabilityLogsExplorer.installPackage(apachePkg); + + // Install Bitbucket Integration (package which does not has Dashboards) and ingest logs for it + await PageObjects.observabilityLogsExplorer.installPackage(bitbucketPkg); + + await synthtrace.index([ + // Ingest basic logs + getInitialTestLogs({ to, count: 4 }), + // Ingest Degraded Logs + createDegradedFieldsRecord({ + to: new Date().toISOString(), + count: 2, + dataset: degradedDatasetName, + }), + // Index 15 logs for `logs-apache.access` dataset + getLogsForDataset({ + to: new Date().toISOString(), + count: 15, + dataset: apacheAccessDatasetName, + namespace: productionNamespace, + }), + // Index degraded docs for Apache integration + getLogsForDataset({ + to: new Date().toISOString(), + count: 1, + dataset: apacheAccessDatasetName, + namespace: productionNamespace, + isMalformed: true, + }), + // Index logs for Bitbucket integration + getLogsForDataset({ to, count: 10, dataset: bitbucketDatasetName }), + ]); + + await PageObjects.svlCommonPage.loginWithRole('admin'); + + await PageObjects.datasetQuality.navigateTo(); + }); - after(async () => { - await synthtrace.clean(); - await PageObjects.observabilityLogsExplorer.removeInstalledPackages(); - }); + after(async () => { + await PageObjects.observabilityLogsExplorer.uninstallPackage(apachePkg); + await PageObjects.observabilityLogsExplorer.uninstallPackage(bitbucketPkg); + await synthtrace.clean(); + }); - it('opens the flyout for the right dataset', async () => { + describe('open flyout', () => { + it('should open the flyout for the right dataset', async () => { const testDatasetName = datasetNames[1]; await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName); @@ -62,44 +110,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.existOrFail( PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutTitle ); - }); - // Fails on Serverless. TODO: Need to update the UI as well as the test - it.skip('shows the correct last activity', async () => { - const testDatasetName = datasetNames[0]; - - // Update last activity for the dataset await PageObjects.datasetQuality.closeFlyout(); - await synthtrace.index( - getLogsForDataset({ to: new Date().toISOString(), count: 1, dataset: testDatasetName }) - ); - await PageObjects.datasetQuality.refreshTable(); - - const cols = await PageObjects.datasetQuality.parseDatasetTable(); - - const datasetNameCol = cols['Data Set Name']; - const datasetNameColCellTexts = await datasetNameCol.getCellTexts(); - - const testDatasetRowIndex = datasetNameColCellTexts.findIndex( - (dName: string) => dName === testDatasetName - ); - - const lastActivityText = (await cols['Last Activity'].getCellTexts())[testDatasetRowIndex]; - - await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName); - - const lastActivityTextExists = await PageObjects.datasetQuality.doestTextExistInFlyout( - lastActivityText, - `[data-test-subj=${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutFieldValue}]` - ); - - expect(lastActivityTextExists).to.eql(true); }); - // FLAKY: https://github.com/elastic/kibana/issues/180994 - it.skip('reflects the breakdown field state in url', async () => { - const testDatasetName = datasetNames[0]; - await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName); + it('reflects the breakdown field state in url', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); const breakdownField = 'service.name'; await PageObjects.datasetQuality.selectBreakdownField(breakdownField); @@ -118,25 +134,25 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const currentUrl = await browser.getCurrentUrl(); expect(currentUrl).to.not.contain('breakdownField'); }); + await PageObjects.datasetQuality.closeFlyout(); }); + }); - it('shows the integration details', async () => { - const apacheAccessDatasetName = 'apache.access'; - const apacheAccessDatasetHumanName = 'Apache access logs'; - const apacheIntegrationId = 'apache'; - - await PageObjects.observabilityLogsExplorer.navigateTo(); + describe('integrations', () => { + it('should hide the integration section for non integrations', async () => { + const testDatasetName = datasetNames[1]; - // Add initial integrations - await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); + await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName); - // Index 10 logs for `logs-apache.access` dataset - await synthtrace.index( - getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName }) + await testSubjects.missingOrFail( + PageObjects.datasetQuality.testSubjectSelectors + .datasetQualityFlyoutFieldsListIntegrationDetails ); - await PageObjects.datasetQuality.navigateTo(); + await PageObjects.datasetQuality.closeFlyout(); + }); + it('should shows the integration section for integrations', async () => { await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); const integrationNameElements = await PageObjects.datasetQuality.getFlyoutElementsByText( @@ -144,134 +160,140 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { apacheIntegrationId ); - await PageObjects.datasetQuality.closeFlyout(); + await testSubjects.existOrFail( + PageObjects.datasetQuality.testSubjectSelectors + .datasetQualityFlyoutFieldsListIntegrationDetails + ); expect(integrationNameElements.length).to.eql(1); + + await PageObjects.datasetQuality.closeFlyout(); }); - it('goes to log explorer page when open button is clicked', async () => { - const testDatasetName = datasetNames[2]; - await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName); + it('should show the integration actions menu with correct actions', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); + await PageObjects.datasetQuality.openIntegrationActionsMenu(); - await (await PageObjects.datasetQuality.getFlyoutLogsExplorerButton()).click(); + const actions = await Promise.all( + Object.values(integrationActions).map((action) => + PageObjects.datasetQuality.getIntegrationActionButtonByAction(action) + ) + ); - // Confirm dataset selector text in observability logs explorer - const datasetSelectorText = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - expect(datasetSelectorText).to.eql(testDatasetName); + expect(actions.length).to.eql(3); + await PageObjects.datasetQuality.closeFlyout(); }); - it('shows summary KPIs', async () => { - await PageObjects.datasetQuality.navigateTo(); + it('should hide integration dashboard for integrations without dashboards', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(bitbucketDatasetHumanName); + await PageObjects.datasetQuality.openIntegrationActionsMenu(); - const apacheAccessDatasetHumanName = 'Apache access logs'; - await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); + await testSubjects.missingOrFail( + PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutIntegrationAction( + integrationActions.viewDashboards + ) + ); + await PageObjects.datasetQuality.closeFlyout(); + }); + + it('Should navigate to integration overview page on clicking integration overview action', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(bitbucketDatasetHumanName); + await PageObjects.datasetQuality.openIntegrationActionsMenu(); - const summary = await PageObjects.datasetQuality.parseFlyoutKpis(excludeKeysFromServerless); - expect(summary).to.eql({ - docsCountTotal: '0', - // size: '0.0 B', // `_stats` not available on Serverless - services: '0', - hosts: '0', - degradedDocs: '0', + const action = await PageObjects.datasetQuality.getIntegrationActionButtonByAction( + integrationActions.overview + ); + + await action.click(); + + await retry.tryForTime(5000, async () => { + const currentUrl = await browser.getCurrentUrl(); + const parsedUrl = new URL(currentUrl); + + expect(parsedUrl.pathname).to.contain('/app/integrations/detail/atlassian_bitbucket'); }); + + await PageObjects.datasetQuality.navigateTo(); }); - it('shows the updated KPIs', async () => { - const apacheAccessDatasetName = 'apache.access'; - const apacheAccessDatasetHumanName = 'Apache access logs'; + it('should navigate to index template page in clicking Integration template', async () => { await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); + await PageObjects.datasetQuality.openIntegrationActionsMenu(); - const summaryBefore = await PageObjects.datasetQuality.parseFlyoutKpis( - excludeKeysFromServerless + const action = await PageObjects.datasetQuality.getIntegrationActionButtonByAction( + integrationActions.template ); - // Set time range to 3 days ago - const flyoutBodyContainer = await testSubjects.find( - PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutBody - ); - await PageObjects.datasetQuality.setDatePickerLastXUnits(flyoutBodyContainer, 3, 'd'); + await action.click(); - // Index 2 doc 2 days ago - const time2DaysAgo = Date.now() - 2 * 24 * 60 * 60 * 1000; - await synthtrace.index( - getLogsForDataset({ - to: time2DaysAgo, - count: 2, - dataset: apacheAccessDatasetName, - isMalformed: false, - }) - ); + await retry.tryForTime(5000, async () => { + const currentUrl = await browser.getCurrentUrl(); + const parsedUrl = new URL(currentUrl); + expect(parsedUrl.pathname).to.contain( + `/app/management/data/index_management/templates/logs-${apacheAccessDatasetName}` + ); + }); + await PageObjects.datasetQuality.navigateTo(); + }); - // Index 5 degraded docs 2 days ago - await synthtrace.index( - getLogsForDataset({ - to: time2DaysAgo, - count: 5, - dataset: apacheAccessDatasetName, - isMalformed: true, - }) - ); + it('should navigate to the selected dashboard on clicking integration dashboard action ', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); + await PageObjects.datasetQuality.openIntegrationActionsMenu(); - await PageObjects.datasetQuality.refreshFlyout(); - const summaryAfter = await PageObjects.datasetQuality.parseFlyoutKpis( - excludeKeysFromServerless + const action = await PageObjects.datasetQuality.getIntegrationActionButtonByAction( + integrationActions.viewDashboards ); - expect(parseInt(summaryAfter.docsCountTotal, 10)).to.be.greaterThan( - parseInt(summaryBefore.docsCountTotal, 10) - ); + await action.click(); - expect(parseInt(summaryAfter.degradedDocs, 10)).to.be.greaterThan( - parseInt(summaryBefore.degradedDocs, 10) - ); + const dashboardButtons = await PageObjects.datasetQuality.getIntegrationDashboardButtons(); + const firstDashboardButton = await dashboardButtons[0]; + const dashboardText = await firstDashboardButton.getVisibleText(); - // `_stats` not available on Serverless so we can't compare size // https://github.com/elastic/kibana/issues/178954 - // expect(parseInt(summaryAfter.size, 10)).to.be.greaterThan(parseInt(summaryBefore.size, 10)); + await firstDashboardButton.click(); - expect(parseInt(summaryAfter.services, 10)).to.be.greaterThan( - parseInt(summaryBefore.services, 10) - ); - expect(parseInt(summaryAfter.hosts, 10)).to.be.greaterThan( - parseInt(summaryBefore.hosts, 10) - ); + const breadcrumbText = await testSubjects.getVisibleText('breadcrumb last'); + + expect(breadcrumbText).to.eql(dashboardText); + + await PageObjects.datasetQuality.navigateTo(); }); + }); - it('shows the right number of services', async () => { - const apacheAccessDatasetName = 'apache.access'; - const apacheAccessDatasetHumanName = 'Apache access logs'; + describe('summary panel', () => { + it('should show summary KPIs', async () => { await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); - const summaryBefore = await PageObjects.datasetQuality.parseFlyoutKpis( - excludeKeysFromServerless - ); - const testServices = ['test-srv-1', 'test-srv-2']; + const { docsCountTotal, degradedDocs, services, hosts } = + await PageObjects.datasetQuality.parseFlyoutKpis(excludeKeysFromServerless); + expect(parseInt(docsCountTotal, 10)).to.be(226); + expect(parseInt(degradedDocs, 10)).to.be(1); + expect(parseInt(services, 10)).to.be(3); + expect(parseInt(hosts, 10)).to.be(52); - // Index 2 docs with different services - const timeNow = Date.now(); - await synthtrace.index( - getLogsForDataset({ - to: timeNow, - count: 2, - dataset: apacheAccessDatasetName, - isMalformed: false, - services: testServices, - }) - ); + await PageObjects.datasetQuality.closeFlyout(); + }); + }); - await PageObjects.datasetQuality.refreshFlyout(); - const summaryAfter = await PageObjects.datasetQuality.parseFlyoutKpis( - excludeKeysFromServerless - ); + describe('navigation', () => { + it('should go to log explorer page when the open in log explorer button is clicked', async () => { + const testDatasetName = datasetNames[2]; + await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName); - expect(parseInt(summaryAfter.services, 10)).to.eql( - parseInt(summaryBefore.services, 10) + testServices.length - ); + const logExplorerButton = await PageObjects.datasetQuality.getFlyoutLogsExplorerButton(); + + await logExplorerButton.click(); + + // Confirm dataset selector text in observability logs explorer + const datasetSelectorText = + await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); + expect(datasetSelectorText).to.eql(testDatasetName); + + // Should bring back the test to the dataset quality page + await PageObjects.datasetQuality.navigateTo(); }); - it('goes to log explorer for degraded docs when show all is clicked', async () => { - const apacheAccessDatasetName = 'apache.access'; - const apacheAccessDatasetHumanName = 'Apache access logs'; + it('should go log explorer for degraded docs when the show all button is clicked', async () => { await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); const degradedDocsShowAllSelector = `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutKpiLink}-${PageObjects.datasetQuality.texts.degradedDocs}`; @@ -285,11 +307,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.closeCurrentWindow(); await browser.switchTab(0); + + await PageObjects.datasetQuality.closeFlyout(); }); // Blocked by https://github.com/elastic/kibana/issues/181705 + // Its a test written ahead of its time. it.skip('goes to infra hosts for hosts when show all is clicked', async () => { - const apacheAccessDatasetHumanName = 'Apache access logs'; await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); const hostsShowAllSelector = `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutKpiLink}-${PageObjects.datasetQuality.texts.hosts}`; @@ -305,308 +329,132 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.closeCurrentWindow(); await browser.switchTab(0); - }); - - it('Integration actions menu is present with correct actions', async () => { - const apacheAccessDatasetName = 'apache.access'; - const apacheAccessDatasetHumanName = 'Apache access logs'; - await PageObjects.observabilityLogsExplorer.navigateTo(); + await PageObjects.datasetQuality.closeFlyout(); + }); + }); - // Add initial integrations - await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); + describe('degraded fields table', () => { + it(' should show empty degraded fields table when no degraded fields are present', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(datasetNames[0]); - // Index 10 logs for `logs-apache.access` dataset - await synthtrace.index( - getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName }) + await testSubjects.existOrFail( + PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutDegradedTableNoData ); - await PageObjects.datasetQuality.navigateTo(); + await PageObjects.datasetQuality.closeFlyout(); + }); - await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); - await PageObjects.datasetQuality.openIntegrationActionsMenu(); + it('should show the degraded fields table with data when present', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); - const actions = await Promise.all( - Object.values(integrationActions).map((action) => - PageObjects.datasetQuality.getIntegrationActionButtonByAction(action) - ) + await testSubjects.existOrFail( + PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutDegradedFieldTable ); - expect(actions.length).to.eql(3); - }); + const rows = + await PageObjects.datasetQuality.getDatasetQualityFlyoutDegradedFieldTableRows(); - it('Integration dashboard action hidden for integrations without dashboards', async () => { - const bitbucketDatasetName = 'atlassian_bitbucket.audit'; - const bitbucketDatasetHumanName = 'Bitbucket Audit Logs'; + expect(rows.length).to.eql(2); - await PageObjects.observabilityLogsExplorer.navigateTo(); + await PageObjects.datasetQuality.closeFlyout(); + }); - // Add initial integrations - await PageObjects.observabilityLogsExplorer.installPackage({ - name: 'atlassian_bitbucket', - version: '1.14.0', - }); + it('should display Spark Plot for every row of degraded fields', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); - // Index 10 logs for `atlassian_bitbucket.audit` dataset - await synthtrace.index(getLogsForDataset({ to, count: 10, dataset: bitbucketDatasetName })); + const rows = + await PageObjects.datasetQuality.getDatasetQualityFlyoutDegradedFieldTableRows(); - await PageObjects.datasetQuality.navigateTo(); + const sparkPlots = await testSubjects.findAll( + PageObjects.datasetQuality.testSubjectSelectors.datasetQualitySparkPlot + ); - await PageObjects.datasetQuality.openDatasetFlyout(bitbucketDatasetHumanName); - await PageObjects.datasetQuality.openIntegrationActionsMenu(); + expect(rows.length).to.be(sparkPlots.length); - await testSubjects.missingOrFail( - PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutIntegrationAction( - integrationActions.viewDashboards - ) - ); + await PageObjects.datasetQuality.closeFlyout(); }); - it('Integration overview action should navigate to the integration overview page', async () => { - const bitbucketDatasetName = 'atlassian_bitbucket.audit'; - const bitbucketDatasetHumanName = 'Bitbucket Audit Logs'; + it('should sort the table when the count table header is clicked', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); - await PageObjects.observabilityLogsExplorer.navigateTo(); + const table = await PageObjects.datasetQuality.parseDegradedFieldTable(); - // Add initial integrations - await PageObjects.observabilityLogsExplorer.installPackage({ - name: 'atlassian_bitbucket', - version: '1.14.0', - }); + const countColumn = table['Docs count']; + const cellTexts = await countColumn.getCellTexts(); - // Index 10 logs for `atlassian_bitbucket.audit` dataset - await synthtrace.index(getLogsForDataset({ to, count: 10, dataset: bitbucketDatasetName })); + await countColumn.sort('ascending'); + const sortedCellTexts = await countColumn.getCellTexts(); - await PageObjects.datasetQuality.navigateTo(); + expect(cellTexts.reverse()).to.eql(sortedCellTexts); - await PageObjects.datasetQuality.openDatasetFlyout(bitbucketDatasetHumanName); - await PageObjects.datasetQuality.openIntegrationActionsMenu(); + await PageObjects.datasetQuality.closeFlyout(); + }); - const action = await PageObjects.datasetQuality.getIntegrationActionButtonByAction( - integrationActions.overview - ); + it('should update the URL when the table is sorted', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); - await action.click(); + const table = await PageObjects.datasetQuality.parseDegradedFieldTable(); + const countColumn = table['Docs count']; await retry.tryForTime(5000, async () => { const currentUrl = await browser.getCurrentUrl(); const parsedUrl = new URL(currentUrl); + const pageState = parsedUrl.searchParams.get('pageState'); - expect(parsedUrl.pathname).to.contain('/app/integrations/detail/atlassian_bitbucket'); + expect(decodeURIComponent(pageState as string)).to.contain( + 'sort:(direction:desc,field:count)' + ); }); - }); - - it('Integration template action should navigate to the index template page', async () => { - const apacheAccessDatasetName = 'apache.access'; - const apacheAccessDatasetHumanName = 'Apache access logs'; - - await PageObjects.observabilityLogsExplorer.navigateTo(); - - // Add initial integrations - await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); - - // Index 10 logs for `logs-apache.access` dataset - await synthtrace.index( - getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName }) - ); - - await PageObjects.datasetQuality.navigateTo(); - await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); - await PageObjects.datasetQuality.openIntegrationActionsMenu(); + countColumn.sort('ascending'); await retry.tryForTime(5000, async () => { - const action = await PageObjects.datasetQuality.getIntegrationActionButtonByAction( - integrationActions.template - ); - - await action.click(); - const currentUrl = await browser.getCurrentUrl(); const parsedUrl = new URL(currentUrl); - expect(parsedUrl.pathname).to.contain( - `/app/management/data/index_management/templates/logs-${apacheAccessDatasetName}` - ); - }); - }); - - it('Integration dashboard action should navigate to the selected dashboard', async () => { - const apacheAccessDatasetName = 'apache.access'; - const apacheAccessDatasetHumanName = 'Apache access logs'; - - await PageObjects.observabilityLogsExplorer.navigateTo(); - - // Add initial integrations - await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); - - // Index 10 logs for `logs-apache.access` dataset - await synthtrace.index( - getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName }) - ); - - await PageObjects.datasetQuality.navigateTo(); - - await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName); - await PageObjects.datasetQuality.openIntegrationActionsMenu(); - - const action = await PageObjects.datasetQuality.getIntegrationActionButtonByAction( - integrationActions.viewDashboards - ); - - await action.click(); - - const dashboardButtons = await PageObjects.datasetQuality.getIntegrationDashboardButtons(); - const firstDashboardButton = await dashboardButtons[0]; - const dashboardText = await firstDashboardButton.getVisibleText(); - - await firstDashboardButton.click(); - - const breadcrumbText = await testSubjects.getVisibleText('breadcrumb last'); - - expect(breadcrumbText).to.eql(dashboardText); - }); - }); + const pageState = parsedUrl.searchParams.get('pageState'); - // The above describe block has some failing/flaky tests which will - // be fixed as part of the tech debt mentioned here - // https://github.com/elastic/kibana/issues/184145 - // Until then, the below describe block is added to cover the tests for the - // newly added degraded Fields Table. This must be merged under the above - // describe block once the tech debt is fixed. - // - // FLAKY: https://github.com/elastic/kibana/issues/184438 - describe.skip('Dataset quality flyout with degraded fields', () => { - const goodDatasetName = 'good'; - const degradedDatasetName = 'degraded'; - const today = new Date().toISOString(); - describe('Degraded Fields Table with common data', () => { - before(async () => { - await PageObjects.svlCommonPage.loginWithRole('admin'); - await synthtrace.index([ - getLogsForDataset({ - to: today, - count: 2, - dataset: goodDatasetName, - isMalformed: false, - }), - createDegradedFieldsRecord({ - to: today, - count: 2, - dataset: degradedDatasetName, - }), - ]); - await PageObjects.datasetQuality.navigateTo(); - }); - - after(async () => { - await synthtrace.clean(); - }); - it('shows the degraded fields table with no data when no degraded fields are present', async () => { - await PageObjects.datasetQuality.openDatasetFlyout(goodDatasetName); - - await testSubjects.existOrFail( - PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutDegradedTableNoData - ); - - await PageObjects.datasetQuality.closeFlyout(); - }); - - it('should load the degraded fields table with data', async () => { - await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); - - await testSubjects.existOrFail( - PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutDegradedFieldTable + expect(decodeURIComponent(pageState as string)).to.contain( + 'sort:(direction:asc,field:count)' ); - - const rows = - await PageObjects.datasetQuality.getDatasetQualityFlyoutDegradedFieldTableRows(); - - expect(rows.length).to.eql(2); - - await PageObjects.datasetQuality.closeFlyout(); }); - it('should display Spark Plot for every row of degraded fields', async () => { - await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); - - const rows = - await PageObjects.datasetQuality.getDatasetQualityFlyoutDegradedFieldTableRows(); - - const sparkPlots = await testSubjects.findAll( - PageObjects.datasetQuality.testSubjectSelectors.datasetQualitySparkPlot - ); - - expect(rows.length).to.be(sparkPlots.length); - - await PageObjects.datasetQuality.closeFlyout(); - }); - - it('should sort the table when the count table header is clicked', async () => { - await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); - - const table = await PageObjects.datasetQuality.parseDegradedFieldTable(); - - const countColumn = table['Docs count']; - const cellTexts = await countColumn.getCellTexts(); - - await countColumn.sort('ascending'); - const sortedCellTexts = await countColumn.getCellTexts(); - - expect(cellTexts.reverse()).to.eql(sortedCellTexts); - - await PageObjects.datasetQuality.closeFlyout(); - }); + await PageObjects.datasetQuality.closeFlyout(); }); - describe('Degraded Fields Table with data ingestion', () => { - before(async () => { - await PageObjects.svlCommonPage.loginWithRole('admin'); - await synthtrace.index([ - getLogsForDataset({ - to: today, - count: 2, - dataset: goodDatasetName, - isMalformed: false, - }), - createDegradedFieldsRecord({ - to: today, - count: 2, - dataset: degradedDatasetName, - }), - ]); - await PageObjects.datasetQuality.navigateTo(); - }); + // This is the only test which ingest data during the test. + // This block tests the refresh behavior of the degraded fields table. + // Even though this test ingest data, it can also be freely moved inside + // this describe block, and it won't affect any of the existing tests + it('should update the table when new data is ingested and the flyout is refreshed using the time selector', async () => { + await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); - after(async () => { - await synthtrace.clean(); - }); - it('should update the table when new data is ingested and the flyout is refreshed using the time selector', async () => { - await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName); + const table = await PageObjects.datasetQuality.parseDegradedFieldTable(); - const table = await PageObjects.datasetQuality.parseDegradedFieldTable(); + const countColumn = table['Docs count']; + const cellTexts = await countColumn.getCellTexts(); - const countColumn = table['Docs count']; - const cellTexts = await countColumn.getCellTexts(); - const singleValuePreviously = parseInt(cellTexts[0], 10); + await synthtrace.index([ + createDegradedFieldsRecord({ + to: new Date().toISOString(), + count: 2, + dataset: degradedDatasetName, + }), + ]); - await synthtrace.index([ - createDegradedFieldsRecord({ - to: today, - count: 2, - dataset: degradedDatasetName, - }), - ]); + await PageObjects.datasetQuality.refreshFlyout(); - await PageObjects.datasetQuality.refreshFlyout(); + const updatedTable = await PageObjects.datasetQuality.parseDegradedFieldTable(); + const updatedCountColumn = updatedTable['Docs count']; - const updatedCellTexts = await countColumn.getCellTexts(); + const updatedCellTexts = await updatedCountColumn.getCellTexts(); - const singleValueNow = parseInt(updatedCellTexts[0], 10); + const singleValuePreviously = parseInt(cellTexts[0], 10); + const singleValueNow = parseInt(updatedCellTexts[0], 10); - expect(singleValueNow).to.be(singleValuePreviously * 2); + expect(singleValueNow).to.be.greaterThan(singleValuePreviously); - await PageObjects.datasetQuality.closeFlyout(); - }); + await PageObjects.datasetQuality.closeFlyout(); }); }); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_privileges.ts b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_privileges.ts new file mode 100644 index 0000000000000..f4a60d6ab98ac --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_privileges.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { getInitialTestLogs } from './data'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects([ + 'common', + 'navigationalSearch', + 'observabilityLogsExplorer', + 'datasetQuality', + 'svlCommonNavigation', + 'svlCommonPage', + ]); + const testSubjects = getService('testSubjects'); + const synthtrace = getService('svlLogsSynthtraceClient'); + const find = getService('find'); + const to = '2024-01-01T12:00:00.000Z'; + + describe('Dataset quality user privileges', function () { + this.tags(['failsOnMKI']); + + before(async () => { + await PageObjects.svlCommonPage.loginWithRole('viewer'); + await synthtrace.index(getInitialTestLogs({ to, count: 4 })); + await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); + await PageObjects.datasetQuality.navigateTo(); + }); + + after(async () => { + await synthtrace.clean(); + }); + + it('Active Datasets stat is not available due to underprivileged user', async () => { + await testSubjects.existOrFail( + `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityInsufficientPrivileges}-${PageObjects.datasetQuality.texts.activeDatasets}` + ); + }); + + it('"Show inactive datasets" is hidden when lastActivity is not available', async () => { + await find.waitForDeletedByCssSelector( + PageObjects.datasetQuality.selectors.showInactiveDatasetsNamesSwitch + ); + }); + + it('does not show last activity column for underprivileged data stream', async () => { + const cols = await PageObjects.datasetQuality.getDatasetTableHeaderTexts(); + + expect(cols).to.not.contain('Last Activity'); + }); + + it('does not show size and last activity columns for underprivileged data stream', async () => { + const cols = await PageObjects.datasetQuality.getDatasetTableHeaderTexts(); + + expect(cols).to.not.contain('Size'); + expect(cols).to.not.contain('Last Activity'); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_summary.ts b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_summary.ts index f5a6fd31a25f3..5821612c0b9b2 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_summary.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_summary.ts @@ -17,11 +17,37 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'svlCommonPage', ]); const synthtrace = getService('svlLogsSynthtraceClient'); - const browser = getService('browser'); - const retry = getService('retry'); const to = '2024-01-01T12:00:00.000Z'; const excludeKeysFromServerless = ['estimatedData']; // https://github.com/elastic/kibana/issues/178954 + const ingestDataForSummary = async () => { + // Ingest documents for 3 type of datasets + return synthtrace.index([ + // Ingest good data to all 3 datasets + getInitialTestLogs({ to, count: 4 }), + // Ingesting poor data to one dataset + getLogsForDataset({ + to: Date.now(), + count: 1, + dataset: datasetNames[1], + isMalformed: true, + }), + // Ingesting degraded docs into another dataset by ingesting malformed 1st and then good data + getLogsForDataset({ + to: Date.now(), + count: 1, + dataset: datasetNames[2], + isMalformed: true, + }), + getLogsForDataset({ + to: Date.now(), + count: 10, + dataset: datasetNames[2], + isMalformed: false, + }), + ]); + }; + describe('Dataset quality summary', () => { before(async () => { await synthtrace.index(getInitialTestLogs({ to, count: 4 })); @@ -29,110 +55,31 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.datasetQuality.navigateTo(); }); - after(async () => { + afterEach(async () => { await synthtrace.clean(); }); - it('shows poor, degraded and good count', async () => { + it('shows poor, degraded and good count as 0 and all dataset as healthy', async () => { + await PageObjects.datasetQuality.refreshTable(); const summary = await PageObjects.datasetQuality.parseSummaryPanel(excludeKeysFromServerless); expect(summary).to.eql({ datasetHealthPoor: '0', datasetHealthDegraded: '0', datasetHealthGood: '3', activeDatasets: '0 of 3', - // estimatedData: '0.0 B', https://github.com/elastic/kibana/issues/178954 - }); - }); - - it('updates the poor count when degraded docs are ingested', async () => { - // Index malformed document with current timestamp - await synthtrace.index( - getLogsForDataset({ - to: Date.now(), - count: 1, - dataset: datasetNames[2], - isMalformed: true, - }) - ); - - await browser.refresh(); - await PageObjects.datasetQuality.waitUntilSummaryPanelLoaded(); - - await retry.try(async () => { - const summary = await PageObjects.datasetQuality.parseSummaryPanel( - excludeKeysFromServerless - ); - const { estimatedData, ...restOfSummary } = summary; - expect(restOfSummary).to.eql({ - datasetHealthPoor: '1', - datasetHealthDegraded: '0', - datasetHealthGood: '2', - activeDatasets: '1 of 3', - }); - }); - }); - - it('updates the degraded count when degraded docs are ingested', async () => { - // Index malformed document with current timestamp - await synthtrace.index( - getLogsForDataset({ - to: Date.now(), - count: 1, - dataset: datasetNames[1], - isMalformed: true, - }) - ); - - // Index healthy documents - await synthtrace.index( - getLogsForDataset({ - to: Date.now(), - count: 10, - dataset: datasetNames[1], - isMalformed: false, - }) - ); - - await browser.refresh(); - await PageObjects.datasetQuality.waitUntilSummaryPanelLoaded(); - - await retry.try(async () => { - const { estimatedData, ...restOfSummary } = - await PageObjects.datasetQuality.parseSummaryPanel(excludeKeysFromServerless); - expect(restOfSummary).to.eql({ - datasetHealthPoor: '1', - datasetHealthDegraded: '1', - datasetHealthGood: '1', - activeDatasets: '2 of 3', - }); }); }); - it('updates active datasets and estimated data KPIs', async () => { - const { estimatedData: _existingEstimatedData } = - await PageObjects.datasetQuality.parseSummaryPanel(excludeKeysFromServerless); - - // Index document at current time to mark dataset as active - await synthtrace.index( - getLogsForDataset({ - to: Date.now(), - count: 4, - dataset: datasetNames[0], - isMalformed: false, - }) - ); - - await browser.refresh(); // Summary panel doesn't update reactively - await PageObjects.datasetQuality.waitUntilSummaryPanelLoaded(); + it('shows updated count for poor, degraded and good datasets and updates active datasets', async () => { + await ingestDataForSummary(); + await PageObjects.datasetQuality.refreshTable(); - await retry.try(async () => { - const { activeDatasets: updatedActiveDatasets, estimatedData: _updatedEstimatedData } = - await PageObjects.datasetQuality.parseSummaryPanel(excludeKeysFromServerless); - - expect(updatedActiveDatasets).to.eql('3 of 3'); - - // TODO: `_stats` not available on Serverless. // https://github.com/elastic/kibana/issues/178954 - // expect(_updatedEstimatedData).to.not.eql(_existingEstimatedData); + const summary = await PageObjects.datasetQuality.parseSummaryPanel(excludeKeysFromServerless); + expect(summary).to.eql({ + datasetHealthPoor: '1', + datasetHealthDegraded: '1', + datasetHealthGood: '1', + activeDatasets: '2 of 3', }); }); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_table.ts b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_table.ts index b86bf82a6266a..40531e4b04d18 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_table.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_table.ts @@ -7,7 +7,13 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { datasetNames, defaultNamespace, getInitialTestLogs, getLogsForDataset } from './data'; +import { + datasetNames, + defaultNamespace, + getInitialTestLogs, + getLogsForDataset, + productionNamespace, +} from './data'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects([ @@ -19,42 +25,79 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'svlCommonPage', ]); const synthtrace = getService('svlLogsSynthtraceClient'); - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); const to = '2024-01-01T12:00:00.000Z'; + const apacheAccessDatasetName = 'apache.access'; + const apacheAccessDatasetHumanName = 'Apache access logs'; + const pkg = { + name: 'apache', + version: '1.14.0', + }; describe('Dataset quality table', function () { - this.tags(['failsOnMKI']); // Failing https://github.com/elastic/kibana/issues/183495 - before(async () => { - await synthtrace.index(getInitialTestLogs({ to, count: 4 })); + // Install Integration and ingest logs for it + await PageObjects.observabilityLogsExplorer.installPackage(pkg); + // Ingest basic logs + await synthtrace.index([ + // Ingest basic logs + getInitialTestLogs({ to, count: 4 }), + // Ingest Degraded Logs + getLogsForDataset({ + to: new Date().toISOString(), + count: 1, + dataset: datasetNames[2], + isMalformed: true, + }), + // Index 10 logs for `logs-apache.access` dataset + getLogsForDataset({ + to, + count: 10, + dataset: apacheAccessDatasetName, + namespace: productionNamespace, + }), + ]); await PageObjects.svlCommonPage.loginWithRole('admin'); await PageObjects.datasetQuality.navigateTo(); }); after(async () => { await synthtrace.clean(); - await PageObjects.observabilityLogsExplorer.removeInstalledPackages(); + await PageObjects.observabilityLogsExplorer.uninstallPackage(pkg); }); - it('shows the right number of rows in correct order', async () => { + it('shows sort by dataset name and show namespace', async () => { const cols = await PageObjects.datasetQuality.parseDatasetTable(); const datasetNameCol = cols['Data Set Name']; await datasetNameCol.sort('descending'); const datasetNameColCellTexts = await datasetNameCol.getCellTexts(); - expect(datasetNameColCellTexts).to.eql([...datasetNames].reverse()); + expect(datasetNameColCellTexts).to.eql( + [apacheAccessDatasetHumanName, ...datasetNames].reverse() + ); const namespaceCol = cols.Namespace; const namespaceColCellTexts = await namespaceCol.getCellTexts(); - expect(namespaceColCellTexts).to.eql([defaultNamespace, defaultNamespace, defaultNamespace]); + expect(namespaceColCellTexts).to.eql([ + defaultNamespace, + defaultNamespace, + defaultNamespace, + productionNamespace, + ]); - const degradedDocsCol = cols['Degraded Docs (%)']; - const degradedDocsColCellTexts = await degradedDocsCol.getCellTexts(); - expect(degradedDocsColCellTexts).to.eql(['0%', '0%', '0%']); + // Cleaning the sort + await datasetNameCol.sort('ascending'); + }); + it('shows the last activity', async () => { + const cols = await PageObjects.datasetQuality.parseDatasetTable(); const lastActivityCol = cols['Last Activity']; - const lastActivityColCellTexts = await lastActivityCol.getCellTexts(); - expect(lastActivityColCellTexts).to.eql([ + const activityCells = await lastActivityCol.getCellTexts(); + const lastActivityCell = activityCells[activityCells.length - 1]; + const restActivityCells = activityCells.slice(0, -1); + + // The first cell of lastActivity should have data + expect(lastActivityCell).to.not.eql(PageObjects.datasetQuality.texts.noActivityText); + // The rest of the rows must show no activity + expect(restActivityCells).to.eql([ PageObjects.datasetQuality.texts.noActivityText, PageObjects.datasetQuality.texts.noActivityText, PageObjects.datasetQuality.texts.noActivityText, @@ -63,112 +106,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('shows degraded docs percentage', async () => { const cols = await PageObjects.datasetQuality.parseDatasetTable(); - const datasetNameCol = cols['Data Set Name']; - await datasetNameCol.sort('ascending'); const degradedDocsCol = cols['Degraded Docs (%)']; const degradedDocsColCellTexts = await degradedDocsCol.getCellTexts(); - expect(degradedDocsColCellTexts).to.eql(['0%', '0%', '0%']); - - // Index malformed document with current timestamp - await synthtrace.index( - getLogsForDataset({ - to: Date.now(), - count: 1, - dataset: datasetNames[2], - isMalformed: true, - }) - ); - - // Set time range to Last 5 minute - const filtersContainer = await testSubjects.find( - PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFiltersContainer - ); - await PageObjects.datasetQuality.setDatePickerLastXUnits(filtersContainer, 5, 'm'); - - const updatedDegradedDocsColCellTexts = await degradedDocsCol.getCellTexts(); - expect(updatedDegradedDocsColCellTexts[2]).to.not.eql('0%'); - }); - - // https://github.com/elastic/kibana/issues/178954 - it.skip('shows the updated size of the index', async () => { - const testDatasetIndex = 2; - const cols = await PageObjects.datasetQuality.parseDatasetTable(); - const datasetNameCol = cols['Data Set Name']; - await datasetNameCol.sort('ascending'); - const datasetNameColCellTexts = await datasetNameCol.getCellTexts(); - - const datasetToUpdateRowIndex = datasetNameColCellTexts.findIndex( - (dName: string) => dName === datasetNames[testDatasetIndex] - ); - - const sizeColCellTexts = await cols.Size.getCellTexts(); - const beforeSize = sizeColCellTexts[datasetToUpdateRowIndex]; - - // Index documents with current timestamp - await synthtrace.index( - getLogsForDataset({ - to: Date.now(), - count: 4, - dataset: datasetNames[testDatasetIndex], - isMalformed: false, - }) - ); - - const colsAfterUpdate = await PageObjects.datasetQuality.parseDatasetTable(); - - // Assert that size has changed - await retry.tryForTime(15000, async () => { - // Refresh the table - await PageObjects.datasetQuality.refreshTable(); - const updatedSizeColCellTexts = await colsAfterUpdate.Size.getCellTexts(); - expect(updatedSizeColCellTexts[datasetToUpdateRowIndex]).to.not.eql(beforeSize); - }); - }); - - it('sorts by dataset name', async () => { - // const header = await PageObjects.datasetQuality.getDatasetTableHeader('Dataset Name'); - const cols = await PageObjects.datasetQuality.parseDatasetTable(); - const datasetNameCol = cols['Data Set Name']; - - // Sort ascending - await datasetNameCol.sort('ascending'); - const cellTexts = await datasetNameCol.getCellTexts(); - - const datasetNamesAsc = [...datasetNames].sort(); - - expect(cellTexts).to.eql(datasetNamesAsc); + expect(degradedDocsColCellTexts).to.eql(['0%', '0%', '0%', '100%']); }); it('shows dataset from integration', async () => { - const apacheAccessDatasetName = 'apache.access'; - const apacheAccessDatasetHumanName = 'Apache access logs'; - - await PageObjects.observabilityLogsExplorer.navigateTo(); - - // Add initial integrations - await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); - - // Index 10 logs for `logs-apache.access` dataset - await synthtrace.index( - getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName }) - ); - - await PageObjects.datasetQuality.navigateTo(); - const cols = await PageObjects.datasetQuality.parseDatasetTable(); const datasetNameCol = cols['Data Set Name']; - // Sort ascending - await datasetNameCol.sort('ascending'); const datasetNameColCellTexts = await datasetNameCol.getCellTexts(); - const datasetNamesAsc = [...datasetNames, apacheAccessDatasetHumanName].sort(); - - // Assert there are 4 rows - expect(datasetNameColCellTexts.length).to.eql(4); - - expect(datasetNameColCellTexts).to.eql(datasetNamesAsc); + expect(datasetNameColCellTexts[0]).to.eql(apacheAccessDatasetHumanName); }); it('goes to log explorer page when opened', async () => { @@ -184,50 +134,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const datasetSelectorText = await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); expect(datasetSelectorText).to.eql(datasetName); - }); - it('shows the last activity when in time range', async () => { + // Return to Dataset Quality Page await PageObjects.datasetQuality.navigateTo(); - const cols = await PageObjects.datasetQuality.parseDatasetTable(); - const lastActivityCol = cols['Last Activity']; - const datasetNameCol = cols['Data Set Name']; - - // Set time range to Last 1 minute - const filtersContainer = await testSubjects.find( - PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFiltersContainer - ); - - await PageObjects.datasetQuality.setDatePickerLastXUnits(filtersContainer, 1, 's'); - const lastActivityColCellTexts = await lastActivityCol.getCellTexts(); - expect(lastActivityColCellTexts).to.eql([ - PageObjects.datasetQuality.texts.noActivityText, - PageObjects.datasetQuality.texts.noActivityText, - PageObjects.datasetQuality.texts.noActivityText, - PageObjects.datasetQuality.texts.noActivityText, - ]); - - const datasetToUpdate = datasetNames[0]; - await synthtrace.index( - getLogsForDataset({ to: new Date().toISOString(), count: 1, dataset: datasetToUpdate }) - ); - const datasetNameColCellTexts = await datasetNameCol.getCellTexts(); - const datasetToUpdateRowIndex = datasetNameColCellTexts.findIndex( - (dName: string) => dName === datasetToUpdate - ); - - await PageObjects.datasetQuality.setDatePickerLastXUnits(filtersContainer, 1, 'h'); - - await retry.tryForTime(5000, async () => { - const updatedLastActivityColCellTexts = await lastActivityCol.getCellTexts(); - expect(updatedLastActivityColCellTexts[datasetToUpdateRowIndex]).to.not.eql( - PageObjects.datasetQuality.texts.noActivityText - ); - }); }); it('hides inactive datasets', async () => { - await PageObjects.datasetQuality.waitUntilTableLoaded(); - // Get number of rows with Last Activity not equal to "No activity in the selected timeframe" const cols = await PageObjects.datasetQuality.parseDatasetTable(); const lastActivityCol = cols['Last Activity']; diff --git a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_table_filters.ts b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_table_filters.ts index 239e4322876b0..b53ffe313e86e 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_table_filters.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_table_filters.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { datasetNames, defaultNamespace, getInitialTestLogs, getLogsForDataset } from './data'; +import { datasetNames, getInitialTestLogs, getLogsForDataset, productionNamespace } from './data'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects([ @@ -20,58 +20,73 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const synthtrace = getService('svlLogsSynthtraceClient'); const testSubjects = getService('testSubjects'); const to = '2024-01-01T12:00:00.000Z'; + const apacheAccessDatasetName = 'apache.access'; + const apacheAccessDatasetHumanName = 'Apache access logs'; + const apacheIntegrationName = 'Apache HTTP Server'; + const pkg = { + name: 'apache', + version: '1.14.0', + }; + const allDatasetNames = [apacheAccessDatasetHumanName, ...datasetNames]; describe('Dataset quality table filters', function () { - this.tags(['failsOnMKI']); // Failing https://github.com/elastic/kibana/issues/183495 - before(async () => { - await synthtrace.index(getInitialTestLogs({ to, count: 4 })); + // Install Integration and ingest logs for it + await PageObjects.observabilityLogsExplorer.installPackage(pkg); + // Ingest basic logs + await synthtrace.index([ + // Ingest basic logs + getInitialTestLogs({ to, count: 4 }), + // Ingest Degraded Logs + getLogsForDataset({ + to: new Date().toISOString(), + count: 1, + dataset: datasetNames[2], + isMalformed: true, + }), + // Index 10 logs for `logs-apache.access` dataset + getLogsForDataset({ + to, + count: 10, + dataset: apacheAccessDatasetName, + namespace: productionNamespace, + }), + ]); await PageObjects.svlCommonPage.loginWithRole('admin'); await PageObjects.datasetQuality.navigateTo(); }); after(async () => { + await PageObjects.observabilityLogsExplorer.uninstallPackage(pkg); await synthtrace.clean(); - await PageObjects.observabilityLogsExplorer.removeInstalledPackages(); - }); - - it('hides inactive datasets when toggled', async () => { - const initialRows = await PageObjects.datasetQuality.getDatasetTableRows(); - expect(initialRows.length).to.eql(3); - - await PageObjects.datasetQuality.toggleShowInactiveDatasets(); - - const afterToggleRows = await PageObjects.datasetQuality.getDatasetTableRows(); - expect(afterToggleRows.length).to.eql(1); - - await PageObjects.datasetQuality.toggleShowInactiveDatasets(); - - const afterReToggleRows = await PageObjects.datasetQuality.getDatasetTableRows(); - expect(afterReToggleRows.length).to.eql(3); }); it('shows full dataset names when toggled', async () => { const cols = await PageObjects.datasetQuality.parseDatasetTable(); const datasetNameCol = cols['Data Set Name']; const datasetNameColCellTexts = await datasetNameCol.getCellTexts(); - expect(datasetNameColCellTexts).to.eql(datasetNames); + expect(datasetNameColCellTexts).to.eql(allDatasetNames); await PageObjects.datasetQuality.toggleShowFullDatasetNames(); const datasetNameColCellTextsAfterToggle = await datasetNameCol.getCellTexts(); - const duplicateNames = datasetNames.map((name) => `${name}\n${name}`); + const duplicateNames = [ + `${apacheAccessDatasetHumanName}\n${apacheAccessDatasetName}`, + ...datasetNames.map((name) => `${name}\n${name}`), + ]; expect(datasetNameColCellTextsAfterToggle).to.eql(duplicateNames); + // resetting the toggle await PageObjects.datasetQuality.toggleShowFullDatasetNames(); const datasetNameColCellTextsAfterReToggle = await datasetNameCol.getCellTexts(); - expect(datasetNameColCellTextsAfterReToggle).to.eql(datasetNames); + expect(datasetNameColCellTextsAfterReToggle).to.eql(allDatasetNames); }); it('searches the datasets', async () => { const cols = await PageObjects.datasetQuality.parseDatasetTable(); const datasetNameCol = cols['Data Set Name']; const datasetNameColCellTexts = await datasetNameCol.getCellTexts(); - expect(datasetNameColCellTexts).to.eql(datasetNames); + expect(datasetNameColCellTexts).to.eql(allDatasetNames); // Search for a dataset await testSubjects.setValue( @@ -83,33 +98,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const datasetNameColAfterSearch = colsAfterSearch['Data Set Name']; const datasetNameColCellTextsAfterSearch = await datasetNameColAfterSearch.getCellTexts(); expect(datasetNameColCellTextsAfterSearch).to.eql([datasetNames[2]]); - await testSubjects.setValue( - PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFilterBarFieldSearch, - '' - ); + // Reset the search field + await testSubjects.click('clearSearchButton'); }); it('filters for integration', async () => { - const apacheAccessDatasetName = 'apache.access'; - const apacheAccessDatasetHumanName = 'Apache access logs'; - const apacheIntegrationName = 'Apache HTTP Server'; - - await PageObjects.observabilityLogsExplorer.navigateTo(); - - // Add initial integrations - await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); - - // Index 10 logs for `logs-apache.access` dataset - await synthtrace.index( - getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName }) - ); - - await PageObjects.datasetQuality.navigateTo(); - const cols = await PageObjects.datasetQuality.parseDatasetTable(); const datasetNameCol = cols['Data Set Name']; const datasetNameColCellTexts = await datasetNameCol.getCellTexts(); - expect(datasetNameColCellTexts).to.eql([apacheAccessDatasetHumanName, ...datasetNames]); + expect(datasetNameColCellTexts).to.eql(allDatasetNames); // Filter for integration await PageObjects.datasetQuality.filterForIntegrations([apacheIntegrationName]); @@ -118,64 +115,31 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const datasetNameColAfterFilter = colsAfterFilter['Data Set Name']; const datasetNameColCellTextsAfterFilter = await datasetNameColAfterFilter.getCellTexts(); expect(datasetNameColCellTextsAfterFilter).to.eql([apacheAccessDatasetHumanName]); + // Reset the filter by selecting from the dropdown again + await PageObjects.datasetQuality.filterForIntegrations([apacheIntegrationName]); }); it('filters for namespace', async () => { - const apacheAccessDatasetName = 'apache.access'; - const datasetNamespace = 'prod'; - - await PageObjects.observabilityLogsExplorer.navigateTo(); - - // Add initial integrations - await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); - - // Index 10 logs for `logs-apache.access` dataset - await synthtrace.index( - getLogsForDataset({ - to, - count: 10, - dataset: apacheAccessDatasetName, - namespace: datasetNamespace, - }) - ); - - await PageObjects.datasetQuality.navigateTo(); - // Get default namespaces const cols = await PageObjects.datasetQuality.parseDatasetTable(); const namespaceCol = cols.Namespace; const namespaceColCellTexts = await namespaceCol.getCellTexts(); - expect(namespaceColCellTexts).to.contain(defaultNamespace); + expect(namespaceColCellTexts).to.contain(productionNamespace); // Filter for prod namespace - await PageObjects.datasetQuality.filterForNamespaces([datasetNamespace]); + await PageObjects.datasetQuality.filterForNamespaces([productionNamespace]); const colsAfterFilter = await PageObjects.datasetQuality.parseDatasetTable(); const namespaceColAfterFilter = colsAfterFilter.Namespace; const namespaceColCellTextsAfterFilter = await namespaceColAfterFilter.getCellTexts(); - expect(namespaceColCellTextsAfterFilter).to.eql([datasetNamespace]); + expect(namespaceColCellTextsAfterFilter).to.eql([productionNamespace]); + // Reset the namespace by selecting from the dropdown again + await PageObjects.datasetQuality.filterForNamespaces([productionNamespace]); }); it('filters for quality', async () => { - const apacheAccessDatasetName = 'apache.access'; const expectedQuality = 'Poor'; - - // Add initial integrations - await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); - - // Index 10 logs for `logs-apache.access` dataset - await synthtrace.index( - getLogsForDataset({ - to: new Date().toISOString(), - count: 10, - dataset: apacheAccessDatasetName, - isMalformed: true, - }) - ); - - await PageObjects.datasetQuality.navigateTo(); - // Get default quality const cols = await PageObjects.datasetQuality.parseDatasetTable(); const datasetQuality = cols['Data Set Quality']; @@ -190,6 +154,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const datasetQualityCellTextsAfterFilter = await datasetQualityAfterFilter.getCellTexts(); expect(datasetQualityCellTextsAfterFilter).to.eql([expectedQuality]); + + // Reset the namespace by selecting from the dropdown again + await PageObjects.datasetQuality.filterForQualities([expectedQuality]); }); }); } diff --git a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/home.ts b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/home.ts index cb3e2605925b5..7deeeff7e9bc1 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/home.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/home.ts @@ -6,6 +6,7 @@ */ import { FtrProviderContext } from '../../../ftr_provider_context'; +import { getInitialTestLogs } from './data'; export default function ({ getPageObjects, getService }: FtrProviderContext) { const PageObjects = getPageObjects([ @@ -16,17 +17,42 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ]); const testSubjects = getService('testSubjects'); + const synthtrace = getService('svlLogsSynthtraceClient'); + const to = '2024-01-01T12:00:00.000Z'; describe('Dataset quality home', () => { before(async () => { await PageObjects.svlCommonPage.loginWithRole('admin'); }); - it('dataset quality table exists', async () => { - await PageObjects.datasetQuality.navigateTo(); - await testSubjects.existOrFail( - PageObjects.datasetQuality.testSubjectSelectors.datasetQualityTable - ); + describe('with no datasets available', () => { + before(async () => { + await PageObjects.datasetQuality.navigateTo(); + }); + + it('shows the empty state', async () => { + await testSubjects.existOrFail( + PageObjects.datasetQuality.testSubjectSelectors.datasetQualityNoDataEmptyState + ); + }); + }); + + describe('with datasets available', () => { + before(async () => { + await synthtrace.index(getInitialTestLogs({ to, count: 1 })); + await PageObjects.datasetQuality.navigateTo(); + }); + + after(async () => { + await synthtrace.clean(); + }); + + it('dataset quality table exists', async () => { + await PageObjects.datasetQuality.navigateTo(); + await testSubjects.existOrFail( + PageObjects.datasetQuality.testSubjectSelectors.datasetQualityTable + ); + }); }); }); } diff --git a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/index.ts b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/index.ts index 0706610dd475c..30547ceb941b3 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/index.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/index.ts @@ -14,5 +14,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./dataset_quality_table')); loadTestFile(require.resolve('./dataset_quality_table_filters')); loadTestFile(require.resolve('./dataset_quality_flyout')); + loadTestFile(require.resolve('./dataset_quality_privileges')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/search/config.context_awareness.ts b/x-pack/test_serverless/functional/test_suites/search/config.context_awareness.ts new file mode 100644 index 0000000000000..7b608c29c9f3a --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/search/config.context_awareness.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 { createTestConfig } from '../../config.base'; + +export default createTestConfig({ + serverlessProject: 'es', + testFiles: [require.resolve('../common/discover/context_awareness')], + junit: { + reportName: 'Serverless Search Discover Context Awareness Functional Tests', + }, + kbnServerArgs: [ + '--discover.experimental.enabledProfiles=["example-root-profile","example-data-source-profile","example-document-profile"]', + ], + // include settings from project controller + // https://github.com/elastic/project-controller/blob/main/internal/project/observability/config/elasticsearch.yml + esServerArgs: ['xpack.ml.dfa.enabled=false'], +}); diff --git a/x-pack/test_serverless/functional/test_suites/search/index.ts b/x-pack/test_serverless/functional/test_suites/search/index.ts index 455acc0404429..8c3cfd83e04e9 100644 --- a/x-pack/test_serverless/functional/test_suites/search/index.ts +++ b/x-pack/test_serverless/functional/test_suites/search/index.ts @@ -23,5 +23,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./playground_overview')); loadTestFile(require.resolve('./ml')); + loadTestFile(require.resolve('./search_homepage')); }); } diff --git a/x-pack/test_serverless/functional/test_suites/search/search_homepage.ts b/x-pack/test_serverless/functional/test_suites/search/search_homepage.ts new file mode 100644 index 0000000000000..3138e0e7f0242 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/search/search_homepage.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; +import { RoleCredentials } from '../../../shared/services'; + +import { testHasEmbeddedConsole } from './embedded_console'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const pageObjects = getPageObjects(['svlCommonPage', 'svlCommonNavigation', 'svlSearchHomePage']); + const svlUserManager = getService('svlUserManager'); + const uiSettings = getService('uiSettings'); + let roleAuthc: RoleCredentials; + + const HOMEPAGE_FF_UI_SETTING = 'searchHomepage:homepageEnabled'; + describe('Search Homepage', function () { + this.tags('skipMKI'); + before(async () => { + roleAuthc = await svlUserManager.createApiKeyForRole('admin'); + // Enable Homepage Feature Flag + await uiSettings.setUiSetting(roleAuthc, HOMEPAGE_FF_UI_SETTING, true); + + await pageObjects.svlCommonPage.login(); + }); + + after(async () => { + if (!roleAuthc) return; + + // Disable Homepage Feature Flag + await uiSettings.deleteUISetting(roleAuthc, HOMEPAGE_FF_UI_SETTING); + + await pageObjects.svlCommonPage.forceLogout(); + }); + + it('has search homepage with Home sidenav', async () => { + pageObjects.svlSearchHomePage.expectToBeOnHomepage(); + pageObjects.svlSearchHomePage.expectHomepageHeader(); + // Navigate to another page + await pageObjects.svlCommonNavigation.sidenav.clickLink({ + deepLinkId: 'serverlessConnectors', + }); + pageObjects.svlSearchHomePage.expectToNotBeOnHomepage(); + // Click Home in Side nav + await pageObjects.svlCommonNavigation.sidenav.clickLink({ + deepLinkId: 'searchHomepage', + }); + pageObjects.svlSearchHomePage.expectToBeOnHomepage(); + }); + + it('has embedded dev console', async () => { + testHasEmbeddedConsole(pageObjects); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.ts b/x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.basic.ts similarity index 81% rename from x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.ts rename to x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.basic.ts index 6a755c26e91d4..7063ec943dfba 100644 --- a/x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.ts +++ b/x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.basic.ts @@ -16,6 +16,8 @@ export default createTestConfig({ kbnServerArgs: [ `--xpack.fleet.packages.0.name=cloud_security_posture`, `--xpack.fleet.packages.0.version=${CLOUD_SECURITY_PLUGIN_VERSION}`, + // configs the environment to run on the basic product tier, which may include PLI block components or messages + `--xpack.securitySolutionServerless.productTypes=${JSON.stringify([])}`, ], // load tests in the index file testFiles: [require.resolve('./ftr/cloud_security_posture')], diff --git a/x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.essentials.ts b/x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.essentials.ts new file mode 100644 index 0000000000000..320b130e63e91 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/security/config.cloud_security_posture.essentials.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createTestConfig } from '../../config.base'; + +export default createTestConfig({ + serverlessProject: 'security', + junit: { + reportName: 'Serverless Security Cloud Security Functional Tests', + }, + kbnServerArgs: [ + `--xpack.fleet.packages.0.name=cloud_security_posture`, + `--xpack.fleet.packages.0.version=1.5.2`, + `--xpack.securitySolutionServerless.productTypes=${JSON.stringify([ + { product_line: 'security', product_tier: 'essentials' }, + { product_line: 'endpoint', product_tier: 'essentials' }, + { product_line: 'cloud', product_tier: 'essentials' }, + ])}`, + ], + // we should only resolve files which are ending with `.essentials.ts` + testFiles: [require.resolve('./ftr/cloud_security_posture/csp_integrations_form.essentials.ts')], +}); diff --git a/x-pack/test_serverless/functional/test_suites/security/config.context_awareness.ts b/x-pack/test_serverless/functional/test_suites/security/config.context_awareness.ts new file mode 100644 index 0000000000000..6276922df83f4 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/security/config.context_awareness.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 { createTestConfig } from '../../config.base'; + +export default createTestConfig({ + serverlessProject: 'security', + testFiles: [require.resolve('../common/discover/context_awareness')], + junit: { + reportName: 'Serverless Security Discover Context Awareness Functional Tests', + }, + kbnServerArgs: [ + '--discover.experimental.enabledProfiles=["example-root-profile","example-data-source-profile","example-document-profile"]', + ], + // include settings from project controller + // https://github.com/elastic/project-controller/blob/main/internal/project/observability/config/elasticsearch.yml + esServerArgs: ['xpack.ml.dfa.enabled=false'], +}); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/csp_integrations_form.essentials.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/csp_integrations_form.essentials.ts new file mode 100644 index 0000000000000..28f494598835a --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/csp_integrations_form.essentials.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 expect from '@kbn/expect'; +import type { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function (providerContext: FtrProviderContext) { + const { getPageObjects } = providerContext; + const pageObjects = getPageObjects(['cisAddIntegration', 'header', 'svlCommonPage']); + + describe('[Essentials PLI] Test Cloud Security Posture Integrations on Serverless', function () { + this.tags(['skipMKI']); + let cisIntegration: typeof pageObjects.cisAddIntegration; + + before(async () => { + await pageObjects.svlCommonPage.login(); + }); + + beforeEach(async () => { + cisIntegration = pageObjects.cisAddIntegration; + await cisIntegration.navigateToAddIntegrationCspmPage(); + }); + + it('[Essentials PLI] Integration installation form should be available with Essentials or Complete PLI', async () => { + await pageObjects.header.waitUntilLoadingHasFinished(); + const pliBlockExists = await cisIntegration.checkIntegrationPliAuthBlockExists(); + + expect(pliBlockExists).to.be(false); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/csp_integrations_form.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/csp_integrations_form.ts new file mode 100644 index 0000000000000..a136d707cdb84 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/csp_integrations_form.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 expect from '@kbn/expect'; +import type { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function (providerContext: FtrProviderContext) { + const { getPageObjects } = providerContext; + const pageObjects = getPageObjects(['cisAddIntegration', 'header', 'svlCommonPage']); + + describe('Test Cloud Security Posture Integrations on Serverless', function () { + this.tags(['skipMKI']); + let cisIntegration: typeof pageObjects.cisAddIntegration; + + before(async () => { + await pageObjects.svlCommonPage.login(); + }); + + beforeEach(async () => { + cisIntegration = pageObjects.cisAddIntegration; + await cisIntegration.navigateToAddIntegrationCspmPage(); + }); + + it('Integration installation form should not be available without required PLI', async () => { + await pageObjects.header.waitUntilLoadingHasFinished(); + const pliBlockExists = await cisIntegration.checkIntegrationPliAuthBlockExists(); + + expect(pliBlockExists).to.be(true); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/index.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/index.ts index 4750199f7f566..c94ab36cd46df 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/index.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/cloud_security_posture/index.ts @@ -10,6 +10,9 @@ import { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('cloud_security_posture', function () { this.tags(['cloud_security_posture']); + + // do not resolve files which are ending with `.essentials.ts` loadTestFile(require.resolve('./compliance_dashboard')); + loadTestFile(require.resolve('./csp_integrations_form')); }); } diff --git a/x-pack/test_serverless/shared/services/svl_user_manager.ts b/x-pack/test_serverless/shared/services/svl_user_manager.ts index 3568da0c6b87b..12fa18eee6673 100644 --- a/x-pack/test_serverless/shared/services/svl_user_manager.ts +++ b/x-pack/test_serverless/shared/services/svl_user_manager.ts @@ -78,6 +78,10 @@ export function SvlUserManagerProvider({ getService }: FtrProviderContext) { async getApiCredentialsForRole(role: string) { return sessionManager.getApiCredentialsForRole(role); }, + async getEmail(role: string) { + return sessionManager.getEmail(role); + }, + async getUserData(role: string) { return sessionManager.getUserData(role); }, diff --git a/x-pack/test_serverless/tsconfig.json b/x-pack/test_serverless/tsconfig.json index e80bebb0e90ae..95b99a8a08f20 100644 --- a/x-pack/test_serverless/tsconfig.json +++ b/x-pack/test_serverless/tsconfig.json @@ -102,6 +102,7 @@ "@kbn/alerting-comparators", "@kbn/search-types", "@kbn/reporting-server", - "@kbn/features-plugin" + "@kbn/config-schema", + "@kbn/features-plugin", ] } diff --git a/yarn.lock b/yarn.lock index 45616dcfbc56e..d59f566dc8627 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35,21 +35,6 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" -"@anthropic-ai/sdk@^0.9.1": - version "0.9.1" - resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.9.1.tgz#b2d2b7bf05c90dce502c9a2e869066870f69ba88" - integrity sha512-wa1meQ2WSfoY8Uor3EdrJq0jTiZJoKoSii2ZVWRY1oN4Tlr5s59pADg9T79FTbPe1/se5c3pBeZgJL63wmuoBA== - dependencies: - "@types/node" "^18.11.18" - "@types/node-fetch" "^2.6.4" - abort-controller "^3.0.0" - agentkeepalive "^4.2.1" - digest-fetch "^1.3.0" - form-data-encoder "1.7.2" - formdata-node "^4.3.2" - node-fetch "^2.6.7" - web-streams-polyfill "^3.2.1" - "@apidevtools/json-schema-ref-parser@^9.0.6": version "9.0.9" resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#d720f9256e3609621280584f2b47ae165359268b" @@ -111,12 +96,12 @@ tslib "^1.11.1" "@aws-sdk/types@^3.222.0": - version "3.433.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.433.0.tgz#0f94eae2a4a3525ca872c9ab04e143c01806d755" - integrity sha512-0jEE2mSrNDd8VGFjTc1otYrwYPIkzZJEIK90ZxisKvQ/EURGBhNzWn7ejWB9XCMFT6XumYLBR0V9qq5UPisWtA== + version "3.577.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.577.0.tgz#7700784d368ce386745f8c340d9d68cea4716f90" + integrity sha512-FT2JZES3wBKN/alfmhlo+3ZOq/XJ0C7QOZcDNrpKjB0kqYoKjhVKZ/Hx6ArR0czkKfHzBBEs6y40ebIHx2nSmA== dependencies: - "@smithy/types" "^2.4.0" - tslib "^2.5.0" + "@smithy/types" "^3.0.0" + tslib "^2.6.2" "@aws-sdk/util-utf8-browser@^3.0.0": version "3.259.0" @@ -1446,6 +1431,20 @@ resolved "https://registry.yarnpkg.com/@bufbuild/protobuf/-/protobuf-1.2.1.tgz#f8b1fbbe79726a4eafa9772ddde147b57f85d177" integrity sha512-cwwGvLGqvoaOZmoP5+i4v/rbW+rHkguvTehuZyM2p/xpmaNSdT2h3B7kHw33aiffv35t1XrYHIkdJSEkSEMJuA== +"@bundled-es-modules/cookie@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@bundled-es-modules/cookie/-/cookie-2.0.0.tgz#c3b82703969a61cf6a46e959a012b2c257f6b164" + integrity sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw== + dependencies: + cookie "^0.5.0" + +"@bundled-es-modules/statuses@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz#761d10f44e51a94902c4da48675b71a76cc98872" + integrity sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg== + dependencies: + statuses "^2.0.1" + "@cbor-extract/cbor-extract-darwin-arm64@2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.0.0.tgz#cf0667e4c22111c9d45e16c29964892b12460a76" @@ -1735,10 +1734,10 @@ resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314" integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ== -"@elastic/eui@94.6.0": - version "94.6.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-94.6.0.tgz#fd56be1dbdcdea259cdb3504085c8479fa35bcef" - integrity sha512-lYXVcylXn4Iz2WumBuOEkc1PRFoUby7CTnNhTS/gVrbTP7Mn0ombcoPFUSiZcA7VuN2mHfPmTUdBQptC/apTzA== +"@elastic/eui@95.0.0-backport.0": + version "95.0.0-backport.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-95.0.0-backport.0.tgz#6fdfbc813259cf82896f24641bb6e8a03587f3e8" + integrity sha512-aaCQYLdJ4/1uQUb8xsToa94HwUDN2HSjtZzrgh3iT3El7tieNk6TNzX9QtNePw9M57snpdt41wyg6QFM7Jhltg== dependencies: "@hello-pangea/dnd" "^16.6.0" "@types/lodash" "^4.14.202" @@ -2505,10 +2504,10 @@ resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== -"@grpc/grpc-js@^1.6.8", "@grpc/grpc-js@^1.7.1": - version "1.8.17" - resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.8.17.tgz#a3a2f826fc033eae7d2f5ee41e0ab39cee948838" - integrity sha512-DGuSbtMFbaRsyffMf+VEkVu8HkSXEUfO3UyGJNtqxW9ABdtTIA+2UXAJpwbJS+xfQxuwqLUeELmL6FuZkOqPxw== +"@grpc/grpc-js@^1.7.1", "@grpc/grpc-js@^1.8.22": + version "1.8.22" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.8.22.tgz#847930c9af46e14df05b57fc12325db140ceff1d" + integrity sha512-oAjDdN7fzbUi+4hZjKG96MR6KTEubAeMpQEb+77qy+3r0Ua5xTFuie6JOLr4ZZgl5g+W5/uRTS2M1V8mVAFPuA== dependencies: "@grpc/proto-loader" "^0.7.0" "@types/node" ">=12.12.47" @@ -2845,6 +2844,43 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== +"@inquirer/confirm@^3.0.0": + version "3.1.8" + resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-3.1.8.tgz#db80f23f775d9b980c6de2425dde39f9786bf1d3" + integrity sha512-f3INZ+ca4dQdn+MQiq1yP/mOIR/Oc8BLRYuDh6ciToWd6z4W8yArfzjBCMQ0BPY8PcJKwZxGIt8Z6yNT32eSTw== + dependencies: + "@inquirer/core" "^8.2.1" + "@inquirer/type" "^1.3.2" + +"@inquirer/core@^8.2.1": + version "8.2.1" + resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-8.2.1.tgz#ee92c2bf25f378819f56290f8ed8bfef8c6cc94d" + integrity sha512-TIcuQMn2qrtyYe0j136UpHeYpk7AcR/trKeT/7YY0vRgcS9YSfJuQ2+PudPhSofLLsHNnRYAHScQCcVZrJkMqA== + dependencies: + "@inquirer/figures" "^1.0.2" + "@inquirer/type" "^1.3.2" + "@types/mute-stream" "^0.0.4" + "@types/node" "^20.12.12" + "@types/wrap-ansi" "^3.0.0" + ansi-escapes "^4.3.2" + chalk "^4.1.2" + cli-spinners "^2.9.2" + cli-width "^4.1.0" + mute-stream "^1.0.0" + signal-exit "^4.1.0" + strip-ansi "^6.0.1" + wrap-ansi "^6.2.0" + +"@inquirer/figures@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.2.tgz#a6af5e9f9969efb9ed3469130566315c36506b8a" + integrity sha512-4F1MBwVr3c/m4bAUef6LgkvBfSjzwH+OfldgHqcuacWwSUetFebM2wi58WfG9uk1rR98U6GwLed4asLJbwdV5w== + +"@inquirer/type@^1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-1.3.2.tgz#439b0b50c152c89fd369d2a17eff54869b4d79b8" + integrity sha512-5Frickan9c89QbPkSu6I6y8p+9eR6hZkdPahGmNDsTFX8FHLPAozyzCZMKUeW8FyYwnlCKUjqIEqxY+UctARiw== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -3292,10 +3328,6 @@ version "0.0.0" uid "" -"@kbn/analytics-client@link:packages/analytics/client": - version "0.0.0" - uid "" - "@kbn/analytics-collection-utils@link:packages/analytics/utils/analytics_collection_utils": version "0.0.0" uid "" @@ -3308,22 +3340,6 @@ version "0.0.0" uid "" -"@kbn/analytics-shippers-elastic-v3-browser@link:packages/analytics/shippers/elastic_v3/browser": - version "0.0.0" - uid "" - -"@kbn/analytics-shippers-elastic-v3-common@link:packages/analytics/shippers/elastic_v3/common": - version "0.0.0" - uid "" - -"@kbn/analytics-shippers-elastic-v3-server@link:packages/analytics/shippers/elastic_v3/server": - version "0.0.0" - uid "" - -"@kbn/analytics-shippers-fullstory@link:packages/analytics/shippers/fullstory": - version "0.0.0" - uid "" - "@kbn/analytics@link:packages/kbn-analytics": version "0.0.0" uid "" @@ -3580,6 +3596,10 @@ version "0.0.0" uid "" +"@kbn/content-management-user-profiles@link:packages/content-management/user_profiles": + version "0.0.0" + uid "" + "@kbn/content-management-utils@link:packages/kbn-content-management-utils": version "0.0.0" uid "" @@ -4672,6 +4692,10 @@ version "0.0.0" uid "" +"@kbn/ebt@link:packages/analytics/ebt": + version "0.0.0" + uid "" + "@kbn/ecs-data-quality-dashboard-plugin@link:x-pack/plugins/ecs_data_quality_dashboard": version "0.0.0" uid "" @@ -5168,6 +5192,10 @@ version "0.0.0" uid "" +"@kbn/integration-assistant-plugin@link:x-pack/plugins/integration_assistant": + version "0.0.0" + uid "" + "@kbn/interactive-setup-plugin@link:src/plugins/interactive_setup": version "0.0.0" uid "" @@ -5652,6 +5680,10 @@ version "0.0.0" uid "" +"@kbn/openapi-common@link:packages/kbn-openapi-common": + version "0.0.0" + uid "" + "@kbn/openapi-generator@link:packages/kbn-openapi-generator": version "0.0.0" uid "" @@ -5880,6 +5912,10 @@ version "0.0.0" uid "" +"@kbn/response-ops-feature-flag-service@link:packages/response-ops/feature_flag_service": + version "0.0.0" + uid "" + "@kbn/response-stream-plugin@link:examples/response_stream": version "0.0.0" uid "" @@ -5928,6 +5964,10 @@ version "0.0.0" uid "" +"@kbn/sample-task-plugin-mget@link:x-pack/test/task_manager_claimer_mget/plugins/sample_task_plugin_mget": + version "0.0.0" + uid "" + "@kbn/sample-task-plugin@link:x-pack/test/plugin_api_integration/plugins/sample_task_plugin": version "0.0.0" uid "" @@ -6016,10 +6056,18 @@ version "0.0.0" uid "" +"@kbn/search-homepage@link:x-pack/plugins/search_homepage": + version "0.0.0" + uid "" + "@kbn/search-index-documents@link:packages/kbn-search-index-documents": version "0.0.0" uid "" +"@kbn/search-inference-endpoints@link:x-pack/plugins/search_inference_endpoints": + version "0.0.0" + uid "" + "@kbn/search-notebooks@link:x-pack/plugins/search_notebooks": version "0.0.0" uid "" @@ -6160,6 +6208,10 @@ version "0.0.0" uid "" +"@kbn/securitysolution-lists-common@link:packages/kbn-securitysolution-lists-common": + version "0.0.0" + uid "" + "@kbn/securitysolution-rules@link:packages/kbn-securitysolution-rules": version "0.0.0" uid "" @@ -6896,48 +6948,68 @@ resolved "https://registry.yarnpkg.com/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz#8ace5259254426ccef57f3175bc64ed7095ed919" integrity sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw== -"@langchain/community@^0.0.44", "@langchain/community@~0.0.41": - version "0.0.44" - resolved "https://registry.yarnpkg.com/@langchain/community/-/community-0.0.44.tgz#b4f3453e3fd0b7a8c704fc35b004d7d738bd3416" - integrity sha512-II9Hz90jJmfWRICtxTg1auQWzFw0npqacWiiOpaxNhzs6rptdf56gyfC48Z6n1ii4R8FfAlfX6YxhOE7lGGKXg== +"@langchain/community@^0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@langchain/community/-/community-0.2.4.tgz#fb5feb4f4a01a1b33adfd28ce7126d0dedb3e6d1" + integrity sha512-rwrPNQLyIe84TPqPYbYOfDA4G/ba1rdj7OtZg63dQmxIvNDOmUCh4xIQac2iuRUnM3o4Ben0Faa9qz+V5oPgIA== dependencies: - "@langchain/core" "~0.1.44" - "@langchain/openai" "~0.0.19" + "@langchain/core" "~0.2.0" + "@langchain/openai" "~0.0.28" + binary-extensions "^2.2.0" expr-eval "^2.0.2" flat "^5.0.2" + js-yaml "^4.1.0" + langchain "0.2.3" langsmith "~0.1.1" uuid "^9.0.0" zod "^3.22.3" zod-to-json-schema "^3.22.5" -"@langchain/core@0.1.53", "@langchain/core@^0.1.53", "@langchain/core@~0.1.44", "@langchain/core@~0.1.45": - version "0.1.53" - resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.1.53.tgz#40bf273b6d5e1426c60ce9cc259562fe656573f1" - integrity sha512-khfRTu2DSCNMPUmnKx7iH0TpEaunW/4BgR6STTteRRDd0NFtXGfAwUuY9sm0+EKi/XKhdAmpGnfLwSfNg5F0Qw== +"@langchain/core@0.2.3", "@langchain/core@>0.1.0 <0.3.0", "@langchain/core@>0.1.56 <0.3.0", "@langchain/core@>0.1.61 <0.3.0", "@langchain/core@~0.2.0": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@langchain/core/-/core-0.2.3.tgz#7faa82f92b0c7843506e827a38bfcbb60f009d13" + integrity sha512-mVuFHSLpPQ4yOHNXeoSA3LnmIMuFmUiit5rvbYcPZqM6SrB2zCNN2nD4Ty5+3H5X4tYItDoSqsTuUNUQySXRQw== dependencies: ansi-styles "^5.0.0" camelcase "6" decamelize "1.2.0" - js-tiktoken "^1.0.8" + js-tiktoken "^1.0.12" langsmith "~0.1.7" ml-distance "^4.0.0" + mustache "^4.2.0" p-queue "^6.6.2" p-retry "4" uuid "^9.0.0" zod "^3.22.4" zod-to-json-schema "^3.22.3" -"@langchain/openai@^0.0.25", "@langchain/openai@~0.0.19": - version "0.0.25" - resolved "https://registry.yarnpkg.com/@langchain/openai/-/openai-0.0.25.tgz#8332abea1e3acb9b1169f90636e518c0ee90622e" - integrity sha512-cD9xPDDXK2Cjs6yYg27BpdzBnQZvBb1yaNgMoGLWIT27UQVRyT96PLC1OVMQOmMmHaKDBCj/1bW4GQQgX7+d2Q== +"@langchain/langgraph@^0.0.23": + version "0.0.23" + resolved "https://registry.yarnpkg.com/@langchain/langgraph/-/langgraph-0.0.23.tgz#34b5ad5dc9fe644ee96bcfcf11197ec1d7f9e0e2" + integrity sha512-pXlcsBOseT5xdf9enUqbLQ/59LaZxgMI2dL2vFJ+EpcoK7bQnlzzhRtRPp+vubMyMeEKRoAXlaA9ObwpVi93CA== dependencies: - "@langchain/core" "~0.1.45" - js-tiktoken "^1.0.7" - openai "^4.26.0" + "@langchain/core" ">0.1.61 <0.3.0" + uuid "^9.0.1" + +"@langchain/openai@^0.0.34", "@langchain/openai@~0.0.28": + version "0.0.34" + resolved "https://registry.yarnpkg.com/@langchain/openai/-/openai-0.0.34.tgz#36c9bca0721ab9f7e5d40927e7c0429cacbd5b56" + integrity sha512-M+CW4oXle5fdoz2T2SwdOef8pl3/1XmUx1vjn2mXUVM/128aO0l23FMF0SNBsAbRV6P+p/TuzjodchJbi0Ht/A== + dependencies: + "@langchain/core" ">0.1.56 <0.3.0" + js-tiktoken "^1.0.12" + openai "^4.41.1" zod "^3.22.4" zod-to-json-schema "^3.22.3" +"@langchain/textsplitters@~0.0.0": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@langchain/textsplitters/-/textsplitters-0.0.2.tgz#500baa8341fb7fc86fca531a4192665a319504a3" + integrity sha512-6bQOuYHTGYlkgPY/8M5WPq4nnXZpEysGzRopQCYjg2WLcEoIPUMMrXsAaNNdvU3BOeMrhin8izvpDPD165hX6Q== + dependencies: + "@langchain/core" ">0.1.0 <0.3.0" + js-tiktoken "^1.0.12" + "@langtrase/trace-attributes@^3.0.8": version "3.0.8" resolved "https://registry.yarnpkg.com/@langtrase/trace-attributes/-/trace-attributes-3.0.8.tgz#ff6ae44cfc048a9da10a7949664b2060a71b6304" @@ -6945,20 +7017,6 @@ dependencies: ncp "^2.0.0" -"@langtrase/typescript-sdk@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@langtrase/typescript-sdk/-/typescript-sdk-2.2.1.tgz#8ec60290b9882e161bc42bd51cf0a72396438633" - integrity sha512-CRzhOcGMLv68ICvENDBQ/PWIHyBNlc6lNBaqF9EM/s+HUusSPnBmvQ+nGObqpPR+uo7m9hDRRUmUXOlAooj0PQ== - dependencies: - "@langtrase/trace-attributes" "^3.0.8" - "@opentelemetry/api" "^1.7.0" - "@opentelemetry/instrumentation" "^0.49.1" - "@opentelemetry/sdk-trace-base" "^1.22.0" - "@opentelemetry/sdk-trace-node" "^1.22.0" - axios "^1.6.7" - node-loader "^2.0.0" - tiktoken "^1.0.13" - "@launchdarkly/js-sdk-common@2.5.0": version "2.5.0" resolved "https://registry.yarnpkg.com/@launchdarkly/js-sdk-common/-/js-sdk-common-2.5.0.tgz#d1dc595034bf6ee09b0313add5b8901fe9b82f26" @@ -7310,6 +7368,23 @@ resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.2.tgz#0f164b726869f71da3c594171df5ebc1c4b0a407" integrity sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ== +"@mswjs/cookies@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@mswjs/cookies/-/cookies-1.1.0.tgz#1528eb43630caf83a1d75d5332b30e75e9bb1b5b" + integrity sha512-0ZcCVQxifZmhwNBoQIrystCb+2sWBY2Zw8lpfJBPCHGCA/HWqehITeCRVIv4VMy8MPlaHo2w2pTHFV2pFfqKPw== + +"@mswjs/interceptors@^0.29.0": + version "0.29.1" + resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.29.1.tgz#e77fc58b5188569041d0440b25c9e9ebb1ccd60a" + integrity sha512-3rDakgJZ77+RiQUuSK69t1F0m8BQKA8Vh5DCS5V0DWvNY67zob2JhhQrhCO0AKLGINTRSFd1tBaHcJTkhefoSw== + dependencies: + "@open-draft/deferred-promise" "^2.2.0" + "@open-draft/logger" "^0.3.0" + "@open-draft/until" "^2.0.0" + is-node-process "^1.2.0" + outvariant "^1.2.1" + strict-event-emitter "^0.5.1" + "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": version "2.1.8-no-fsevents.3" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b" @@ -7608,12 +7683,23 @@ dependencies: "@octokit/openapi-types" "^18.0.0" -"@opentelemetry/api-logs@0.49.1": - version "0.49.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.49.1.tgz#51a66ed5eb5eeeafffbd36c1713aa91cbfdd5259" - integrity sha512-kaNl/T7WzyMUQHQlVq7q0oV4Kev6+0xFwqzofryC66jgGMacd0QH5TwfpbUwSTby+SdAdprAe5UKMvBw4tKS5Q== +"@open-draft/deferred-promise@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz#4a822d10f6f0e316be4d67b4d4f8c9a124b073bd" + integrity sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA== + +"@open-draft/logger@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@open-draft/logger/-/logger-0.3.0.tgz#2b3ab1242b360aa0adb28b85f5d7da1c133a0954" + integrity sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ== dependencies: - "@opentelemetry/api" "^1.0.0" + is-node-process "^1.2.0" + outvariant "^1.4.0" + +"@open-draft/until@^2.0.0", "@open-draft/until@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-2.1.0.tgz#0acf32f470af2ceaf47f095cdecd40d68666efda" + integrity sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg== "@opentelemetry/api-metrics@0.31.0", "@opentelemetry/api-metrics@^0.31.0": version "0.31.0" @@ -7622,16 +7708,11 @@ dependencies: "@opentelemetry/api" "^1.0.0" -"@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.1.0", "@opentelemetry/api@^1.4.1", "@opentelemetry/api@^1.7.0": +"@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.1.0", "@opentelemetry/api@^1.4.1": version "1.8.0" resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.8.0.tgz#5aa7abb48f23f693068ed2999ae627d2f7d902ec" integrity sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w== -"@opentelemetry/context-async-hooks@1.24.0": - version "1.24.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-1.24.0.tgz#f5f8cc15038d293a8e9b570543c1f36aa4ee17ec" - integrity sha512-s7xaQ9ifDpJvwbWRLkZD/J5hY35w+MECm4TQUkg6szRcny9lf6oVhWij4w3JJFQgvHQMXU7oXOpX8Z05HxV/8g== - "@opentelemetry/core@1.15.0": version "1.15.0" resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.15.0.tgz#2ba928df0443732825a72a766c2edae9a7f9863f" @@ -7694,18 +7775,6 @@ "@opentelemetry/core" "1.5.0" "@opentelemetry/sdk-metrics-base" "0.31.0" -"@opentelemetry/instrumentation@^0.49.1": - version "0.49.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.49.1.tgz#1b95e5f9448a96e7af97e03846772829439a9a91" - integrity sha512-0DLtWtaIppuNNRRllSD4bjU8ZIiLp1cDXvJEbp752/Zf+y3gaLNaoGRGIlX4UHhcsrmtL+P2qxi3Hodi8VuKiQ== - dependencies: - "@opentelemetry/api-logs" "0.49.1" - "@types/shimmer" "^1.0.2" - import-in-the-middle "1.7.1" - require-in-the-middle "^7.1.1" - semver "^7.5.2" - shimmer "^1.2.1" - "@opentelemetry/otlp-exporter-base@0.34.0": version "0.34.0" resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.34.0.tgz#c6020b63590d4b8ac3833eda345a6f582fa014b1" @@ -7733,20 +7802,6 @@ "@opentelemetry/sdk-metrics" "1.8.0" "@opentelemetry/sdk-trace-base" "1.8.0" -"@opentelemetry/propagator-b3@1.24.0": - version "1.24.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-b3/-/propagator-b3-1.24.0.tgz#88a2ffede42ac6df7c409bacec0c9f9cc181bc13" - integrity sha512-7TMIDE4+NO5vnkor+zned42wqca+hmhW5gWKhmYjUHC5B5uojo1PvtmBrd7kigFu96XvL4ZUWVzibWRWIQ/++Q== - dependencies: - "@opentelemetry/core" "1.24.0" - -"@opentelemetry/propagator-jaeger@1.24.0": - version "1.24.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.24.0.tgz#e67fe6f8f2f1d74335909a7f7352d0761039ab79" - integrity sha512-r3MX3AmJiUeiWTXSDOdwBeaO+ahvWcFCpuKxmhhsH8Q8LqDnjhNd3krqBh4Qsq9wa0WhWtiQaDs/NOCWoMOlOw== - dependencies: - "@opentelemetry/core" "1.24.0" - "@opentelemetry/resources@1.15.0": version "1.15.0" resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.15.0.tgz#748a6ae9017636b8b30f5dee1fff3e166e51f63d" @@ -7809,15 +7864,6 @@ lodash.merge "^4.6.2" tslib "^2.3.1" -"@opentelemetry/sdk-trace-base@1.24.0", "@opentelemetry/sdk-trace-base@^1.22.0", "@opentelemetry/sdk-trace-base@^1.24.0": - version "1.24.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.24.0.tgz#e2de869e33fd224f6d9f39bafa4172074d1086c8" - integrity sha512-H9sLETZ4jw9UJ3totV8oM5R0m4CW0ZIOLfp4NV3g0CM8HD5zGZcaW88xqzWDgiYRpctFxd+WmHtGX/Upoa2vRg== - dependencies: - "@opentelemetry/core" "1.24.0" - "@opentelemetry/resources" "1.24.0" - "@opentelemetry/semantic-conventions" "1.24.0" - "@opentelemetry/sdk-trace-base@1.8.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.8.0.tgz#70713aab90978a16dea188c8335209f857be7384" @@ -7827,17 +7873,14 @@ "@opentelemetry/resources" "1.8.0" "@opentelemetry/semantic-conventions" "1.8.0" -"@opentelemetry/sdk-trace-node@^1.22.0": +"@opentelemetry/sdk-trace-base@^1.24.0": version "1.24.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.24.0.tgz#34c56f092f98a16e0e045152c9a4baf50ed8dcee" - integrity sha512-QgByHmM9uloTpcYEEyW9YJEIMKHFSIM677RH9pJPWWwtM2NQFbEp/8HIJw80Ymtaz6cAxg1Kay1ByqIVzq3t5g== + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.24.0.tgz#e2de869e33fd224f6d9f39bafa4172074d1086c8" + integrity sha512-H9sLETZ4jw9UJ3totV8oM5R0m4CW0ZIOLfp4NV3g0CM8HD5zGZcaW88xqzWDgiYRpctFxd+WmHtGX/Upoa2vRg== dependencies: - "@opentelemetry/context-async-hooks" "1.24.0" "@opentelemetry/core" "1.24.0" - "@opentelemetry/propagator-b3" "1.24.0" - "@opentelemetry/propagator-jaeger" "1.24.0" - "@opentelemetry/sdk-trace-base" "1.24.0" - semver "^7.5.2" + "@opentelemetry/resources" "1.24.0" + "@opentelemetry/semantic-conventions" "1.24.0" "@opentelemetry/semantic-conventions@1.15.0": version "1.15.0" @@ -8230,70 +8273,70 @@ "@types/node" ">=18.0.0" axios "^1.6.0" -"@smithy/eventstream-codec@^2.0.12", "@smithy/eventstream-codec@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-codec/-/eventstream-codec-2.1.1.tgz#4405ab0f9c77d439c575560c4886e59ee17d6d38" - integrity sha512-E8KYBxBIuU4c+zrpR22VsVrOPoEDzk35bQR3E+xm4k6Pa6JqzkDOdMyf9Atac5GPNKHJBdVaQ4JtjdWX2rl/nw== +"@smithy/eventstream-codec@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-codec/-/eventstream-codec-3.0.0.tgz#81d30391220f73d41f432f65384b606d67673e46" + integrity sha512-PUtyEA0Oik50SaEFCZ0WPVtF9tz/teze2fDptW6WRXl+RrEenH8UbEjudOz8iakiMl3lE3lCVqYf2Y+znL8QFQ== dependencies: "@aws-crypto/crc32" "3.0.0" - "@smithy/types" "^2.9.1" - "@smithy/util-hex-encoding" "^2.1.1" - tslib "^2.5.0" + "@smithy/types" "^3.0.0" + "@smithy/util-hex-encoding" "^3.0.0" + tslib "^2.6.2" -"@smithy/eventstream-serde-node@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-node/-/eventstream-serde-node-2.1.1.tgz#2e1afa27f9c7eb524c1c53621049c5e4e3cea6a5" - integrity sha512-LF882q/aFidFNDX7uROAGxq3H0B7rjyPkV6QDn6/KDQ+CG7AFkRccjxRf1xqajq/Pe4bMGGr+VKAaoF6lELIQw== +"@smithy/eventstream-serde-node@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.0.tgz#6519523fbb429307be29b151b8ba35bcca2b6e64" + integrity sha512-baRPdMBDMBExZXIUAoPGm/hntixjt/VFpU6+VmCyiYJYzRHRxoaI1MN+5XE+hIS8AJ2GCHLMFEIOLzq9xx1EgQ== dependencies: - "@smithy/eventstream-serde-universal" "^2.1.1" - "@smithy/types" "^2.9.1" - tslib "^2.5.0" + "@smithy/eventstream-serde-universal" "^3.0.0" + "@smithy/types" "^3.0.0" + tslib "^2.6.2" -"@smithy/eventstream-serde-universal@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-2.1.1.tgz#0f5eec9ad033017973a67bafb5549782499488d2" - integrity sha512-LR0mMT+XIYTxk4k2fIxEA1BPtW3685QlqufUEUAX1AJcfFfxNDKEvuCRZbO8ntJb10DrIFVJR9vb0MhDCi0sAQ== +"@smithy/eventstream-serde-universal@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.0.tgz#cb8441a73fbde4cbaa68e4a21236f658d914a073" + integrity sha512-HNFfShmotWGeAoW4ujP8meV9BZavcpmerDbPIjkJbxKbN8RsUcpRQ/2OyIxWNxXNH2GWCAxuSB7ynmIGJlQ3Dw== dependencies: - "@smithy/eventstream-codec" "^2.1.1" - "@smithy/types" "^2.9.1" - tslib "^2.5.0" + "@smithy/eventstream-codec" "^3.0.0" + "@smithy/types" "^3.0.0" + tslib "^2.6.2" -"@smithy/is-array-buffer@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz#8fa9b8040651e7ba0b2f6106e636a91354ff7d34" - integrity sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug== +"@smithy/is-array-buffer@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz#9a95c2d46b8768946a9eec7f935feaddcffa5e7a" + integrity sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ== dependencies: - tslib "^2.5.0" + tslib "^2.6.2" -"@smithy/types@^2.4.0", "@smithy/types@^2.9.1": - version "2.9.1" - resolved "https://registry.yarnpkg.com/@smithy/types/-/types-2.9.1.tgz#ed04d4144eed3b8bd26d20fc85aae8d6e357ebb9" - integrity sha512-vjXlKNXyprDYDuJ7UW5iobdmyDm6g8dDG+BFUncAg/3XJaN45Gy5RWWWUVgrzIK7S4R1KWgIX5LeJcfvSI24bw== +"@smithy/types@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/types/-/types-3.0.0.tgz#00231052945159c64ffd8b91e8909d8d3006cb7e" + integrity sha512-VvWuQk2RKFuOr98gFhjca7fkBS+xLLURT8bUjk5XQoV0ZLm7WPwWPPY3/AwzTLuUBDeoKDCthfe1AsTUWaSEhw== dependencies: - tslib "^2.5.0" + tslib "^2.6.2" -"@smithy/util-buffer-from@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz#7eb75d72288b6b3001bc5f75b48b711513091deb" - integrity sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw== +"@smithy/util-buffer-from@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz#559fc1c86138a89b2edaefc1e6677780c24594e3" + integrity sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA== dependencies: - "@smithy/is-array-buffer" "^2.0.0" - tslib "^2.5.0" + "@smithy/is-array-buffer" "^3.0.0" + tslib "^2.6.2" -"@smithy/util-hex-encoding@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-2.1.1.tgz#978252b9fb242e0a59bae4ead491210688e0d15f" - integrity sha512-3UNdP2pkYUUBGEXzQI9ODTDK+Tcu1BlCyDBaRHwyxhA+8xLP8agEKQq4MGmpjqb4VQAjq9TwlCQX0kP6XDKYLg== +"@smithy/util-hex-encoding@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz#32938b33d5bf2a15796cd3f178a55b4155c535e6" + integrity sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ== dependencies: - tslib "^2.5.0" + tslib "^2.6.2" -"@smithy/util-utf8@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-2.0.0.tgz#b4da87566ea7757435e153799df9da717262ad42" - integrity sha512-rctU1VkziY84n5OXe3bPNpKR001ZCME2JCaBBFgtiM2hfKbHFudc/BkMuPab8hRbLd0j3vbnBTTZ1igBf0wgiQ== +"@smithy/util-utf8@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-3.0.0.tgz#1a6a823d47cbec1fd6933e5fc87df975286d9d6a" + integrity sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA== dependencies: - "@smithy/util-buffer-from" "^2.0.0" - tslib "^2.5.0" + "@smithy/util-buffer-from" "^3.0.0" + tslib "^2.6.2" "@statoscope/extensions@5.28.1": version "5.28.1" @@ -9838,6 +9881,11 @@ dependencies: "@types/node" "*" +"@types/cookie@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5" + integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA== + "@types/cookiejar@^2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78" @@ -10529,6 +10577,13 @@ resolved "https://registry.yarnpkg.com/@types/mustache/-/mustache-0.8.31.tgz#7c86cbf74f7733f9e3bdc28817623927eb386616" integrity sha512-72flCZJkEJHPwhmpHgg4a0ZBLssMhg5NB0yltRblRlZMo4py3B/u/d7icevc4EeN9MPQUo/dPtuVOoVy9ih6cQ== +"@types/mute-stream@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@types/mute-stream/-/mute-stream-0.0.4.tgz#77208e56a08767af6c5e1237be8888e2f255c478" + integrity sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow== + dependencies: + "@types/node" "*" + "@types/nock@^10.0.3": version "10.0.3" resolved "https://registry.yarnpkg.com/@types/nock/-/nock-10.0.3.tgz#dab1d18ffbccfbf2db811dab9584304eeb6e1c4c" @@ -10566,7 +10621,7 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@14 || 16 || 17", "@types/node@20.10.5", "@types/node@>= 8", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@>=18.0.0", "@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0", "@types/node@^18.0.0", "@types/node@^18.11.18": +"@types/node@*", "@types/node@14 || 16 || 17", "@types/node@20.10.5", "@types/node@>= 8", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@>=18.0.0", "@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0", "@types/node@^18.0.0", "@types/node@^18.11.18", "@types/node@^20.12.12": version "20.10.5" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.5.tgz#47ad460b514096b7ed63a1dae26fad0914ed3ab2" integrity sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw== @@ -10600,6 +10655,11 @@ resolved "https://registry.yarnpkg.com/@types/numeral/-/numeral-2.0.5.tgz#388e5c4ff4b0e1787f130753cbbe83d3ba770858" integrity sha512-kH8I7OSSwQu9DS9JYdFWbuvhVzvFRoCPCkGxNwoGgaPeDfEPJlcxNvEOypZhQ3XXHsGbfIuYcxcJxKUfJHnRfw== +"@types/nunjucks@^3.2.6": + version "3.2.6" + resolved "https://registry.yarnpkg.com/@types/nunjucks/-/nunjucks-3.2.6.tgz#6d6e0363719545df8b9a024279902edf68b2caa9" + integrity sha512-pHiGtf83na1nCzliuAdq8GowYiXvH5l931xZ0YEHaLMNFgynpEqx+IPStlu7UaDkehfvl01e4x/9Tpwhy7Ue3w== + "@types/object-hash@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@types/object-hash/-/object-hash-1.3.0.tgz#b20db2074129f71829d61ff404e618c4ac3d73cf" @@ -10935,11 +10995,6 @@ resolved "https://registry.yarnpkg.com/@types/set-value/-/set-value-2.0.0.tgz#63d386b103926dcf49b50e16e0f6dd49983046be" integrity sha512-k8dCJEC80F/mbsIOZ5Hj3YSzTVVVBwMdtP/M9Rtc2TM4F5etVd+2UG8QUiAUfbXm4fABedL2tBZnrBheY7UwpA== -"@types/shimmer@^1.0.2": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/shimmer/-/shimmer-1.0.5.tgz#491d8984d4510e550bfeb02d518791d7f59d2b88" - integrity sha512-9Hp0ObzwwO57DpLFF0InUjUm/II8GmKAvzbefxQTihCb7KI6yc9yzf0nLc4mVdby5N4DRCgQM2wCup9KTieeww== - "@types/sinon@^7.0.13": version "7.0.13" resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.0.13.tgz#ca039c23a9e27ebea53e0901ef928ea2a1a6d313" @@ -10984,6 +11039,11 @@ resolved "https://registry.yarnpkg.com/@types/stats-lite/-/stats-lite-2.2.0.tgz#bc8190bf9dfa1e16b89eaa2b433c99dff0804de9" integrity sha512-YV6SS4QC+pbzqjMIV8qVSTDOOazgKBLTVaN+7PfuxELjz/eyzc20KwDVGPrbHt2OcYMA7K2ezLB45Cp6DpNOSQ== +"@types/statuses@^2.0.4": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/statuses/-/statuses-2.0.5.tgz#f61ab46d5352fd73c863a1ea4e1cef3b0b51ae63" + integrity sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A== + "@types/styled-components@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.0.tgz#24d3412ba5395aa06e14fbc93c52f9454cebd0d6" @@ -11194,6 +11254,11 @@ tapable "^2.2.0" webpack "^5" +"@types/wrap-ansi@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" + integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g== + "@types/ws@*", "@types/ws@^8.5.1": version "8.5.3" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" @@ -11693,6 +11758,11 @@ resolved "https://registry.yarnpkg.com/@zip.js/zip.js/-/zip.js-2.7.45.tgz#823fe2789401d8c1d836ce866578379ec1bd6f0b" integrity sha512-Mm2EXF33DJQ/3GWWEWeP1UCqzpQ5+fiMvT3QWspsXY05DyqqxWu7a9awSzU4/spHMHVFrTjani1PR0vprgZpow== +a-sync-waterfall@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz#75b6b6aa72598b497a125e7a2770f14f4c8a1fa7" + integrity sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA== + abab@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" @@ -11942,12 +12012,12 @@ ansi-colors@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== -ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.0.tgz#a4ce2b33d6b214b7950d8595c212f12ac9cc569d" - integrity sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg== +ansi-escapes@^4.2.1, ansi-escapes@^4.3.0, ansi-escapes@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== dependencies: - type-fest "^0.8.1" + type-fest "^0.21.3" ansi-escapes@^6.2.0: version "6.2.1" @@ -12353,7 +12423,7 @@ arrify@^2.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== -asap@^2.0.0: +asap@^2.0.0, asap@^2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= @@ -12467,7 +12537,7 @@ async@^1.4.2: resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= -async@^3.2.0, async@^3.2.3: +async@^3.2.0, async@^3.2.3, async@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== @@ -12865,11 +12935,6 @@ bare-path@^2.0.0, bare-path@^2.1.0: dependencies: bare-os "^2.1.0" -base-64@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb" - integrity sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA== - base64-js@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" @@ -13138,7 +13203,7 @@ brace@0.11.1, brace@^0.11.1: braces@^2.3.1: version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + resolved "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz" integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== dependencies: arr-flatten "^1.1.0" @@ -13152,12 +13217,12 @@ braces@^2.3.1: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" breadth-filter@^2.0.0: version "2.0.0" @@ -13977,7 +14042,7 @@ cli-progress@^3.12.0: dependencies: string-width "^4.2.3" -cli-spinners@^2.2.0, cli-spinners@^2.5.0: +cli-spinners@^2.2.0, cli-spinners@^2.5.0, cli-spinners@^2.9.2: version "2.9.2" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== @@ -14012,6 +14077,11 @@ cli-width@^3.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== +cli-width@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5" + integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== + client-only@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" @@ -14086,16 +14156,16 @@ clone-stats@^1.0.0: resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= +clone@2.x, clone@^2.1.1, clone@^2.1.2, clone@~2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + clone@^1.0.2, clone@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= -clone@^2.1.1, clone@^2.1.2, clone@~2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" - integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= - cloneable-readable@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.2.tgz#d591dee4a8f8bc15da43ce97dceeba13d43e2a65" @@ -14273,7 +14343,7 @@ commander@^4.0.1, commander@^4.1.1: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== -commander@^5.0.0: +commander@^5.0.0, commander@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== @@ -14289,9 +14359,9 @@ common-path-prefix@^3.0.0: integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w== common-tags@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" - integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== + version "1.8.2" + resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" + integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA== commondir@^1.0.1: version "1.0.1" @@ -15082,10 +15152,10 @@ d3-collection@^1.0.7: resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.7.tgz#349bd2aa9977db071091c13144d5e4f16b5b310e" integrity sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A== -"d3-color@1 - 2", "d3-color@npm:@elastic/d3-color@2.0.1": +"d3-color@1 - 2", "d3-color@npm:@elastic/kibana-d3-color@2.0.1": version "2.0.1" - resolved "https://registry.yarnpkg.com/@elastic/d3-color/-/d3-color-2.0.1.tgz#afb7f58ace7e5ebcdbeddeb06d5e0b16aad5e7c0" - integrity sha512-XIZ+7nbjdjwpdBQLFEE+THVyDAlBGxjvNt8CDGos1f/apkdRHzOiZNgp/LtLdpfG+z3zYApDP9enWCaWCVvHSQ== + resolved "https://registry.yarnpkg.com/@elastic/kibana-d3-color/-/kibana-d3-color-2.0.1.tgz#f83b9c2fea09273a918659de04d5e8098c82f65c" + integrity sha512-YZ8hV2bWNyYi833Yj3UWczmTxdHzmo/Xc2IVkNXr/ZqtkrTDlTLysCyJm7SfAt9iBy6EVRGWTn8cPz8QOY6Ixw== "d3-color@1 - 3", d3-color@^3.1.0: version "3.1.0" @@ -15889,14 +15959,6 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -digest-fetch@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/digest-fetch/-/digest-fetch-1.3.0.tgz#898e69264d00012a23cf26e8a3e40320143fc661" - integrity sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA== - dependencies: - base-64 "^0.1.0" - md5 "^2.3.0" - dir-glob@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4" @@ -16214,10 +16276,10 @@ elastic-apm-node@3.46.0: traverse "^0.6.6" unicode-byte-truncate "^1.0.0" -elastic-apm-node@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-4.6.0.tgz#9de3b758158c85dee5932857eb93e21cf204c45a" - integrity sha512-mieujz8IA30wkavF8JG/mSBep2DG2zGMblQAh1uEkeXIjb8njrW3Ef+cqKPEdaFPN/Iqm2LZzZuy+Fu20MsfkA== +elastic-apm-node@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-4.7.0.tgz#27d7d78f659dbed6e03a5c4d50b5bab62b775bc2" + integrity sha512-fOcDYOcZ/180Ib0p8xLNF8iDu9e1QGqZiEizOntdh1GmkurN3sIGVgGlE25s9LRBUMmD2bMWwlER56T8moW+tg== dependencies: "@elastic/ecs-pino-format" "^1.5.0" "@opentelemetry/api" "^1.4.1" @@ -17739,10 +17801,10 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -18788,7 +18850,7 @@ graphql-tag@^2.12.6: dependencies: tslib "^2.1.0" -graphql@^16.6.0: +graphql@^16.6.0, graphql@^16.8.1: version "16.8.1" resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== @@ -19144,6 +19206,11 @@ he@1.2.0, he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +headers-polyfill@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-4.0.3.tgz#922a0155de30ecc1f785bcf04be77844ca95ad07" + integrity sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ== + heap@^0.2.6: version "0.2.6" resolved "https://registry.yarnpkg.com/heap/-/heap-0.2.6.tgz#087e1f10b046932fc8594dd9e6d378afc9d1e5ac" @@ -19595,16 +19662,6 @@ import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.3.0: parent-module "^1.0.0" resolve-from "^4.0.0" -import-in-the-middle@1.7.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.7.1.tgz#3e111ff79c639d0bde459bd7ba29dd9fdf357364" - integrity sha512-1LrZPDtW+atAxH42S6288qyDFNQ2YCty+2mxEPRtfazH6Z5QwkaBSTS2ods7hnVJioF6rkRfNoA6A/MstpFXLg== - dependencies: - acorn "^8.8.2" - acorn-import-assertions "^1.9.0" - cjs-module-lexer "^1.2.2" - module-details-from-path "^1.0.3" - import-in-the-middle@1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.8.0.tgz#c94d88d53701de9a248f9710b41f533e67f598a4" @@ -20156,6 +20213,11 @@ is-nil@^1.0.0: resolved "https://registry.yarnpkg.com/is-nil/-/is-nil-1.0.1.tgz#2daba29e0b585063875e7b539d071f5b15937969" integrity sha1-LauingtYUGOHXntTnQcfWxWTeWk= +is-node-process@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-node-process/-/is-node-process-1.2.0.tgz#ea02a1b90ddb3934a19aea414e88edef7e11d134" + integrity sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw== + is-npm@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-6.0.0.tgz#b59e75e8915543ca5d881ecff864077cba095261" @@ -21224,10 +21286,10 @@ js-string-escape@^1.0.1: resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" integrity sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8= -js-tiktoken@^1.0.7, js-tiktoken@^1.0.8: - version "1.0.10" - resolved "https://registry.yarnpkg.com/js-tiktoken/-/js-tiktoken-1.0.10.tgz#2b343ec169399dcee8f9ef9807dbd4fafd3b30dc" - integrity sha512-ZoSxbGjvGyMT13x6ACo9ebhDha/0FHdKA+OsQcMOWcm1Zs7r90Rhk5lhERLzji+3rA7EKpXCgwXcM5fF3DMpdA== +js-tiktoken@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/js-tiktoken/-/js-tiktoken-1.0.12.tgz#af0f5cf58e5e7318240d050c8413234019424211" + integrity sha512-L7wURW1fH9Qaext0VzaUDpFGVQgjkdE3Dgsy9/+yXyGEpBKnylTd0mU0bfbNkKDlXRb6TEsZkwuflu1B8uQbJQ== dependencies: base64-js "^1.5.1" @@ -21631,17 +21693,16 @@ kuler@^2.0.0: resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== -langchain@^0.1.30: - version "0.1.30" - resolved "https://registry.yarnpkg.com/langchain/-/langchain-0.1.30.tgz#e1adb3f1849fcd5c596c668300afd5dc8cb37a97" - integrity sha512-5h/vNMmutQ98tbB0sPDlAileZVca6A2McFgGa3+D56Dm8mSSCzTQL2DngPA6h09DlKDpSr7+6PdFw5Hoj0ZDSw== +langchain@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/langchain/-/langchain-0.2.3.tgz#c14bb05cf871b21bd63b84b3ab89580b1d62539f" + integrity sha512-T9xR7zd+Nj0oXy6WoYKmZLy0DlQiDLFPGYWdOXDxy+AvqlujoPdVQgDSpdqiOHvAjezrByAoKxoHCz5XMwTP/Q== dependencies: - "@anthropic-ai/sdk" "^0.9.1" - "@langchain/community" "~0.0.41" - "@langchain/core" "~0.1.44" - "@langchain/openai" "~0.0.19" + "@langchain/core" "~0.2.0" + "@langchain/openai" "~0.0.28" + "@langchain/textsplitters" "~0.0.0" binary-extensions "^2.2.0" - js-tiktoken "^1.0.7" + js-tiktoken "^1.0.12" js-yaml "^4.1.0" jsonpointer "^5.0.1" langchainhub "~0.0.8" @@ -21659,10 +21720,10 @@ langchainhub@~0.0.8: resolved "https://registry.yarnpkg.com/langchainhub/-/langchainhub-0.0.8.tgz#fd4b96dc795e22e36c1a20bad31b61b0c33d3110" integrity sha512-Woyb8YDHgqqTOZvWIbm2CaFDGfZ4NTSyXV687AG4vXEfoNo7cGQp7nhl7wL3ehenKWmNEmcxCLgOZzW8jE6lOQ== -langsmith@^0.1.14, langsmith@~0.1.1, langsmith@~0.1.7: - version "0.1.14" - resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.1.14.tgz#2b889dbcfb49547614df276a4a5a063092a1585d" - integrity sha512-iEzQLLB7/0nRpAwNBAR7B7N64fyByg5UsNjSvLaCCkQ9AS68PSafjB8xQkyI8QXXrGjU1dEqDRoa8m4SUuRdUw== +langsmith@^0.1.30, langsmith@~0.1.1, langsmith@~0.1.7: + version "0.1.30" + resolved "https://registry.yarnpkg.com/langsmith/-/langsmith-0.1.30.tgz#3000e441605b26e15a87fb991a3929c944edbc0a" + integrity sha512-g8f10H1iiRjCweXJjgM3Y9xl6ApCa1OThDvc0BlSDLVrGVPy1on9wT39vAzYkeadC7oG48p7gfpGlYH3kLkJ9Q== dependencies: "@types/uuid" "^9.0.1" commander "^10.0.1" @@ -21689,6 +21750,11 @@ latest-version@^7.0.0: dependencies: package-json "^8.1.0" +launchdarkly-eventsource@1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/launchdarkly-eventsource/-/launchdarkly-eventsource-1.4.4.tgz#fa595af8602e487c61520787170376c6a1104459" + integrity sha512-GL+r2Y3WccJlhFyL2buNKel+9VaMnYpbE/FfCkOST5jSNSFodahlxtGyrE8o7R+Qhobyq0Ree4a7iafJDQi9VQ== + launchdarkly-eventsource@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/launchdarkly-eventsource/-/launchdarkly-eventsource-2.0.3.tgz#8a7b8da5538153f438f7d452b1c87643d900f984" @@ -21711,6 +21777,19 @@ launchdarkly-js-sdk-common@5.2.0: fast-deep-equal "^2.0.1" uuid "^8.0.0" +launchdarkly-node-server-sdk@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/launchdarkly-node-server-sdk/-/launchdarkly-node-server-sdk-7.0.3.tgz#d7a8b996d992b0ca5d4972db5df1ae49332b094c" + integrity sha512-uSkBezAiQ9nwv8N6CmI7OmyJ9e3xpueJzYOso8+5vMf7VtBtPjz6RRsUkUsSzUDo7siclmW8USjCwqn9aX2EbQ== + dependencies: + async "^3.2.4" + launchdarkly-eventsource "1.4.4" + lru-cache "^6.0.0" + node-cache "^5.1.0" + semver "^7.5.4" + tunnel "0.0.6" + uuid "^8.3.2" + lazy-ass@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" @@ -22131,9 +22210,9 @@ loglevel-plugin-prefix@^0.8.4: integrity sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g== loglevel@^1.6.0: - version "1.6.8" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171" - integrity sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA== + version "1.9.1" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.1.tgz#d63976ac9bcd03c7c873116d41c2a85bafff1be7" + integrity sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg== lolex@^4.2.0: version "4.2.0" @@ -22792,7 +22871,7 @@ micromark@^2.11.3, micromark@~2.11.0, micromark@~2.11.3: micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== dependencies: arr-diff "^4.0.0" @@ -22809,12 +22888,12 @@ micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" + integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" miller-rabin@^4.0.0: @@ -22920,9 +22999,9 @@ minimatch@^5.0.1, minimatch@^5.1.0: brace-expansion "^2.0.1" minimatch@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.1.tgz#8a555f541cf976c622daf078bb28f29fb927c253" - integrity sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w== + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== dependencies: brace-expansion "^2.0.1" @@ -23392,6 +23471,29 @@ msgpackr@^1.9.9: optionalDependencies: msgpackr-extract "^3.0.2" +msw@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/msw/-/msw-2.3.1.tgz#bfc73e256ffc2c74ec4381b604abb258df35f32b" + integrity sha512-ocgvBCLn/5l3jpl1lssIb3cniuACJLoOfZu01e3n5dbJrpA5PeeWn28jCLgQDNt6d7QT8tF2fYRzm9JoEHtiig== + dependencies: + "@bundled-es-modules/cookie" "^2.0.0" + "@bundled-es-modules/statuses" "^1.0.1" + "@inquirer/confirm" "^3.0.0" + "@mswjs/cookies" "^1.1.0" + "@mswjs/interceptors" "^0.29.0" + "@open-draft/until" "^2.1.0" + "@types/cookie" "^0.6.0" + "@types/statuses" "^2.0.4" + chalk "^4.1.2" + graphql "^16.8.1" + headers-polyfill "^4.0.2" + is-node-process "^1.2.0" + outvariant "^1.4.2" + path-to-regexp "^6.2.0" + strict-event-emitter "^0.5.1" + type-fest "^4.9.0" + yargs "^17.7.2" + multicast-dns@^7.2.5: version "7.2.5" resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" @@ -23426,6 +23528,11 @@ mustache@^2.3.2: resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.2.tgz#a6d4d9c3f91d13359ab889a812954f9230a3d0c5" integrity sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ== +mustache@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz" + integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ== + mutation-observer@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/mutation-observer/-/mutation-observer-1.0.3.tgz#42e9222b101bca82e5ba9d5a7acf4a14c0f263d0" @@ -23436,6 +23543,11 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +mute-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" + integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== + nan@^2.18.0: version "2.18.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" @@ -23616,6 +23728,13 @@ node-addon-api@^6.1.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76" integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== +node-cache@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-5.1.2.tgz#f264dc2ccad0a780e76253a694e9fd0ed19c398d" + integrity sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg== + dependencies: + clone "2.x" + node-dir@^0.1.10: version "0.1.17" resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" @@ -23750,13 +23869,6 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-loader@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/node-loader/-/node-loader-2.0.0.tgz#9109a6d828703fd3e0aa03c1baec12a798071562" - integrity sha512-I5VN34NO4/5UYJaUBtkrODPWxbobrE4hgDqPrjB25yPkonFhCmZ146vTH+Zg417E9Iwoh1l/MbRs1apc5J295Q== - dependencies: - loader-utils "^2.0.0" - node-preload@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" @@ -23921,6 +24033,15 @@ numeral@^2.0.6: resolved "https://registry.yarnpkg.com/numeral/-/numeral-2.0.6.tgz#4ad080936d443c2561aed9f2197efffe25f4e506" integrity sha1-StCAk21EPCVhrtnyGX7//iX05QY= +nunjucks@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/nunjucks/-/nunjucks-3.2.4.tgz#f0878eef528ce7b0aa35d67cc6898635fd74649e" + integrity sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ== + dependencies: + a-sync-waterfall "^1.0.0" + asap "^2.0.3" + commander "^5.1.0" + nwsapi@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0" @@ -24215,16 +24336,15 @@ open@^8.0.9, open@^8.4.0, open@~8.4.0: is-docker "^2.1.1" is-wsl "^2.2.0" -openai@^4.24.1, openai@^4.26.0: - version "4.26.1" - resolved "https://registry.yarnpkg.com/openai/-/openai-4.26.1.tgz#7b7c0225c09922445f68f3c4cdbd5775ed31108c" - integrity sha512-DvWbjhWbappsFRatOWmu4Dp1/Q4RG9oOz6CfOSjy0/Drb8G+5iAiqWAO4PfpGIkhOOKtvvNfQri2SItl+U7LhQ== +openai@^4.24.1, openai@^4.41.1: + version "4.47.1" + resolved "https://registry.yarnpkg.com/openai/-/openai-4.47.1.tgz#1d23c7a8eb3d7bcdc69709cd905f4c9af0181dba" + integrity sha512-WWSxhC/69ZhYWxH/OBsLEirIjUcfpQ5+ihkXKp06hmeYXgBBIUCa9IptMzYx6NdkiOCsSGYCnTIsxaic3AjRCQ== dependencies: "@types/node" "^18.11.18" "@types/node-fetch" "^2.6.4" abort-controller "^3.0.0" agentkeepalive "^4.2.1" - digest-fetch "^1.3.0" form-data-encoder "1.7.2" formdata-node "^4.3.2" node-fetch "^2.6.7" @@ -24385,6 +24505,11 @@ ospath@^1.2.2: resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" integrity sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs= +outvariant@^1.2.1, outvariant@^1.4.0, outvariant@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/outvariant/-/outvariant-1.4.2.tgz#f54f19240eeb7f15b28263d5147405752d8e2066" + integrity sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ== + p-all@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-all/-/p-all-2.1.0.tgz#91419be56b7dee8fe4c5db875d55e0da084244a0" @@ -24780,6 +24905,11 @@ path-to-regexp@^2.2.1: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704" integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w== +path-to-regexp@^6.2.0: + version "6.2.2" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.2.tgz#324377a83e5049cbecadc5554d6a63a9a4866b36" + integrity sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw== + path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -27995,7 +28125,7 @@ semver@7.5.4: dependencies: lru-cache "^6.0.0" -semver@7.6.0, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.0, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4: +semver@7.6.0: version "7.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== @@ -28007,6 +28137,11 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== +semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.0, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4: + version "7.6.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" + integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== + send@0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -28241,11 +28376,6 @@ shelljs@^0.8.5: interpret "^1.0.0" rechoir "^0.6.2" -shimmer@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" - integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== - should-equal@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3" @@ -28304,10 +28434,10 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -signal-exit@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.2.tgz#ff55bb1d9ff2114c13b400688fa544ac63c36967" - integrity sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q== +signal-exit@^4.0.1, signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== simple-bin-help@^1.8.0: version "1.8.0" @@ -28981,7 +29111,7 @@ stats-lite@^2.2.0: dependencies: isnumber "~1.0.0" -statuses@2.0.1: +statuses@2.0.1, statuses@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== @@ -29069,6 +29199,11 @@ streamx@^2.12.0, streamx@^2.12.5, streamx@^2.13.0, streamx@^2.13.2, streamx@^2.1 optionalDependencies: bare-events "^2.2.0" +strict-event-emitter@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz#1602ece81c51574ca39c6815e09f1a3e8550bd93" + integrity sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ== + strict-uri-encode@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" @@ -29930,11 +30065,6 @@ thunky@^1.0.2: resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.2.tgz#a862e018e3fb1ea2ec3fce5d55605cf57f247371" integrity sha1-qGLgGOP7HqLsP85dVWBc9X8kc3E= -tiktoken@^1.0.13: - version "1.0.14" - resolved "https://registry.yarnpkg.com/tiktoken/-/tiktoken-1.0.14.tgz#1263821f4ba0a4ec71604db8608a3accd43001c9" - integrity sha512-g5zd5r/DoH8Kw0fiYbYpVhb6WO8BHO1unXqmBBWKwoT17HwSounnDtMDFUKm2Pko8U47sjQarOe+9aUrnqmmTg== - time-stamp@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" @@ -30261,7 +30391,7 @@ tslib@2.5.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== -tslib@2.6.2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.5.2: +tslib@2.6.2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.5.0, tslib@^2.5.2, tslib@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -30295,7 +30425,7 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -tunnel@^0.0.6: +tunnel@0.0.6, tunnel@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== @@ -30334,6 +30464,11 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + type-fest@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" @@ -30354,10 +30489,10 @@ type-fest@^2.13.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== -type-fest@^4.15.0, type-fest@^4.17.0: - version "4.17.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.17.0.tgz#4c1b2c2852d2a40ba8c0236d3afc6fc68229e5bf" - integrity sha512-9flrz1zkfLRH3jO3bLflmTxryzKMxVa7841VeMgBaNQGY6vH4RCcpN/sQLB7mQQYh1GZ5utT2deypMuCy4yicw== +type-fest@^4.15.0, type-fest@^4.17.0, type-fest@^4.9.0: + version "4.18.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.18.3.tgz#5249f96e7c2c3f0f1561625f54050e343f1c8f68" + integrity sha512-Q08/0IrpvM+NMY9PA2rti9Jb+JejTddwmwmVQGskAlhtcrw1wsRzoR6ode6mR+OAabNa75w/dxedSUY2mlphaQ== type-is@~1.6.18: version "1.6.18" @@ -32175,15 +32310,20 @@ write-file-atomic@^4.0.1, write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" -ws@8.17.0, ws@>=8.16.0, ws@^8.2.3, ws@^8.4.2, ws@^8.9.0: +ws@8.17.0: version "8.17.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.0.tgz#d145d18eca2ed25aaf791a183903f7be5e295fea" integrity sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow== +ws@>=8.16.0, ws@^8.2.3, ws@^8.4.2, ws@^8.9.0: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== + ws@^7.3.1, ws@^7.4.2: - version "7.5.9" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" - integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + version "7.5.10" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== x-default-browser@^0.4.0: version "0.4.0"